import QtQuick import QtQuick.Effects import qs.Common Item { id: root property color rippleColor: Theme.primary property real cornerRadius: 0 property bool enableRipple: typeof SettingsData !== "undefined" ? (SettingsData.enableRippleEffects ?? true) : true property real _rippleX: 0 property real _rippleY: 0 property real _rippleSize: 0 readonly property alias animating: rippleAnim.running anchors.fill: parent function trigger(x, y) { if (!enableRipple || Theme.currentAnimationSpeed === SettingsData.AnimationSpeed.None) return; _rippleX = x; _rippleY = y; const dist = (ox, oy) => ox * ox + oy * oy; _rippleSize = Math.sqrt(Math.max(dist(x, y), dist(x, height - y), dist(width - x, y), dist(width - x, height - y))) * 2; rippleAnim.restart(); } SequentialAnimation { id: rippleAnim PropertyAction { target: ripple property: "x" value: root._rippleX } PropertyAction { target: ripple property: "y" value: root._rippleY } PropertyAction { target: ripple property: "implicitWidth" value: 0 } PropertyAction { target: ripple property: "implicitHeight" value: 0 } PropertyAction { target: ripple property: "opacity" value: 0.10 } ParallelAnimation { DankAnim { target: ripple property: "implicitWidth" from: 0 to: root._rippleSize duration: Theme.expressiveDurations.expressiveDefaultSpatial easing.bezierCurve: Theme.expressiveCurves.standardDecel } DankAnim { target: ripple property: "implicitHeight" from: 0 to: root._rippleSize duration: Theme.expressiveDurations.expressiveDefaultSpatial easing.bezierCurve: Theme.expressiveCurves.standardDecel } SequentialAnimation { PauseAnimation { duration: Math.round(Theme.expressiveDurations.expressiveDefaultSpatial * 0.6) } DankAnim { target: ripple property: "opacity" to: 0 duration: Theme.expressiveDurations.expressiveDefaultSpatial easing.bezierCurve: Theme.expressiveCurves.standard } } } } // VRAM optimization: Use clip instead of layer+MultiEffect for rounded corners // The layer.enabled approach creates an offscreen texture for every clickable element // with rounded corners, which adds 200-400MB VRAM across the UI. // Using clip: true is GPU-native and much cheaper. Rectangle { anchors.fill: parent radius: root.cornerRadius color: "transparent" clip: root.cornerRadius > 0 Rectangle { id: ripple radius: Math.min(width, height) / 2 color: root.rippleColor opacity: 0 transform: Translate { x: -ripple.width / 2 y: -ripple.height / 2 } } } }