Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,23 @@ a CRT look where each bell sends a split-flap wave across the screen:
<img src="docs/screenshots/crt-splitflap.gif" alt="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" width="720">
</p>

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
Expand Down
43 changes: 43 additions & 0 deletions examples/shaders/cursor-glow.glsl
Original file line number Diff line number Diff line change
@@ -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;
}
Loading