qskinny/src/controls/QskDrawer.cpp

468 lines
11 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 "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
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 inline QRectF qskAlignedToEdge(
const QRectF& r, const QSizeF& sz, Qt::Edge edge )
{
switch( edge )
{
case Qt::LeftEdge:
return QRectF( r.left(), r.top(), sz.width(), r.height() );
case Qt::RightEdge:
return QRectF( r.right() - sz.width(), r.top(), sz.width(), r.height() );
case Qt::TopEdge:
return QRectF( r.left(), r.top(), r.width(), sz.height() );
case Qt::BottomEdge:
return QRectF( r.left(), r.bottom() - sz.height(), r.width(), sz.height() );
}
return QRectF();
}
static QPointF qskDrawerTranslation( const QskDrawer* drawer, const QSizeF& size )
2023-10-17 15:53:30 +02:00
{
const auto ratio = 1.0 - drawer->fadingFactor();
2023-10-17 15:53:30 +02:00
auto dx = 0.0;
auto dy = 0.0;
switch( drawer->edge() )
2023-10-17 15:53:30 +02:00
{
case Qt::LeftEdge:
dx = -ratio * size.width();
2023-10-17 15:53:30 +02:00
break;
case Qt::RightEdge:
dx = ratio * size.width();
2023-10-17 15:53:30 +02:00
break;
case Qt::TopEdge:
dy = -ratio * size.height();
2023-10-17 15:53:30 +02:00
break;
case Qt::BottomEdge:
dy = ratio * size.height();
2023-10-17 15:53:30 +02:00
break;
}
return QPointF( dx, dy );
2023-10-17 15:53:30 +02:00
}
namespace
{
// Using an eventFilter for QskEvent::GeometryChange instead ???
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()
{
m_adjustedItem->polish();
}
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;
};
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->isFading() )
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:
PrivateData( Qt::Edge edge )
: edge( edge )
{
}
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 );
}
GeometryListener* listener = nullptr;
GestureRecognizer* gestureRecognizer = nullptr;
qreal dragMargin = qskDefaultDragMargin();
Qt::Edge edge;
2023-05-02 18:51:09 +02:00
};
QskDrawer::QskDrawer( QQuickItem* parentItem )
: QskDrawer( Qt::LeftEdge, parentItem )
{
}
QskDrawer::QskDrawer( Qt::Edge edge, QQuickItem* parentItem )
2023-05-02 18:51:09 +02:00
: Inherited ( parentItem )
, m_data( new PrivateData( edge ) )
2023-05-02 18:51:09 +02:00
{
#if 1
2023-05-02 18:51:09 +02:00
setZ( 1 );
#endif
2023-05-02 18:51:09 +02:00
setPopupFlag( PopupFlag::CloseOnPressOutside, true );
/*
A drawer wants to be on top of its parent - not being
layouted into its layoutRect(). So we opt out and do
the layout updates manually.
*/
setPlacementPolicy( QskPlacementPolicy::Ignore );
2023-05-02 18:51:09 +02:00
setAutoLayoutChildren( true );
setInteractive( true );
connect( this, &QskPopup::fadingChanged, this, &QQuickItem::setClip );
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
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::updateResources()
2023-05-02 18:51:09 +02:00
{
Inherited::updateResources();
/*
Adjusting the geometry to the parent needs to be done before
the layouting of the children ( -> autoLayoutChildren ) is done.
So we are using this hook even if it is not about resources: TODO ...
*/
if ( const auto item = parentItem() )
{
auto r = qskItemRect( item );
r = qskAlignedToEdge( r, sizeConstraint( Qt::PreferredSize ), edge() );
2023-05-02 18:51:09 +02:00
r.translate( qskDrawerTranslation( this, r.size() ) );
setGeometry( r );
}
2023-05-02 18:51:09 +02:00
}
void QskDrawer::updateNode( QSGNode* node )
2023-10-18 15:05:17 +02:00
{
if ( isFading() && clip() )
{
if ( auto clipNode = QQuickItemPrivate::get( this )->clipNode() )
{
/*
The clipRect is changing while fading. Couldn't
find a way how to trigger updates - maybe be enabling/disabling
the clip. So we do the updates manually. TODO ...
*/
const auto r = clipRect();
if ( r != clipNode->rect() )
{
clipNode->setRect( r );
clipNode->update();
}
}
}
2023-10-18 15:05:17 +02:00
Inherited::updateNode( node );
}
2023-10-18 15:05:17 +02:00
QRectF QskDrawer::clipRect() const
{
if ( isFading() && parentItem() )
{
/*
We might not fit into our parent and our children not
into our rect. So we want to have a clip against the
edge, where the drawer slides in/out only.
Otherwise we would have unwanted effects, when clipping gets
disabled once the transition is over.
*/
constexpr qreal d = 1e6;
2023-10-18 15:05:17 +02:00
QRectF r( -d, -d, 2.0 * d, 2.0 * d );
2023-10-18 15:05:17 +02:00
switch( edge() )
{
case Qt::LeftEdge:
r.setLeft( -x() );
break;
2023-10-18 15:05:17 +02:00
case Qt::RightEdge:
r.setRight( parentItem()->width() - x() );
break;
2023-10-18 15:05:17 +02:00
case Qt::TopEdge:
r.setTop( -y() );
break;
2023-10-18 15:05:17 +02:00
case Qt::BottomEdge:
r.setBottom( parentItem()->height() - y() );
break;
}
2023-10-18 15:05:17 +02:00
return r;
2023-10-18 15:05:17 +02:00
}
return Inherited::clipRect();
2023-10-18 15:05:17 +02:00
}
QskAspect QskDrawer::fadingAspect() const
2023-10-18 15:05:17 +02:00
{
return QskDrawer::Panel | QskAspect::Position;
2023-10-18 15:05:17 +02:00
}
QRectF QskDrawer::layoutRectForSize( const QSizeF& size ) const
2023-10-18 15:05:17 +02:00
{
return subControlContentsRect( size, Panel );
2023-10-18 15:05:17 +02:00
}
2023-05-02 18:51:09 +02:00
#include "moc_QskDrawer.cpp"