Weather: rain and snow shaders.

This commit is contained in:
ItsLemmy
2025-11-19 00:16:54 -05:00
parent 770c667794
commit fef8535384
11 changed files with 203 additions and 3 deletions
@@ -1,5 +1,6 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import Quickshell
import qs.Commons
import qs.Services.Location
@@ -13,14 +14,54 @@ NBox {
property bool showLocation: true
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
// Test mode: set to "rain" or "snow"
property string testWeatherEffect: ""
// Weather condition detection
readonly property int currentWeatherCode: weatherReady ? LocationService.data.weather.current_weather.weathercode : 0
readonly property bool isRaining: testWeatherEffect === "rain" || (testWeatherEffect === "" && currentWeatherCode >= 51 && currentWeatherCode <= 67)
readonly property bool isSnowing: testWeatherEffect === "snow" || (testWeatherEffect === "" && ((currentWeatherCode >= 71 && currentWeatherCode <= 77) || (currentWeatherCode >= 85 && currentWeatherCode <= 86)))
// Animated time for shaders
property real shaderTime: 0
NumberAnimation on shaderTime {
running: root.isRaining || root.isSnowing
loops: Animation.Infinite
from: 0
to: 1000
duration: 100000
}
visible: Settings.data.location.weatherEnabled
implicitHeight: Math.max(100 * Style.uiScaleRatio, content.implicitHeight + (Style.marginXL * 2))
// Weather effect layer (rain/snow)
ShaderEffect {
id: weatherEffect
anchors.fill: parent
// Snow fills the box, rain matches content margins
anchors.margins: root.isSnowing ? root.border.width : Style.marginXL
visible: root.isRaining || root.isSnowing
property var source: ShaderEffectSource {
sourceItem: content
hideSource: root.isRaining // Only hide for rain (distortion), show for snow
}
property real time: root.shaderTime
property real itemWidth: weatherEffect.width
property real itemHeight: weatherEffect.height
property color bgColor: root.color
property real cornerRadius: root.isSnowing ? (root.radius - root.border.width) : 0
fragmentShader: root.isSnowing ?
Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/weather_snow.frag.qsb") :
Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/weather_rain.frag.qsb")
}
ColumnLayout {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.fill: parent
anchors.margins: Style.marginXL
spacing: Style.marginM
clip: true
+84
View File
@@ -0,0 +1,84 @@
#version 450
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D source;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float time;
float itemWidth;
float itemHeight;
vec4 bgColor;
float cornerRadius;
} ubuf;
// Signed distance function for rounded rectangle
float roundedBoxSDF(vec2 center, vec2 size, float radius) {
vec2 q = abs(center) - size + radius;
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
}
vec3 hash3(vec2 p) {
vec3 q = vec3(dot(p, vec2(127.1, 311.7)),
dot(p, vec2(269.5, 183.3)),
dot(p, vec2(419.2, 371.9)));
return fract(sin(q) * 43758.5453);
}
float noise(vec2 x, float iTime) {
vec2 p = floor(x);
vec2 f = fract(x);
float va = 0.0;
for (int j = -2; j <= 2; j++) {
for (int i = -2; i <= 2; i++) {
vec2 g = vec2(float(i), float(j));
vec3 o = hash3(p + g);
vec2 r = g - f + o.xy;
float d = sqrt(dot(r, r));
float ripple = max(mix(smoothstep(0.99, 0.999, max(cos(d - iTime * 2.0 + (o.x + o.y) * 5.0), 0.0)), 0.0, d), 0.0);
va += ripple;
}
}
return va;
}
void main() {
vec2 uv = qt_TexCoord0;
float iTime = ubuf.time * 0.07;
// Aspect ratio correction for circular ripples
float aspect = ubuf.itemWidth / ubuf.itemHeight;
vec2 uvAspect = vec2(uv.x * aspect, uv.y);
float f = noise(6.0 * uvAspect, iTime) * smoothstep(0.0, 0.2, sin(uv.x * 3.141592) * sin(uv.y * 3.141592));
// Calculate normal from noise for distortion
float normalScale = 0.5;
vec2 e = normalScale / vec2(ubuf.itemWidth, ubuf.itemHeight);
vec2 eAspect = vec2(e.x * aspect, e.y);
float cx = noise(6.0 * (uvAspect + eAspect), iTime) * smoothstep(0.0, 0.2, sin((uv.x + e.x) * 3.141592) * sin(uv.y * 3.141592));
float cy = noise(6.0 * (uvAspect + eAspect.yx), iTime) * smoothstep(0.0, 0.2, sin(uv.x * 3.141592) * sin((uv.y + e.y) * 3.141592));
vec2 n = vec2(cx - f, cy - f);
// Scale distortion back to texture space (undo aspect correction for X)
vec2 distortion = vec2(n.x / aspect, n.y);
// Sample source with distortion
vec4 col = texture(source, uv + distortion);
// Apply rounded corner mask
vec2 pixelPos = qt_TexCoord0 * vec2(ubuf.itemWidth, ubuf.itemHeight);
vec2 center = pixelPos - vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5;
vec2 halfSize = vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5;
float dist = roundedBoxSDF(center, halfSize, ubuf.cornerRadius);
float cornerMask = 1.0 - smoothstep(-1.0, 0.0, dist);
// Output with premultiplied alpha
float finalAlpha = col.a * ubuf.qt_Opacity * cornerMask;
fragColor = vec4(col.rgb * finalAlpha, finalAlpha);
}
+75
View File
@@ -0,0 +1,75 @@
#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 time;
float itemWidth;
float itemHeight;
vec4 bgColor;
float cornerRadius;
} ubuf;
// Signed distance function for rounded rectangle
float roundedBoxSDF(vec2 center, vec2 size, float radius) {
vec2 q = abs(center) - size + radius;
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
}
void main() {
// Aspect ratio correction
float aspect = ubuf.itemWidth / ubuf.itemHeight;
vec2 uv = qt_TexCoord0;
uv.x *= aspect;
uv.y = 1.0 - uv.y;
float iTime = ubuf.time * 0.15;
float snow = 0.0;
for (int k = 0; k < 6; k++) {
for (int i = 0; i < 12; i++) {
float cellSize = 2.0 + (float(i) * 3.0);
float downSpeed = 0.3 + (sin(iTime * 0.4 + float(k + i * 20)) + 1.0) * 0.00008;
vec2 uvAnim = uv + vec2(
0.01 * sin((iTime + float(k * 6185)) * 0.6 + float(i)) * (5.0 / float(i + 1)),
downSpeed * (iTime + float(k * 1352)) * (1.0 / float(i + 1))
);
vec2 uvStep = (ceil((uvAnim) * cellSize - vec2(0.5, 0.5)) / cellSize);
float x = fract(sin(dot(uvStep.xy, vec2(12.9898 + float(k) * 12.0, 78.233 + float(k) * 315.156))) * 43758.5453 + float(k) * 12.0) - 0.5;
float y = fract(sin(dot(uvStep.xy, vec2(62.2364 + float(k) * 23.0, 94.674 + float(k) * 95.0))) * 62159.8432 + float(k) * 12.0) - 0.5;
float randomMagnitude1 = sin(iTime * 2.5) * 0.7 / cellSize;
float randomMagnitude2 = cos(iTime * 1.65) * 0.7 / cellSize;
float d = 5.0 * distance((uvStep.xy + vec2(x * sin(y), y) * randomMagnitude1 + vec2(y, x) * randomMagnitude2), uvAnim.xy);
float omiVal = fract(sin(dot(uvStep.xy, vec2(32.4691, 94.615))) * 31572.1684);
if (omiVal < 0.03) {
float newd = (x + 1.0) * 0.4 * clamp(1.9 - d * (15.0 + (x * 6.3)) * (cellSize / 1.4), 0.0, 1.0);
snow += newd;
}
}
}
// Blend white snow over background color
float snowAlpha = clamp(snow * 2.0, 0.0, 1.0);
vec3 snowColor = vec3(1.0);
vec3 blended = mix(ubuf.bgColor.rgb, snowColor, snowAlpha);
// Apply rounded corner mask
vec2 pixelPos = qt_TexCoord0 * vec2(ubuf.itemWidth, ubuf.itemHeight);
vec2 center = pixelPos - vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5;
vec2 halfSize = vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5;
float dist = roundedBoxSDF(center, halfSize, ubuf.cornerRadius);
float cornerMask = 1.0 - smoothstep(-1.0, 0.0, dist);
// Output with premultiplied alpha
float finalAlpha = ubuf.qt_Opacity * cornerMask;
fragColor = vec4(blended * finalAlpha, finalAlpha);
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.