mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-12 08:42:13 -04:00
ripple: use a shader for ripple effect
This commit is contained in:
56
quickshell/Shaders/frag/ripple.frag
Normal file
56
quickshell/Shaders/frag/ripple.frag
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 qt_TexCoord0;
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
layout(std140, binding = 0) uniform buf {
|
||||||
|
mat4 qt_Matrix;
|
||||||
|
float qt_Opacity;
|
||||||
|
float widthPx;
|
||||||
|
float heightPx;
|
||||||
|
float cornerRadiusPx;
|
||||||
|
float rippleCenterX;
|
||||||
|
float rippleCenterY;
|
||||||
|
float rippleRadius;
|
||||||
|
float rippleOpacity;
|
||||||
|
float offsetX;
|
||||||
|
float offsetY;
|
||||||
|
float parentWidth;
|
||||||
|
float parentHeight;
|
||||||
|
vec4 rippleCol;
|
||||||
|
} ubuf;
|
||||||
|
|
||||||
|
float sdRoundRect(vec2 p, vec2 b, float r) {
|
||||||
|
vec2 q = abs(p) - (b - vec2(r));
|
||||||
|
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx) + vec2(ubuf.offsetX, ubuf.offsetY);
|
||||||
|
|
||||||
|
float aa = fwidth(length(px)) * 1.5;
|
||||||
|
|
||||||
|
float circleD = length(px - vec2(ubuf.rippleCenterX, ubuf.rippleCenterY)) - ubuf.rippleRadius;
|
||||||
|
float circleMask = 1.0 - smoothstep(-aa, aa, circleD);
|
||||||
|
if (circleMask <= 0.0) {
|
||||||
|
fragColor = vec4(0.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float rrMask = 1.0;
|
||||||
|
if (ubuf.cornerRadiusPx > 0.0) {
|
||||||
|
vec2 halfSize = vec2(ubuf.parentWidth, ubuf.parentHeight) * 0.5;
|
||||||
|
float r = min(ubuf.cornerRadiusPx, min(halfSize.x, halfSize.y));
|
||||||
|
float rrD = sdRoundRect(px - halfSize, halfSize, r);
|
||||||
|
rrMask = 1.0 - smoothstep(-aa, aa, rrD);
|
||||||
|
}
|
||||||
|
|
||||||
|
float mask = circleMask * rrMask;
|
||||||
|
if (mask <= 0.0) {
|
||||||
|
fragColor = vec4(0.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float a = ubuf.rippleCol.a * ubuf.rippleOpacity * mask * ubuf.qt_Opacity;
|
||||||
|
fragColor = vec4(ubuf.rippleCol.rgb * a, a);
|
||||||
|
}
|
||||||
BIN
quickshell/Shaders/qsb/ripple.frag.qsb
Normal file
BIN
quickshell/Shaders/qsb/ripple.frag.qsb
Normal file
Binary file not shown.
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -11,7 +10,7 @@ Item {
|
|||||||
|
|
||||||
property real _rippleX: 0
|
property real _rippleX: 0
|
||||||
property real _rippleY: 0
|
property real _rippleY: 0
|
||||||
property real _rippleSize: 0
|
property real _rippleMaxRadius: 0
|
||||||
readonly property alias animating: rippleAnim.running
|
readonly property alias animating: rippleAnim.running
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -24,7 +23,7 @@ Item {
|
|||||||
_rippleY = y;
|
_rippleY = y;
|
||||||
|
|
||||||
const dist = (ox, oy) => ox * ox + oy * oy;
|
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;
|
_rippleMaxRadius = Math.sqrt(Math.max(dist(x, y), dist(x, height - y), dist(width - x, y), dist(width - x, height - y)));
|
||||||
|
|
||||||
rippleAnim.restart();
|
rippleAnim.restart();
|
||||||
}
|
}
|
||||||
@@ -33,45 +32,32 @@ Item {
|
|||||||
id: rippleAnim
|
id: rippleAnim
|
||||||
|
|
||||||
PropertyAction {
|
PropertyAction {
|
||||||
target: ripple
|
target: rippleFx
|
||||||
property: "x"
|
property: "rippleCenterX"
|
||||||
value: root._rippleX
|
value: root._rippleX
|
||||||
}
|
}
|
||||||
PropertyAction {
|
PropertyAction {
|
||||||
target: ripple
|
target: rippleFx
|
||||||
property: "y"
|
property: "rippleCenterY"
|
||||||
value: root._rippleY
|
value: root._rippleY
|
||||||
}
|
}
|
||||||
PropertyAction {
|
PropertyAction {
|
||||||
target: ripple
|
target: rippleFx
|
||||||
property: "implicitWidth"
|
property: "rippleRadius"
|
||||||
value: 0
|
value: 0
|
||||||
}
|
}
|
||||||
PropertyAction {
|
PropertyAction {
|
||||||
target: ripple
|
target: rippleFx
|
||||||
property: "implicitHeight"
|
property: "rippleOpacity"
|
||||||
value: 0
|
|
||||||
}
|
|
||||||
PropertyAction {
|
|
||||||
target: ripple
|
|
||||||
property: "opacity"
|
|
||||||
value: 0.10
|
value: 0.10
|
||||||
}
|
}
|
||||||
|
|
||||||
ParallelAnimation {
|
ParallelAnimation {
|
||||||
DankAnim {
|
DankAnim {
|
||||||
target: ripple
|
target: rippleFx
|
||||||
property: "implicitWidth"
|
property: "rippleRadius"
|
||||||
from: 0
|
from: 0
|
||||||
to: root._rippleSize
|
to: root._rippleMaxRadius
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
|
||||||
easing.bezierCurve: Theme.expressiveCurves.standardDecel
|
|
||||||
}
|
|
||||||
DankAnim {
|
|
||||||
target: ripple
|
|
||||||
property: "implicitHeight"
|
|
||||||
from: 0
|
|
||||||
to: root._rippleSize
|
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||||
easing.bezierCurve: Theme.expressiveCurves.standardDecel
|
easing.bezierCurve: Theme.expressiveCurves.standardDecel
|
||||||
}
|
}
|
||||||
@@ -80,8 +66,8 @@ Item {
|
|||||||
duration: Math.round(Theme.expressiveDurations.expressiveDefaultSpatial * 0.6)
|
duration: Math.round(Theme.expressiveDurations.expressiveDefaultSpatial * 0.6)
|
||||||
}
|
}
|
||||||
DankAnim {
|
DankAnim {
|
||||||
target: ripple
|
target: rippleFx
|
||||||
property: "opacity"
|
property: "rippleOpacity"
|
||||||
to: 0
|
to: 0
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||||
easing.bezierCurve: Theme.expressiveCurves.standard
|
easing.bezierCurve: Theme.expressiveCurves.standard
|
||||||
@@ -90,27 +76,29 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VRAM optimization: Use clip instead of layer+MultiEffect for rounded corners
|
ShaderEffect {
|
||||||
// The layer.enabled approach creates an offscreen texture for every clickable element
|
id: rippleFx
|
||||||
// with rounded corners, which adds 200-400MB VRAM across the UI.
|
visible: rippleAnim.running
|
||||||
// Using clip: true is GPU-native and much cheaper.
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: root.cornerRadius
|
|
||||||
color: "transparent"
|
|
||||||
clip: root.cornerRadius > 0
|
|
||||||
|
|
||||||
Rectangle {
|
property real rippleCenterX: 0
|
||||||
id: ripple
|
property real rippleCenterY: 0
|
||||||
|
property real rippleRadius: 0
|
||||||
|
property real rippleOpacity: 0
|
||||||
|
|
||||||
radius: Math.min(width, height) / 2
|
x: Math.max(0, rippleCenterX - rippleRadius)
|
||||||
color: root.rippleColor
|
y: Math.max(0, rippleCenterY - rippleRadius)
|
||||||
opacity: 0
|
width: Math.max(0, Math.min(root.width, rippleCenterX + rippleRadius) - x)
|
||||||
|
height: Math.max(0, Math.min(root.height, rippleCenterY + rippleRadius) - y)
|
||||||
|
|
||||||
transform: Translate {
|
property real widthPx: width
|
||||||
x: -ripple.width / 2
|
property real heightPx: height
|
||||||
y: -ripple.height / 2
|
property real cornerRadiusPx: root.cornerRadius
|
||||||
}
|
property real offsetX: x
|
||||||
}
|
property real offsetY: y
|
||||||
|
property real parentWidth: root.width
|
||||||
|
property real parentHeight: root.height
|
||||||
|
property vector4d rippleCol: Qt.vector4d(root.rippleColor.r, root.rippleColor.g, root.rippleColor.b, root.rippleColor.a)
|
||||||
|
|
||||||
|
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/ripple.frag.qsb")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user