diff options
| author | user <user@clank> | 2026-08-15 13:45:21 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-15 13:45:21 +0200 |
| commit | 015ba12fde4fcce15440b39cb6369a4ad66238cf (patch) | |
| tree | d5cf6edb0d7de3535a07e9249380d14ec391fe4e /src | |
| parent | sl0ppty: centre the middle of the status line on the row (diff) | |
| download | slosh-015ba12fde4fcce15440b39cb6369a4ad66238cf.tar.gz slosh-015ba12fde4fcce15440b39cb6369a4ad66238cf.tar.xz slosh-015ba12fde4fcce15440b39cb6369a4ad66238cf.zip | |
sl0ppty: dim the panes you are not in, and a knob to say how much
`dim_unfocused 60`, on by default, 0 to turn it off.
The dimming itself was already possible -- `states { unfocused { dim } }` --
and deliberately not the default. The argument then was that ambient contrast
is a taste, and shipping a taste as a default is how a tool earns a
reputation for fighting you. That argument was mostly about the escape hatch:
"turn it off" meant knowing the states block exists and what a shader chain
is. A single number does not have that problem, so the objection goes away
and the default can be the one most people want.
The knob and the table are one mechanism -- the knob fills the unfocused
chain in when the config has not -- so writing the chain by hand still wins,
including writing an empty one. `states { unfocused { } }` means nothing at
all, and is not quietly refilled, which needed the parser to distinguish
"said nothing" from "said none".
60 by choice: white text lands on #c3c3c3, which reads as "not this one"
without reading as "not available", because half the time you are reading the
other pane. Picked by looking at 40, 55, 70, 90 and 120 rather than by taste
alone.
The tests that had to change are the interesting part. Six in test_shaders.py
asserted "back to green after the drag", which is no longer what normal looks
like -- and rewriting them to compare against a snapshot taken before the
drag failed too, because a drag *moves focus*: press a title and that pane is
yours, so the two panes legitimately swap appearances across one. They assert
the rule now (the pane you are in keeps its colour, the others are pushed
back), which is both what we mean and immune to the next change of defaults.
test_states.py's "an unfocused pane is left alone" is inverted, with the
reason written where the assertion used to be, because a test that flips
should say why it flipped.
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.c | 30 | ||||
| -rw-r--r-- | src/config.h | 13 |
2 files changed, 41 insertions, 2 deletions
diff --git a/src/config.c b/src/config.c index c31ca76..71dbb49 100644 --- a/src/config.c +++ b/src/config.c @@ -201,6 +201,21 @@ static void bind_add(config_t *c, int key, uint16_t mods, action_t action, c->binds[c->nbinds++] = (binding_t){key, mods, action, direct}; } +/* The knob and the states table are one mechanism: the knob writes the table. + * `declared` is whether the config named `unfocused` itself, in which case it + * has said what it wants -- including `states { unfocused { } }`, which says + * "nothing", and must not be quietly refilled. */ +static void apply_dim_unfocused(config_t *c, bool declared) { + if (declared) return; + if (!c->dim_unfocused) { + c->state_n[PSTATE_UNFOCUSED] = 0; + return; + } + shader_make(&c->state_shaders[PSTATE_UNFOCUSED][0], "dim", (color_t){0}, + c->dim_unfocused); + c->state_n[PSTATE_UNFOCUSED] = 1; +} + void config_defaults(config_t *c) { memset(c, 0, sizeof *c); c->gap = 1; @@ -220,6 +235,10 @@ void config_defaults(config_t *c) { c->bell_indicator = true; snprintf(c->bell_mark, sizeof c->bell_mark, "\u2022"); c->keep_dead = true; + /* Gentle: an unfocused pane is one you are still reading half the time. + * At 60 white text lands on #c3c3c3, which reads as "not this one" without + * reading as "not available". */ + c->dim_unfocused = 60; c->min_pane_cols = 24; c->min_pane_rows = 6; c->min_split_cols = 32; @@ -280,6 +299,8 @@ void config_defaults(config_t *c) { shader_make(&c->state_shaders[PSTATE_SUSPENDED][1], "dim", (color_t){0}, 60); c->state_n[PSTATE_SUSPENDED] = 2; + apply_dim_unfocused(c, false); + const color_t accent = rgb(0xff, 0x5f, 0xd7); const color_t ink = rgb(0x14, 0x14, 0x18); const color_t dim = rgb(0x45, 0x45, 0x4a); @@ -634,6 +655,10 @@ bool config_load(config_t *c, const char *path, char *err, size_t errcap) { const char *nt = kdl_arg(kdl_child(root, "newtab_mark"), 0, NULL); if (nt) snprintf(c->newtab_mark, sizeof c->newtab_mark, "%s", nt); c->keep_dead = kdl_arg_bool(kdl_child(root, "keep_dead"), 0, c->keep_dead); + { + long v = kdl_arg_int(kdl_child(root, "dim_unfocused"), 0, c->dim_unfocused); + c->dim_unfocused = (uint8_t)(v < 0 ? 0 : v > 255 ? 255 : v); + } c->bell_indicator = kdl_arg_bool(kdl_child(root, "bell_indicator"), 0, c->bell_indicator); const char *bm = kdl_arg(kdl_child(root, "bell_mark"), 0, NULL); @@ -710,6 +735,7 @@ bool config_load(config_t *c, const char *path, char *err, size_t errcap) { * pane looks like while it is in a state. Naming a state at all replaces its * default outright, including with nothing, which is how you turn one off. */ const kdl_node_t *states = kdl_child(root, "states"); + bool unfocused_declared = false; if (states) { for (size_t i = 0; i < states->nkids; i++) { const kdl_node_t *k = states->kids[i]; @@ -722,9 +748,13 @@ bool config_load(config_t *c, const char *path, char *err, size_t errcap) { if (err && !err[0]) snprintf(err, errcap, "unknown pane state: %s", k->name); continue; } + if (st == PSTATE_UNFOCUSED) unfocused_declared = true; c->state_n[st] = parse_shader_list(c, k, c->state_shaders[st], err, errcap); } } + /* After the states block, so a config that wrote its own chain keeps it and + * one that did not gets the knob's. */ + apply_dim_unfocused(c, unfocused_declared); const kdl_node_t *theme = kdl_child(root, "theme"); if (theme) { diff --git a/src/config.h b/src/config.h index 2f844ee..6a93b45 100644 --- a/src/config.h +++ b/src/config.h @@ -141,8 +141,17 @@ typedef struct { * A modal that floats over a screen as bright as it is reads as another * pane rather than as something in front. */ uint8_t modal_scrim; - /* Shader strengths, 0..255, 0 being off. Ambient dimming is a taste and is - * off by default; the drag greying is transient and on. */ + /* How far the panes you are *not* in are pushed back, 0..255 and 0 for not + * at all. A knob rather than only a `states { unfocused { } }` chain, + * because "dim the other panes" is a thing people want by name and should + * not have to know the shader vocabulary to ask for -- and because the + * argument against shipping it on was that turning it off should be + * obvious, which a single number is and a chain is not. + * + * Writing the state chain by hand still wins: the knob only fills it in + * when the config has not. */ + uint8_t dim_unfocused; + /* Colour passes every pane gets, in the order they were written. Ordinary * shaders rather than policy: the session has no opinion about these, you * asked for them. */ |
