diff options
| author | user <user@clank> | 2026-08-15 00:09:23 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-15 00:09:23 +0200 |
| commit | f4e4e52ff9293b413bd477464ccf492c063e9442 (patch) | |
| tree | a8714ea0bf1bd1d02fa23e96cae2f0c6f219dc19 | |
| parent | contrib: presets worth stealing, and a test that they do anything (diff) | |
| download | slosh-f4e4e52ff9293b413bd477464ccf492c063e9442.tar.gz slosh-f4e4e52ff9293b413bd477464ccf492c063e9442.tar.xz slosh-f4e4e52ff9293b413bd477464ccf492c063e9442.zip | |
contrib: the shadertoy presets, usable in sl0ppty
They only existed in a browser, which is a strange place for a terminal
multiplexer's shaders to live. Now each one is a file you can paste.
contrib/shaders/ holds all thirty-two, one per file, *generated* from the
presets in shadertoy.html by contrib/gen-shaders. The page stays the source:
it is where they are written, previewed and conformance-tested against the
compiler, and a hand-kept second copy would drift the day after it was
written. `gen-shaders --check` says whether the files are current, which is a
test, so the drift is a failing build rather than a stale example.
contrib/shader-tour cycles a session through them, in the shape theme-tour
already established — write the config the session watches and let the
watcher do the rest. It takes one shader, one group, or nothing for all of
them, and it resolves a short name (`torch`) as well as the whole slug
(`guides-torch`), because nobody remembers which group a shader is in. The
tour writes the theme alongside the shader: shaders resolve default-coloured
text through theme's default_fg/default_bg, so a preset judged without one is
being judged against a guess.
The new test is the one that matters. Every shipped file is loaded into a
real session and has to *change the cells of a pane* -- an example that
previews beautifully and does nothing when pasted is worse than no example,
and neither the toy's conformance test nor a config parse would notice. Three
of them are then checked for the specific thing they claim: scanlines
alternate row by row, indent guides land on every fourth column, a flat dim
is flat. One session, reloaded per preset, which is both quick and a fair
exercise of the path anyone will actually use.
Renamed the "the built-ins, as expressions" group to drop the qualifier from
its filenames; it was generating built-ins-as-expressions-scanlines.kdl,
which is a sentence rather than a name.
38 files changed, 820 insertions, 2 deletions
@@ -139,7 +139,10 @@ shaders { Integer arithmetic over `x y cols rows curx cury focused t`, with `min max abs clamp dist` and comparisons that give 0 or 1 — so `(x < 10) * 200` is how you -write a rule. There are no loops, so a config cannot hang a session. The +write a rule. Thirty-two ready-made ones are in +[`contrib/shaders`](contrib/shaders) — a cursor line, a crosshair, a torch, a +phosphor CRT, sonar pings that follow your cursor — and `contrib/shader-tour` +cycles a live session through them. There are no loops, so a config cannot hang a session. The expression produces the *strength* and never the colour, which keeps the mixing in C and means the whole program can be worked out once into a per-cell map and reused: a shader you wrote in your config costs about what a compiled diff --git a/contrib/README.md b/contrib/README.md index 5aba78f..eabd14e 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -34,6 +34,38 @@ It works by writing the theme over the file the session was started with and letting the config watcher notice, which is also a fair demonstration of the watcher. +## shaders/ and shader-tour + +Thirty-two ready-made shaders, one file each, generated from the presets in +`shadertoy.html` by `gen-shaders` — the page is where they are written and +previewed, and a second copy kept by hand would be a second copy kept badly. +`tests/test_shader_presets.py` fails if the two ever disagree. + +To use one, append it to `~/.config/sl0ppty/config.kdl`; a running session +picks it up when you save. + +```sh +cat contrib/shaders/guides-torch.kdl >> ~/.config/sl0ppty/config.kdl +``` + +To look before choosing: + +```sh +contrib/shader-tour # spawn a session and cycle every one +contrib/shader-tour crt # cycle one group +contrib/shader-tour torch # apply one to the session named by $SESSION +DWELL=8 contrib/shader-tour motion # linger longer on each +``` + +The groups are `guides` (a cursor line, a crosshair, indent guides, a torch — +things that tell you where you are), `crt` (phosphor, amber, film grain, a +rolling bar), `motion` (sonar, ripples, plasma, matrix rain), `built-ins` +(every compiled-in shader written as an expression, which is the point: +`vignette` is `dim` with an argument) and `pane-states`. + +Move the mouse while the cursor-following ones are up. `curx`/`cury` is where +the cursor is, and standing still tells you nothing about a torch. + ## shader-plugin/ A skeleton for adding your own shaders as a shared library, with a Makefile diff --git a/contrib/gen-shaders b/contrib/gen-shaders new file mode 100755 index 0000000..0ec27c3 --- /dev/null +++ b/contrib/gen-shaders @@ -0,0 +1,92 @@ +#!/usr/bin/env node +/* + * Write contrib/shaders/*.kdl from the presets in contrib/shadertoy.html. + * + * The page is the source: it is where the presets are written, previewed and + * conformance-tested, and a second copy maintained by hand would be a second + * copy maintained badly. tests/test_shader_presets.py regenerates into a temp + * directory and fails if the committed files have drifted, so "I edited the + * page and forgot" is a failing test rather than a stale example. + * + * contrib/gen-shaders write the files + * contrib/gen-shaders --check say whether they are current, write nothing + */ +"use strict"; +const fs = require("fs"); +const path = require("path"); + +const here = __dirname; +const html = fs.readFileSync(path.join(here, "shadertoy.html"), "utf8"); +const script = html.split("<script>")[1].split("</script>")[0]; +/* Everything up to the DOM: the presets and nothing that needs a browser. */ +const prelude = script.slice(0, script.indexOf("/* ---- the chain UI")); +const PRESETS = new Function(prelude + "; return PRESETS;")(); + +/* "built-ins, as expressions" is a heading, not a filename: the qualifier is + * dropped so the file is built-ins-scanlines.kdl and not a sentence. */ +const slug = (s) => s.toLowerCase().replace(/,.*$/, "") + .replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""); + +function render(group, name, chain) { + const body = chain.map((p) => { + const color = p.op === "tint" ? ` color="${p.color}"` : ""; + return ` ${p.op}${color} amount="${p.amount}"`; + }).join("\n"); + return `// ${name} — ${group} +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { +${body} +} +`; +} + +const outDir = process.argv.includes("--out") + ? process.argv[process.argv.indexOf("--out") + 1] + : path.join(here, "shaders"); +const check = process.argv.includes("--check"); + +if (!check) fs.mkdirSync(outDir, { recursive: true }); + +let stale = []; +let count = 0; +for (const [group, entries] of Object.entries(PRESETS)) { + for (const [name, chain] of Object.entries(entries)) { + const file = path.join(outDir, `${slug(group)}-${slug(name)}.kdl`); + const text = render(group, name, chain); + count++; + if (check) { + let have = null; + try { have = fs.readFileSync(file, "utf8"); } catch { /* missing */ } + if (have !== text) stale.push(path.basename(file)); + } else { + fs.writeFileSync(file, text); + } + } +} + +if (check) { + /* A file nobody generates any more is as stale as one that has changed. */ + const want = new Set(); + for (const [group, entries] of Object.entries(PRESETS)) + for (const name of Object.keys(entries)) + want.add(`${slug(group)}-${slug(name)}.kdl`); + for (const f of fs.readdirSync(outDir)) + if (f.endsWith(".kdl") && !want.has(f)) stale.push(`${f} (no longer a preset)`); + + if (stale.length) { + console.error("stale, run contrib/gen-shaders:\n " + stale.join("\n ")); + process.exit(1); + } + console.log(`${count} preset files are current`); +} else { + console.log(`wrote ${count} files to ${path.relative(process.cwd(), outDir)}`); +} diff --git a/contrib/shader-tour b/contrib/shader-tour new file mode 100755 index 0000000..e0678b9 --- /dev/null +++ b/contrib/shader-tour @@ -0,0 +1,81 @@ +#!/bin/sh +# Show the shaders in contrib/shaders on a running session. +# +# contrib/shader-tour spawn a session and cycle every one +# contrib/shader-tour torch apply one to the session named below +# contrib/shader-tour guides cycle just that group +# SESSION=work contrib/shader-tour ...to a different session +# DWELL=8 contrib/shader-tour linger longer on each +# +# Like theme-tour, it works by writing the config the session was started with +# and letting the watcher notice. The cursor-following ones (torch, sonar, +# ripples, crosshair) want the mouse moved over the session while they are up: +# `curx`/`cury` is where the cursor is, and standing still tells you nothing. +set -e +here=$(cd "$(dirname "$0")" && pwd) +bin="$here/../build/sl0ppty" +SESSION=${SESSION:-shaders} +CONF=${SL0PPTY_CONFIG:-/tmp/sl0ppty-shader-tour.kdl} +DWELL=${DWELL:-5} + +# A name can be the whole slug (guides-torch), or just the interesting half +# (torch) when that is unambiguous, because nobody wants to remember which +# group a shader is in. +resolve() { + [ -f "$here/shaders/$1.kdl" ] && { echo "$1"; return 0; } + set -- $(cd "$here/shaders" && ls -- *-"$1".kdl 2>/dev/null | sed 's|\.kdl$||') + [ $# -eq 1 ] && { echo "$1"; return 0; } + return 1 +} + +apply() { + name=$(resolve "$1") || { echo "no such shader: $1"; exit 1; } + f="$here/shaders/$name.kdl" + # The theme goes with it: shaders resolve default-coloured text through + # theme's default_fg/default_bg, so a preset judged without one is being + # judged against a guess. + { + cat "$here/themes/sl0p.kdl" + echo + cat "$f" + } > "$CONF" + printf ' %-26s %s\n' "$name" "$(sed -n '1s|// ||p' "$f")" +} + +# A tour needs something to tour. Applying a single shader does not: it may +# well be aimed at a session you already have, or at a config you are editing. +ensure_session() { + "$bin" ls 2>/dev/null | grep -q "^$SESSION " && return 0 + cp "$here/themes/sl0p.kdl" "$CONF" + echo "starting a session called $SESSION..." + SL0PPTY_CONFIG="$CONF" "$bin" -s "$SESSION" -- "${SHELL:-/bin/sh}" & + sleep 1 +} + +if [ -n "$1" ]; then + # A whole group (guides, crt, motion, built-ins, pane-states) or one shader. + set +e + ls "$here/shaders/$1-"*.kdl >/dev/null 2>&1 + isgroup=$? + set -e + if [ $isgroup -eq 0 ]; then + ensure_session + echo "cycling $1, ${DWELL}s each. ^C to stop wherever you like." + for f in "$here/shaders/$1-"*.kdl; do + apply "$(basename "$f" .kdl)" + sleep "$DWELL" + done + else + apply "$1" + fi + exit 0 +fi + +ensure_session + +echo "cycling every shader, ${DWELL}s each. ^C to stop wherever you like." +echo "move the mouse over the session for the ones that follow the cursor." +for f in "$here"/shaders/*.kdl; do + apply "$(basename "$f" .kdl)" + sleep "$DWELL" +done diff --git a/contrib/shaders/built-ins-checker.kdl b/contrib/shaders/built-ins-checker.kdl new file mode 100644 index 0000000..e689305 --- /dev/null +++ b/contrib/shaders/built-ins-checker.kdl @@ -0,0 +1,14 @@ +// checker — built-ins, as expressions +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="((x / 8 + y / 4) % 2) * 45" +} diff --git a/contrib/shaders/built-ins-gradient.kdl b/contrib/shaders/built-ins-gradient.kdl new file mode 100644 index 0000000..69f977f --- /dev/null +++ b/contrib/shaders/built-ins-gradient.kdl @@ -0,0 +1,14 @@ +// gradient — built-ins, as expressions +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="y * 200 / rows" +} diff --git a/contrib/shaders/built-ins-ruler.kdl b/contrib/shaders/built-ins-ruler.kdl new file mode 100644 index 0000000..e4643aa --- /dev/null +++ b/contrib/shaders/built-ins-ruler.kdl @@ -0,0 +1,14 @@ +// ruler — built-ins, as expressions +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + tint color="#ff5fd7" amount="(x == 40) * 220" +} diff --git a/contrib/shaders/built-ins-scanlines.kdl b/contrib/shaders/built-ins-scanlines.kdl new file mode 100644 index 0000000..6d25c05 --- /dev/null +++ b/contrib/shaders/built-ins-scanlines.kdl @@ -0,0 +1,14 @@ +// scanlines — built-ins, as expressions +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="(y % 2) * 40" +} diff --git a/contrib/shaders/built-ins-spotlight.kdl b/contrib/shaders/built-ins-spotlight.kdl new file mode 100644 index 0000000..2635871 --- /dev/null +++ b/contrib/shaders/built-ins-spotlight.kdl @@ -0,0 +1,14 @@ +// spotlight — built-ins, as expressions +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="clamp(dist(x, y, curx, cury) * 9, 0, 200)" +} diff --git a/contrib/shaders/built-ins-vignette.kdl b/contrib/shaders/built-ins-vignette.kdl new file mode 100644 index 0000000..138bd84 --- /dev/null +++ b/contrib/shaders/built-ins-vignette.kdl @@ -0,0 +1,14 @@ +// vignette — built-ins, as expressions +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="clamp(dist(x, y, cols / 2, rows / 2) * 4, 0, 190)" +} diff --git a/contrib/shaders/built-ins-zebra.kdl b/contrib/shaders/built-ins-zebra.kdl new file mode 100644 index 0000000..852e66a --- /dev/null +++ b/contrib/shaders/built-ins-zebra.kdl @@ -0,0 +1,14 @@ +// zebra — built-ins, as expressions +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="(y / 3 % 2) * 60" +} diff --git a/contrib/shaders/crt-amber.kdl b/contrib/shaders/crt-amber.kdl new file mode 100644 index 0000000..746aa4e --- /dev/null +++ b/contrib/shaders/crt-amber.kdl @@ -0,0 +1,16 @@ +// amber — crt +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + grayscale amount="255" + tint color="#ffb000" amount="95" + dim amount="(y % 2) * 40" +} diff --git a/contrib/shaders/crt-film.kdl b/contrib/shaders/crt-film.kdl new file mode 100644 index 0000000..19b295f --- /dev/null +++ b/contrib/shaders/crt-film.kdl @@ -0,0 +1,16 @@ +// film — crt +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + grayscale amount="255" + dim amount="((x * 7 + y * 13 + t / 40) % 17 < 2) * 70" + dim amount="clamp(dist(x, y, cols / 2, rows / 2) * 5, 0, 165)" +} diff --git a/contrib/shaders/crt-interlace.kdl b/contrib/shaders/crt-interlace.kdl new file mode 100644 index 0000000..18bd1c5 --- /dev/null +++ b/contrib/shaders/crt-interlace.kdl @@ -0,0 +1,14 @@ +// interlace — crt +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="((y + t / 80) % 2) * 45" +} diff --git a/contrib/shaders/crt-phosphor.kdl b/contrib/shaders/crt-phosphor.kdl new file mode 100644 index 0000000..7cdb397 --- /dev/null +++ b/contrib/shaders/crt-phosphor.kdl @@ -0,0 +1,17 @@ +// phosphor — crt +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + grayscale amount="255" + tint color="#33ff66" amount="80" + dim amount="(y % 3 == 0) * 55" + dim amount="clamp(dist(x, y, cols / 2, rows / 2) * 4, 0, 150)" +} diff --git a/contrib/shaders/crt-rolling-bar.kdl b/contrib/shaders/crt-rolling-bar.kdl new file mode 100644 index 0000000..b9ce7ef --- /dev/null +++ b/contrib/shaders/crt-rolling-bar.kdl @@ -0,0 +1,14 @@ +// rolling bar — crt +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="(abs(y - (t / 90) % rows) < 2) * 70" +} diff --git a/contrib/shaders/guides-crosshair.kdl b/contrib/shaders/guides-crosshair.kdl new file mode 100644 index 0000000..7c8ab09 --- /dev/null +++ b/contrib/shaders/guides-crosshair.kdl @@ -0,0 +1,14 @@ +// crosshair — guides +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="((y == cury) || (x == curx)) * 55" +} diff --git a/contrib/shaders/guides-cursor-line.kdl b/contrib/shaders/guides-cursor-line.kdl new file mode 100644 index 0000000..b3cb744 --- /dev/null +++ b/contrib/shaders/guides-cursor-line.kdl @@ -0,0 +1,14 @@ +// cursor line — guides +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + tint color="#ff5fd7" amount="(y == cury) * 45" +} diff --git a/contrib/shaders/guides-edge-falloff.kdl b/contrib/shaders/guides-edge-falloff.kdl new file mode 100644 index 0000000..865a8f5 --- /dev/null +++ b/contrib/shaders/guides-edge-falloff.kdl @@ -0,0 +1,14 @@ +// edge falloff — guides +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="clamp((5 - min(x, cols - 1 - x)) * 34, 0, 170)" +} diff --git a/contrib/shaders/guides-focus-mode.kdl b/contrib/shaders/guides-focus-mode.kdl new file mode 100644 index 0000000..2acf5ac --- /dev/null +++ b/contrib/shaders/guides-focus-mode.kdl @@ -0,0 +1,14 @@ +// focus mode — guides +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="clamp(abs(y - cury) * 26, 0, 190)" +} diff --git a/contrib/shaders/guides-indent-guides.kdl b/contrib/shaders/guides-indent-guides.kdl new file mode 100644 index 0000000..210b495 --- /dev/null +++ b/contrib/shaders/guides-indent-guides.kdl @@ -0,0 +1,14 @@ +// indent guides — guides +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + tint color="#7aa2f7" amount="(x % 4 == 0) * 38" +} diff --git a/contrib/shaders/guides-right-margin.kdl b/contrib/shaders/guides-right-margin.kdl new file mode 100644 index 0000000..3d6a96d --- /dev/null +++ b/contrib/shaders/guides-right-margin.kdl @@ -0,0 +1,14 @@ +// right margin — guides +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="clamp((x - cols * 3 / 4) * 30, 0, 170)" +} diff --git a/contrib/shaders/guides-thirds.kdl b/contrib/shaders/guides-thirds.kdl new file mode 100644 index 0000000..4c099cd --- /dev/null +++ b/contrib/shaders/guides-thirds.kdl @@ -0,0 +1,14 @@ +// thirds — guides +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + tint color="#ff5fd7" amount="(x == cols / 3 || x == cols * 2 / 3) * 140" +} diff --git a/contrib/shaders/guides-torch.kdl b/contrib/shaders/guides-torch.kdl new file mode 100644 index 0000000..41e1f3c --- /dev/null +++ b/contrib/shaders/guides-torch.kdl @@ -0,0 +1,14 @@ +// torch — guides +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="clamp((dist(x, y, curx, cury) - 8) * 22, 0, 215)" +} diff --git a/contrib/shaders/motion-barber-pole.kdl b/contrib/shaders/motion-barber-pole.kdl new file mode 100644 index 0000000..21c01ba --- /dev/null +++ b/contrib/shaders/motion-barber-pole.kdl @@ -0,0 +1,14 @@ +// barber pole — motion +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="((x + y + t / 50) / 3 % 2) * 50" +} diff --git a/contrib/shaders/motion-matrix-rain.kdl b/contrib/shaders/motion-matrix-rain.kdl new file mode 100644 index 0000000..cb46843 --- /dev/null +++ b/contrib/shaders/motion-matrix-rain.kdl @@ -0,0 +1,15 @@ +// matrix rain — motion +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + grayscale amount="220" + tint color="#33ff66" amount="clamp(240 - (((t / 70 + (x * 11) % 23 * 3) % rows - y + rows) % rows) * 45, 0, 240)" +} diff --git a/contrib/shaders/motion-plasma.kdl b/contrib/shaders/motion-plasma.kdl new file mode 100644 index 0000000..6689753 --- /dev/null +++ b/contrib/shaders/motion-plasma.kdl @@ -0,0 +1,14 @@ +// plasma — motion +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="clamp(abs((x * 2 + t / 40) % 34 - 17) * 7 + abs((y * 3 - t / 55) % 26 - 13) * 6 - 40, 0, 170)" +} diff --git a/contrib/shaders/motion-pulse.kdl b/contrib/shaders/motion-pulse.kdl new file mode 100644 index 0000000..1646fe5 --- /dev/null +++ b/contrib/shaders/motion-pulse.kdl @@ -0,0 +1,14 @@ +// pulse — motion +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + tint color="#ff5fd7" amount="clamp((t / 6) % 300 - 45, 0, 160)" +} diff --git a/contrib/shaders/motion-ripples.kdl b/contrib/shaders/motion-ripples.kdl new file mode 100644 index 0000000..551a888 --- /dev/null +++ b/contrib/shaders/motion-ripples.kdl @@ -0,0 +1,14 @@ +// ripples — motion +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + tint color="#7aa2f7" amount="((dist(x, y, curx, cury) + t / 30) % 10 < 5) * 50" +} diff --git a/contrib/shaders/motion-sonar.kdl b/contrib/shaders/motion-sonar.kdl new file mode 100644 index 0000000..d62ef09 --- /dev/null +++ b/contrib/shaders/motion-sonar.kdl @@ -0,0 +1,14 @@ +// sonar — motion +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + tint color="#33ff66" amount="(abs(dist(x, y, curx, cury) - (t / 22) % 80) < 3) * 220" +} diff --git a/contrib/shaders/motion-sweep.kdl b/contrib/shaders/motion-sweep.kdl new file mode 100644 index 0000000..bb6eef3 --- /dev/null +++ b/contrib/shaders/motion-sweep.kdl @@ -0,0 +1,14 @@ +// sweep — motion +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="clamp(abs(x - (t / 24) % cols) * 22, 0, 200)" +} diff --git a/contrib/shaders/motion-typewriter.kdl b/contrib/shaders/motion-typewriter.kdl new file mode 100644 index 0000000..e8ce5c0 --- /dev/null +++ b/contrib/shaders/motion-typewriter.kdl @@ -0,0 +1,14 @@ +// typewriter — motion +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="(y > (t / 400) % rows) * 200" +} diff --git a/contrib/shaders/pane-states-dead.kdl b/contrib/shaders/pane-states-dead.kdl new file mode 100644 index 0000000..d2ab61c --- /dev/null +++ b/contrib/shaders/pane-states-dead.kdl @@ -0,0 +1,15 @@ +// dead — pane states +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + grayscale amount="200" + dim amount="90" +} diff --git a/contrib/shaders/pane-states-scrolled.kdl b/contrib/shaders/pane-states-scrolled.kdl new file mode 100644 index 0000000..6c8699f --- /dev/null +++ b/contrib/shaders/pane-states-scrolled.kdl @@ -0,0 +1,14 @@ +// scrolled — pane states +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + tint color="#ffcc88" amount="30" +} diff --git a/contrib/shaders/pane-states-suspended.kdl b/contrib/shaders/pane-states-suspended.kdl new file mode 100644 index 0000000..9cc6cb3 --- /dev/null +++ b/contrib/shaders/pane-states-suspended.kdl @@ -0,0 +1,15 @@ +// suspended — pane states +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + grayscale amount="180" + dim amount="90" +} diff --git a/contrib/shaders/pane-states-unfocused.kdl b/contrib/shaders/pane-states-unfocused.kdl new file mode 100644 index 0000000..746ac08 --- /dev/null +++ b/contrib/shaders/pane-states-unfocused.kdl @@ -0,0 +1,14 @@ +// unfocused — pane states +// +// Generated from contrib/shadertoy.html by contrib/gen-shaders; edit the +// preset there, where you can see what it does, and run that again. +// +// Drop this into ~/.config/sl0ppty/config.kdl (a running session picks it up +// when you save), or point $SL0PPTY_CONFIG at this file. To hang it off a +// pane state instead of every pane, put the same passes inside one: +// +// states { unfocused { ... } } + +shaders { + dim amount="90" +} diff --git a/contrib/shadertoy.html b/contrib/shadertoy.html index 809cdd4..d796d12 100644 --- a/contrib/shadertoy.html +++ b/contrib/shadertoy.html @@ -392,7 +392,7 @@ const PRESETS = { "typewriter": [{ op: "dim", amount: "(y > (t / 400) % rows) * 200", color: "#ff5fd7" }], }, - "the built-ins, as expressions": { + "built-ins, as expressions": { "scanlines": [{ op: "dim", amount: "(y % 2) * 40", color: "#ff5fd7" }], "vignette": [{ op: "dim", amount: "clamp(dist(x, y, cols / 2, rows / 2) * 4, 0, 190)", color: "#ff5fd7" }], "spotlight": [{ op: "dim", amount: "clamp(dist(x, y, curx, cury) * 9, 0, 200)", color: "#ff5fd7" }], diff --git a/tests/test_shader_presets.py b/tests/test_shader_presets.py new file mode 100644 index 0000000..f6a2b14 --- /dev/null +++ b/tests/test_shader_presets.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Every shader in contrib/shaders/ works in sl0ppty. + +The toy proves the *language* matches (test_shadertoy.py). This proves the +shipped files: that each one parses as config, is accepted by the shader +registry, and actually changes the cells of a real pane. An example that +previews beautifully and does nothing when you paste it is worse than no +example, and nothing else would catch that. + +One session, reloaded per preset, because that is both fast and a fair +exercise of the reload path -- which is how anyone will actually try these. +""" +import glob +import os +import shutil +import subprocess +import sys +import tempfile + +from harness import Session, check, report + +HERE = os.path.dirname(os.path.abspath(__file__)) +CONTRIB = os.path.join(HERE, "..", "contrib") +FILES = sorted(glob.glob(os.path.join(CONTRIB, "shaders", "*.kdl"))) + +# Prints a screenful of known-coloured text, then waits. +SH = ["/bin/sh", "-c", + 'printf "\\033]2;p\\007"; ' + 'i=0; while [ $i -lt 12 ]; do printf "%s\\n" ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789; ' + 'i=$((i+1)); done; read x'] + +# Every colour in the pane's content area, so "did anything change" is a +# question about the whole pane rather than one lucky cell. +def cells(snap, pane): + out = [] + for row in range(pane["content_h"]): + y = pane["content_y"] + row + for col in range(pane["content_w"]): + run = snap.style_at(pane["content_x"] + col, y) + out.append((run or {}).get("fg")) + return out + + +def test_every_preset_changes_the_pane(): + check("contrib ships shader presets", len(FILES) >= 20, str(len(FILES))) + + cfg = tempfile.NamedTemporaryFile("w", suffix=".kdl", delete=False) + cfg.write('theme { default_fg "#ffffff" default_bg "#000000" }\n') + cfg.close() + + with Session(SH, cols=70, rows=18, config=cfg.name) as s: + s.until_text("ABCDEF") + base = cells(s.snapshot(), s.pane()) + + for path in FILES: + name = os.path.basename(path) + # The theme keeps "what colour is a cell" answerable; the preset is + # appended so it is exactly the file a user would paste. + with open(cfg.name, "w") as f: + f.write('theme { default_fg "#ffffff" default_bg "#000000" }\n') + f.write(open(path).read()) + reply = s.api("reload") + check(f"{name} loads", reply.get("ok") is True, str(reply)) + + s.settle(20) + now = cells(s.snapshot(), s.pane()) + check(f"{name} changes the pane", now != base, + f"identical to unshaded: {now[:3]}") + + os.unlink(cfg.name) + + +def test_a_few_of_them_do_the_specific_thing_they_claim(): + """Spot checks with a known answer, so "it changed something" cannot be + satisfied by an effect that changed the wrong thing.""" + cfg = tempfile.NamedTemporaryFile("w", suffix=".kdl", delete=False) + + def load(basename): + with open(cfg.name, "w") as f: + f.write('theme { default_fg "#ffffff" default_bg "#000000" }\n') + f.write(open(os.path.join(CONTRIB, "shaders", basename)).read()) + + load("built-ins-scanlines.kdl") + with Session(SH, cols=70, rows=18, config=cfg.name) as s: + s.until_text("ABCDEF") + pane = s.pane() + rows = [cells(s.snapshot(), pane)[r * pane["content_w"]] + for r in range(4)] + check("scanlines alternate row by row", + rows[0] == rows[2] and rows[1] == rows[3] and rows[0] != rows[1], + str(rows)) + + load("guides-indent-guides.kdl") + with Session(SH, cols=70, rows=18, config=cfg.name) as s: + s.until_text("ABCDEF") + pane = s.pane() + row = cells(s.snapshot(), pane)[:9] + check("indent guides mark every fourth column", + row[0] == row[4] == row[8] and row[0] != row[1] and row[1] == row[2], + str(row)) + + load("pane-states-unfocused.kdl") + with Session(SH, cols=70, rows=18, config=cfg.name) as s: + s.until_text("ABCDEF") + pane = s.pane() + flat = cells(s.snapshot(), pane) + check("a flat dim is the same everywhere", + len({c for c in flat[:40] if c}) == 1, str(flat[:6])) + + os.unlink(cfg.name) + + +def test_the_tour_can_name_every_one_of_them(): + """contrib/shader-tour resolves a short name (`torch`) as well as the whole + slug. A tour that cannot name a shader it ships is a broken demo.""" + tour = os.path.join(CONTRIB, "shader-tour") + conf = tempfile.NamedTemporaryFile("w", suffix=".kdl", delete=False) + conf.close() + env = dict(os.environ, SL0PPTY_CONFIG=conf.name) + + # Named explicitly rather than derived: a group name with a hyphen in it + # (built-ins) makes "the short half" ambiguous to compute and easy to get + # accidentally right, which is not a test. + for name in ("guides-torch", "torch", "matrix-rain"): + out = subprocess.run([tour, name], capture_output=True, text=True, env=env) + check(f"the tour applies {name!r}", + out.returncode == 0 and "shaders {" in open(conf.name).read(), + (out.stdout + out.stderr).strip()) + + out = subprocess.run([tour, "definitely-not-a-shader"], + capture_output=True, text=True, env=env) + check("and refuses one it does not have", out.returncode != 0, + (out.stdout + out.stderr).strip()) + os.unlink(conf.name) + + +def test_the_files_match_the_page_they_came_from(): + """They are generated from contrib/shadertoy.html. A hand-edit there and a + forgotten regeneration would leave the two disagreeing silently.""" + if not shutil.which("node"): + print("SKIP no node: cannot check the generated files are current") + return + out = subprocess.run([os.path.join(CONTRIB, "gen-shaders"), "--check"], + capture_output=True, text=True) + check("contrib/shaders is current with contrib/shadertoy.html", + out.returncode == 0, (out.stderr or out.stdout).strip()) + + +for name, fn in sorted(list(globals().items())): + if name.startswith("test_"): + fn() +sys.exit(report()) |
