better fading support for popups, being used in QskDrawer/QskMenu/QskSubWindow

This commit is contained in:
Uwe Rathmann 2023-11-03 18:13:24 +01:00
parent 3b4f167709
commit 1ab236de9f
21 changed files with 454 additions and 437 deletions

View File

@ -623,13 +623,14 @@ void Editor::setupDrawerMetrics()
using Q = QskDrawer;
using A = QskAspect;
#if 1
setAnimation( Q::Panel | A::Metric | A::Position, 200 );
#endif
setAnimation( Q::Panel | A::Position, 300, QEasingCurve::OutCubic );
}
void Editor::setupDrawerColors( QskAspect::Section, const QskFluent2Theme& )
void Editor::setupDrawerColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
setGradient( QskDrawer::Panel | section,
theme.palette.background.solid.base );
}
void Editor::setupFocusIndicatorMetrics()
@ -759,13 +760,7 @@ void Editor::setupMenuMetrics()
setStrutSize( Q::Icon, 12, 12 );
setPadding( Q::Icon, { 8, 8, 0, 8 } );
#if 1
setPosition( Q::Panel, 1 );
setPosition( Q::Panel | QskPopup::Closed, 0 );
// copied from Mat3 - what are the correct values for Fluent2 ???
setAnimation( Q::Panel | A::Metric, 150 );
#endif
setAnimation( Q::Panel | A::Position, 100 );
}
void Editor::setupMenuColors(
@ -876,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 );
}
@ -1885,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 } );
@ -1902,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()

View File

@ -378,11 +378,9 @@ void Editor::setupMenu()
setColor( Q::Text, m_pal.onSurface );
setFontRole( Q::Text, QskMaterial3Skin::M3BodyMedium );
setPosition( Q::Panel, 1 );
setPosition( Q::Panel | QskPopup::Closed, 0 );
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()
@ -811,7 +809,8 @@ void Editor::setupDrawer()
using Q = QskDrawer;
using A = QskAspect;
setAnimation( Q::Panel | A::Metric | A::Position, qskDuration );
setGradient( Q::Panel, m_pal.background );
setAnimation( Q::Panel | A::Position, 300, QEasingCurve::OutCubic );
}
void Editor::setupSlider()
@ -1261,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 )

View File

@ -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, 1 );
setPosition( Q::Panel | QskPopup::Closed, 0 );
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()
@ -761,10 +758,11 @@ void Editor::setupDialogButtonBox()
void Editor::setupDrawer()
{
using A = QskAspect;
using Q = QskDrawer;
using A = QskAspect;
setAnimation( Q::Panel | A::Metric | A::Position, qskDuration );
setPanel( Q::Panel, Plain );
setAnimation( Q::Panel | A::Position, 300, QEasingCurve::OutCubic );
}
void Editor::setupTabButton()
@ -1126,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()

View File

@ -115,7 +115,6 @@ list(APPEND HEADERS
nodes/QskRichTextRenderer.h
nodes/QskScaleRenderer.h
nodes/QskSGNode.h
nodes/QskSlideInNode.h
nodes/QskStrokeNode.h
nodes/QskStippledLineRenderer.h
nodes/QskShapeNode.h
@ -147,7 +146,6 @@ list(APPEND SOURCES
nodes/QskRichTextRenderer.cpp
nodes/QskScaleRenderer.cpp
nodes/QskSGNode.cpp
nodes/QskSlideInNode.cpp
nodes/QskStrokeNode.cpp
nodes/QskStippledLineRenderer.cpp
nodes/QskShapeNode.cpp
@ -178,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
@ -281,6 +280,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

View File

@ -5,7 +5,6 @@
#include "QskDrawer.h"
#include "QskAspect.h"
#include "QskAnimationHint.h"
#include "QskQuick.h"
#include "QskEvent.h"
@ -20,10 +19,6 @@ QSK_QT_PRIVATE_BEGIN
#include <private/qquickitemchangelistener_p.h>
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 )
static inline qreal qskDefaultDragMargin()
@ -63,77 +58,60 @@ static bool qskCheckDirection( Qt::Edge edge, const QskPanGesture* gesture )
return false;
}
static void qskLayoutDrawer( const QRectF& rect, QskDrawer* drawer )
static inline QRectF qskAlignedToEdge(
const QRectF& r, const QSizeF& sz, Qt::Edge edge )
{
const auto size = qskSizeConstraint( drawer, Qt::PreferredSize );
switch( edge )
{
case Qt::LeftEdge:
return QRectF( r.left(), r.top(), sz.width(), r.height() );
QRectF r( 0.0, 0.0, size.width(), size.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:
{
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 );
}
static inline QRectF qskSlidingRect(
const QSizeF& size, Qt::Edge edge, qreal ratio )
{
auto x = 0.0;
auto y = 0.0;
ratio = 1.0 - ratio;
switch( edge )
{
case Qt::LeftEdge:
x = -ratio * size.width();
dx = -ratio * size.width();
break;
case Qt::RightEdge:
x = ratio * size.width();
dx = ratio * size.width();
break;
case Qt::TopEdge:
y = -ratio * size.height();
dy = -ratio * size.height();
break;
case Qt::BottomEdge:
y = ratio * size.height();
dy = ratio * size.height();
break;
}
return QRectF( x, y, size.width(), size.height() );
return QPointF( dx, dy );
}
namespace
{
// Using an eventFilter for QskEvent::GeometryChange instead ???
class GeometryListener final : public QQuickItemChangeListener
{
public:
@ -160,14 +138,7 @@ namespace
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
m_adjustedItem->polish();
}
void setEnabled( bool on )
@ -184,10 +155,7 @@ namespace
QQuickItem* m_item;
QQuickItem* m_adjustedItem;
};
}
namespace
{
class GestureRecognizer : public QskPanGestureRecognizer
{
using Inherited = QskPanGestureRecognizer;
@ -204,7 +172,7 @@ namespace
bool isAcceptedPos( const QPointF& pos ) const override
{
auto drawer = qobject_cast< const QskDrawer* >( targetItem() );
if ( drawer->isTransitioning() )
if ( drawer->isFading() )
return false;
auto rect = qskItemRect( watchedItem() );
@ -243,6 +211,11 @@ namespace
class QskDrawer::PrivateData
{
public:
PrivateData( Qt::Edge edge )
: edge( edge )
{
}
inline void resetListener( QskDrawer* drawer )
{
delete listener;
@ -252,37 +225,39 @@ class QskDrawer::PrivateData
listener = new GeometryListener( drawer->parentItem(), drawer );
}
Qt::Edge edge = Qt::LeftEdge;
GestureRecognizer* gestureRecognizer = nullptr;
GeometryListener* listener = nullptr;
GestureRecognizer* gestureRecognizer = nullptr;
qreal dragMargin = qskDefaultDragMargin();
Qt::Edge edge;
};
QskDrawer::QskDrawer( QQuickItem* parentItem )
: QskDrawer( Qt::LeftEdge, parentItem )
{
}
QskDrawer::QskDrawer( Qt::Edge edge, QQuickItem* parentItem )
: Inherited ( parentItem )
, m_data( new PrivateData )
, m_data( new PrivateData( edge ) )
{
#if 1
setZ( 1 );
#endif
setAutoLayoutChildren( true );
setInteractive( true );
setPopupFlag( PopupFlag::CloseOnPressOutside, true );
setTransitionAspect( Panel | QskAspect::Position | QskAspect::Metric );
/*
The drawer wants to be on top of the parent - not being
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 );
m_data->resetListener( this );
connect( this, &QskPopup::openChanged, this, &QskDrawer::setSliding );
connect( this, &QskPopup::transitioningChanged, this, &QskDrawer::setIntermediate );
setAutoLayoutChildren( true );
setInteractive( true );
connect( this, &QskPopup::fadingChanged, this, &QQuickItem::setClip );
}
QskDrawer::~QskDrawer()
@ -374,18 +349,6 @@ void QskDrawer::gestureEvent( QskGestureEvent* event )
Inherited::gestureEvent( event );
}
QRectF QskDrawer::layoutRectForSize( const QSizeF& size ) const
{
qreal ratio;
if ( isTransitioning() )
ratio = metric( transitionAspect() );
else
ratio = isOpen() ? 1.0 : 0.0;
return qskSlidingRect( size, m_data->edge, ratio );
}
void QskDrawer::itemChange( QQuickItem::ItemChange change,
const QQuickItem::ItemChangeData& value )
{
@ -409,74 +372,96 @@ void QskDrawer::itemChange( QQuickItem::ItemChange change,
}
}
void QskDrawer::setSliding( bool on )
void QskDrawer::updateResources()
{
const qreal from = on ? 0.0 : 1.0;
const qreal to = on ? 1.0 : 0.0;
Inherited::updateResources();
const auto aspect = transitionAspect();
/*
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() );
auto hint = animationHint( aspect );
hint.updateFlags = QskAnimationHint::UpdatePolish;
r.translate( qskDrawerTranslation( this, r.size() ) );
setGeometry( r );
}
}
startTransition( aspect, hint, from, to );
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 ( !isTransitioning() )
return Inherited::clipRect();
/*
When fading we want to clip against the edge, where the drawer
slides in/out. However the size of the drawer is often smaller than the
one of the parent and we would clip the overlay node
and all content, that is located outside the drawer geometry.
So we expand the clip rectangle to "unbounded" at the other edges.
Note, that clipping against "rounded" rectangles can't be done
properly by overloading clipRect. We would have to manipulate the clip node
manually - like it is done in QskScrollArea. TODO ..
*/
constexpr qreal d = std::numeric_limits< short >::max();
QRectF r( -d, -d, 2.0 * d, 2.0 * d );
switch( m_data->edge )
if ( isFading() && parentItem() )
{
case Qt::LeftEdge:
r.setLeft( 0.0 );
break;
/*
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;
case Qt::RightEdge:
r.setRight( width() );
break;
QRectF r( -d, -d, 2.0 * d, 2.0 * d );
case Qt::TopEdge:
r.setTop( 0.0 );
break;
switch( edge() )
{
case Qt::LeftEdge:
r.setLeft( -x() );
break;
case Qt::BottomEdge:
r.setBottom( height() );
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 r;
return Inherited::clipRect();
}
void QskDrawer::setIntermediate( bool on )
QskAspect QskDrawer::fadingAspect() const
{
setClip( on );
Q_EMIT focusIndicatorRectChanged();
return QskDrawer::Panel | QskAspect::Position;
}
QRectF QskDrawer::focusIndicatorRect() const
QRectF QskDrawer::layoutRectForSize( const QSizeF& size ) const
{
if ( isTransitioning() )
return QRectF();
return Inherited::focusIndicatorRect();
return subControlContentsRect( size, Panel );
}
#include "moc_QskDrawer.cpp"

View File

@ -7,7 +7,6 @@
#define QSK_DRAWER_H
#include "QskPopup.h"
#include <qnamespace.h>
class QSK_EXPORT QskDrawer : public QskPopup
{
@ -27,6 +26,8 @@ class QSK_EXPORT QskDrawer : public QskPopup
QSK_SUBCONTROLS( Panel )
QskDrawer( QQuickItem* = nullptr );
QskDrawer( Qt::Edge, QQuickItem* = nullptr );
~QskDrawer() override;
void setEdge( Qt::Edge );
@ -39,10 +40,10 @@ class QSK_EXPORT QskDrawer : public QskPopup
void resetDragMargin();
qreal dragMargin() const;
QRectF layoutRectForSize( const QSizeF& ) const override;
QRectF clipRect() const override;
QRectF focusIndicatorRect() const override;
QskAspect fadingAspect() const override;
QRectF layoutRectForSize( const QSizeF& ) const override;
Q_SIGNALS:
void edgeChanged( Qt::Edge );
@ -50,14 +51,13 @@ class QSK_EXPORT QskDrawer : public QskPopup
void interactiveChanged( bool );
protected:
void itemChange( ItemChange, const ItemChangeData& ) override;
void gestureEvent( QskGestureEvent* ) override;
void itemChange( ItemChange, const ItemChangeData& ) override;
void updateResources() override;
void updateNode( QSGNode* ) override;
private:
void setSliding( bool );
void setIntermediate( bool );
class PrivateData;
std::unique_ptr< PrivateData > m_data;
};

View File

@ -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"

View File

@ -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

View File

@ -18,7 +18,10 @@
#include <qvariant.h>
#include <qeventloop.h>
QSK_SUBCONTROL( QskMenu, Overlay )
QSK_QT_PRIVATE_BEGIN
#include <private/qquickitem_p.h>
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 );
setTransitionAspect( 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 sliding
connect( this, &QskMenu::transitioningChanged, this,
&QskControl::focusIndicatorRectChanged );
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,6 +280,40 @@ 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 )
@ -435,7 +486,7 @@ void QskMenu::mouseReleaseEvent( QMouseEvent* event )
void QskMenu::aboutToShow()
{
setGeometry( QRectF( m_data->origin, sizeConstraint() ) );
setSize( sizeConstraint() );
if ( m_data->currentIndex < 0 )
{
@ -448,7 +499,7 @@ void QskMenu::aboutToShow()
QRectF QskMenu::focusIndicatorRect() const
{
if ( isTransitioning() )
if ( isFading() )
return QRectF();
if( currentIndex() >= 0 )
@ -492,6 +543,11 @@ void QskMenu::trigger( int index )
}
}
QskAspect QskMenu::fadingAspect() const
{
return QskMenu::Panel | QskAspect::Position;
}
int QskMenu::exec()
{
(void) execPopup();

View File

@ -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 );

View File

@ -15,7 +15,6 @@
#include "QskLabelData.h"
#include "QskSGNode.h"
#include "QskSlideInNode.h"
#include <qfontmetrics.h>
#include <qmath.h>
@ -223,26 +222,13 @@ QSGNode* QskMenuSkinlet::updateSubNode(
{
case ContentsRole:
{
/*
QskSlideInNode works for controls made of nodes - not for
containers of other quick items. TODO ...
*/
const auto popup = static_cast< const QskPopup* >( skinnable );
auto rect = popup->contentsRect();
if ( rect.isEmpty() )
return nullptr;
auto slideInNode = QskSGNode::ensureNode< QskSlideInNode >( node );
const auto progress = popup->metric( popup->transitionAspect() );
slideInNode->updateTranslation( rect, Qt::TopEdge, progress );
auto contentsNode = updateContentsNode( popup, slideInNode->contentsNode() );
slideInNode->setContentsNode( contentsNode );
return slideInNode;
return updateContentsNode( popup, node );
}
}

View File

@ -76,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
@ -139,7 +156,6 @@ class QskPopup::PrivateData
InputGrabber* inputGrabber = nullptr;
uint priority = 0;
QskAspect transitionAspect;
int flags : 4;
bool isModal : 1;
@ -217,9 +233,11 @@ void QskPopup::setOpen( bool on )
else
Q_EMIT closed();
if ( isTransitioning() )
qskStartFading( this, on );
if ( isFading() )
{
Q_EMIT transitioningChanged( true );
Q_EMIT fadingChanged( true );
}
else
{
@ -238,9 +256,22 @@ bool QskPopup::isOpen() const
return !hasSkinState( QskPopup::Closed );
}
bool QskPopup::isTransitioning() const
QskAspect QskPopup::fadingAspect() const
{
return runningHintAnimator( m_data->transitionAspect ) != nullptr;
return QskAspect();
}
bool QskPopup::isFading() const
{
return runningHintAnimator( fadingAspect() ) != nullptr;
}
qreal QskPopup::fadingFactor() const
{
if ( auto animator = runningHintAnimator( fadingAspect() ) )
return animator->currentValue().value< qreal >();
return isOpen() ? 1.0 : 0.0;
}
QRectF QskPopup::overlayRect() const
@ -291,44 +322,23 @@ void QskPopup::updateInputGrabber()
}
}
QskAspect QskPopup::transitionAspect() const
{
return m_data->transitionAspect;
}
void QskPopup::setTransitionAspect( QskAspect aspect )
{
auto transitionAspect = aspect;
transitionAspect.clearStates(); // animated values are always stateless
if ( transitionAspect == m_data->transitionAspect )
return;
if ( isTransitioning() )
{
// stop the running animation TODO ...
}
m_data->transitionAspect = transitionAspect;
}
bool QskPopup::isTransitionAccepted( QskAspect aspect ) const
{
if ( isVisible() )
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->transitionAspect )
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 );
@ -475,7 +485,7 @@ bool QskPopup::event( QEvent* event )
const auto animatorEvent = static_cast< QskAnimatorEvent* >( event );
if ( ( animatorEvent->state() == QskAnimatorEvent::Terminated )
&& ( animatorEvent->aspect() == m_data->transitionAspect ) )
&& ( animatorEvent->aspect() == fadingAspect() ) )
{
if ( !isOpen() )
{
@ -485,7 +495,7 @@ bool QskPopup::event( QEvent* event )
deleteLater();
}
Q_EMIT transitioningChanged( false );
Q_EMIT fadingChanged( false );
}
break;
@ -633,7 +643,7 @@ int QskPopup::execPopup()
*/
connect( popup, &QObject::destroyed, this, &EventLoop::reject );
connect( popup, &QskPopup::transitioningChanged, this, &EventLoop::maybeQuit );
connect( popup, &QskPopup::fadingChanged, this, &EventLoop::maybeQuit );
connect( popup, &QskPopup::openChanged, this, &EventLoop::maybeQuit );
}
@ -648,7 +658,7 @@ int QskPopup::execPopup()
{
if ( auto popup = qobject_cast< const QskPopup* >( parent() ) )
{
if ( popup->isOpen() || popup->isTransitioning() )
if ( popup->isOpen() || popup->isFading() )
return;
}
@ -656,7 +666,7 @@ int QskPopup::execPopup()
}
};
if ( isOpen() || isTransitioning() )
if ( isOpen() || isFading() )
{
qWarning() << "QskPopup::exec: popup is already opened";
return -1;

View File

@ -14,7 +14,7 @@ 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 transitioning READ isTransitioning NOTIFY transitioningChanged )
Q_PROPERTY( bool fading READ isFading NOTIFY fadingChanged )
Q_PROPERTY( bool overlay READ hasOverlay
WRITE setOverlay RESET resetOverlay NOTIFY overlayChanged )
@ -56,15 +56,13 @@ class QSK_EXPORT QskPopup : public QskControl
void setPriority( uint );
uint priority() const;
// transitions between open/closed states
QskAspect transitionAspect() const;
void setTransitionAspect( QskAspect );
bool isTransitioning() const;
bool isOpen() const;
bool isClosed() const;
bool isFading() const;
qreal fadingFactor() const;
virtual QskAspect fadingAspect() const;
virtual QRectF overlayRect() const;
public Q_SLOTS:
@ -78,7 +76,7 @@ class QSK_EXPORT QskPopup : public QskControl
void opened();
void closed();
void openChanged( bool );
void transitioningChanged( bool );
void fadingChanged( bool );
void modalChanged( bool );
void overlayChanged( bool );

View File

@ -5,6 +5,12 @@
#include "QskPopupSkinlet.h"
#include "QskPopup.h"
#include "QskRgbValue.h"
static inline QRgb qskInterpolatedRgb( QRgb rgb, qreal factor )
{
return QskRgb::toTransparent( rgb, qRound( factor * qAlpha( rgb ) ) );
}
QskPopupSkinlet::QskPopupSkinlet( QskSkin* skin )
: Inherited( skin )
@ -28,13 +34,43 @@ QRectF QskPopupSkinlet::subControlRect( const QskSkinnable* skinnable,
QSGNode* QskPopupSkinlet::updateSubNode(
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
const auto popup = static_cast< const QskPopup* >( skinnable );
switch ( nodeRole )
{
case OverlayRole:
return updateBoxNode( skinnable, node, QskPopup::Overlay );
return updateOverlayNode( popup, node );
}
return Inherited::updateSubNode( skinnable, nodeRole, node );
}
QSGNode* QskPopupSkinlet::updateOverlayNode(
const QskPopup* popup, QSGNode* node ) const
{
using Q = QskPopup;
const auto factor = popup->fadingFactor();
if ( factor <= 0.0 )
return nullptr;
const auto rect = popup->subControlRect( Q::Overlay );
if ( rect.isEmpty() )
return nullptr;
auto gradient = popup->gradientHint( Q::Overlay );
if ( gradient.isVisible() && factor != 1.0 )
{
auto stops = gradient.stops();
for ( auto& stop : stops )
stop.setRgb( qskInterpolatedRgb( stop.rgb(), factor ) );
gradient.setStops( stops );
}
return updateBoxNode( popup, node, rect, gradient, QskPopup::Overlay );
}
#include "moc_QskPopupSkinlet.cpp"

View File

@ -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 >();

View File

@ -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"

View File

@ -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,

View File

@ -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 );

View File

@ -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;
}

View File

@ -1,146 +0,0 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskSlideInNode.h"
#include "QskSGNode.h"
#include <qtransform.h>
QSK_QT_PRIVATE_BEGIN
#include <private/qsgnode_p.h>
QSK_QT_PRIVATE_END
class QskSlideInNodePrivate final : public QSGNodePrivate
{
public:
~QskSlideInNodePrivate()
{
delete clipNode;
delete transformNode;
delete contentsNode;
}
void reparentContentNode( QskSlideInNode* node )
{
if ( contentsNode )
{
QSGNode* parentNode = transformNode;
if ( parentNode == nullptr )
parentNode = clipNode;
if ( parentNode == nullptr )
parentNode = node;
QskSGNode::setParentNode( contentsNode, parentNode );
}
}
QSGClipNode* clipNode = nullptr;
QSGTransformNode* transformNode = nullptr;
QSGNode* contentsNode = nullptr;
};
QskSlideInNode::QskSlideInNode()
: QSGNode( *new QskSlideInNodePrivate, QSGNode::BasicNodeType )
{
}
QskSlideInNode::~QskSlideInNode() = default;
void QskSlideInNode::updateTranslation( const QRectF& rect,
Qt::Edge edge, qreal progress )
{
Q_UNUSED( edge ); // TODO ...
Q_D( QskSlideInNode );
{
// clipping
if ( progress >= 0.0 && progress < 1.0 )
{
if ( d->clipNode == nullptr )
{
d->clipNode = new QSGClipNode();
d->clipNode->setFlag( QSGNode::OwnedByParent, false );
d->clipNode->setIsRectangular( true );
}
d->clipNode->setClipRect( rect );
}
else
{
delete d->clipNode;
d->clipNode = nullptr;
}
if ( d->clipNode )
QskSGNode::setParentNode( d->clipNode, this );
}
{
// translation
qreal dx = 0.0;
qreal dy = ( progress - 1.0 ) * rect.height();
if ( dx != 0.0 || dy != 0.0 )
{
if ( d->transformNode == nullptr )
{
d->transformNode = new QSGTransformNode();
d->transformNode->setFlag( QSGNode::OwnedByParent, false );
}
QTransform transform;
transform.translate( dx, dy );
d->transformNode->setMatrix( transform );
}
else
{
delete d->transformNode;
d->transformNode = nullptr;
}
if ( d->transformNode )
{
QSGNode* parentNode = d->clipNode;
if ( parentNode == nullptr )
parentNode = this;
QskSGNode::setParentNode( d->transformNode, parentNode );
}
}
d->reparentContentNode( this );
}
void QskSlideInNode::setContentsNode( QSGNode* node )
{
Q_D( QskSlideInNode );
if ( d->contentsNode == node )
return;
if ( node )
node->setFlag( QSGNode::OwnedByParent, false );
delete d->contentsNode;
d->contentsNode = node;
d->reparentContentNode( this );
}
QSGNode* QskSlideInNode::contentsNode()
{
return d_func()->contentsNode;
}
const QSGNode* QskSlideInNode::contentsNode() const
{
return d_func()->contentsNode;
}

View File

@ -1,33 +0,0 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_SLIDE_IN_NODE_H
#define QSK_SLIDE_IN_NODE_H
#include "QskGlobal.h"
#include <qsgnode.h>
#include <qnamespace.h>
class QskSlideInNodePrivate;
class QSK_EXPORT QskSlideInNode : public QSGNode
{
public:
QskSlideInNode();
~QskSlideInNode() override;
void updateTranslation( const QRectF&, Qt::Edge, qreal progress );
void setContentsNode( QSGNode* );
QSGNode* contentsNode();
const QSGNode* contentsNode() const;
private:
Q_DECLARE_PRIVATE( QskSlideInNode )
};
#endif