From 5f31eb1e785152dee44b0be6a6d2c3f50111c784 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Tue, 17 Oct 2023 12:14:42 +0200 Subject: [PATCH] QskDrawer reimplemented, can be open by a swipe gesture now. more work to do --- examples/gallery/main.cpp | 5 +- skins/fluent2/QskFluent2Skin.cpp | 12 +- skins/material3/QskMaterial3Skin.cpp | 7 +- skins/squiek/QskSquiekSkin.cpp | 9 +- src/controls/QskDrawer.cpp | 435 ++++++++++++++++++++++----- src/controls/QskDrawer.h | 23 +- 6 files changed, 396 insertions(+), 95 deletions(-) diff --git a/examples/gallery/main.cpp b/examples/gallery/main.cpp index f0916df0..611c620f 100644 --- a/examples/gallery/main.cpp +++ b/examples/gallery/main.cpp @@ -62,7 +62,8 @@ namespace Drawer( QQuickItem* parent = nullptr ) : QskDrawer( parent ) { - auto box = new QskLinearBox( Qt::Vertical ); + auto box = new QskLinearBox( Qt::Vertical, this ); + box->setSection( QskAspect::Header ); box->setPanel( true ); box->setPaddingHint( QskBox::Panel, 20 ); @@ -75,8 +76,6 @@ namespace auto btn = new QskPushButton( "Close", box ); connect( btn, &QskPushButton::clicked, this, &QskDrawer::close ); - - setContent( box ); } }; diff --git a/skins/fluent2/QskFluent2Skin.cpp b/skins/fluent2/QskFluent2Skin.cpp index 7b56147c..30829413 100644 --- a/skins/fluent2/QskFluent2Skin.cpp +++ b/skins/fluent2/QskFluent2Skin.cpp @@ -606,21 +606,15 @@ void Editor::setupDialogButtonBoxColors( void Editor::setupDrawerMetrics() { using Q = QskDrawer; - - setPadding( Q::Panel, 5 ); - setHint( Q::Overlay | QskAspect::Style, false ); + using A = QskAspect; #if 1 - setAnimation( Q::Panel | QskAspect::Position, 200 ); + setAnimation( Q::Panel | A::Metric | A::Position, 200 ); #endif } -void Editor::setupDrawerColors( - QskAspect::Section section, const QskFluent2Theme& theme ) +void Editor::setupDrawerColors( QskAspect::Section, const QskFluent2Theme& ) { - using Q = QskDrawer; - - setGradient( Q::Panel | section, theme.palette.background.solid.base ); } void Editor::setupFocusIndicatorMetrics() diff --git a/skins/material3/QskMaterial3Skin.cpp b/skins/material3/QskMaterial3Skin.cpp index 6653e202..9ea1c054 100644 --- a/skins/material3/QskMaterial3Skin.cpp +++ b/skins/material3/QskMaterial3Skin.cpp @@ -809,12 +809,9 @@ void Editor::setupDialogButtonBox() void Editor::setupDrawer() { using Q = QskDrawer; + using A = QskAspect; - setPadding( Q::Panel, 5_dp ); - setGradient( Q::Panel, m_pal.background ); - setHint( Q::Overlay | QskAspect::Style, false ); - - setAnimation( Q::Panel | QskAspect::Position, qskDuration ); + setAnimation( Q::Panel | A::Metric | A::Position, qskDuration ); } void Editor::setupSlider() diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index d116d9db..c5ea53ab 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -759,13 +759,12 @@ void Editor::setupDialogButtonBox() setBoxShape( Q::Panel, 2 ); } -void Editor::setupDrawer() { +void Editor::setupDrawer() +{ + using A = QskAspect; using Q = QskDrawer; - setPadding( Q::Panel, 5 ); - setGradient( Q::Panel, m_pal.darker125 ); - setAnimation( Q::Panel | QskAspect::Position, qskDuration ); - setHint( Q::Overlay | QskAspect::Style, false ); + setAnimation( Q::Panel | A::Metric | A::Position, qskDuration ); } void Editor::setupTabButton() diff --git a/src/controls/QskDrawer.cpp b/src/controls/QskDrawer.cpp index 905a5987..fe422e92 100644 --- a/src/controls/QskDrawer.cpp +++ b/src/controls/QskDrawer.cpp @@ -1,48 +1,258 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + #include "QskDrawer.h" #include "QskAspect.h" -#include "QskControl.h" +#include "QskAnimationHint.h" +#include "QskQuick.h" +#include "QskEvent.h" -#include -#include -#include -#include +#include "QskPanGestureRecognizer.h" +#include "QskGesture.h" +#include +#include + +QSK_QT_PRIVATE_BEGIN +#include +#include +QSK_QT_PRIVATE_END + +/* + Only used for the sliding in animation. Do we want to + introduce a specific panel as background ??? + */ QSK_SUBCONTROL( QskDrawer, Panel ) -QSK_SUBCONTROL( QskDrawer, Overlay ) + +static 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 ); +} + +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() ); + + const auto dragMargin = drawer->dragMargin(); + if ( dragMargin <= 0.0 ) + return false; + + auto rect = qskItemRect( watchedItem() ); + + 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: - QskControl* content = nullptr; - QskBox* contentBox = nullptr; Qt::Edge edge = Qt::LeftEdge; + GestureRecognizer* gestureRecognizer = nullptr; + GeometryListener* listener = nullptr; + + // a skin hint ??? + qreal dragMargin = QGuiApplication::styleHints()->startDragDistance(); }; QskDrawer::QskDrawer( QQuickItem* parentItem ) : Inherited ( parentItem ) , m_data( new PrivateData ) { +#if 1 setZ( 1 ); +#endif + + setOverlay( true ); + setPolishOnResize( true ); setPopupFlag( PopupFlag::CloseOnPressOutside, true ); + setFaderAspect( Panel | QskAspect::Position | QskAspect::Metric ); - m_data->contentBox = new QskBox(this); - m_data->contentBox->setSubcontrolProxy( QskBox::Panel, Panel ); - m_data->contentBox->setPanel( true ); + /* + 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 ); + if ( parentItem ) + { + m_data->listener = new GeometryListener( parentItem, this ); + qskCatchMouseEvents( parentItem ); + } - setSubcontrolProxy( Inherited::Overlay, Overlay ); + m_data->gestureRecognizer = new GestureRecognizer( this ); - setFaderAspect( Panel | QskAspect::Metric ); - connect(this, &QskDrawer::closed, this, [this]() { - startTransition( Panel | QskAspect::Metric, - animationHint( Panel | QskAspect::Position ), - 0.0, 1.0 ); - }); + connect( this, &QskPopup::openChanged, this, &QskDrawer::setFading ); + + /* + When the content of the parentItem does not fit we will have + a difference between fading and normal state. To overcome this problem + we need to expand the rectangle of the QQuickDefaultClipNode manually to + the window borders: TODO ... + */ + connect( this, &QskPopup::fadingChanged, parentItem, &QQuickItem::setClip ); } QskDrawer::~QskDrawer() { + delete m_data->listener; } Qt::Edge QskDrawer::edge() const @@ -60,79 +270,166 @@ void QskDrawer::setEdge( Qt::Edge edge ) edgeChanged( edge ); } -void QskDrawer::setContent( QskControl* content ) +void QskDrawer::setDragMargin( qreal margin ) { - content->setParentItem( m_data->contentBox ); - if ( content->parent() == nullptr ) - content->setParent( m_data->contentBox ); + margin = std::max( margin, 0.0 ); - m_data->content = content; + if ( margin != m_data->dragMargin ) + { + m_data->dragMargin = margin; + Q_EMIT dragMarginChanged( margin ); + } +} + +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 ) + { + if ( qskCheckDirection( m_data->edge, gesture ) ) + open(); + } + + return; + } + + Inherited::gestureEvent( event ); +} + +QSizeF QskDrawer::layoutSizeHint( + Qt::SizeHint which, const QSizeF& constraint ) const +{ + if ( which == Qt::MaximumSize ) + return QSizeF(); + + qreal w = -1.0; + qreal h = -1.0; + + const auto children = childItems(); + + for ( const auto child : children ) + { + if ( !qskIsVisibleToLayout( child ) ) + continue; + + const auto policy = qskSizePolicy( child ); + + if ( constraint.width() >= 0.0 && policy.isConstrained( Qt::Vertical ) ) + { + const auto hint = qskSizeConstraint( child, which, constraint ); + h = qMax( h, hint.height() ); + } + else if ( constraint.height() >= 0.0 && policy.isConstrained( Qt::Horizontal ) ) + { + const auto hint = qskSizeConstraint( child, which, constraint ); + w = qMax( w, hint.width() ); + } + else + { + const auto hint = qskSizeConstraint( child, which ); + + w = qMax( w, hint.width() ); + h = qMax( h, hint.height() ); + } + } + + return QSizeF( w, h ); } void QskDrawer::updateLayout() { - if ( !isOpen() && !isFading() ) + if ( !( isOpen() || isFading() ) || size().isEmpty() ) return; - const auto padding = paddingHint( Panel ); + auto dx = 0.0; + auto dy = 0.0; - auto contentSize = m_data->content->preferredSize(); - contentSize = contentSize.grownBy( padding ); - - const auto parentSize = parentItem()->size(); - - switch( m_data->edge ) + if ( isFading() ) { - case Qt::Edge::LeftEdge: + const auto f = metric( faderAspect() ); + + switch( m_data->edge ) { - qreal x = metric( faderAspect() ) * contentSize.width() * -1.0; + case Qt::LeftEdge: + dx = -f * width(); + break; - qskSetItemGeometry( m_data->contentBox, - x, 0, contentSize.width(), parentSize.height() ); - break; - } - case Qt::Edge::RightEdge: - { - qreal x = ( metric( faderAspect() ) * contentSize.width() ) - + parentSize.width() - contentSize.width(); + case Qt::RightEdge: + dx = f * width(); + break; - qskSetItemGeometry( m_data->contentBox, - x, 0, contentSize.width(), parentSize.height() ); - break; - } + case Qt::TopEdge: + dy = -f * height(); + break; - case Qt::Edge::TopEdge: - { - qreal y = metric( faderAspect() ) * contentSize.height(); - - qskSetItemGeometry( m_data->contentBox, - 0, -y, parentSize.width(), contentSize.height() ); - break; - } - - case Qt::Edge::BottomEdge: - { - qreal y = metric( faderAspect() ) * contentSize.height() + parentSize.height() - - contentSize.height(); - - qskSetItemGeometry( m_data->contentBox, - 0, y, parentSize.width(), contentSize.height() ); - break; + case Qt::BottomEdge: + dy = f * height(); + break; } } - m_data->content->setGeometry( QPointF( padding.left(), padding.top() ), - m_data->contentBox->size().shrunkBy( padding ) ); + const QRectF layoutRect( dx, dy, width(), height() ); - Inherited::updateLayout(); + const auto children = childItems(); + for ( auto child : children ) + { + if ( qskIsAdjustableByLayout( child ) ) + { + const auto r = qskConstrainedItemRect( child, layoutRect ); + qskSetItemGeometry( child, r ); + } + } } -void QskDrawer::aboutToShow() +void QskDrawer::itemChange( QQuickItem::ItemChange change, + const QQuickItem::ItemChangeData& value ) { - startTransition( Panel | QskAspect::Metric, - animationHint( Panel | QskAspect::Position ), 1.0, 0.0 ); + Inherited::itemChange( change, value ); - Inherited::aboutToShow(); + switch( static_cast< int >( change ) ) + { + case QQuickItem::ItemParentHasChanged: + { + if ( parentItem() ) + qskCatchMouseEvents( parentItem() ); + + Q_FALLTHROUGH(); + } + case QQuickItem::ItemVisibleHasChanged: + { + delete m_data->listener; + m_data->listener = nullptr; + + if ( parentItem() && isVisible() ) + m_data->listener = new GeometryListener( parentItem(), this ); + + break; + } + } +} + +void QskDrawer::setFading( bool on ) +{ + const qreal from = on ? 1.0 : 0.0; + const qreal to = on ? 0.0 : 1.0; + + const auto aspect = faderAspect(); + + auto hint = animationHint( aspect ); + hint.updateFlags = QskAnimationHint::UpdatePolish | QskAnimationHint::UpdateNode; + + startTransition( aspect, hint, from, to ); } #include "moc_QskDrawer.cpp" diff --git a/src/controls/QskDrawer.h b/src/controls/QskDrawer.h index 442f95e7..2c8d68e7 100644 --- a/src/controls/QskDrawer.h +++ b/src/controls/QskDrawer.h @@ -1,3 +1,8 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + #ifndef QSK_DRAWER_H #define QSK_DRAWER_H @@ -12,8 +17,11 @@ class QSK_EXPORT QskDrawer : public QskPopup Q_PROPERTY( Qt::Edge edge READ edge WRITE setEdge NOTIFY edgeChanged ) + Q_PROPERTY( qreal dragMargin READ dragMargin + WRITE setDragMargin NOTIFY dragMarginChanged ) + public: - QSK_SUBCONTROLS( Panel, Overlay ) + QSK_SUBCONTROLS( Panel ) QskDrawer( QQuickItem* = nullptr ); ~QskDrawer() override; @@ -21,17 +29,24 @@ class QSK_EXPORT QskDrawer : public QskPopup void setEdge( Qt::Edge ); Qt::Edge edge() const; - void updateLayout() override; + void setDragMargin( qreal ); + qreal dragMargin() const; - void setContent( QskControl* ); + void updateLayout() override; Q_SIGNALS: void edgeChanged( Qt::Edge ); + void dragMarginChanged( qreal ); protected: - void aboutToShow() override; + void itemChange( ItemChange, const ItemChangeData& ) override; + + QSizeF layoutSizeHint( Qt::SizeHint, const QSizeF& ) const override; + void gestureEvent( QskGestureEvent* ) override; private: + void setFading( bool ); + class PrivateData; std::unique_ptr< PrivateData > m_data; };