qskinny/src/controls/QskDrawer.cpp

483 lines
12 KiB
C++
Raw Normal View History

/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
2023-05-02 18:51:09 +02:00
#include "QskDrawer.h"
#include "QskAspect.h"
#include "QskAnimationHint.h"
#include "QskQuick.h"
#include "QskEvent.h"
#include "QskPanGestureRecognizer.h"
#include "QskGesture.h"
#include <qguiapplication.h>
#include <qstylehints.h>
2023-05-02 18:51:09 +02:00
QSK_QT_PRIVATE_BEGIN
#include <private/qquickitem_p.h>
#include <private/qquickitemchangelistener_p.h>
QSK_QT_PRIVATE_END
2023-05-02 18:51:09 +02:00
/*
Only used for the sliding in animation. Do we want to
introduce a specific panel as background ???
*/
2023-05-02 18:51:09 +02:00
QSK_SUBCONTROL( QskDrawer, Panel )
static inline qreal qskDefaultDragMargin()
{
// a skin hint ???
return QGuiApplication::styleHints()->startDragDistance();
}
2023-10-25 10:07:38 +02:00
static inline void qskCatchMouseEvents( QQuickItem* item )
{
#if 1
// manipulating other items - do we really want to do this ?
item->setAcceptedMouseButtons( Qt::LeftButton );
item->setFiltersChildMouseEvents( true );
#endif
}
static bool qskCheckDirection( Qt::Edge edge, const QskPanGesture* gesture )
{
const auto degrees = gesture->angle();
switch( edge )
{
case Qt::LeftEdge:
return ( degrees < 90.0 ) || ( degrees ) > 270.0;
case Qt::RightEdge:
return ( degrees > 90.0 ) && ( degrees < 270.0 );
case Qt::TopEdge:
return degrees > 180.0;
case Qt::BottomEdge:
return degrees < 180.0;
}
return false;
}
static void qskLayoutDrawer( const QRectF& rect, QskDrawer* drawer )
{
const auto size = qskSizeConstraint( drawer, Qt::PreferredSize );
QRectF r( 0.0, 0.0, size.width(), size.height() );
switch( drawer->edge() )
{
case Qt::LeftEdge:
{
r.setHeight( rect.height() );
r.moveRight( rect.left() + size.width() );
break;
}
case Qt::RightEdge:
{
r.setHeight( rect.height() );
r.moveLeft( rect.right() - size.width() );
break;
}
case Qt::TopEdge:
{
r.setWidth( rect.width() );
r.moveBottom( rect.top() + size.height() );
break;
}
case Qt::BottomEdge:
{
r.setWidth( rect.width() );
r.moveTop( rect.bottom() - size.height() );
break;
}
}
drawer->setGeometry( r );
}
2023-10-17 15:53:30 +02:00
static inline QRectF qskSlidingRect(
const QSizeF& size, Qt::Edge edge, qreal ratio )
{
auto x = 0.0;
auto y = 0.0;
ratio = 1.0 - ratio;
2023-10-17 15:53:30 +02:00
switch( edge )
{
case Qt::LeftEdge:
x = -ratio * size.width();
break;
case Qt::RightEdge:
x = ratio * size.width();
break;
case Qt::TopEdge:
y = -ratio * size.height();
break;
case Qt::BottomEdge:
y = ratio * size.height();
break;
}
return QRectF( x, y, size.width(), size.height() );
}
namespace
{
class GeometryListener final : public QQuickItemChangeListener
{
public:
GeometryListener( QQuickItem* item, QQuickItem* adjustedItem )
: m_item( item )
, m_adjustedItem( adjustedItem )
{
adjust();
setEnabled( true );
}
~GeometryListener()
{
setEnabled( false );
}
private:
void itemGeometryChanged( QQuickItem*,
QQuickGeometryChange, const QRectF& ) override
{
adjust();
}
private:
void adjust()
{
#if 0
const auto pos = m_adjustedItem->mapFromItem( m_item, QPointF() );
qskSetItemGeometry( m_adjustedItem,
pos.x(), pos.y(), m_item->width(), m_item->height() );
#else
qskLayoutDrawer( QRectF( QPointF(), m_item->size() ),
qobject_cast< QskDrawer* >( m_adjustedItem ) );
#endif
}
void setEnabled( bool on )
{
const auto changeTypes = QQuickItemPrivate::Geometry;
auto d = QQuickItemPrivate::get( m_item );
if ( on )
d->addItemChangeListener( this, changeTypes );
else
d->removeItemChangeListener( this, changeTypes );
}
QQuickItem* m_item;
QQuickItem* m_adjustedItem;
};
}
namespace
{
class GestureRecognizer : public QskPanGestureRecognizer
{
using Inherited = QskPanGestureRecognizer;
public:
GestureRecognizer( QskDrawer* drawer )
: QskPanGestureRecognizer( drawer )
{
setWatchedItem( drawer->parentItem() );
setTargetItem( drawer );
}
protected:
bool isAcceptedPos( const QPointF& pos ) const override
{
auto drawer = qobject_cast< const QskDrawer* >( targetItem() );
if ( drawer->isTransitioning() )
return false;
auto rect = qskItemRect( watchedItem() );
if ( !drawer->isOpen() )
{
const auto dragMargin = drawer->dragMargin();
if ( dragMargin <= 0.0 )
return false;
switch( drawer->edge() )
{
case Qt::LeftEdge:
rect.setRight( rect.left() + dragMargin );
break;
case Qt::RightEdge:
rect.setLeft( rect.right() - dragMargin );
break;
case Qt::TopEdge:
rect.setBottom( rect.top() + dragMargin );
break;
case Qt::BottomEdge:
rect.setTop( rect.bottom() - dragMargin );
break;
}
}
return rect.contains( pos );
}
};
}
2023-05-02 18:51:09 +02:00
class QskDrawer::PrivateData
{
public:
2023-10-18 15:05:17 +02:00
inline void resetListener( QskDrawer* drawer )
{
delete listener;
listener = nullptr;
if ( drawer->parentItem() && drawer->isVisible() )
listener = new GeometryListener( drawer->parentItem(), drawer );
}
2023-05-02 18:51:09 +02:00
Qt::Edge edge = Qt::LeftEdge;
GestureRecognizer* gestureRecognizer = nullptr;
GeometryListener* listener = nullptr;
qreal dragMargin = qskDefaultDragMargin();
2023-05-02 18:51:09 +02:00
};
QskDrawer::QskDrawer( QQuickItem* parentItem )
: Inherited ( parentItem )
, m_data( new PrivateData )
{
#if 1
2023-05-02 18:51:09 +02:00
setZ( 1 );
#endif
2023-10-17 15:53:30 +02:00
setAutoLayoutChildren( true );
setInteractive( true );
2023-05-02 18:51:09 +02:00
setPopupFlag( PopupFlag::CloseOnPressOutside, true );
setTransitionAspect( Panel | QskAspect::Position | QskAspect::Metric );
/*
The drawer wants to be on top of the parent - not being
layouted into its layoutRect(). So we opt out and do
the layout updates manually.
*/
setPlacementPolicy( QskPlacementPolicy::Ignore );
2023-10-18 15:05:17 +02:00
m_data->resetListener( this );
2023-05-02 18:51:09 +02:00
connect( this, &QskPopup::openChanged, this, &QskDrawer::setSliding );
connect( this, &QskPopup::transitioningChanged, this, &QskDrawer::setIntermediate );
2023-05-02 18:51:09 +02:00
}
QskDrawer::~QskDrawer()
{
delete m_data->listener;
2023-05-02 18:51:09 +02:00
}
Qt::Edge QskDrawer::edge() const
{
return m_data->edge;
}
void QskDrawer::setEdge( Qt::Edge edge )
{
if( m_data->edge == edge )
return;
update();
m_data->edge = edge;
edgeChanged( edge );
}
void QskDrawer::setInteractive( bool on )
{
if ( on == isInteractive() )
return;
if ( on )
{
m_data->gestureRecognizer = new GestureRecognizer( this );
if ( parentItem() )
qskCatchMouseEvents( parentItem() );
}
else
{
// how to revert qskCatchMouseEvents properly ???
delete m_data->gestureRecognizer;
m_data->gestureRecognizer = nullptr;
}
Q_EMIT interactiveChanged( on );
}
bool QskDrawer::isInteractive() const
{
return m_data->gestureRecognizer != nullptr;
}
void QskDrawer::setDragMargin( qreal margin )
2023-05-02 18:51:09 +02:00
{
margin = std::max( margin, 0.0 );
2023-05-11 08:15:48 +02:00
if ( margin != m_data->dragMargin )
{
m_data->dragMargin = margin;
Q_EMIT dragMarginChanged( margin );
}
2023-05-02 18:51:09 +02:00
}
void QskDrawer::resetDragMargin()
{
setDragMargin( qskDefaultDragMargin() );
}
qreal QskDrawer::dragMargin() const
2023-05-02 18:51:09 +02:00
{
return m_data->dragMargin;
}
void QskDrawer::gestureEvent( QskGestureEvent* event )
{
if ( event->gesture()->type() == QskGesture::Pan )
{
/*
For the moment we treat the gesture like a swipe gesture
without dragging the drawer when moving the mouse. TODO ...
*/
const auto gesture = static_cast< const QskPanGesture* >( event->gesture().get() );
if ( gesture->state() == QskGesture::Finished )
{
const auto forwards = qskCheckDirection( m_data->edge, gesture );
if ( forwards != isOpen() )
setOpen( forwards );
}
2023-05-02 18:51:09 +02:00
return;
}
Inherited::gestureEvent( event );
}
2023-05-02 18:51:09 +02:00
2023-10-17 15:53:30 +02:00
QRectF QskDrawer::layoutRectForSize( const QSizeF& size ) const
{
2023-10-17 15:53:30 +02:00
qreal ratio;
2023-05-02 18:51:09 +02:00
if ( isTransitioning() )
ratio = metric( transitionAspect() );
2023-10-17 15:53:30 +02:00
else
ratio = isOpen() ? 1.0 : 0.0;
2023-10-17 15:53:30 +02:00
return qskSlidingRect( size, m_data->edge, ratio );
}
void QskDrawer::itemChange( QQuickItem::ItemChange change,
const QQuickItem::ItemChangeData& value )
{
Inherited::itemChange( change, value );
switch( static_cast< int >( change ) )
{
case QQuickItem::ItemParentHasChanged:
{
if ( parentItem() && isInteractive() )
qskCatchMouseEvents( parentItem() );
2023-05-02 18:51:09 +02:00
2023-10-18 15:05:17 +02:00
m_data->resetListener( this );
break;
}
case QQuickItem::ItemVisibleHasChanged:
{
2023-10-18 15:05:17 +02:00
m_data->resetListener( this );
break;
}
}
2023-05-02 18:51:09 +02:00
}
void QskDrawer::setSliding( bool on )
2023-05-02 18:51:09 +02:00
{
const qreal from = on ? 0.0 : 1.0;
const qreal to = on ? 1.0 : 0.0;
const auto aspect = transitionAspect();
auto hint = animationHint( aspect );
2023-10-17 14:36:44 +02:00
hint.updateFlags = QskAnimationHint::UpdatePolish;
2023-05-02 18:51:09 +02:00
startTransition( aspect, hint, from, to );
2023-05-02 18:51:09 +02:00
}
2023-10-18 15:05:17 +02:00
QRectF QskDrawer::clipRect() const
{
if ( !isTransitioning() )
2023-10-18 15:05:17 +02:00
return Inherited::clipRect();
/*
When fading we want to clip against the edge, where the drawer
slides in/out. However the size of the drawer is often smaller than the
one of the parent and we would clip the overlay node
and all content, that is located outside the drawer geometry.
So we expand the clip rectangle to "unbounded" at the other edges.
Note, that clipping against "rounded" rectangles can't be done
properly by overloading clipRect. We would have to manipulate the clip node
manually - like it is done in QskScrollArea. TODO ..
*/
constexpr qreal d = std::numeric_limits< short >::max();
QRectF r( -d, -d, 2.0 * d, 2.0 * d );
switch( m_data->edge )
{
case Qt::LeftEdge:
r.setLeft( 0.0 );
break;
case Qt::RightEdge:
r.setRight( width() );
break;
case Qt::TopEdge:
r.setTop( 0.0 );
break;
case Qt::BottomEdge:
r.setBottom( height() );
break;
}
return r;
}
void QskDrawer::setIntermediate( bool on )
{
setClip( on );
Q_EMIT focusIndicatorRectChanged();
}
QRectF QskDrawer::focusIndicatorRect() const
{
if ( isTransitioning() )
2023-10-18 15:05:17 +02:00
return QRectF();
return Inherited::focusIndicatorRect();
}
2023-05-02 18:51:09 +02:00
#include "moc_QskDrawer.cpp"