aboutsummaryrefslogtreecommitdiffstats
path: root/src/expr.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-15 23:06:32 +0200
committeruser <user@clank>2026-08-15 23:06:32 +0200
commitf6281fbec93d92bfc7e9164492e436d0236d9179 (patch)
treec78b435dec7f62c948e2e8144f29f47ccab2fa7d /src/expr.c
parentsl0ppty: a command palette, on a key you choose (diff)
downloadslosh-f6281fbec93d92bfc7e9164492e436d0236d9179.tar.gz
slosh-f6281fbec93d92bfc7e9164492e436d0236d9179.tar.xz
slosh-f6281fbec93d92bfc7e9164492e436d0236d9179.zip
sl0ppty: shaders on the chrome, and two keys for the arrangement
Five things, in one commit because they land in the same few files -- the action table, the shader pass, config.kdl -- and splitting them by hunk would buy a tidier log at the price of commits that do not build. Each is verified end to end; the suite is 46 files green. Chrome shaders. `where="chrome"` on any entry in `shaders` or in a `states` block runs that pass over a pane's frame -- border, padding, title, buttons -- instead of its contents. Nothing else changes: same registry, same `amount`, same expressions, same chain order, one new word, because a frame is another rect and so it is another pass. It runs over the frame's whole rect with the contents punched out as a hole, rather than four passes over four sides: an effect has to be able to travel round a border, and four passes would each count x/y from zero and restart at every corner. The hole keeps the two honest -- a skipped cell is never visited, so a full-strength chrome tint cannot reach your text. It is also the only kind of pass that can colour a pane a small window has collapsed to a single row (D6), which is the state where a colour is worth the most. `channel="fg"|"bg"|"both"` came out of looking at the first border flash: `tint` mixes both of a cell's colours, and a frame's background is the terminal's own default, so the pass materialised it to our guess at that default and mixed towards the colour -- a dark rectangle behind the glyphs, left there until the pane changed state. The mask is enforced by the pass, not the shader: the other colour is snapshotted before materialising and put back after, so an unset colour goes back to unset, it works for a built-in and a plugin alike, and no shader grows an fg-only twin of itself. A `bell` pane state and a `since` variable, because "flash the border when a pane rings" needed both and neither existed. `since` is milliseconds since the pane entered its current state, stamped in the one function that decides the state -- a transition is the one thing a frame cannot tell you, so it is remembered where it cannot disagree with the state it dates. `bell` ranks above the ambient states and below `dead`: a pane that rang while you were elsewhere is news, and a pane that rang and then exited is a pane that exited. It ends when you look at the pane, because that is what answering a bell is. Animation needed a clock and did not have one: a shader reading `t` only advanced when something else caused a frame, so a pulse froze the moment you stopped typing. `app_next_deadline_ms` now reports `anim_ms` while a chain that reads the clock ran over the last frame -- derived from what was applied, so a pulse hung off `dead` costs a clock while a pane is dead and nothing for the rest of the session. A `deadline` verb makes that observable; the server's poll loop already lived on the number. contrib/chrome has seven presets and the perimeter trick they are built on: on a frame the coordinate you want is distance along the ring, which the language can work out, and `min(d, P - d)` is what makes an effect cross the corner it started from. Hand-written, unlike contrib/shaders, because the toy previews contents and a frame has no preview to give -- so tests/test_chrome_presets.py checks each one the only way left: load it into a real session, assert the frame moves and the contents do not. `equalize` (C-a =) puts every share back to even: each split's children weighted by how many visible panes are behind them, so one pane beside a stack of three is a quarter of the width and "even" means the same thing at every depth. Exactly equal cell counts are not something a tree can always express, so the action promises the even division it can. `rotate-layout` (C-a Space) turns the whole tab a quarter turn clockwise. Every split changes axis and a row split's children reverse, because the pane on top of a stack is the pane on the right once you turn it; get that asymmetry wrong and you have a mirror image, which looks almost right until four turns fail to come back. They do come back, exactly, which is why the inverse is three more turns rather than a second mirrored routine with its own chance of being wrong -- and why a key as easy as space is the right one rather than a hazard. A turn is refused when it would not fit: a share of the height is not always a share the width can afford, and a node that cannot give its children their floor takes the whole tab down to a list. So the turn is tried, the layout is asked what it made of it, and one that flattened the tab is put back with a word. Same rule `split_fits` applies to splitting. Keys: the palette moves from `shift+p` to `p`, and cycling tabs from `n`/`p` to tab/shift+tab. `p` is pressed far more often than "the tab before this one" and was the only shifted letter in the defaults; tab is what every other tabbed thing cycles with, and `\e[Z` decodes as shift+tab on a plain terminal, so this costs nothing on a client without the kitty protocol. `n` is free again. The actions are untouched: two lines of config put the old binds back. The shader ABI goes to 3: `shade_ctx_t` gained `state_ms` and struct shader gained `channels`, both the pass's business rather than a shader's. Plugins need a rebuild, which is what the version is for.
Diffstat (limited to 'src/expr.c')
-rw-r--r--src/expr.c12
1 files changed, 11 insertions, 1 deletions
diff --git a/src/expr.c b/src/expr.c
index 3400e34..677b3b5 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -19,7 +19,8 @@ enum {
};
/* Variable slots, in the order the names table lists them. */
-enum { V_X, V_Y, V_COLS, V_ROWS, V_CURX, V_CURY, V_CURSOR, V_FOCUSED, V_T };
+enum { V_X, V_Y, V_COLS, V_ROWS, V_CURX, V_CURY, V_CURSOR, V_FOCUSED, V_T,
+ V_SINCE };
static const struct {
const char *name;
@@ -35,6 +36,9 @@ static const struct {
{"cursor", V_CURSOR, EXPR_DEP_CURSOR},
{"focused", V_FOCUSED, EXPR_DEP_FOCUS},
{"t", V_T, EXPR_DEP_TIME},
+ /* Also a clock, so it carries the same dependency: never cached, and the
+ * session keeps painting while something reads it. */
+ {"since", V_SINCE, EXPR_DEP_TIME},
};
/* cols/rows are a dependency even though the map's key always carries the
@@ -314,6 +318,12 @@ static int vm_run(const expr_prog_t *pr, const expr_env_t *env) {
[V_CURX] = env->curx, [V_CURY] = env->cury,
[V_CURSOR] = env->cursor, [V_FOCUSED] = env->focused,
[V_T] = (int32_t)(env->t & 0x7fffffff),
+ /* Clamped, not wrapped: a pane that has been unfocused for a month
+ * should read as "a long time", and a `since` that rolled over would
+ * make an effect restart for no reason anybody could see. */
+ [V_SINCE] = (int32_t)(env->since < 0 ? 0
+ : env->since > 0x7fffffff ? 0x7fffffff
+ : env->since),
};
for (size_t i = 0; i < pr->n; i++) {