From 1bc2db4bdfe93694452e1a7c129f82294dab8c00 Mon Sep 17 00:00:00 2001 From: Joseph Donofry Date: Mon, 11 Jan 2021 17:50:26 -0500 Subject: [PATCH] Add Ripple effects to qml buttons and avatar --- resources/qml/Avatar.qml | 7 ++ resources/qml/ImageButton.qml | 7 ++ resources/qml/ui/Ripple.qml | 177 ++++++++++++++++++++++++++++++++++ resources/qml/ui/qmldir | 2 + resources/res.qrc | 1 + 5 files changed, 194 insertions(+) create mode 100644 resources/qml/ui/Ripple.qml create mode 100644 resources/qml/ui/qmldir diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index aa873ffe..2801bd37 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -1,3 +1,4 @@ +import "./ui" import QtGraphicalEffects 1.0 import QtQuick 2.6 import QtQuick.Controls 2.3 @@ -43,6 +44,12 @@ Rectangle { anchors.fill: parent onClicked: TimelineManager.openImageOverlay(TimelineManager.timeline.avatarUrl(userid), TimelineManager.timeline.data.id) + + Ripple { + rippleTarget: mouseArea + color: Qt.rgba(colors.alternateBase.r, colors.alternateBase.g, colors.alternateBase.b, 0.5) + } + } layer.effect: OpacityMask { diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml index 4ebda680..b5a34b7b 100644 --- a/resources/qml/ImageButton.qml +++ b/resources/qml/ImageButton.qml @@ -1,3 +1,4 @@ +import "./ui" import QtQuick 2.3 import QtQuick.Controls 2.3 @@ -28,4 +29,10 @@ AbstractButton { cursorShape: Qt.PointingHandCursor } + Ripple { + color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5) + clip: false + rippleTarget: button + } + } diff --git a/resources/qml/ui/Ripple.qml b/resources/qml/ui/Ripple.qml new file mode 100644 index 00000000..9b404a68 --- /dev/null +++ b/resources/qml/ui/Ripple.qml @@ -0,0 +1,177 @@ +import QtGraphicalEffects 1.10 +import QtQuick 2.10 +import QtQuick.Controls 2.3 + +Item { + id: ripple + + property alias clip: backgroundLayer.clip + property real radius: 0 + property color color: "#22000000" + property real maxRadius: Math.max(width, height) + property real radiusAnimationRate: 0.05 + property real radiusTailAnimationRate: 0.5 + property real opacityAnimationDuration: 300 + readonly property real diameter: radius * 2 + property real centerX + property real centerY + property var rippleTarget: parent + + function start() { + console.log("Starting ripple animation"); + ripple.state = "ACTIVE"; + } + + function stop() { + console.log("Stopping ripple animation"); + ripple.state = "NORMAL"; + } + + anchors.fill: parent + state: "NORMAL" + states: [ + State { + name: "NORMAL" + }, + State { + name: "ACTIVE" + } + ] + transitions: [ + Transition { + from: "NORMAL" + to: "ACTIVE" + + SequentialAnimation { + ScriptAction { + script: { + ripple.opacity = 1; + ripple.visible = true; + } + } + + NumberAnimation { + id: radius_animation + + target: ripple + properties: "radius" + from: 0 + to: ripple.maxRadius + duration: ripple.maxRadius / ripple.radiusAnimationRate + + easing { + type: Easing.OutQuad + } + + } + + } + + }, + Transition { + from: "ACTIVE" + to: "NORMAL" + + SequentialAnimation { + ParallelAnimation { + NumberAnimation { + id: radius_tail_animation + + target: ripple + properties: "radius" + to: ripple.maxRadius + duration: ripple.maxRadius / ripple.radiusTailAnimationRate + + easing { + type: Easing.Linear + } + + } + + NumberAnimation { + id: opacity_animation + + target: ripple + properties: "opacity" + to: 0 + duration: ripple.opacityAnimationDuration + + easing { + type: Easing.InQuad + } + + } + + } + + ScriptAction { + script: { + ripple.visible = false; + } + } + + } + + } + ] + + Connections { + function onPressed(mouse) { + // Button + // Default to center + + // MouseArea + if (mouse) { + ripple.centerX = mouse.x; + ripple.centerY = mouse.y; + } else if (rippleTarget.pressX) { + ripple.centerX = rippleTarget.pressX; + ripple.centerY = rippleTarget.pressY; + } else { + ripple.centerX = width / 2; + ripple.centerY = height / 2; + } + ripple.start(); + } + + function onReleased() { + ripple.stop(); + } + + function onExited() { + ripple.stop(); + } + + function onCanceled() { + ripple.stop(); + } + + function onClicked() { + ripple.stop(); + } + + target: rippleTarget + ignoreUnknownSignals: true + } + + Rectangle { + id: backgroundLayer + + anchors.fill: parent + color: "transparent" + clip: true + + Rectangle { + id: circle + + x: ripple.centerX - ripple.radius + y: ripple.centerY - ripple.radius + height: ripple.diameter + width: ripple.diameter + radius: ripple.radius + color: ripple.color + } + + } + +} diff --git a/resources/qml/ui/qmldir b/resources/qml/ui/qmldir new file mode 100644 index 00000000..a8466a10 --- /dev/null +++ b/resources/qml/ui/qmldir @@ -0,0 +1,2 @@ +module im.nheko.UI +Ripple 1.0 Ripple.qml \ No newline at end of file diff --git a/resources/res.qrc b/resources/res.qrc index a01907ec..9b05575e 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -159,6 +159,7 @@ qml/device-verification/NewVerificationRequest.qml qml/device-verification/Failed.qml qml/device-verification/Success.qml + qml/ui/Ripple.qml media/ring.ogg