qskinny/src/controls/QskDrawer.cpp

350 lines
8.1 KiB
C++

/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* 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 <qguiapplication.h>
#include <qstylehints.h>
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& rect, const QSizeF& size, Qt::Edge edge )
{
QRectF r( 0.0, 0.0, size.width(), size.height() );
switch( 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;
}
}
return r;
}
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 );
}
};
}
class QskDrawer::PrivateData
{
public:
Qt::Edge edge = Qt::LeftEdge;
GestureRecognizer* gestureRecognizer = nullptr;
qreal dragMargin = qskDefaultDragMargin();
};
QskDrawer::QskDrawer( QQuickItem* parentItem )
: Inherited ( parentItem )
, m_data( new PrivateData )
{
#if 1
setZ( 1 );
#endif
setAutoLayoutChildren( true );
setInteractive( true );
setAdjustingToParentGeometry( true );
}
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;
}
bool QskDrawer::event( QEvent* event )
{
if ( event->type() == QEvent::PolishRequest )
{
if ( isAdjustingToParentGeometry() && parentItem() )
{
auto r = qskItemRect( parentItem() );
r = qskAlignedToEdge( r, sizeConstraint( Qt::PreferredSize ), edge() );
setGeometry( r );
return true;
}
}
return Inherited::event( event );
}
void QskDrawer::keyPressEvent( QKeyEvent* event )
{
if ( isOpen() )
{
bool doClose = false;
const auto key = event->key();
switch( key )
{
case Qt::Key_Escape:
case Qt::Key_Cancel:
{
doClose = true;
break;
}
#if 0
/*
Do we want to have this - and what about opening with
the same keys ???
*/
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_Left:
case Qt::Key_Right:
{
switch( m_data->edge )
{
case Qt::TopEdge:
doClose = ( key == Qt::Key_Up );
break;
case Qt::BottomEdge:
doClose = ( key == Qt::Key_Down );
break;
case Qt::LeftEdge:
doClose = ( key == Qt::Key_Left );
break;
case Qt::RightEdge:
doClose = ( key == Qt::Key_Right );
break;
}
break;
}
#endif
}
if ( doClose )
{
close();
return;
}
}
Inherited::keyPressEvent( event );
}
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;
}
}
}
#include "moc_QskDrawer.cpp"