aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-15 00:09:23 +0200
committeruser <user@clank>2026-08-15 00:09:23 +0200
commitf4e4e52ff9293b413bd477464ccf492c063e9442 (patch)
treea8714ea0bf1bd1d02fa23e96cae2f0c6f219dc19
parentcontrib: presets worth stealing, and a test that they do anything (diff)
downloadslosh-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.
-rw-r--r--README.md5
-rw-r--r--contrib/README.md32
-rwxr-xr-xcontrib/gen-shaders92
-rwxr-xr-xcontrib/shader-tour81
-rw-r--r--contrib/shaders/built-ins-checker.kdl14
-rw-r--r--contrib/shaders/built-ins-gradient.kdl14
-rw-r--r--contrib/shaders/built-ins-ruler.kdl14
-rw-r--r--contrib/shaders/built-ins-scanlines.kdl14
-rw-r--r--contrib/shaders/built-ins-spotlight.kdl14
-rw-r--r--contrib/shaders/built-ins-vignette.kdl14
-rw-r--r--contrib/shaders/built-ins-zebra.kdl14
-rw-r--r--contrib/shaders/crt-amber.kdl16
-rw-r--r--contrib/shaders/crt-film.kdl16
-rw-r--r--contrib/shaders/crt-interlace.kdl14
-rw-r--r--contrib/shaders/crt-phosphor.kdl17
-rw-r--r--contrib/shaders/crt-rolling-bar.kdl14
-rw-r--r--contrib/shaders/guides-crosshair.kdl14
-rw-r--r--contrib/shaders/guides-cursor-line.kdl14
-rw-r--r--contrib/shaders/guides-edge-falloff.kdl14
-rw-r--r--contrib/shaders/guides-focus-mode.kdl14
-rw-r--r--contrib/shaders/guides-indent-guides.kdl14
-rw-r--r--contrib/shaders/guides-right-margin.kdl14
-rw-r--r--contrib/shaders/guides-thirds.kdl14
-rw-r--r--contrib/shaders/guides-torch.kdl14
-rw-r--r--contrib/shaders/motion-barber-pole.kdl14
-rw-r--r--contrib/shaders/motion-matrix-rain.kdl15
-rw-r--r--contrib/shaders/motion-plasma.kdl14
-rw-r--r--contrib/shaders/motion-pulse.kdl14
-rw-r--r--contrib/shaders/motion-ripples.kdl14
-rw-r--r--contrib/shaders/motion-sonar.kdl14
-rw-r--r--contrib/shaders/motion-sweep.kdl14
-rw-r--r--contrib/shaders/motion-typewriter.kdl14
-rw-r--r--contrib/shaders/pane-states-dead.kdl15
-rw-r--r--contrib/shaders/pane-states-scrolled.kdl14
-rw-r--r--contrib/shaders/pane-states-suspended.kdl15
-rw-r--r--contrib/shaders/pane-states-unfocused.kdl14
-rw-r--r--contrib/shadertoy.html2
-rw-r--r--tests/test_shader_presets.py152
38 files changed, 820 insertions, 2 deletions
diff --git a/README.md b/README.md
index f0e3b8e..7b75aac 100644
--- a/README.md
+++ b/README.md
@@ -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())