diff --git a/CMakeLists.txt b/CMakeLists.txt index 1832968e..695fadf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -805,6 +805,7 @@ set(QML_SOURCES resources/qml/pages/WelcomePage.qml resources/qml/ui/NhekoSlider.qml resources/qml/ui/Ripple.qml + resources/qml/ui/RippleImpl.qml resources/qml/ui/Snackbar.qml resources/qml/ui/Spinner.qml resources/qml/ui/animations/BlinkAnimation.qml diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml index 4ceadf6c..655c4f07 100644 --- a/resources/qml/MatrixTextField.qml +++ b/resources/qml/MatrixTextField.qml @@ -79,6 +79,7 @@ ColumnLayout { from: "" reversible: true to: "focused" + enabled: !Settings.reducedMotion NumberAnimation { alwaysRunToEnd: true @@ -164,6 +165,7 @@ ColumnLayout { from: "" reversible: true to: "focused" + enabled: !Settings.reducedMotion NumberAnimation { alwaysRunToEnd: true diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 58ae90bb..c90c5bf7 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -335,6 +335,7 @@ Rectangle { y: messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height enter: Transition { + enabled: !Settings.reducedMotion NumberAnimation { duration: 100 from: 0 @@ -343,6 +344,7 @@ Rectangle { } } exit: Transition { + enabled: !Settings.reducedMotion NumberAnimation { duration: 100 from: 1 diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index a49761b2..f89cb851 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -666,6 +666,7 @@ Item { from: "" reversible: true to: "shown" + enabled: !Settings.reducedMotion SequentialAnimation { PauseAnimation { diff --git a/resources/qml/PrivacyScreen.qml b/resources/qml/PrivacyScreen.qml index 764bdf62..07d1f639 100644 --- a/resources/qml/PrivacyScreen.qml +++ b/resources/qml/PrivacyScreen.qml @@ -68,6 +68,7 @@ Item { from: "Invisible" reversible: true to: "Visible" + enabled: !Settings.reducedMotion SequentialAnimation { NumberAnimation { diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 7ec8cce5..c756f72e 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -430,6 +430,7 @@ Pane { Transition { id: reducedMotionTransitionExit + enabled: !Settings.reducedMotion PropertyAnimation { duration: 200 @@ -440,6 +441,7 @@ Pane { } Transition { id: reducedMotionTransitionEnter + enabled: !Settings.reducedMotion SequentialAnimation { PropertyAction { diff --git a/resources/qml/TimelineBubbleMessageStyle.qml b/resources/qml/TimelineBubbleMessageStyle.qml index dd197264..71314e9f 100644 --- a/resources/qml/TimelineBubbleMessageStyle.qml +++ b/resources/qml/TimelineBubbleMessageStyle.qml @@ -107,6 +107,7 @@ TimelineEvent { transitions: Transition { from: "" to: "revealed" + enabled: !Settings.reducedMotion SequentialAnimation { PropertyAnimation { diff --git a/resources/qml/TimelineDefaultMessageStyle.qml b/resources/qml/TimelineDefaultMessageStyle.qml index 16db9964..ec5276c5 100644 --- a/resources/qml/TimelineDefaultMessageStyle.qml +++ b/resources/qml/TimelineDefaultMessageStyle.qml @@ -107,6 +107,7 @@ TimelineEvent { transitions: Transition { from: "" to: "revealed" + enabled: !Settings.reducedMotion SequentialAnimation { PropertyAnimation { diff --git a/resources/qml/ToggleButton.qml b/resources/qml/ToggleButton.qml index 50ed5187..19952b33 100644 --- a/resources/qml/ToggleButton.qml +++ b/resources/qml/ToggleButton.qml @@ -5,6 +5,7 @@ import QtQuick 2.5 import QtQuick 2.12 import QtQuick.Controls 2.12 +import im.nheko 1.0 Switch { id: toggleButton @@ -66,6 +67,7 @@ Switch { Transition { reversible: true to: "off" + enabled: !Settings.reducedMotion ParallelAnimation { NumberAnimation { diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index a97ba9f7..955160b7 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -74,6 +74,7 @@ AbstractButton { from: "ImageVisible" to: "BlurhashVisible" reversible: true + enabled: !Settings.reducedMotion SequentialAnimation { PropertyAction { diff --git a/resources/qml/ui/Ripple.qml b/resources/qml/ui/Ripple.qml index 9d871419..d062002f 100644 --- a/resources/qml/ui/Ripple.qml +++ b/resources/qml/ui/Ripple.qml @@ -2,128 +2,18 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -import QtQuick +import QtQuick 2.15 +import im.nheko 1.0 Item { - id: ripple + id: wrapper + property color color - property color color: "#22000000" - property real maxRadius: Math.max(width, height) - readonly property real radiusAnimationRate: 0.05 - readonly property real radiusTailAnimationRate: 0.5 - readonly property real opacityAnimationDuration: 300 - property var rippleTarget: parent - - anchors.fill: parent - - PointHandler { - id: ph - - onGrabChanged: (_, point) => { - circle.centerX = point.position.x - circle.centerY = point.position.y - } - - target: Rectangle { - id: backgroundLayer - parent: rippleTarget - - anchors.fill: parent - color: "transparent" - clip: true - - Rectangle { - id: circle - - property real centerX - property real centerY - - x: centerX - radius - y: centerY - radius - - height: radius*2 - width: radius*2 - radius: 0 - color: ripple.color - - state: ph.active ? "ACTIVE" : "NORMAL" - states: [ - State { - name: "NORMAL" - }, - State { - name: "ACTIVE" - } - ] - transitions: [ - Transition { - from: "NORMAL" - to: "ACTIVE" - - SequentialAnimation { - //PropertyAction { target: circle; property: "centerX"; value: ph.point.position.x } - //PropertyAction { target: circle; property: "centerY"; value: ph.point.position.y } - PropertyAction { target: circle; property: "visible"; value: true } - PropertyAction { target: circle; property: "opacity"; value: 1 } - - NumberAnimation { - id: radius_animation - - target: circle - 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: circle - properties: "radius" - to: ripple.maxRadius - duration: ripple.maxRadius / ripple.radiusTailAnimationRate - - easing { - type: Easing.Linear - } - - } - - NumberAnimation { - id: opacity_animation - - target: circle - properties: "opacity" - to: 0 - duration: ripple.opacityAnimationDuration - - easing { - type: Easing.InQuad - } - - } - - } - PropertyAction { target: circle; property: "visible"; value: false } - } - } - ] - } + Loader { + Component { + id: ripple + RippleImpl { color: color; parent: wrapper.parent; } } + sourceComponent: !Settings.reducedMotion ? ripple : undefined } } diff --git a/resources/qml/ui/RippleImpl.qml b/resources/qml/ui/RippleImpl.qml new file mode 100644 index 00000000..01f054c1 --- /dev/null +++ b/resources/qml/ui/RippleImpl.qml @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 + +Item { + id: ripple + + property color color: "#22000000" + property real maxRadius: Math.max(width, height) + readonly property real radiusAnimationRate: 0.05 + readonly property real radiusTailAnimationRate: 0.5 + readonly property real opacityAnimationDuration: 300 + property var rippleTarget: parent + + anchors.fill: parent + + PointHandler { + id: ph + + onGrabChanged: (_, point) => { + circle.centerX = point.position.x + circle.centerY = point.position.y + } + + target: Rectangle { + id: backgroundLayer + parent: rippleTarget + + anchors.fill: parent + color: "transparent" + clip: true + + Rectangle { + id: circle + + property real centerX + property real centerY + + x: centerX - radius + y: centerY - radius + + height: radius*2 + width: radius*2 + radius: 0 + color: ripple.color + + state: ph.active ? "ACTIVE" : "NORMAL" + states: [ + State { + name: "NORMAL" + }, + State { + name: "ACTIVE" + } + ] + transitions: [ + Transition { + from: "NORMAL" + to: "ACTIVE" + enabled: !Settings.reducedMotion + + SequentialAnimation { + //PropertyAction { target: circle; property: "centerX"; value: ph.point.position.x } + //PropertyAction { target: circle; property: "centerY"; value: ph.point.position.y } + PropertyAction { target: circle; property: "visible"; value: true } + PropertyAction { target: circle; property: "opacity"; value: 1 } + + NumberAnimation { + id: radius_animation + + target: circle + properties: "radius" + from: 0 + to: ripple.maxRadius + duration: ripple.maxRadius / ripple.radiusAnimationRate + + easing { + type: Easing.OutQuad + } + + } + + } + + }, + Transition { + from: "ACTIVE" + to: "NORMAL" + enabled: !Settings.reducedMotion + + SequentialAnimation { + ParallelAnimation { + NumberAnimation { + id: radius_tail_animation + + target: circle + properties: "radius" + to: ripple.maxRadius + duration: ripple.maxRadius / ripple.radiusTailAnimationRate + + easing { + type: Easing.Linear + } + + } + + NumberAnimation { + id: opacity_animation + + target: circle + properties: "opacity" + to: 0 + duration: ripple.opacityAnimationDuration + + easing { + type: Easing.InQuad + } + + } + + } + PropertyAction { target: circle; property: "visible"; value: false } + } + } + ] + } + } + } +} diff --git a/resources/qml/ui/Snackbar.qml b/resources/qml/ui/Snackbar.qml index 3c526d8d..c2d3b72b 100644 --- a/resources/qml/ui/Snackbar.qml +++ b/resources/qml/ui/Snackbar.qml @@ -61,6 +61,7 @@ Popup { } enter: Transition { + enabled: !Settings.reducedMotion NumberAnimation { target: snackbar property: "opacity" @@ -79,6 +80,7 @@ Popup { } } exit: Transition { + enabled: !Settings.reducedMotion NumberAnimation { target: snackbar property: "opacity" diff --git a/resources/qml/ui/media/MediaControls.qml b/resources/qml/ui/media/MediaControls.qml index f4373908..4108064e 100644 --- a/resources/qml/ui/media/MediaControls.qml +++ b/resources/qml/ui/media/MediaControls.qml @@ -146,6 +146,7 @@ Rectangle { Transition { from: "" to: "shown" + enabled: !Settings.reducedMotion SequentialAnimation { PauseAnimation { @@ -163,6 +164,7 @@ Rectangle { Transition { from: "shown" to: "" + enabled: !Settings.reducedMotion SequentialAnimation { PauseAnimation { diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp index e28b1bc1..98caeae4 100644 --- a/src/ui/NhekoGlobalObject.cpp +++ b/src/ui/NhekoGlobalObject.cpp @@ -55,21 +55,21 @@ Nheko::inactiveColors() const auto theme = UserSettings::instance()->theme(); if (theme == QLatin1String("light")) { static QPalette lightInactive = [] { - auto lightInactive = Theme::paletteFromTheme(u"light"); + auto lightInactive = Theme::paletteFromTheme(Theme::Kind::Light); lightInactive.setCurrentColorGroup(QPalette::ColorGroup::Inactive); return lightInactive; }(); return lightInactive; } else if (theme == QLatin1String("dark")) { static QPalette darkInactive = [] { - auto darkInactive = Theme::paletteFromTheme(u"dark"); + auto darkInactive = Theme::paletteFromTheme(Theme::Kind::Dark); darkInactive.setCurrentColorGroup(QPalette::ColorGroup::Inactive); return darkInactive; }(); return darkInactive; } else { static QPalette originalInactive = [] { - auto originalInactive = Theme::paletteFromTheme(u"system"); + auto originalInactive = Theme::paletteFromTheme(Theme::Kind::System); originalInactive.setCurrentColorGroup(QPalette::ColorGroup::Inactive); return originalInactive; }(); @@ -80,7 +80,7 @@ Nheko::inactiveColors() const Theme Nheko::theme() const { - return Theme(UserSettings::instance()->theme()); + return Theme(Theme::kindFromString(UserSettings::instance()->theme())); } int diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp index 4d46db13..11d755d9 100644 --- a/src/ui/Theme.cpp +++ b/src/ui/Theme.cpp @@ -5,10 +5,10 @@ #include "Theme.h" QPalette -Theme::paletteFromTheme(QStringView theme) +Theme::paletteFromTheme(Theme::Kind theme) { static QPalette original; - if (theme == u"light") { + if (theme == Kind::Light) { static QPalette lightActive = [] { QPalette lightActive( /*windowText*/ QColor(0x33, 0x33, 0x33), @@ -30,7 +30,7 @@ Theme::paletteFromTheme(QStringView theme) return lightActive; }(); return lightActive; - } else if (theme == u"dark") { + } else if (theme == Kind::Dark) { static QPalette darkActive = [] { QPalette darkActive( /*windowText*/ QColor(0xca, 0xcc, 0xd1), @@ -57,27 +57,30 @@ Theme::paletteFromTheme(QStringView theme) } } -Theme::Theme(QStringView theme) +QPalette +Theme::paletteFromTheme(QStringView theme) +{ + return paletteFromTheme(kindFromString(theme)); +} + +Theme::Theme(Theme::Kind theme) { auto p = paletteFromTheme(theme); separator_ = p.mid().color(); - if (theme == u"light") { + if (theme == Kind::Light) { sidebarBackground_ = QColor(0x23, 0x36, 0x49); - alternateButton_ = QColor(0xcc, 0xcc, 0xcc); red_ = QColor(0xa8, 0x23, 0x53); green_ = QColor(QColorConstants::Svg::green); orange_ = QColor(0xfc, 0xbe, 0x05); error_ = QColor(0xdd, 0x3d, 0x3d); - } else if (theme == u"dark") { + } else if (theme == Kind::Dark) { sidebarBackground_ = QColor(0x2d, 0x31, 0x39); - alternateButton_ = QColor(0x41, 0x4A, 0x59); red_ = QColor(0xa8, 0x23, 0x53); green_ = QColor(QColorConstants::Svg::green); orange_ = QColor(0xfc, 0xc5, 0x3a); error_ = QColor(0xdd, 0x3d, 0x3d); } else { sidebarBackground_ = p.window().color(); - alternateButton_ = p.dark().color(); red_ = QColor(QColorConstants::Svg::red); green_ = QColor(QColorConstants::Svg::green); orange_ = QColor(QColorConstants::Svg::orange); // SVG orange @@ -85,4 +88,18 @@ Theme::Theme(QStringView theme) } } +Theme::Kind +Theme::kindFromString(QStringView kind) +{ + if (kind == u"light") { + return Kind::Light; + } else if (kind == u"dark") { + return Kind::Dark; + } else if (kind == u"system") { + return Kind::System; + } else { + throw std::invalid_argument("Unknown theme kind: " + kind.toString().toStdString()); + } +} + #include "moc_Theme.cpp" diff --git a/src/ui/Theme.h b/src/ui/Theme.h index d581ffe4..2d3f6b9f 100644 --- a/src/ui/Theme.h +++ b/src/ui/Theme.h @@ -14,7 +14,6 @@ class Theme final : public QPalette QML_ANONYMOUS Q_PROPERTY(QColor sidebarBackground READ sidebarBackground CONSTANT) - Q_PROPERTY(QColor alternateButton READ alternateButton CONSTANT) Q_PROPERTY(QColor separator READ separator CONSTANT) Q_PROPERTY(QColor red READ red CONSTANT) Q_PROPERTY(QColor green READ green CONSTANT) @@ -23,12 +22,20 @@ class Theme final : public QPalette Q_PROPERTY(QColor online READ online CONSTANT) Q_PROPERTY(QColor unavailable READ unavailable CONSTANT) public: - Theme() {} - explicit Theme(QStringView theme); + enum class Kind { + Light, + Dark, + System, + }; + static Kind kindFromString(QStringView kind); + + static QPalette paletteFromTheme(Kind theme); static QPalette paletteFromTheme(QStringView theme); + Theme() {} + explicit Theme(Kind theme); + QColor sidebarBackground() const { return sidebarBackground_; } - QColor alternateButton() const { return alternateButton_; } QColor separator() const { return separator_; } QColor red() const { return red_; } QColor green() const { return green_; } @@ -38,5 +45,5 @@ public: QColor unavailable() const { return QColor(0xff, 0x99, 0x33); } private: - QColor sidebarBackground_, separator_, red_, green_, error_, orange_, alternateButton_; + QColor sidebarBackground_, separator_, red_, green_, error_, orange_; };