diff --git a/skins/material3/QskMaterial3Skin.cpp b/skins/material3/QskMaterial3Skin.cpp index 532184ec..429e23a9 100644 --- a/skins/material3/QskMaterial3Skin.cpp +++ b/skins/material3/QskMaterial3Skin.cpp @@ -476,11 +476,16 @@ void Editor::setupPushButton() setGradient( Q::Panel | Q::Pressed, focusColor ); + // we cannot use relative size here because the rect changes size during the animation: + setBoxShape( Q::Ripple, 15.5 ); + setGradient( Q::Ripple, stateLayerColor( m_pal.onPrimary, m_pal.hoverOpacity ) ); + setColor( Q::Text, m_pal.onPrimary ); setColor( Q::Text | Q::Disabled, m_pal.onSurface38 ); setAnimation( Q::Panel | A::Color, qskDuration ); setAnimation( Q::Panel | A::Metric, qskDuration ); + setAnimation( Q::Ripple | A::Color, qskDuration ); setAnimation( Q::Text | A::Color, qskDuration ); } diff --git a/src/controls/QskPushButton.cpp b/src/controls/QskPushButton.cpp index dde79462..c9e6f7a9 100644 --- a/src/controls/QskPushButton.cpp +++ b/src/controls/QskPushButton.cpp @@ -4,6 +4,8 @@ *****************************************************************************/ #include "QskPushButton.h" +#include "QskAnimationHint.h" +#include "QskAnimator.h" #include "QskAspect.h" #include "QskBoxShapeMetrics.h" #include "QskGraphic.h" @@ -16,9 +18,41 @@ #include QSK_SUBCONTROL( QskPushButton, Panel ) +QSK_SUBCONTROL( QskPushButton, Ripple ) QSK_SUBCONTROL( QskPushButton, Text ) QSK_SUBCONTROL( QskPushButton, Graphic ) +namespace +{ + class ClickAnimator : public QskAnimator + { + public: + ClickAnimator() + : QskAnimator() + { + } + + void setButton( QskPushButton* button ) + { + m_button = button; + } + + protected: + void advance( qreal value ) override + { + m_button->setMetric( QskPushButton::Ripple | QskAspect::Size, value ); + } + + void done() override + { + m_button->setMetric( QskPushButton::Ripple | QskAspect::Size, 0.0 ); + } + + private: + QskPushButton* m_button; + }; +} + class QskPushButton::PrivateData { public: @@ -49,6 +83,9 @@ class QskPushButton::PrivateData QSizeF graphicSourceSize; + ClickAnimator clickAnimator; + qreal rippleSize = 0.0; + bool isCheckable : 1; bool isGraphicSourceDirty : 1; }; @@ -273,6 +310,24 @@ void QskPushButton::changeEvent( QEvent* event ) Inherited::changeEvent( event ); } +void QskPushButton::mousePressEvent( QMouseEvent* event ) +{ + Inherited::mousePressEvent( event ); + + const auto hint = animationHint( Ripple | QskAspect::Color ); + + if( hint.isValid() ) + { + setSkinHint( Ripple | QskAspect::Position, event->pos() ); + + m_data->clickAnimator.setWindow( window() ); + m_data->clickAnimator.setButton( this ); + m_data->clickAnimator.setDuration( hint.duration ); + m_data->clickAnimator.setEasingCurve( hint.type ); + m_data->clickAnimator.start(); + } +} + QskGraphic QskPushButton::loadGraphic( const QUrl& url ) const { return Qsk::loadGraphic( url ); diff --git a/src/controls/QskPushButton.h b/src/controls/QskPushButton.h index 25d83d92..95a55994 100644 --- a/src/controls/QskPushButton.h +++ b/src/controls/QskPushButton.h @@ -42,7 +42,7 @@ class QSK_EXPORT QskPushButton : public QskAbstractButton using Inherited = QskAbstractButton; public: - QSK_SUBCONTROLS( Panel, Text, Graphic ) + QSK_SUBCONTROLS( Panel, Ripple, Text, Graphic ) QskPushButton( QQuickItem* parent = nullptr ); QskPushButton( const QString& text, QQuickItem* parent = nullptr ); @@ -90,6 +90,7 @@ class QSK_EXPORT QskPushButton : public QskAbstractButton protected: void changeEvent( QEvent* ) override; + void mousePressEvent( QMouseEvent* ) override; void updateResources() override; virtual QskGraphic loadGraphic( const QUrl& ) const; diff --git a/src/controls/QskPushButtonSkinlet.cpp b/src/controls/QskPushButtonSkinlet.cpp index b42e85fa..8ba2bef7 100644 --- a/src/controls/QskPushButtonSkinlet.cpp +++ b/src/controls/QskPushButtonSkinlet.cpp @@ -6,6 +6,7 @@ #include "QskPushButtonSkinlet.h" #include "QskPushButton.h" +#include "QskAnimationHint.h" #include "QskGraphic.h" #include "QskTextOptions.h" #include "QskFunctions.h" @@ -29,7 +30,7 @@ static inline Qt::Orientation qskOrientation( const QskPushButton* button ) QskPushButtonSkinlet::QskPushButtonSkinlet( QskSkin* skin ) : Inherited( skin ) { - setNodeRoles( { PanelRole, GraphicRole, TextRole } ); + setNodeRoles( { PanelRole, RippleRole, GraphicRole, TextRole } ); } QskPushButtonSkinlet::~QskPushButtonSkinlet() = default; @@ -51,6 +52,17 @@ QRectF QskPushButtonSkinlet::subControlRect( const QskSkinnable* skinnable, { return contentsRect; } + else if ( subControl == QskPushButton::Ripple ) + { + const auto clickPos = button->effectiveSkinHint( QskPushButton::Ripple | QskAspect::Position ).toPointF(); + const auto ratio = button->metric( QskPushButton::Ripple | QskAspect::Size ); + const auto w = contentsRect.width() * ratio; + const auto h = contentsRect.height() * ratio; + const auto x = clickPos.x() - w; + const auto y = clickPos.y() - h; + const QRectF r( x, y, w * 2, h * 2 ); + return r.intersected( contentsRect ); + } return Inherited::subControlRect( skinnable, contentsRect, subControl ); } @@ -67,6 +79,19 @@ QSGNode* QskPushButtonSkinlet::updateSubNode( return updateBoxNode( button, node, QskPushButton::Panel ); } + case RippleRole: + { + if( button->hasAnimationHint( QskPushButton::Ripple | QskAspect::Color ) + && button->metric( QskPushButton::Ripple | QskAspect::Size ) > 0.0 ) + { + return updateBoxNode( button, node, QskPushButton::Ripple ); + } + else + { + return nullptr; + } + } + case TextRole: { return updateTextNode( button, node ); diff --git a/src/controls/QskPushButtonSkinlet.h b/src/controls/QskPushButtonSkinlet.h index faafb4da..7fb2c56c 100644 --- a/src/controls/QskPushButtonSkinlet.h +++ b/src/controls/QskPushButtonSkinlet.h @@ -20,6 +20,7 @@ class QSK_EXPORT QskPushButtonSkinlet : public QskSkinlet enum NodeRole { PanelRole, + RippleRole, TextRole, GraphicRole,