From 53756513955d3da519ae7c832ebb59c0f0441296 Mon Sep 17 00:00:00 2001 From: EDToaster Date: Thu, 22 Oct 2026 12:47:56 -0700 Subject: [PATCH] examples: subtle warm cursor-glow shader; README: how to install the example shaders --- README.md | 13 +++++++++- examples/shaders/cursor-glow.glsl | 43 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 examples/shaders/cursor-glow.glsl diff --git a/README.md b/README.md index 8ca45e7..3084736 100644 --- a/README.md +++ b/README.md @@ -115,12 +115,23 @@ a CRT look where each bell sends a split-flap wave across the screen: toastty with a curved, scanlined CRT shader; after a bell, the character cells flip like split-flap display tiles in a wave spreading out from the cursor

+Shaders are plain files, not built in. Copy one next to your config and +list it; relative paths are relative to the config file's directory: + +```sh +mkdir -p ~/.config/toastty/shaders +cp examples/shaders/*.glsl ~/.config/toastty/shaders/ +``` + ```toml [shader] -paths = ["~/.config/toastty/shaders/crt-splitflap.glsl"] # run in order +paths = ["shaders/crt-splitflap.glsl"] # run in order animation = "auto" # "auto" | "always" | "never" ``` +[`cursor-glow.glsl`](examples/shaders/cursor-glow.glsl) is a smaller +example: a subtle warm glow around the cursor. + Shaders that don't use time cost nothing when the screen is idle. toastty reads which uniforms a shader uses and only draws frames when they can change. A shader whose animation is triggered by events can diff --git a/examples/shaders/cursor-glow.glsl b/examples/shaders/cursor-glow.glsl new file mode 100644 index 0000000..130df3f --- /dev/null +++ b/examples/shaders/cursor-glow.glsl @@ -0,0 +1,43 @@ +// A soft, warm yellow glow around the cursor. +// +// Static: it reads only the cursor uniforms, which change only when the +// cursor moves (and that redraws anyway), so it costs nothing at idle. +// The glow follows the cursor's visibility, so it blinks with a blinking +// cursor and disappears when an app hides the cursor. +// +// [shader] +// paths = ["shaders/cursor-glow.glsl"] + +const vec3 GLOW_COLOR = vec3(1.0, 0.78, 0.38); // warm yellow +const float STRENGTH = 0.16; // peak brightness next to the cursor, 0..1 +const float REACH_CELLS = 2.2; // how far the glow fades out, in cell heights + +// Distance from `p` to the rectangle with corners `lo`, `hi` (0 inside). +float rect_distance(vec2 p, vec2 lo, vec2 hi) { + vec2 d = max(max(lo - p, p - hi), 0.0); + return length(d); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec4 base = texture(iChannel0, fragCoord / iResolution.xy); + if (iCursorVisible == 0) { + fragColor = base; + return; + } + + // iCurrentCursor: xy = left, bottom edge (y points down); zw = size. + vec2 lo = vec2(iCurrentCursor.x, iCurrentCursor.y - iCurrentCursor.w); + vec2 hi = vec2(iCurrentCursor.x + iCurrentCursor.z, iCurrentCursor.y); + float d = rect_distance(fragCoord, lo, hi); + + // Smooth falloff to zero at the reach, and none on the cursor itself + // so the character under it keeps its colours. + float reach = REACH_CELLS * iCellSize.y; + float falloff = 1.0 - smoothstep(0.0, reach, d); + float g = STRENGTH * falloff * falloff * smoothstep(0.0, 1.5, d); + + // Screen blend in premultiplied space: brightens without clipping, + // and stays a valid premultiplied colour over a transparent background. + vec4 glow = vec4(GLOW_COLOR * g, g); + fragColor = base + glow - base * glow; +}