diff --git a/examples/gallery/main.cpp b/examples/gallery/main.cpp index 7db65abd..dfb9f9c6 100644 --- a/examples/gallery/main.cpp +++ b/examples/gallery/main.cpp @@ -63,7 +63,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 ); @@ -76,8 +77,6 @@ namespace auto btn = new QskPushButton( "Close", box ); connect( btn, &QskPushButton::clicked, this, &QskDrawer::close ); - - setContent( box ); } }; @@ -211,19 +210,17 @@ namespace } { - auto drawer = new Drawer( parentItem() ); - drawer->setEdge( Qt::RightEdge ); - auto burger = new QskPushButton( "≡", this ); burger->setEmphasis( QskPushButton::LowEmphasis ); connect( burger, &QskPushButton::clicked, - drawer, &QskPopup::open ); + this, &Header::drawerRequested ); } } Q_SIGNALS: void enabledToggled( bool ); + void drawerRequested(); }; class MainView : public QskMainView @@ -246,6 +243,12 @@ namespace connect( header, &Header::enabledToggled, tabView, &TabView::setPagesEnabled ); + auto drawer = new Drawer( tabView ); + drawer->setEdge( Qt::RightEdge ); + + connect( header, &Header::drawerRequested, + drawer, &QskPopup::toggle ); + setHeader( header ); setBody( tabView ); } diff --git a/skins/fluent2/QskFluent2Skin.cpp b/skins/fluent2/QskFluent2Skin.cpp index 1b90f732..4f5de05d 100644 --- a/skins/fluent2/QskFluent2Skin.cpp +++ b/skins/fluent2/QskFluent2Skin.cpp @@ -4,7 +4,7 @@ *****************************************************************************/ /* - Definitions ( where possible ) taken from + Definitions ( where possible ) taken from https://www.figma.com/file/NAWMapFlXnoOb86Q2H5GKr/Windows-UI-(Community) */ @@ -35,35 +35,18 @@ */ /* - The palette is made of a specific configurable colors and - predefined semitransparent shades of gray. Both need to - be resolved to opaque colors with the base colors of the sections. + The palette is made of a couple of configurable base/accent colors and a + predefined set of semitransparent shades of gray. These grays are used + to create darkened/lightend colors from the configurable colors that can + be used for borders etc. - Resolving the colors can be done in 2 ways: + However borders need to be darkened for light base colors and lightened for + dark ones. So we actually have 2 different sets of grays for light/dark + colors schemes. - - render time - - This actually means, that we do not create opaque colors and - create the scene graph nodes with semitransparent colors. - - - definition time - - We create opaque colors for the base colors of the sections - and set them as skin hints. - - Resolving at render time sounds like the right solution as we - background colors set in application code will just work. - - Unfortunately we have 2 different sets of grays for light/dark - base colors and when applications are setting a light color, where a - dark color ( or v.v ) is expected we might end up with unacceptable - results: ( white on light or black on dark ). - - So there are pros and cons and we do not have a final opinion - about waht to do. For the moment we implement resolving at definition - time as an option to be able to play with both solutions. + The main advantage of this concept is, that a working color schemes + can be defined by setting the accent/base colors only. */ - #include "QskFluent2Skin.h" #include "QskFluent2Theme.h" @@ -139,9 +122,30 @@ namespace return qRgba( value, value, value, qRound( opacity * 255 ) ); } + /* + When application code is manipulating base colors manually the colors of + the borders will be lightened/darkened from it like expected - as long + as the application color matches the color scheme. Otherwise we end + up with lightened borders on light backgrounds or v.v. + + To avoid this problem we could resolve the grays with the background + colors of the sections at definition time. This solves the problem with + applications using backgrounds from the "wrong" color scheme, but requires + more work for customizing controls hen using the "correct" scheme + ( -> border colors need to be set as well ). + + When enabling QSK_RESOLVE_COLORS a code path is enabled that is intended + to play with resolving the grays at definition time. But I'm not decided + if it is worth to make a feature from it. TODO ... + */ + +#define QSK_RESOLVE_COLORS 0 + +#if QSK_RESOLVE_COLORS + inline constexpr QRgb rgbFlattened( QRgb foreground, QRgb background ) { - //Q_ASSERT( qAlpha( background ) == 255 ); + Q_ASSERT( qAlpha( background ) == 255 ); const auto r2 = qAlpha( foreground ) / 255.0; const auto r1 = 1.0 - r2; @@ -160,15 +164,25 @@ namespace or without resolving the foreground alpha value */ -#if 0 return rgbFlattened( foreground, background ); -#else - //Q_ASSERT( qAlpha( background ) == 255 ); + } + +#else // !QSK_RESOLVE_COLORS + + inline QRgb rgbFlattened( QRgb foreground, QRgb background ) + { + const auto alpha = qAlpha( foreground ) / 255.0; + return QskRgb::interpolated( background, foreground, alpha ); + } + + inline constexpr QRgb rgbSolid( QRgb foreground, QRgb background ) + { Q_UNUSED( background ); return foreground; -#endif } +#endif + class Editor : private QskSkinHintTableEditor { public: @@ -521,7 +535,8 @@ void Editor::setupComboBoxColors( const auto& pal = theme.palette; - for ( const auto state : { QskAspect::NoState, Q::Hovered, Q::Focused, Q::Pressed, Q::Disabled } ) + for ( const auto state : + { QskAspect::NoState, Q::Hovered, Q::Focused, Q::Pressed, Q::Disabled } ) { QRgb panelColor, borderColor1, borderColor2, textColor; @@ -606,21 +621,16 @@ void Editor::setupDialogButtonBoxColors( void Editor::setupDrawerMetrics() { using Q = QskDrawer; + using A = QskAspect; - setPadding( Q::Panel, 5 ); - setHint( Q::Overlay | QskAspect::Style, false ); - -#if 1 - setAnimation( Q::Panel | QskAspect::Position, 200 ); -#endif + setAnimation( Q::Panel | A::Position, 300, QEasingCurve::OutCubic ); } void Editor::setupDrawerColors( QskAspect::Section section, const QskFluent2Theme& theme ) { - using Q = QskDrawer; - - setGradient( Q::Panel | section, theme.palette.background.solid.base ); + setGradient( QskDrawer::Panel | section, + theme.palette.background.solid.base ); } void Editor::setupFocusIndicatorMetrics() @@ -699,7 +709,7 @@ void Editor::setupListViewColors( { if ( state1 == Q::Hovered ) cellColor = pal.fillColor.subtle.tertiary; - else + else cellColor = pal.fillColor.subtle.secondary; } @@ -735,6 +745,7 @@ void Editor::setupListViewColors( void Editor::setupMenuMetrics() { using Q = QskMenu; + using A = QskAspect; setPadding( Q::Panel, { 4, 6, 4, 6 } ); setBoxBorderMetrics( Q::Panel, 1 ); @@ -748,6 +759,8 @@ void Editor::setupMenuMetrics() setStrutSize( Q::Icon, 12, 12 ); setPadding( Q::Icon, { 8, 8, 0, 8 } ); + + setAnimation( Q::Panel | A::Position, 100 ); } void Editor::setupMenuColors( @@ -790,7 +803,8 @@ void Editor::setupMenuColors( setColor( Q::Text | Q::Selected | Q::Pressed, pal.fillColor.text.secondary ); setGraphicRole( Q::Icon, QskFluent2Skin::GraphicRoleFillColorTextPrimary ); - setGraphicRole( Q::Icon | Q::Selected | Q::Pressed, QskFluent2Skin::GraphicRoleFillColorTextSecondary ); + setGraphicRole( Q::Icon | Q::Selected | Q::Pressed, + QskFluent2Skin::GraphicRoleFillColorTextSecondary ); } void Editor::setupPageIndicatorMetrics() @@ -808,7 +822,7 @@ void Editor::setupPageIndicatorMetrics() /* Pressed/Hovered are not yet implemented. - Sizes would be: + Sizes would be: - Q::Pressed : 3 - Q::Pressed | Q::Selected : 5 @@ -857,9 +871,11 @@ void Editor::setupPageIndicatorColors( void Editor::setupPopup( const QskFluent2Theme& theme ) { using Q = QskPopup; + using A = QskAspect; const auto& pal = theme.palette; + setHint( Q::Overlay | A::Style, true ); setGradient( Q::Overlay, pal.background.overlay.defaultColor ); } @@ -1045,7 +1061,7 @@ void Editor::setupRadioBoxMetrics() However the colors of the inner side of the border are not solid for the selected states and we use a dummy indicator to get this done. - How to solve this in a better way, TODO ... + How to solve this in a better way, TODO ... */ setBoxShape( Q::CheckIndicator, 100, Qt::RelativeSize ); @@ -1095,7 +1111,7 @@ void Editor::setupRadioBoxColors( if ( states & Q::Disabled ) textColor = pal.fillColor.text.disabled; - QRgb panelBorderColor; + QRgb panelBorderColor; if ( states & ( Q::Disabled | Q::Pressed ) ) panelBorderColor = pal.strokeColor.controlStrong.disabled; else @@ -1408,7 +1424,7 @@ void Editor::setupSliderColors( setBoxBorderGradient( Q::Handle, pal.elevation.circle.border, handleColor ); } - for ( auto state : { A::NoState , Q::Pressed , Q::Disabled } ) + for ( auto state : { A::NoState, Q::Pressed, Q::Disabled } ) { QRgb grooveColor, fillColor, rippleColor; @@ -1568,7 +1584,7 @@ void Editor::setupTabButtonColors( using Q = QskTabButton; const auto& pal = theme.palette; - for ( const auto state : { QskAspect::NoState, + for ( const auto state : { QskAspect::NoState, Q::Checked, Q::Hovered, Q::Pressed, Q::Disabled } ) { QRgb panelColor, textColor; @@ -1866,6 +1882,8 @@ void Editor::setupSwitchButtonColors( void Editor::setupSubWindow( const QskFluent2Theme& theme ) { using Q = QskSubWindow; + using A = QskAspect; + const auto& pal = theme.palette; setPadding( Q::Panel, { 0, 31, 0, 0 } ); @@ -1883,6 +1901,8 @@ void Editor::setupSubWindow( const QskFluent2Theme& theme ) setColor( Q::TitleBarText, pal.fillColor.text.primary ); setAlignment( Q::TitleBarText, Qt::AlignLeft ); setTextOptions( Q::TitleBarText, Qt::ElideRight, QskTextOptions::NoWrap ); + + setAnimation( Q::Panel | A::Position, 300, QEasingCurve::OutCubic ); } void Editor::setupVirtualKeyboardMetrics() diff --git a/skins/material3/QskMaterial3Skin.cpp b/skins/material3/QskMaterial3Skin.cpp index 957c1ba2..8443dbf5 100644 --- a/skins/material3/QskMaterial3Skin.cpp +++ b/skins/material3/QskMaterial3Skin.cpp @@ -378,11 +378,9 @@ void Editor::setupMenu() setColor( Q::Text, m_pal.onSurface ); setFontRole( Q::Text, QskMaterial3Skin::M3BodyMedium ); - setPosition( Q::Panel, 0 ); - setPosition( Q::Panel | QskPopup::Closed, 1_dp ); - - setAnimation( Q::Panel | A::Metric, 150 ); setAnimation( Q::Cursor | A::Position | A::Metric, 75, QEasingCurve::OutCubic ); + + setAnimation( Q::Panel | A::Position, 75 ); } void Editor::setupTextLabel() @@ -809,12 +807,10 @@ 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::Position, 300, QEasingCurve::OutCubic ); } void Editor::setupSlider() @@ -1264,6 +1260,7 @@ void Editor::setupSubWindow() for ( auto subControl : { Q::Panel, Q::TitleBarPanel, Q::TitleBarText } ) setAnimation( subControl | A::Color, qskDuration ); + setAnimation( Q::Panel | A::Position, qskDuration, QEasingCurve::OutCubic ); } QskMaterial3Theme::QskMaterial3Theme( QskSkin::ColorScheme colorScheme ) diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index d116d9db..19530b32 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -409,7 +409,7 @@ void Editor::setupPopup() using Q = QskPopup; setHint( Q::Overlay | A::Style, true ); - setGradient( Q::Overlay, QColor( 220, 220, 220, 150 ) ); + setGradient( Q::Overlay, qRgba( 220, 220, 220, 150 ) ); } void Editor::setupMenu() @@ -446,11 +446,8 @@ void Editor::setupMenu() setGraphicRole( Q::Icon | Q::Disabled, DisabledSymbol ); setGraphicRole( Q::Icon | Q::Selected, CursorSymbol ); - setPosition( Q::Panel, 0 ); - setPosition( Q::Panel | QskPopup::Closed, 1 ); - - setAnimation( Q::Panel | A::Metric, 150 ); setAnimation( Q::Cursor | A::Position | A::Metric, 75, QEasingCurve::OutCubic ); + setAnimation( Q::Panel | A::Position, 100 ); } void Editor::setupTextLabel() @@ -759,13 +756,13 @@ void Editor::setupDialogButtonBox() setBoxShape( Q::Panel, 2 ); } -void Editor::setupDrawer() { +void Editor::setupDrawer() +{ using Q = QskDrawer; + using A = QskAspect; - setPadding( Q::Panel, 5 ); - setGradient( Q::Panel, m_pal.darker125 ); - setAnimation( Q::Panel | QskAspect::Position, qskDuration ); - setHint( Q::Overlay | QskAspect::Style, false ); + setPanel( Q::Panel, Plain ); + setAnimation( Q::Panel | A::Position, 300, QEasingCurve::OutCubic ); } void Editor::setupTabButton() @@ -1127,8 +1124,12 @@ void Editor::setupSubWindow() setAlignment( Q::TitleBarText, Qt::AlignLeft | Qt::AlignVCenter ); +#if 1 for ( auto subControl : { Q::Panel, Q::TitleBarPanel, Q::TitleBarText } ) setAnimation( subControl | A::Color, qskDuration ); +#endif + + setAnimation( Q::Panel | A::Position, qskDuration, QEasingCurve::OutCubic ); } void Editor::setupSpinBox() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d725cb96..323932f7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -176,6 +176,7 @@ list(APPEND HEADERS controls/QskComboBoxSkinlet.h controls/QskControl.h controls/QskDrawer.h + controls/QskDrawerSkinlet.h controls/QskEvent.h controls/QskFlickAnimator.h controls/QskFocusIndicator.h @@ -280,6 +281,7 @@ list(APPEND SOURCES controls/QskControlPrivate.cpp controls/QskDirtyItemFilter.cpp controls/QskDrawer.cpp + controls/QskDrawerSkinlet.cpp controls/QskEvent.cpp controls/QskFlickAnimator.cpp controls/QskFocusIndicator.cpp diff --git a/src/controls/QskControl.cpp b/src/controls/QskControl.cpp index a293882b..9b3d5113 100644 --- a/src/controls/QskControl.cpp +++ b/src/controls/QskControl.cpp @@ -915,10 +915,16 @@ void QskControl::itemChange( QQuickItem::ItemChange change, break; } case QQuickItem::ItemChildAddedChange: + case QQuickItem::ItemChildRemovedChange: { - if ( autoLayoutChildren() && qskIsAdjustableByLayout( value.item ) ) - polish(); + if ( autoLayoutChildren() ) + { + if ( qskIsVisibleToLayout( value.item ) ) + resetImplicitSize(); + if ( qskIsAdjustableByLayout( value.item ) ) + polish(); + } break; } case QQuickItem::ItemActiveFocusHasChanged: diff --git a/src/controls/QskDrawer.cpp b/src/controls/QskDrawer.cpp index 905a5987..87890a8d 100644 --- a/src/controls/QskDrawer.cpp +++ b/src/controls/QskDrawer.cpp @@ -1,48 +1,268 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + #include "QskDrawer.h" #include "QskAspect.h" -#include "QskControl.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 QSK_SUBCONTROL( QskDrawer, Panel ) -QSK_SUBCONTROL( QskDrawer, Overlay ) + +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 +{ + // 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 ); + } + }; +} class QskDrawer::PrivateData { public: - QskControl* content = nullptr; - QskBox* contentBox = nullptr; - Qt::Edge edge = Qt::LeftEdge; + PrivateData( Qt::Edge edge ) + : edge( edge ) + { + } + + 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; }; QskDrawer::QskDrawer( QQuickItem* parentItem ) - : Inherited ( parentItem ) - , m_data( new PrivateData ) + : QskDrawer( Qt::LeftEdge, parentItem ) { +} + +QskDrawer::QskDrawer( Qt::Edge edge, QQuickItem* parentItem ) + : Inherited ( parentItem ) + , m_data( new PrivateData( edge ) ) +{ +#if 1 setZ( 1 ); +#endif setPopupFlag( PopupFlag::CloseOnPressOutside, true ); - m_data->contentBox = new QskBox(this); - m_data->contentBox->setSubcontrolProxy( QskBox::Panel, Panel ); - m_data->contentBox->setPanel( 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 ); - setSubcontrolProxy( Inherited::Overlay, Overlay ); + setAutoLayoutChildren( true ); + setInteractive( true ); - 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::fadingChanged, this, &QQuickItem::setClip ); } QskDrawer::~QskDrawer() { + delete m_data->listener; } Qt::Edge QskDrawer::edge() const @@ -60,79 +280,188 @@ void QskDrawer::setEdge( Qt::Edge edge ) edgeChanged( edge ); } -void QskDrawer::setContent( QskControl* content ) +void QskDrawer::setInteractive( bool on ) { - content->setParentItem( m_data->contentBox ); - if ( content->parent() == nullptr ) - content->setParent( m_data->contentBox ); - - m_data->content = content; -} - -void QskDrawer::updateLayout() -{ - if ( !isOpen() && !isFading() ) + if ( on == isInteractive() ) return; - const auto padding = paddingHint( Panel ); - - auto contentSize = m_data->content->preferredSize(); - contentSize = contentSize.grownBy( padding ); - - const auto parentSize = parentItem()->size(); - - switch( m_data->edge ) + if ( on ) { - case Qt::Edge::LeftEdge: - { - qreal x = metric( faderAspect() ) * contentSize.width() * -1.0; + m_data->gestureRecognizer = new GestureRecognizer( this ); + if ( parentItem() ) + qskCatchMouseEvents( parentItem() ); + } + else + { + // how to revert qskCatchMouseEvents properly ??? + delete m_data->gestureRecognizer; + m_data->gestureRecognizer = nullptr; + } - 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(); + Q_EMIT interactiveChanged( on ); +} - qskSetItemGeometry( m_data->contentBox, - x, 0, contentSize.width(), parentSize.height() ); - break; +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 ); } - case Qt::Edge::TopEdge: - { - qreal y = metric( faderAspect() ) * contentSize.height(); + return; + } - qskSetItemGeometry( m_data->contentBox, - 0, -y, parentSize.width(), contentSize.height() ); + 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() ); + + m_data->resetListener( this ); break; } - - case Qt::Edge::BottomEdge: + case QQuickItem::ItemVisibleHasChanged: { - qreal y = metric( faderAspect() ) * contentSize.height() + parentSize.height() - - contentSize.height(); - - qskSetItemGeometry( m_data->contentBox, - 0, y, parentSize.width(), contentSize.height() ); + m_data->resetListener( this ); break; } } - - m_data->content->setGeometry( QPointF( padding.left(), padding.top() ), - m_data->contentBox->size().shrunkBy( padding ) ); - - Inherited::updateLayout(); } -void QskDrawer::aboutToShow() +void QskDrawer::updateResources() { - startTransition( Panel | QskAspect::Metric, - animationHint( Panel | QskAspect::Position ), 1.0, 0.0 ); + Inherited::updateResources(); - Inherited::aboutToShow(); + /* + 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" diff --git a/src/controls/QskDrawer.h b/src/controls/QskDrawer.h index 442f95e7..b6c852f2 100644 --- a/src/controls/QskDrawer.h +++ b/src/controls/QskDrawer.h @@ -1,8 +1,12 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + #ifndef QSK_DRAWER_H #define QSK_DRAWER_H #include "QskPopup.h" -#include class QSK_EXPORT QskDrawer : public QskPopup { @@ -12,24 +16,46 @@ 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 RESET resetDragMargin NOTIFY dragMarginChanged ) + + Q_PROPERTY( bool interactive READ isInteractive + WRITE setInteractive NOTIFY interactiveChanged ) + public: - QSK_SUBCONTROLS( Panel, Overlay ) + QSK_SUBCONTROLS( Panel ) QskDrawer( QQuickItem* = nullptr ); + QskDrawer( Qt::Edge, QQuickItem* = nullptr ); + ~QskDrawer() override; void setEdge( Qt::Edge ); Qt::Edge edge() const; - void updateLayout() override; + void setInteractive( bool ); + bool isInteractive() const; - void setContent( QskControl* ); + void setDragMargin( qreal ); + void resetDragMargin(); + qreal dragMargin() const; + + QRectF clipRect() const override; + QskAspect fadingAspect() const override; + + QRectF layoutRectForSize( const QSizeF& ) const override; Q_SIGNALS: void edgeChanged( Qt::Edge ); + void dragMarginChanged( qreal ); + void interactiveChanged( bool ); protected: - void aboutToShow() override; + void gestureEvent( QskGestureEvent* ) override; + void itemChange( ItemChange, const ItemChangeData& ) override; + + void updateResources() override; + void updateNode( QSGNode* ) override; private: class PrivateData; diff --git a/src/controls/QskDrawerSkinlet.cpp b/src/controls/QskDrawerSkinlet.cpp new file mode 100644 index 00000000..e10f5923 --- /dev/null +++ b/src/controls/QskDrawerSkinlet.cpp @@ -0,0 +1,45 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskDrawerSkinlet.h" +#include "QskDrawer.h" + +QskDrawerSkinlet::QskDrawerSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ + appendNodeRoles( { PanelRole } ); +} + +QskDrawerSkinlet::~QskDrawerSkinlet() = default; + +QRectF QskDrawerSkinlet::subControlRect( + const QskSkinnable* skinnable, const QRectF& contentsRect, + QskAspect::Subcontrol subControl ) const +{ + if ( subControl == QskDrawer::Panel ) + return contentsRect; + + return Inherited::subControlRect( skinnable, contentsRect, subControl ); +} + +QSGNode* QskDrawerSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + if ( nodeRole == PanelRole ) + return updateBoxNode( skinnable, node, QskDrawer::Panel ); + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QSizeF QskDrawerSkinlet::sizeHint( const QskSkinnable* skinnable, + Qt::SizeHint which, const QSizeF& constraint ) const +{ + if ( which == Qt::PreferredSize ) + return skinnable->strutSizeHint( QskDrawer::Panel ); + + return Inherited::sizeHint( skinnable, which, constraint ); +} + +#include "moc_QskDrawerSkinlet.cpp" diff --git a/src/controls/QskDrawerSkinlet.h b/src/controls/QskDrawerSkinlet.h new file mode 100644 index 00000000..b6d1c771 --- /dev/null +++ b/src/controls/QskDrawerSkinlet.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_DRAWER_SKINLET_H +#define QSK_DRAWER_SKINLET_H + +#include "QskPopupSkinlet.h" + +class QSK_EXPORT QskDrawerSkinlet : public QskPopupSkinlet +{ + Q_GADGET + + using Inherited = QskPopupSkinlet; + + public: + enum NodeRole + { + ContentsRole = Inherited::RoleCount, + PanelRole, + + RoleCount + }; + + Q_INVOKABLE QskDrawerSkinlet( QskSkin* = nullptr ); + ~QskDrawerSkinlet() override; + + QRectF subControlRect( const QskSkinnable*, + const QRectF&, QskAspect::Subcontrol ) const override; + + QSizeF sizeHint( const QskSkinnable*, + Qt::SizeHint, const QSizeF& ) const override; + + protected: + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; +}; + +#endif diff --git a/src/controls/QskGestureRecognizer.cpp b/src/controls/QskGestureRecognizer.cpp index 07a44f2d..099307ab 100644 --- a/src/controls/QskGestureRecognizer.cpp +++ b/src/controls/QskGestureRecognizer.cpp @@ -68,7 +68,8 @@ class QskGestureRecognizer::PrivateData return watchedItem->acceptedMouseButtons(); } - QQuickItem* watchedItem = nullptr; + QPointer< QQuickItem > watchedItem = nullptr; + QPointer< QQuickItem > targetItem = nullptr; QVector< QMouseEvent* > pendingEvents; @@ -121,6 +122,16 @@ QQuickItem* QskGestureRecognizer::watchedItem() const return m_data->watchedItem; } +void QskGestureRecognizer::setTargetItem( QQuickItem* item ) +{ + m_data->targetItem = item; +} + +QQuickItem* QskGestureRecognizer::targetItem() const +{ + return m_data->targetItem; +} + void QskGestureRecognizer::setAcceptedMouseButtons( Qt::MouseButtons buttons ) { m_data->buttons = buttons; @@ -131,12 +142,9 @@ Qt::MouseButtons QskGestureRecognizer::acceptedMouseButtons() const return m_data->buttons; } -QRectF QskGestureRecognizer::gestureRect() const +bool QskGestureRecognizer::isAcceptedPos( const QPointF& pos ) const { - if ( m_data->watchedItem ) - return qskItemRect( m_data->watchedItem ); - - return QRectF( 0.0, 0.0, -1.0, -1.0 ); + return m_data->watchedItem && m_data->watchedItem->contains( pos ); } void QskGestureRecognizer::setRejectOnTimeout( bool on ) @@ -302,7 +310,7 @@ bool QskGestureRecognizer::processMouseEvent( if ( event->type() == QEvent::MouseButtonPress ) { - if ( !gestureRect().contains( pos ) ) + if ( !isAcceptedPos( pos ) ) return false; if ( m_data->state != Idle ) diff --git a/src/controls/QskGestureRecognizer.h b/src/controls/QskGestureRecognizer.h index 012aa839..c4ac00dc 100644 --- a/src/controls/QskGestureRecognizer.h +++ b/src/controls/QskGestureRecognizer.h @@ -22,6 +22,7 @@ class QSK_EXPORT QskGestureRecognizer : public QObject Q_PROPERTY( State state READ state NOTIFY stateChanged ) Q_PROPERTY( QQuickItem* watchedItem READ watchedItem WRITE setWatchedItem ) + Q_PROPERTY( QQuickItem* targetItem READ targetItem WRITE setTargetItem ) Q_PROPERTY( Qt::MouseButtons acceptedMouseButtons READ acceptedMouseButtons WRITE setAcceptedMouseButtons ) @@ -43,11 +44,16 @@ class QSK_EXPORT QskGestureRecognizer : public QObject QskGestureRecognizer( QObject* parent = nullptr ); ~QskGestureRecognizer() override; - bool eventFilter( QObject* object, QEvent* event) override; + bool eventFilter( QObject*, QEvent* ) override; + // the item where the gesture happens void setWatchedItem( QQuickItem* ); QQuickItem* watchedItem() const; + // the item processing the gesture events + void setTargetItem( QQuickItem* ); + QQuickItem* targetItem() const; + // Qt::NoButton means: all buttons accepted void setAcceptedMouseButtons( Qt::MouseButtons ); Qt::MouseButtons acceptedMouseButtons() const; @@ -67,7 +73,7 @@ class QSK_EXPORT QskGestureRecognizer : public QObject State state() const; - virtual QRectF gestureRect() const; + virtual bool isAcceptedPos( const QPointF& ) const; Q_SIGNALS: void stateChanged( State from, State to ); diff --git a/src/controls/QskHintAnimator.cpp b/src/controls/QskHintAnimator.cpp index db6e7253..cec53ccd 100644 --- a/src/controls/QskHintAnimator.cpp +++ b/src/controls/QskHintAnimator.cpp @@ -76,20 +76,28 @@ static inline QVariant qskAligned05( const QVariant& value ) #endif -static inline bool qskCheckReceiverThread( const QObject* receiver ) +static inline void qskSendAnimatorEvent( + const QskAspect aspect, int index, bool on, QObject* receiver ) { - /* - QskInputPanelSkinlet changes the skin state, what leads to - sending events from the wrong thread. Until we have fixed it - let's block sending the event to avoid running into assertions - in QCoreApplication::sendEvent - */ + const auto state = on ? QskAnimatorEvent::Started : QskAnimatorEvent::Terminated; - const QThread* thread = receiver->thread(); - if ( thread == nullptr ) - return true; - - return ( thread == QThread::currentThread() ); + const auto thread = receiver->thread(); + if ( thread && ( thread != QThread::currentThread() ) ) + { + /* + QskInputPanelSkinlet changes the skin state, what leads to + sending events from the wrong thread. We can't use + QCoreApplication::sendEvent then, TODO ... + */ + + auto event = new QskAnimatorEvent( aspect, index, state ); + QCoreApplication::postEvent( receiver, event ); + } + else + { + QskAnimatorEvent event( aspect, index, state ); + QCoreApplication::sendEvent( receiver, &event ); + } } QskHintAnimator::QskHintAnimator() noexcept @@ -141,7 +149,7 @@ void QskHintAnimator::advance( qreal progress ) { if ( m_updateFlags == QskAnimationHint::UpdateAuto ) { - if ( m_aspect.isMetric() ) + if ( !m_aspect.isColor() ) { m_control->resetImplicitSize(); @@ -338,11 +346,7 @@ void QskHintAnimatorTable::start( QskControl* control, animator->start(); - if ( qskCheckReceiverThread( control ) ) - { - QskAnimatorEvent event( aspect, index, QskAnimatorEvent::Started ); - QCoreApplication::sendEvent( control, &event ); - } + qskSendAnimatorEvent( aspect, index, true, control ); } const QskHintAnimator* QskHintAnimatorTable::animator( QskAspect aspect, int index ) const @@ -390,15 +394,7 @@ bool QskHintAnimatorTable::cleanup() it = animators.erase( it ); if ( control ) - { - if ( qskCheckReceiverThread( control ) ) - { - auto event = new QskAnimatorEvent( - aspect, index, QskAnimatorEvent::Terminated ); - - QCoreApplication::postEvent( control, event ); - } - } + qskSendAnimatorEvent( aspect, index, false, control ); } else { diff --git a/src/controls/QskListViewSkinlet.h b/src/controls/QskListViewSkinlet.h index e26f4cb1..c16cc637 100644 --- a/src/controls/QskListViewSkinlet.h +++ b/src/controls/QskListViewSkinlet.h @@ -24,7 +24,7 @@ class QSK_EXPORT QskListViewSkinlet : public QskScrollViewSkinlet public: enum NodeRole { - TextRole, + TextRole = Inherited::RoleCount, GraphicRole, RoleCount diff --git a/src/controls/QskMainView.cpp b/src/controls/QskMainView.cpp index ced25d56..cad52776 100644 --- a/src/controls/QskMainView.cpp +++ b/src/controls/QskMainView.cpp @@ -103,7 +103,7 @@ void QskMainView::focusInEvent( QFocusEvent* event ) { if ( auto focusItem = nextItemInFocusChain( true ) ) { - if ( qskIsItemComplete( focusItem ) + if ( !qskIsItemInDestructor( focusItem ) && qskIsAncestorOf( this, focusItem ) ) { focusItem->setFocus( true ); diff --git a/src/controls/QskMenu.cpp b/src/controls/QskMenu.cpp index 7390e317..e3af5707 100644 --- a/src/controls/QskMenu.cpp +++ b/src/controls/QskMenu.cpp @@ -18,7 +18,10 @@ #include #include -QSK_SUBCONTROL( QskMenu, Overlay ) +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + QSK_SUBCONTROL( QskMenu, Panel ) QSK_SUBCONTROL( QskMenu, Segment ) QSK_SUBCONTROL( QskMenu, Cursor ) @@ -58,20 +61,23 @@ QskMenu::QskMenu( QQuickItem* parent ) , m_data( new PrivateData ) { setModal( true ); - setFaderAspect( QskMenu::Panel | QskAspect::Position | QskAspect::Metric ); setPopupFlag( QskPopup::CloseOnPressOutside, true ); setPopupFlag( QskPopup::DeleteOnClose, true ); + setPlacementPolicy( QskPlacementPolicy::Ignore ); setSubcontrolProxy( Inherited::Overlay, Overlay ); initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); - // we hide the focus indicator while fading - connect( this, &QskMenu::fadingChanged, this, - &QskControl::focusIndicatorRectChanged ); + // we hide the focus indicator while sliding + connect( this, &QskPopup::fadingChanged, + this, &QskControl::focusIndicatorRectChanged ); - connect( this, &QskMenu::opened, this, + connect( this, &QskPopup::fadingChanged, + this, &QQuickItem::setClip ); + + connect( this, &QskPopup::opened, this, [this]() { m_data->triggeredIndex = -1; } ); setAcceptHoverEvents( true ); @@ -81,6 +87,17 @@ QskMenu::~QskMenu() { } +QRectF QskMenu::clipRect() const +{ + if ( isFading() ) + { + constexpr qreal d = 1e6; + return QRectF( -d, m_data->origin.y() - y(), 2.0 * d, d ); + } + + return Inherited::clipRect(); +} + #if 1 // has no effect as we do not offer submenus yet. TODO ... @@ -263,25 +280,57 @@ QString QskMenu::triggeredText() const return optionAt( m_data->triggeredIndex ).text(); } +void QskMenu::updateResources() +{ + qreal dy = 0.0; + if ( isFading() ) + dy = ( 1.0 - fadingFactor() ) * height(); + + setPosition( m_data->origin.x(), m_data->origin.y() - dy ); + + Inherited::updateResources(); +} + +void QskMenu::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 ); +} + void QskMenu::keyPressEvent( QKeyEvent* event ) { if( m_data->currentIndex < 0 ) return; - int key = event->key(); - - switch( key ) + switch( event->key() ) { case Qt::Key_Up: { traverse( -1 ); - break; + return; } case Qt::Key_Down: { traverse( 1 ); - break; + return; } case Qt::Key_Select: @@ -293,19 +342,17 @@ void QskMenu::keyPressEvent( QKeyEvent* event ) return; } - case Qt::Key_Escape: - case Qt::Key_Cancel: - { - close(); - return; - } - default: { if ( const int steps = qskFocusChainIncrement( event ) ) + { traverse( steps ); + return; + } } } + + return Inherited::keyPressEvent( event ); } void QskMenu::keyReleaseEvent( QKeyEvent* ) @@ -439,7 +486,7 @@ void QskMenu::mouseReleaseEvent( QMouseEvent* event ) void QskMenu::aboutToShow() { - setGeometry( QRectF( m_data->origin, sizeConstraint() ) ); + setSize( sizeConstraint() ); if ( m_data->currentIndex < 0 ) { @@ -496,6 +543,11 @@ void QskMenu::trigger( int index ) } } +QskAspect QskMenu::fadingAspect() const +{ + return QskMenu::Panel | QskAspect::Position; +} + int QskMenu::exec() { (void) execPopup(); diff --git a/src/controls/QskMenu.h b/src/controls/QskMenu.h index bc4cb2ca..b0042455 100644 --- a/src/controls/QskMenu.h +++ b/src/controls/QskMenu.h @@ -37,7 +37,7 @@ class QSK_EXPORT QskMenu : public QskPopup using Inherited = QskPopup; public: - QSK_SUBCONTROLS( Overlay, Panel, Segment, Cursor, Text, Icon, Separator ) + QSK_SUBCONTROLS( Panel, Segment, Cursor, Text, Icon, Separator ) QSK_STATES( Selected, Pressed ) QskMenu( QQuickItem* parentItem = nullptr ); @@ -81,6 +81,9 @@ class QSK_EXPORT QskMenu : public QskPopup bool isPressed() const; + QRectF clipRect() const override; + QskAspect fadingAspect() const override; + Q_INVOKABLE int exec(); Q_SIGNALS: @@ -115,6 +118,9 @@ class QSK_EXPORT QskMenu : public QskPopup void aboutToShow() override; void trigger( int ); + void updateResources() override; + void updateNode( QSGNode* ) override; + private: void traverse( int steps ); diff --git a/src/controls/QskMenuSkinlet.cpp b/src/controls/QskMenuSkinlet.cpp index 0629b137..dae18d89 100644 --- a/src/controls/QskMenuSkinlet.cpp +++ b/src/controls/QskMenuSkinlet.cpp @@ -9,12 +9,13 @@ #include "QskGraphic.h" #include "QskColorFilter.h" #include "QskTextOptions.h" -#include "QskSGNode.h" #include "QskFunctions.h" #include "QskMargins.h" #include "QskFunctions.h" #include "QskLabelData.h" +#include "QskSGNode.h" + #include #include @@ -209,11 +210,31 @@ QskMenuSkinlet::QskMenuSkinlet( QskSkin* skin ) : Inherited( skin ) , m_data( new PrivateData() ) { - appendNodeRoles( { PanelRole } ); + appendNodeRoles( { ContentsRole, PanelRole } ); } QskMenuSkinlet::~QskMenuSkinlet() = default; +QSGNode* QskMenuSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + switch ( nodeRole ) + { + case ContentsRole: + { + const auto popup = static_cast< const QskPopup* >( skinnable ); + + auto rect = popup->contentsRect(); + if ( rect.isEmpty() ) + return nullptr; + + return updateContentsNode( popup, node ); + } + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + QRectF QskMenuSkinlet::cursorRect( const QskSkinnable* skinnable, const QRectF& contentsRect, int index ) const { @@ -407,7 +428,8 @@ QskAspect::States QskMenuSkinlet::sampleStates( } } - const auto cursorPos = menu->effectiveSkinHint( Q::Segment | Q::Hovered | A::Metric | A::Position ).toPointF(); + const auto cursorPos = menu->effectiveSkinHint( + Q::Segment | Q::Hovered | A::Metric | A::Position ).toPointF(); if( !cursorPos.isNull() && menu->indexAtPosition( cursorPos ) == index ) { diff --git a/src/controls/QskMenuSkinlet.h b/src/controls/QskMenuSkinlet.h index 4ebe5284..34a3c0b5 100644 --- a/src/controls/QskMenuSkinlet.h +++ b/src/controls/QskMenuSkinlet.h @@ -20,7 +20,9 @@ class QSK_EXPORT QskMenuSkinlet : public QskPopupSkinlet public: enum NodeRole { - PanelRole = QskPopupSkinlet::RoleCount, + ContentsRole = Inherited::RoleCount, + PanelRole, + RoleCount }; @@ -48,7 +50,10 @@ class QSK_EXPORT QskMenuSkinlet : public QskPopupSkinlet Qt::SizeHint, const QSizeF& ) const override; protected: - QSGNode* updateContentsNode( const QskPopup*, QSGNode* ) const override; + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; + + QSGNode* updateContentsNode( const QskPopup*, QSGNode* ) const; QSGNode* updateMenuNode( const QskSkinnable*, QSGNode* ) const; QSGNode* updateSampleNode( const QskSkinnable*, diff --git a/src/controls/QskPanGestureRecognizer.cpp b/src/controls/QskPanGestureRecognizer.cpp index f7db9f5a..859b8d91 100644 --- a/src/controls/QskPanGestureRecognizer.cpp +++ b/src/controls/QskPanGestureRecognizer.cpp @@ -7,10 +7,11 @@ #include "QskEvent.h" #include "QskGesture.h" -#include #include #include #include +#include +#include static inline bool qskIsInOrientation( const QPointF& from, const QPointF& to, Qt::Orientations orientations ) @@ -60,9 +61,14 @@ static inline qreal qskAngle( } static void qskSendPanGestureEvent( - QQuickItem* item, QskGesture::State state, qreal velocity, qreal angle, - const QPointF& origin, const QPointF& lastPosition, const QPointF& position ) + QskGestureRecognizer* recognizer, QskGesture::State state, + qreal velocity, qreal angle, const QPointF& origin, + const QPointF& lastPosition, const QPointF& position ) { + auto item = recognizer->targetItem(); + if ( item == nullptr ) + item = recognizer->watchedItem(); + auto gesture = std::make_shared< QskPanGesture >(); gesture->setState( state ); @@ -146,7 +152,7 @@ class QskPanGestureRecognizer::PrivateData public: Qt::Orientations orientations = Qt::Horizontal | Qt::Vertical; - int minDistance = 15; + int minDistance = QGuiApplication::styleHints()->startDragDistance() + 5; quint64 timestampVelocity = 0.0; // timestamp of the last mouse event qreal angle = 0.0; @@ -243,12 +249,12 @@ void QskPanGestureRecognizer::processMove( const QPointF& pos, quint64 timestamp if ( started ) { - qskSendPanGestureEvent( watchedItem(), QskGesture::Started, + qskSendPanGestureEvent( this, QskGesture::Started, velocity, m_data->angle, m_data->origin, m_data->origin, m_data->pos ); } else { - qskSendPanGestureEvent( watchedItem(), QskGesture::Updated, + qskSendPanGestureEvent( this, QskGesture::Updated, velocity, m_data->angle, m_data->origin, oldPos, m_data->pos ); } } @@ -261,7 +267,7 @@ void QskPanGestureRecognizer::processRelease( const QPointF&, quint64 timestamp const ulong elapsedTotal = timestamp - timestampStarted(); const qreal velocity = m_data->velocityTracker.velocity( elapsedTotal ); - qskSendPanGestureEvent( watchedItem(), QskGesture::Finished, + qskSendPanGestureEvent( this, QskGesture::Finished, velocity, m_data->angle, m_data->origin, m_data->pos, m_data->pos ); } } diff --git a/src/controls/QskPanGestureRecognizer.h b/src/controls/QskPanGestureRecognizer.h index 26a72576..ba3bb121 100644 --- a/src/controls/QskPanGestureRecognizer.h +++ b/src/controls/QskPanGestureRecognizer.h @@ -23,11 +23,12 @@ class QSK_EXPORT QskPanGestureRecognizer : public QskGestureRecognizer void setOrientations( Qt::Orientations ); Qt::Orientations orientations() const; - private: + protected: void processPress( const QPointF&, quint64 timestamp, bool isFinal ) override; void processMove( const QPointF&, quint64 timestamp ) override; void processRelease( const QPointF&, quint64 timestamp ) override; + private: class PrivateData; std::unique_ptr< PrivateData > m_data; }; diff --git a/src/controls/QskPopup.cpp b/src/controls/QskPopup.cpp index c3292083..45e73c91 100644 --- a/src/controls/QskPopup.cpp +++ b/src/controls/QskPopup.cpp @@ -10,6 +10,7 @@ #include "QskWindow.h" #include "QskEvent.h" #include "QskPlatform.h" +#include "QskHintAnimator.h" #include @@ -75,6 +76,23 @@ static bool qskReplayMousePress() return false; } +static void qskStartFading( QskPopup* popup, bool on ) +{ + const auto aspect = popup->fadingAspect(); + + auto hint = popup->animationHint( aspect ); + + if ( hint.isValid() ) + { + hint.updateFlags = QskAnimationHint::UpdatePolish | QskAnimationHint::UpdateNode; + + const qreal from = on ? 0.0 : 1.0; + const qreal to = on ? 1.0 : 0.0; + + popup->startTransition( aspect, hint, from, to ); + } +} + namespace { class InputGrabber final : public QskInputGrabber @@ -130,7 +148,6 @@ class QskPopup::PrivateData PrivateData() : flags( 0 ) , isModal( false ) - , hasFaderEffect( true ) , autoGrabFocus( true ) , handoverFocus( true ) { @@ -139,11 +156,9 @@ class QskPopup::PrivateData InputGrabber* inputGrabber = nullptr; uint priority = 0; - QskAspect faderAspect; int flags : 4; bool isModal : 1; - bool hasFaderEffect : 1; const bool autoGrabFocus : 1; const bool handoverFocus : 1; @@ -196,6 +211,11 @@ void QskPopup::close() setOpen( false ); } +void QskPopup::toggle() +{ + setOpen( !isOpen() ); +} + void QskPopup::setOpen( bool on ) { if ( on == isOpen() ) @@ -213,6 +233,8 @@ void QskPopup::setOpen( bool on ) else Q_EMIT closed(); + qskStartFading( this, on ); + if ( isFading() ) { Q_EMIT fadingChanged( true ); @@ -234,15 +256,22 @@ bool QskPopup::isOpen() const return !hasSkinState( QskPopup::Closed ); } +QskAspect QskPopup::fadingAspect() const +{ + return QskAspect(); +} + bool QskPopup::isFading() const { - if ( m_data->faderAspect.value() == 0 ) - return false; + return runningHintAnimator( fadingAspect() ) != nullptr; +} - QskSkinHintStatus status; - (void) effectiveSkinHint( m_data->faderAspect, &status ); +qreal QskPopup::fadingFactor() const +{ + if ( auto animator = runningHintAnimator( fadingAspect() ) ) + return animator->currentValue().value< qreal >(); - return status.source == QskSkinHintStatus::Animator; + return isOpen() ? 1.0 : 0.0; } QRectF QskPopup::overlayRect() const @@ -293,44 +322,23 @@ void QskPopup::updateInputGrabber() } } -QskAspect QskPopup::faderAspect() const -{ - return m_data->faderAspect; -} - -void QskPopup::setFaderAspect( QskAspect aspect ) -{ - auto faderAspect = aspect; - faderAspect.clearStates(); // animated values are always stateless - - if ( faderAspect == m_data->faderAspect ) - return; - - if ( isFading() ) - { - // stop the running animation TODO ... - } - - m_data->faderAspect = faderAspect; -} - bool QskPopup::isTransitionAccepted( QskAspect aspect ) const { - if ( isVisible() && m_data->hasFaderEffect ) + if ( isVisible() && !isInitiallyPainted() ) { + /* + Usually we suppress transitions, when a control has never been + painted before as there is no valid starting point. Popups are + different as we want to have smooth fade/slide appearances. + */ if ( ( aspect.value() == 0 ) ) - { - return true; - } - - if ( aspect == m_data->faderAspect ) return true; - if ( aspect.isColor() ) - { - if ( aspect.subControl() == effectiveSubcontrol( QskPopup::Overlay ) ) - return true; - } + if ( aspect.subControl() == effectiveSubcontrol( fadingAspect().subControl() ) ) + return true; + + if ( aspect.subControl() == effectiveSubcontrol( QskPopup::Overlay ) ) + return true; } return Inherited::isTransitionAccepted( aspect ); @@ -366,20 +374,6 @@ bool QskPopup::isModal() const return m_data->isModal; } -void QskPopup::setFaderEffect( bool on ) -{ - if ( on != m_data->hasFaderEffect ) - { - m_data->hasFaderEffect = on; - Q_EMIT faderEffectChanged( on ); - } -} - -bool QskPopup::hasFaderEffect() const -{ - return m_data->hasFaderEffect; -} - void QskPopup::setPopupFlags( PopupFlags flags ) { const auto newFlags = static_cast< int >( flags ); @@ -488,10 +482,10 @@ bool QskPopup::event( QEvent* event ) } case QskEvent::Animator: { - const auto animtorEvent = static_cast< QskAnimatorEvent* >( event ); + const auto animatorEvent = static_cast< QskAnimatorEvent* >( event ); - if ( ( animtorEvent->state() == QskAnimatorEvent::Terminated ) - && ( animtorEvent->aspect() == m_data->faderAspect ) ) + if ( ( animatorEvent->state() == QskAnimatorEvent::Terminated ) + && ( animatorEvent->aspect() == fadingAspect() ) ) { if ( !isOpen() ) { @@ -523,6 +517,17 @@ bool QskPopup::event( QEvent* event ) return ok; } +void QskPopup::keyPressEvent( QKeyEvent* event ) +{ + if ( qskIsStandardKeyInput( event, QKeySequence::Cancel ) ) + { + close(); + return; + } + + return Inherited::keyPressEvent( event ); +} + void QskPopup::focusInEvent( QFocusEvent* event ) { Inherited::focusInEvent( event ); @@ -543,7 +548,7 @@ void QskPopup::focusInEvent( QFocusEvent* event ) if ( auto focusItem = nextItemInFocusChain( true ) ) { - if ( qskIsItemComplete( focusItem ) + if ( !qskIsItemInDestructor( focusItem ) && qskIsAncestorOf( this, focusItem ) ) { focusItem->setFocus( true ); diff --git a/src/controls/QskPopup.h b/src/controls/QskPopup.h index 8beaed80..caf66346 100644 --- a/src/controls/QskPopup.h +++ b/src/controls/QskPopup.h @@ -14,13 +14,11 @@ class QSK_EXPORT QskPopup : public QskControl Q_PROPERTY( bool open READ isOpen WRITE setOpen NOTIFY openChanged ) Q_PROPERTY( bool modal READ isModal WRITE setModal NOTIFY modalChanged ) + Q_PROPERTY( bool fading READ isFading NOTIFY fadingChanged ) Q_PROPERTY( bool overlay READ hasOverlay WRITE setOverlay RESET resetOverlay NOTIFY overlayChanged ) - Q_PROPERTY( bool faderEffect READ hasFaderEffect - WRITE setFaderEffect NOTIFY faderEffectChanged ) - Q_PROPERTY( uint priority READ priority WRITE setPriority NOTIFY priorityChanged ) using Inherited = QskControl; @@ -58,20 +56,20 @@ class QSK_EXPORT QskPopup : public QskControl void setPriority( uint ); uint priority() const; - void setFaderEffect( bool ); - bool hasFaderEffect() const; + bool isOpen() const; + bool isClosed() const; - QskAspect faderAspect() const; - void setFaderAspect( QskAspect ); + bool isFading() const; + qreal fadingFactor() const; + virtual QskAspect fadingAspect() const; virtual QRectF overlayRect() const; - bool isOpen() const; - bool isFading() const; - public Q_SLOTS: void open(); void close(); + void toggle(); + void setOpen( bool ); Q_SIGNALS: @@ -83,7 +81,6 @@ class QSK_EXPORT QskPopup : public QskControl void modalChanged( bool ); void overlayChanged( bool ); void priorityChanged( uint ); - void faderEffectChanged( bool ); protected: void aboutToShow() override; @@ -92,6 +89,7 @@ class QSK_EXPORT QskPopup : public QskControl bool event( QEvent* ) override; void focusInEvent( QFocusEvent* ) override; void focusOutEvent( QFocusEvent* ) override; + void keyPressEvent( QKeyEvent* ) override; void windowChangeEvent( QskWindowChangeEvent* ) override; void itemChange( QQuickItem::ItemChange, @@ -113,6 +111,11 @@ class QSK_EXPORT QskPopup : public QskControl std::unique_ptr< PrivateData > m_data; }; +inline bool QskPopup::isClosed() const +{ + return !isOpen(); +} + Q_DECLARE_OPERATORS_FOR_FLAGS( QskPopup::PopupFlags ) #endif diff --git a/src/controls/QskPopupSkinlet.cpp b/src/controls/QskPopupSkinlet.cpp index 70cf300e..b989dc1e 100644 --- a/src/controls/QskPopupSkinlet.cpp +++ b/src/controls/QskPopupSkinlet.cpp @@ -5,108 +5,17 @@ #include "QskPopupSkinlet.h" #include "QskPopup.h" -#include "QskSGNode.h" +#include "QskRgbValue.h" -#include -#include -#include - -namespace +static inline QRgb qskInterpolatedRgb( QRgb rgb, qreal factor ) { - class RootNode : public QSGNode - { - public: - ~RootNode() override - { - delete m_clipNode; - delete m_transformNode; - delete m_contentsNode; - } - - void setClipRect( const QRectF& rect ) - { - if ( m_clipNode == nullptr ) - { - m_clipNode = new QSGClipNode(); - m_clipNode->setFlag( QSGNode::OwnedByParent, false ); - m_clipNode->setIsRectangular( true ); - } - - m_clipNode->setClipRect( rect ); - } - - void resetClip() - { - delete m_clipNode; - m_clipNode = nullptr; - } - - void setTranslation( qreal dx, qreal dy ) - { - if ( dx != 0.0 || dy != 0.0 ) - { - if ( m_transformNode == nullptr ) - { - m_transformNode = new QSGTransformNode(); - m_transformNode->setFlag( QSGNode::OwnedByParent, false ); - } - - QTransform transform; - transform.translate( dx, dy ); - - m_transformNode->setMatrix( transform ); - } - else - { - delete m_transformNode; - m_transformNode = nullptr; - } - } - - void setContentsNode( QSGNode* contentsNode ) - { - if ( m_contentsNode != contentsNode ) - { - if ( contentsNode ) - contentsNode->setFlag( QSGNode::OwnedByParent, false ); - - delete m_contentsNode; - m_contentsNode = contentsNode; - } - } - - void rearrangeNodes() - { - const std::initializer_list< QSGNode* > nodes = - { m_clipNode, m_transformNode, m_contentsNode }; - - QSGNode* parentNode = this; - for ( auto node : nodes ) - { - if ( node ) - { - QskSGNode::setParentNode( node, parentNode ); - parentNode = node; - } - } - } - - inline QSGNode* contentsNode() - { - return m_contentsNode; - } - - private: - QSGClipNode* m_clipNode = nullptr; - QSGTransformNode* m_transformNode = nullptr; - QSGNode* m_contentsNode = nullptr; - }; + return QskRgb::toTransparent( rgb, qRound( factor * qAlpha( rgb ) ) ); } QskPopupSkinlet::QskPopupSkinlet( QskSkin* skin ) : Inherited( skin ) { - appendNodeRoles( { OverlayRole, ContentsRole } ); + appendNodeRoles( { OverlayRole } ); } QskPopupSkinlet::~QskPopupSkinlet() = default; @@ -130,49 +39,38 @@ QSGNode* QskPopupSkinlet::updateSubNode( switch ( nodeRole ) { case OverlayRole: - return updateBoxNode( skinnable, node, QskPopup::Overlay ); - - case ContentsRole: - return updateExtraNode( popup, node ); + return updateOverlayNode( popup, node ); } return Inherited::updateSubNode( skinnable, nodeRole, node ); } -QSGNode* QskPopupSkinlet::updateExtraNode( const QskPopup* popup, QSGNode* node ) const +QSGNode* QskPopupSkinlet::updateOverlayNode( + const QskPopup* popup, QSGNode* node ) const { - auto cr = popup->contentsRect(); - if ( cr.isEmpty() ) + using Q = QskPopup; + + const auto factor = popup->fadingFactor(); + if ( factor <= 0.0 ) return nullptr; - auto rootNode = QskSGNode::ensureNode< RootNode >( node ); + const auto rect = popup->subControlRect( Q::Overlay ); + if ( rect.isEmpty() ) + return nullptr; - const auto faderProgress = popup->metric( popup->faderAspect() ); - if ( faderProgress > 0.0 && faderProgress <= 1.0 ) - { - auto clipRect = QRectF( popup->mapFromScene( QPointF() ), popup->window()->size() ); - clipRect.setTop( cr.top() ); + auto gradient = popup->gradientHint( Q::Overlay ); - rootNode->setClipRect( clipRect ); - } - else + if ( gradient.isVisible() && factor != 1.0 ) { - rootNode->resetClip(); + auto stops = gradient.stops(); + + for ( auto& stop : stops ) + stop.setRgb( qskInterpolatedRgb( stop.rgb(), factor ) ); + + gradient.setStops( stops ); } - rootNode->setTranslation( 0.0, -faderProgress * cr.height() ); - - auto contentsNode = updateContentsNode( popup, rootNode->contentsNode() ); - rootNode->setContentsNode( contentsNode ); - - rootNode->rearrangeNodes(); - - return rootNode; -} - -QSGNode* QskPopupSkinlet::updateContentsNode( const QskPopup*, QSGNode* ) const -{ - return nullptr; + return updateBoxNode( popup, node, rect, gradient, QskPopup::Overlay ); } #include "moc_QskPopupSkinlet.cpp" diff --git a/src/controls/QskPopupSkinlet.h b/src/controls/QskPopupSkinlet.h index 076f86f9..ecd3a758 100644 --- a/src/controls/QskPopupSkinlet.h +++ b/src/controls/QskPopupSkinlet.h @@ -20,8 +20,6 @@ class QSK_EXPORT QskPopupSkinlet : public QskSkinlet enum NodeRole { OverlayRole, - ContentsRole, - RoleCount }; @@ -35,10 +33,7 @@ class QSK_EXPORT QskPopupSkinlet : public QskSkinlet QSGNode* updateSubNode( const QskSkinnable*, quint8 nodeRole, QSGNode* ) const override; - virtual QSGNode* updateContentsNode( const QskPopup*, QSGNode* ) const; - private: - QSGNode* updateExtraNode( const QskPopup*, QSGNode* ) const; QSGNode* updateOverlayNode( const QskPopup*, QSGNode* ) const; }; diff --git a/src/controls/QskQuick.cpp b/src/controls/QskQuick.cpp index 436f6f88..6a8f045f 100644 --- a/src/controls/QskQuick.cpp +++ b/src/controls/QskQuick.cpp @@ -47,6 +47,21 @@ bool qskIsItemComplete( const QQuickItem* item ) return QQuickItemPrivate::get( item )->componentComplete; } +bool qskIsItemInDestructor( const QQuickItem* item ) +{ + auto d = QQuickItemPrivate::get( item ); + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 5, 0 ) + return d->inDestructor; +#else + /* + QskQuickItem sets componentComplete to false in its destructor, + but for other items we will will return the wrong information + */ + return !d->componentComplete; +#endif +} + bool qskIsAncestorOf( const QQuickItem* item, const QQuickItem* child ) { if ( item == nullptr || child == nullptr ) @@ -356,7 +371,7 @@ QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* item ) return QList< QQuickItem* >(); } -const QSGNode* qskItemNode( const QQuickItem* item ) +const QSGTransformNode* qskItemNode( const QQuickItem* item ) { if ( item == nullptr ) return nullptr; diff --git a/src/controls/QskQuick.h b/src/controls/QskQuick.h index 4141483e..6b2ae2df 100644 --- a/src/controls/QskQuick.h +++ b/src/controls/QskQuick.h @@ -16,6 +16,7 @@ class QskSizePolicy; class QQuickItem; class QSGNode; +class QSGTransformNode; class QRectF; template< typename T > class QList; @@ -24,6 +25,7 @@ template< typename T > class QList; of QQuickItem. */ +QSK_EXPORT bool qskIsItemInDestructor( const QQuickItem* ); QSK_EXPORT bool qskIsItemComplete( const QQuickItem* ); QSK_EXPORT bool qskIsAncestorOf( const QQuickItem* item, const QQuickItem* child ); QSK_EXPORT bool qskIsTabFence( const QQuickItem* ); @@ -66,7 +68,7 @@ QSK_EXPORT QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* ); QSK_EXPORT void qskUpdateInputMethod( const QQuickItem*, Qt::InputMethodQueries ); QSK_EXPORT void qskInputMethodSetVisible( const QQuickItem*, bool ); -QSK_EXPORT const QSGNode* qskItemNode( const QQuickItem* ); +QSK_EXPORT const QSGTransformNode* qskItemNode( const QQuickItem* ); QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* ); QSK_EXPORT void qskItemUpdateRecursive( QQuickItem* ); diff --git a/src/controls/QskQuickItem.cpp b/src/controls/QskQuickItem.cpp index 41be05a5..76d786ac 100644 --- a/src/controls/QskQuickItem.cpp +++ b/src/controls/QskQuickItem.cpp @@ -180,6 +180,11 @@ QskQuickItem::~QskQuickItem() We set componentComplete to false, so that operations that are triggered by detaching the item from its parent can be aware of the about-to-delete state. + + Note, that since Qt >= 6.5 this information is stored + in QQuickItemPrivate::inDestructor. + + s.a: qskIsItemInDestructor */ d_func()->componentComplete = false; diff --git a/src/controls/QskScrollBox.cpp b/src/controls/QskScrollBox.cpp index a2304842..a975baed 100644 --- a/src/controls/QskScrollBox.cpp +++ b/src/controls/QskScrollBox.cpp @@ -136,15 +136,15 @@ namespace setOrientations( Qt::Horizontal | Qt::Vertical ); } - QRectF gestureRect() const override + bool isAcceptedPos( const QPointF& pos ) const override { if ( auto scrollBox = qobject_cast< const QskScrollBox* >( watchedItem() ) ) { if ( qskIsScrollable( scrollBox, orientations() ) ) - return scrollBox->viewContentsRect(); + return scrollBox->viewContentsRect().contains( pos ); } - return QRectF( 0.0, 0.0, -1.0, -1.0 ); // empty + return false; } }; } diff --git a/src/controls/QskSkin.cpp b/src/controls/QskSkin.cpp index a24a212f..a791eb05 100644 --- a/src/controls/QskSkin.cpp +++ b/src/controls/QskSkin.cpp @@ -32,6 +32,9 @@ #include "QskComboBox.h" #include "QskComboBoxSkinlet.h" +#include "QskDrawer.h" +#include "QskDrawerSkinlet.h" + #include "QskFocusIndicator.h" #include "QskFocusIndicatorSkinlet.h" @@ -160,6 +163,7 @@ QskSkin::QskSkin( QObject* parent ) declareSkinlet< QskBox, QskBoxSkinlet >(); declareSkinlet< QskCheckBox, QskCheckBoxSkinlet >(); declareSkinlet< QskComboBox, QskComboBoxSkinlet >(); + declareSkinlet< QskDrawer, QskDrawerSkinlet >(); declareSkinlet< QskFocusIndicator, QskFocusIndicatorSkinlet >(); declareSkinlet< QskGraphicLabel, QskGraphicLabelSkinlet >(); declareSkinlet< QskListView, QskListViewSkinlet >(); diff --git a/src/controls/QskSkinManager.cpp b/src/controls/QskSkinManager.cpp index 218d2747..33864821 100644 --- a/src/controls/QskSkinManager.cpp +++ b/src/controls/QskSkinManager.cpp @@ -121,7 +121,7 @@ namespace scheme = Qt::ColorScheme::Unknown; } - const auto systemScheme = qGuiApp->styleHints()->colorScheme(); + const auto systemScheme = QGuiApplication::styleHints()->colorScheme(); if( scheme == systemScheme ) { diff --git a/src/controls/QskSkinnable.cpp b/src/controls/QskSkinnable.cpp index fa97a45f..e82f51b3 100644 --- a/src/controls/QskSkinnable.cpp +++ b/src/controls/QskSkinnable.cpp @@ -988,6 +988,23 @@ bool QskSkinnable::moveSkinHint( QskAspect aspect, const QVariant& value ) return moveSkinHint( aspect, effectiveSkinHint( aspect ), value ); } +const QskHintAnimator* QskSkinnable::runningHintAnimator( + QskAspect aspect, int index ) const +{ + const auto& animators = m_data->animators; + + if ( animators.isEmpty() ) + return nullptr; + + aspect = qskAnimatorAspect( aspect ); + + auto animator = animators.animator( aspect, index ); + if ( animator == nullptr && index >= 0 ) + animator = animators.animator( aspect, -1 ); + + return animator; +} + QVariant QskSkinnable::animatedHint( QskAspect aspect, QskSkinHintStatus* status ) const { @@ -1259,13 +1276,19 @@ bool QskSkinnable::isTransitionAccepted( QskAspect aspect ) const { Q_UNUSED( aspect ) - /* - Usually we only need smooth transitions, when state changes - happen while the skinnable is visible. There are few exceptions - like QskPopup::Closed, that is used to slide/fade in. - */ if ( auto control = qskControlCast( owningItem() ) ) - return control->isInitiallyPainted(); + { + /* + Usually we only need smooth transitions, when state changes + happen while the skinnable is visible. There are few exceptions + like QskPopup::Closed, that is used to slide/fade in. + */ + + if ( control->flags() & QQuickItem::ItemHasContents ) + return control->isInitiallyPainted(); + + return true; + } return false; } diff --git a/src/controls/QskSkinnable.h b/src/controls/QskSkinnable.h index 51d2f1a7..94886960 100644 --- a/src/controls/QskSkinnable.h +++ b/src/controls/QskSkinnable.h @@ -25,6 +25,7 @@ class QQuickItem; class QskArcMetrics; class QskControl; class QskAnimationHint; +class QskHintAnimator; class QskColorFilter; class QskBoxShapeMetrics; class QskBoxBorderMetrics; @@ -262,6 +263,8 @@ class QSK_EXPORT QskSkinnable bool startHintTransitions( const QVector< QskAspect::Subcontrol >&, QskAspect::States, QskAspect::States, int index = -1 ); + const QskHintAnimator* runningHintAnimator( QskAspect, int index = -1 ) const; + protected: virtual void updateNode( QSGNode* ); virtual bool isTransitionAccepted( QskAspect ) const; diff --git a/src/controls/QskSubWindow.cpp b/src/controls/QskSubWindow.cpp index c278a889..229b4000 100644 --- a/src/controls/QskSubWindow.cpp +++ b/src/controls/QskSubWindow.cpp @@ -253,4 +253,15 @@ void QskSubWindow::itemChange( QQuickItem::ItemChange change, } } +void QskSubWindow::updateResources() +{ + setOpacity( fadingFactor() ); + Inherited::updateResources(); +} + +QskAspect QskSubWindow::fadingAspect() const +{ + return QskSubWindow::Panel | QskAspect::Position; +} + #include "moc_QskSubWindow.cpp" diff --git a/src/controls/QskSubWindow.h b/src/controls/QskSubWindow.h index 47d08e5f..42afd11b 100644 --- a/src/controls/QskSubWindow.h +++ b/src/controls/QskSubWindow.h @@ -83,6 +83,7 @@ class QSK_EXPORT QskSubWindow : public QskPopup QRectF titleBarRect() const; QRectF layoutRectForSize( const QSizeF& ) const override; + QskAspect fadingAspect() const override; Q_SIGNALS: void decorationsChanged( Decorations ); @@ -95,6 +96,8 @@ class QSK_EXPORT QskSubWindow : public QskPopup bool event( QEvent* ) override; void updateLayout() override; + void updateResources() override; + QSizeF layoutSizeHint( Qt::SizeHint, const QSizeF& ) const override; void itemChange( QQuickItem::ItemChange, diff --git a/src/controls/QskSubWindowSkinlet.cpp b/src/controls/QskSubWindowSkinlet.cpp index beea972b..84e9a571 100644 --- a/src/controls/QskSubWindowSkinlet.cpp +++ b/src/controls/QskSubWindowSkinlet.cpp @@ -94,6 +94,18 @@ QSGNode* QskSubWindowSkinlet::updateSubNode( return nullptr; } + case OverlayRole: + { + /* + Overloading QskPopupSkinlet: as the opacity of the subwindow already + depends on the fadingFactor we do not want the additional opacity + adjustments for the overlay node. + Maybe we should have a flag that indicates if the popup does + opacity or geometry transitions, when fading TODO ... + */ + updateBoxNode( subWindow, node, Q::Overlay ); + break; + } } return Inherited::updateSubNode( skinnable, nodeRole, node ); diff --git a/src/controls/QskWindow.cpp b/src/controls/QskWindow.cpp index a0b7a3b5..f46c6da5 100644 --- a/src/controls/QskWindow.cpp +++ b/src/controls/QskWindow.cpp @@ -424,8 +424,18 @@ void QskWindow::exposeEvent( QExposeEvent* event ) void QskWindow::resizeEvent( QResizeEvent* event ) { + auto rootItem = contentItem(); + + const auto oldRect = qskItemGeometry( rootItem ); Inherited::resizeEvent( event ); + const auto newRect = qskItemGeometry( rootItem ); + if ( newRect != oldRect ) + { + QskGeometryChangeEvent event( newRect, oldRect ); + QCoreApplication::sendEvent( rootItem, &event ); + } + if ( isExposed() ) layoutItems(); } diff --git a/src/dialogs/QskDialog.cpp b/src/dialogs/QskDialog.cpp index cb1b5da7..d75b5b4e 100644 --- a/src/dialogs/QskDialog.cpp +++ b/src/dialogs/QskDialog.cpp @@ -77,6 +77,7 @@ static void qskSetupSubWindow( const QString& title, QskDialog::Actions actions, QskDialog::Action defaultAction, QskDialogSubWindow* subWindow ) { + subWindow->setPopupFlag( QskPopup::DeleteOnClose ); subWindow->setModal( true ); subWindow->setWindowTitle( title ); subWindow->setDialogActions( actions ); @@ -128,14 +129,14 @@ static QskDialog::Action qskMessageSubWindow( const QString& text, int symbolType, QskDialog::Actions actions, QskDialog::Action defaultAction ) { - QskMessageSubWindow subWindow( window->contentItem() ); - subWindow.setSymbolType( symbolType ); - subWindow.setText( text ); + auto subWindow = new QskMessageSubWindow( window->contentItem() ); + subWindow->setSymbolType( symbolType ); + subWindow->setText( text ); - qskSetupSubWindow( title, actions, defaultAction, &subWindow ); - ( void ) subWindow.exec(); + qskSetupSubWindow( title, actions, defaultAction, subWindow ); + ( void ) subWindow->exec(); - auto clickedAction = subWindow.clickedAction(); + auto clickedAction = subWindow->clickedAction(); if ( clickedAction == QskDialog::NoAction ) { // dialog might have been closed by the window menu @@ -172,16 +173,16 @@ static QString qskSelectSubWindow( QskDialog::Actions actions, QskDialog::Action defaultAction, const QStringList& entries, int selectedRow ) { - QskSelectionSubWindow subWindow( window->contentItem() ); - subWindow.setInfoText( text ); - subWindow.setEntries( entries ); - subWindow.setSelectedRow( selectedRow ); + auto subWindow = new QskSelectionSubWindow( window->contentItem() ); + subWindow->setInfoText( text ); + subWindow->setEntries( entries ); + subWindow->setSelectedRow( selectedRow ); QString selectedEntry; - qskSetupSubWindow( title, actions, defaultAction, &subWindow ); - if ( subWindow.exec() == QskDialog::Accepted ) - selectedEntry = subWindow.selectedEntry(); + qskSetupSubWindow( title, actions, defaultAction, subWindow ); + if ( subWindow->exec() == QskDialog::Accepted ) + selectedEntry = subWindow->selectedEntry(); return selectedEntry; } diff --git a/src/dialogs/QskDialogSubWindow.cpp b/src/dialogs/QskDialogSubWindow.cpp index 69e84ffb..5643fec7 100644 --- a/src/dialogs/QskDialogSubWindow.cpp +++ b/src/dialogs/QskDialogSubWindow.cpp @@ -278,15 +278,10 @@ void QskDialogSubWindow::keyPressEvent( QKeyEvent* event ) { auto button = m_data->buttonBox->defaultButton(); if ( button && button->isEnabled() ) + { button->click(); - } - - if ( qskIsStandardKeyInput( event, QKeySequence::Cancel ) ) - { - // using shortcuts instead ??? - - reject(); - return; + return; + } } Inherited::keyPressEvent( event );