406 lines
9.7 KiB
C++
406 lines
9.7 KiB
C++
/******************************************************************************
|
|
* QSkinny - Copyright (C) The authors
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*****************************************************************************/
|
|
|
|
#include "QskDrawer.h"
|
|
#include "QskAspect.h"
|
|
#include "QskQuick.h"
|
|
#include "QskEvent.h"
|
|
|
|
#include "QskPanGestureRecognizer.h"
|
|
#include "QskGesture.h"
|
|
#include "QskInternalMacros.h"
|
|
|
|
#include <qguiapplication.h>
|
|
#include <qstylehints.h>
|
|
|
|
QSK_QT_PRIVATE_BEGIN
|
|
#include <private/qquickitem_p.h>
|
|
QSK_QT_PRIVATE_END
|
|
|
|
QSK_SUBCONTROL( QskDrawer, Panel )
|
|
|
|
static inline qreal qskDefaultDragMargin()
|
|
{
|
|
// a skin hint ???
|
|
return QGuiApplication::styleHints()->startDragDistance();
|
|
}
|
|
|
|
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 )
|
|
{
|
|
const auto ratio = 1.0 - drawer->fadingFactor();
|
|
|
|
auto dx = 0.0;
|
|
auto dy = 0.0;
|
|
|
|
switch( drawer->edge() )
|
|
{
|
|
case Qt::LeftEdge:
|
|
dx = -ratio * size.width();
|
|
break;
|
|
|
|
case Qt::RightEdge:
|
|
dx = ratio * size.width();
|
|
break;
|
|
|
|
case Qt::TopEdge:
|
|
dy = -ratio * size.height();
|
|
break;
|
|
|
|
case Qt::BottomEdge:
|
|
dy = ratio * size.height();
|
|
break;
|
|
}
|
|
|
|
return QPointF( dx, dy );
|
|
}
|
|
|
|
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->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 );
|
|
}
|
|
};
|
|
}
|
|
|
|
class QskDrawer::PrivateData
|
|
{
|
|
public:
|
|
PrivateData( Qt::Edge edge )
|
|
: edge( edge )
|
|
{
|
|
}
|
|
|
|
GestureRecognizer* gestureRecognizer = nullptr;
|
|
|
|
qreal dragMargin = qskDefaultDragMargin();
|
|
Qt::Edge edge;
|
|
};
|
|
|
|
QskDrawer::QskDrawer( QQuickItem* parentItem )
|
|
: QskDrawer( Qt::LeftEdge, parentItem )
|
|
{
|
|
setPolishOnParentResize( true );
|
|
}
|
|
|
|
QskDrawer::QskDrawer( Qt::Edge edge, QQuickItem* parentItem )
|
|
: Inherited ( parentItem )
|
|
, m_data( new PrivateData( edge ) )
|
|
{
|
|
#if 1
|
|
setZ( 1 );
|
|
#endif
|
|
|
|
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 );
|
|
|
|
setAutoLayoutChildren( true );
|
|
setInteractive( true );
|
|
|
|
connect( this, &QskPopup::fadingChanged, this, &QQuickItem::setClip );
|
|
}
|
|
|
|
QskDrawer::~QskDrawer()
|
|
{
|
|
}
|
|
|
|
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 )
|
|
{
|
|
margin = std::max( margin, 0.0 );
|
|
|
|
if ( margin != m_data->dragMargin )
|
|
{
|
|
m_data->dragMargin = margin;
|
|
Q_EMIT dragMarginChanged( margin );
|
|
}
|
|
}
|
|
|
|
void QskDrawer::resetDragMargin()
|
|
{
|
|
setDragMargin( qskDefaultDragMargin() );
|
|
}
|
|
|
|
qreal QskDrawer::dragMargin() const
|
|
{
|
|
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 );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Inherited::gestureEvent( event );
|
|
}
|
|
|
|
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() );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QskDrawer::updateResources()
|
|
{
|
|
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() );
|
|
|
|
r.translate( qskDrawerTranslation( this, r.size() ) );
|
|
setGeometry( r );
|
|
}
|
|
}
|
|
|
|
void QskDrawer::updateNode( QSGNode* node )
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
Inherited::updateNode( node );
|
|
}
|
|
|
|
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;
|
|
|
|
QRectF r( -d, -d, 2.0 * d, 2.0 * d );
|
|
|
|
switch( edge() )
|
|
{
|
|
case Qt::LeftEdge:
|
|
r.setLeft( -x() );
|
|
break;
|
|
|
|
case Qt::RightEdge:
|
|
r.setRight( parentItem()->width() - x() );
|
|
break;
|
|
|
|
case Qt::TopEdge:
|
|
r.setTop( -y() );
|
|
break;
|
|
|
|
case Qt::BottomEdge:
|
|
r.setBottom( parentItem()->height() - y() );
|
|
break;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
return Inherited::clipRect();
|
|
}
|
|
|
|
QskAspect QskDrawer::fadingAspect() const
|
|
{
|
|
return QskDrawer::Panel | QskAspect::Position;
|
|
}
|
|
|
|
QRectF QskDrawer::layoutRectForSize( const QSizeF& size ) const
|
|
{
|
|
return subControlContentsRect( size, Panel );
|
|
}
|
|
|
|
#include "moc_QskDrawer.cpp"
|