aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-14 14:25:13 +0200
committeruser <user@clank>2026-08-14 14:25:13 +0200
commit858234c05031a7bffc3ffe2a9b2aa4530d3ff0d7 (patch)
tree951d60c94949dc43c468252142a93006663b9f76 /src/app.c
parentsl0ppty: positional shaders, and a config block to ask for them (diff)
downloadslosh-858234c05031a7bffc3ffe2a9b2aa4530d3ff0d7.tar.gz
slosh-858234c05031a7bffc3ffe2a9b2aa4530d3ff0d7.tar.xz
slosh-858234c05031a7bffc3ffe2a9b2aa4530d3ff0d7.zip
sl0ppty: pane states are a table, not three hardcoded knobs
dim_unfocused, drag_grayscale and drag_dim were three knobs describing two situations, and there was no way to say anything about the third, fourth or fifth. They are replaced by a table: a state names a chain, and the chain is exactly as expressive as the global `shaders` block because it is the same parser. states { dragging { } drop_target { grayscale amount=200; dim amount=140 } suspended { grayscale amount=180; dim amount=90 } scrolled { tint amount=30 color="#ffcc88" } unfocused { dim amount=90 } } A pane is usually in several states at once, so exactly one wins: the first to match, in a fixed order running from the most deliberate to the most ambient. Fixed rather than read from the block, because it is a ranking by urgency rather than a preference — a pane you are holding should not be recoloured by anything, and a mode the whole screen is in outranks an ambient hint about one pane. They do not stack, which is the same call the drag policy already had to make by hand: two reasons to be grey compound into a muddy grey that reads as neither. Defaults are the old drag behaviour and nothing else, so out of the box this changes nothing. drop_hover is now separable from drop_target. The pane under the pointer was already distinguished by its border; it can now differ in colour too, and defaults to the same chain so it does not have to. Two states asked for did not survive contact with the code, and it is worth writing down why: `dead` cannot exist. app_reap() runs every iteration of the server loop, before the paint, and closing the last pane closes the tab — so a pane whose program exits is gone before a frame containing it is ever composed. A `dead` shader would name something that could never draw. Making it real means keeping dead panes until they are dismissed, which is a feature about lifetimes and not about colour. `suspended` could not be seen either, for a much smaller reason: draw_cb drew its "press a key to run" label and returned before reaching the shader pass. It falls through now. A pane that has not started is a state worth colouring, and its label is the only contents it has.
Diffstat (limited to 'src/app.c')
-rw-r--r--src/app.c55
1 files changed, 31 insertions, 24 deletions
diff --git a/src/app.c b/src/app.c
index 4a3c348..9996f0e 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1468,31 +1468,34 @@ size_t app_shade_count(app_t *a, uint32_t pane_id) {
* that forgot to clear it is a bug that only shows up in front of someone.
* The rect and the collapsed flag are recomputed every pass for exactly this
* reason; this is the same rule applied to colour. */
-static size_t policy_shaders(app_t *a, node_t *n, shader_t *out, size_t cap) {
- size_t k = 0;
+/* Which state this pane is in, or PSTATE_COUNT for none.
+ *
+ * A pane is usually in several at once, so the order here is the answer: the
+ * first that matches wins and the rest are not asked. It runs from the most
+ * transient and deliberate to the most ambient, because a pane you are holding
+ * should not be recoloured by anything, a mode the whole screen is in outranks
+ * a hint about one pane, and "not focused" is the weakest thing that can be
+ * true of a pane. */
+static pane_state_t pane_state(app_t *a, node_t *n) {
/* Only once the pointer has actually moved: a press that turns out to be a
- * click would otherwise flash the whole session grey on its way to nothing. */
+ * click would otherwise flash the whole session on its way to nothing. */
if (a->drag.kind == DRAG_TITLE && a->drag.moved) {
- /* The pane being dragged is left alone and everything else is pushed back
- * — desaturated *and* darkened, because grey alone still competes for
- * attention at the same brightness. Two passes rather than one shader
- * that does both: they are independently tunable, and "colourless" and
- * "recessed" are different things to want.
- *
- * This replaces the focus dimming rather than stacking with it: two
- * reasons to be grey compound into a muddy grey that reads as neither. */
- if (n->id == a->drag.src) return 0;
- if (CFG.drag_grayscale && k < cap &&
- shader_make(&out[k], "grayscale", (color_t){0}, CFG.drag_grayscale))
- k++;
- if (CFG.drag_dim && k < cap &&
- shader_make(&out[k], "dim", (color_t){0}, CFG.drag_dim))
- k++;
- return k;
+ if (n->id == a->drag.src) return PSTATE_DRAGGING;
+ if (n->id == a->drag.target) return PSTATE_DROP_HOVER;
+ return PSTATE_DROP_TARGET;
}
- if (CFG.dim_unfocused && n != cur(a)->focus && k < cap &&
- shader_make(&out[k], "dim", (color_t){0}, CFG.dim_unfocused))
- k++;
+ if (pane_suspended(n->pane)) return PSTATE_SUSPENDED;
+ if (pane_scrolled(n->pane)) return PSTATE_SCROLLED;
+ if (n != cur(a)->focus) return PSTATE_UNFOCUSED;
+ return PSTATE_COUNT;
+}
+
+static size_t policy_shaders(app_t *a, node_t *n, shader_t *out, size_t cap) {
+ pane_state_t st = pane_state(a, n);
+ if (st >= PSTATE_COUNT) return 0;
+ size_t k = CFG.state_n[st];
+ if (k > cap) k = cap;
+ for (size_t i = 0; i < k; i++) out[i] = CFG.state_shaders[st][i];
return k;
}
@@ -1550,6 +1553,9 @@ static void draw_cb(node_t *n, void *ud) {
draw_frame(d->a, d->s, n);
if (pane_suspended(n->pane)) {
+ /* Falls through to the shader pass rather than returning: a pane that has
+ * not started is a state you can want to colour, and its label is the only
+ * content it has. */
/* The pane exists, is laid out, and has run nothing. Say what it would. */
const char *label = pane_label(n->pane);
char line[256];
@@ -1572,9 +1578,10 @@ static void draw_cb(node_t *n, void *ud) {
screen_text(d->s, (uint16_t)(n->content.x + (n->content.w - w) / 2),
(uint16_t)(n->content.y + n->content.h / 2), line, FRAME_IDLE,
NO_COLOR, 0);
- return;
+ } else {
+ pane_compose(n->pane, d->s, n->content.x, n->content.y,
+ n == cur(d->a)->focus);
}
- pane_compose(n->pane, d->s, n->content.x, n->content.y, n == cur(d->a)->focus);
/* Between the contents and the chrome that goes over them: the frame was
* painted before this and lies outside the content rect, and the split guide
* is painted after, so it stays legible on top of a shaded pane. */