diff options
| author | user <user@clank> | 2026-08-14 20:32:16 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-14 20:32:16 +0200 |
| commit | 686421cee2ebf7dac0d4da28cfa23fe201272352 (patch) | |
| tree | bb3c46b23a378323c33845fa532300f135d5d1de /src | |
| parent | sl0ppty: plain ASCII for the frame's buttons (diff) | |
| download | slosh-686421cee2ebf7dac0d4da28cfa23fe201272352.tar.gz slosh-686421cee2ebf7dac0d4da28cfa23fe201272352.tar.xz slosh-686421cee2ebf7dac0d4da28cfa23fe201272352.zip | |
sl0ppty: the status line says what the pointer is on
The frame's affordances are nearly all one character wide, and the widest one
is invisible: a border that splits when you click it looks exactly like a
border. Hovering anything now puts a word in the middle of the status line.
Read off the hit list rather than tracked. Anything that registers a hit gets a
hint by being named in one table, and nothing has to remember to raise or clear
one — which also means the hint cannot disagree about what is under the pointer
with the thing that would actually be clicked, since both are the same lookup.
The status line is drawn after everything else has registered, so the list is
complete by the time it asks.
It is centred in what is left between the two ends rather than in the row.
Centring it in the row would put it under the session name on a narrow screen,
and a hint that overwrites the thing it is explaining is worse than no hint; it
is simply not drawn when it does not fit, which has a test with a deliberately
long pane title.
Two of that test's checks were wrong before they were right, in the ordinary
way: one searched for a substring that also occurs in the text on the right,
and one assumed forty-four columns was too narrow to fit a hint when it was
not. Both now assert against measured positions rather than my guess at them.
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.c | 58 | ||||
| -rw-r--r-- | src/config.c | 4 | ||||
| -rw-r--r-- | src/config.h | 7 |
3 files changed, 67 insertions, 2 deletions
@@ -97,6 +97,7 @@ bool app_reload_config(char *err, size_t errcap) { #define PANE_BTN_HOVER (CFG.pane_button_hover) #define MINBAR (CFG.minbar) #define MINBAR_HOVER (CFG.minbar_hover) +#define HINT_C (CFG.hint) static const color_t NO_COLOR = {0}; @@ -341,6 +342,43 @@ static size_t count_leaves(node_t *n); /* all defined with the layout */ static size_t collect_minimized(node_t *n, node_t **out, size_t cap, size_t k); static size_t collect_leaves(node_t *n, node_t **out, size_t cap, size_t k); +/* What the thing under the pointer would do, in a word. + * + * Read off the hit list rather than tracked, so anything that registers a hit + * gets a hint by being listed here and nothing has to remember to raise one. + * The status line is drawn after everything else has registered, so the list + * is complete by the time this is asked. */ +static const char *hint_for(app_t *a, const char *action) { + if (!action) return NULL; + uint32_t id = 0; + const char *colon = strchr(action, ':'); + if (colon) id = (uint32_t)strtoul(colon + 1, NULL, 10); + + if (strncmp(action, "minimize:", 9) == 0) return "minimise"; + if (strncmp(action, "zoom:", 5) == 0) + return app_pane_zoomed(a, id) ? "back to the layout" : "fill the tab"; + if (strncmp(action, "close:", 6) == 0) return "close this pane"; + if (strncmp(action, "scrollbottom:", 13) == 0) return "back to the bottom"; + if (strncmp(action, "panetitle:", 10) == 0) + return "double-click to rename \u00b7 drag to move"; + if (strncmp(action, "title:", 6) == 0) return "drag to move \u00b7 click to split up"; + if (strncmp(action, "border:", 7) == 0) { + const char *side = strrchr(action, ':'); + switch (side && side[1] ? side[1] : 0) { + case 'l': return "click to split left"; + case 'r': return "click to split right"; + case 't': return "click to split up"; + default: return "click to split down"; + } + } + if (strncmp(action, "edge:", 5) == 0) return "drag to resize"; + if (strncmp(action, "focus:", 6) == 0) return "open this pane"; + if (strncmp(action, "find:", 5) == 0) return "go to this pane"; + if (strncmp(action, "tab:", 4) == 0) return "switch to this tab"; + if (strcmp(action, "newtab") == 0) return "new tab"; + return NULL; /* a pane's own content, and anything not worth a word */ +} + /* The line along the bottom: what you are looking at, rather than what you * could switch to. The strip along the top already answers "which tab" and * "how many panes"; repeating that here would spend a row saying it twice. @@ -486,9 +524,25 @@ static void draw_status_line(app_t *a, screen_t *s) { else if (pt && *pt) n += (size_t)snprintf(line + n, sizeof line - n, "%s%s", n ? " \u00b7 " : "", pt); } - if (!n) return; if (n > (size_t)(right - x)) line[right - x] = 0; - screen_text(s, x, y, line, STATUS_C, NO_COLOR, 0); + if (n) screen_text(s, x, y, line, STATUS_C, NO_COLOR, 0); + + /* The hint sits in what is left between the two ends, centred there rather + * than in the row: centring it in the row would put it under the session + * name on a narrow screen, and a hint that overwrites what it is explaining + * is worse than no hint. It is simply not drawn when it does not fit. */ + if (!CFG.hints || !a->painted) return; + const char *hint = hint_for(a, hit_test(&s->hits, a->ptr_x, a->ptr_y)); + if (!hint || !a->ptr_valid) return; + + uint16_t used = (uint16_t)(x + cells(line)); + uint16_t from = (uint16_t)(used + 2); + if (right <= from) return; + uint16_t span = (uint16_t)(right - from); + uint16_t hw = cells(hint); + if (hw + 2 > span) return; + screen_text(s, (uint16_t)(from + (span - hw) / 2), y, hint, HINT_C, NO_COLOR, + 0); } static void draw_toasts(app_t *a, screen_t *s) { diff --git a/src/config.c b/src/config.c index 7159d47..6ce7a7f 100644 --- a/src/config.c +++ b/src/config.c @@ -171,6 +171,7 @@ void config_defaults(config_t *c) { c->rounded = true; c->title_align = ALIGN_CENTER; c->title_inset = 2; + c->hints = true; c->pane_buttons = true; snprintf(c->zoom_mark, sizeof c->zoom_mark, "#"); snprintf(c->zoom_on_mark, sizeof c->zoom_on_mark, "*"); @@ -256,6 +257,7 @@ void config_defaults(config_t *c) { c->finder_sel_bg = accent; c->bell = accent; + c->hint = bright; c->minbar = dim; c->minbar_hover = accent; c->pane_button = dim; @@ -375,6 +377,7 @@ bool config_load(config_t *c, const char *path, char *err, size_t errcap) { c->title_inset = (uint16_t)kdl_arg_int(kdl_child(root, "title_inset"), 0, c->title_inset); + c->hints = kdl_arg_bool(kdl_child(root, "hints"), 0, c->hints); c->pane_buttons = kdl_arg_bool(kdl_child(root, "pane_buttons"), 0, c->pane_buttons); const char *zm = kdl_arg(kdl_child(root, "zoom_mark"), 0, NULL); @@ -482,6 +485,7 @@ bool config_load(config_t *c, const char *path, char *err, size_t errcap) { {"finder_sel_fg", &c->finder_sel_fg}, {"finder_sel_bg", &c->finder_sel_bg}, {"bell", &c->bell}, + {"hint", &c->hint}, {"minbar", &c->minbar}, {"minbar_hover", &c->minbar_hover}, {"pane_button", &c->pane_button}, diff --git a/src/config.h b/src/config.h index 28350ce..76a9b85 100644 --- a/src/config.h +++ b/src/config.h @@ -84,6 +84,10 @@ typedef struct { * since. Off makes a bell silent *and* invisible, which is a real choice. */ /* Buttons in the top-right of a pane's frame: zoom, and close. Off leaves * the keyboard bindings alone; it is about the affordance, not the verb. */ + /* A word in the middle of the status line for whatever the pointer is on. + * Discoverability for a frame whose affordances are mostly one character + * wide; off if you already know them. */ + bool hints; bool pane_buttons; /* Marks of similar visual weight, so the even spacing between the buttons * reads as even: a glyph that floats in a mostly-empty cell donates its own @@ -196,6 +200,9 @@ typedef struct { /* the row of minimised panes along the bottom */ color_t minbar, minbar_hover; + /* the hint in the middle of the status line */ + color_t hint; + /* announcements */ color_t toast_fg, toast_bg; |
