diff options
| author | user <user@clank> | 2026-09-22 15:01:07 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-22 15:01:07 +0200 |
| commit | 43ab0d0c0dc0c79f04c7e70f5ab3b22b3e36e09a (patch) | |
| tree | ba1a5960438c15391fbf1c0a4e0c49421a8c1fcb /src | |
| parent | sidebar: align the numbers by default (diff) | |
| download | slosh-43ab0d0c0dc0c79f04c7e70f5ab3b22b3e36e09a.tar.gz slosh-43ab0d0c0dc0c79f04c7e70f5ab3b22b3e36e09a.tar.xz slosh-43ab0d0c0dc0c79f04c7e70f5ab3b22b3e36e09a.zip | |
sidebar: air inside it, the way panes have padding (tab_bar_padding)
`padding` is a pane's: the ring between a frame and the text it holds, in rows
times gap_aspect so a `1` looks square around a block of prose. Handing the
same knob to the sidebar looked tempting and is wrong twice over. The sidebar
is chrome, not a pane, and its width is fixed by tab_bar_width -- so `padding
1` would quietly eat four of a sixteen-column strip (two cells a side, aspect
included) and shorten every tab name, because somebody wanted their terminal
text to breathe. And its two axes are not one idea: the columns are an indent
for labels, the rows are air above a list.
So tab_bar_padding, its own knob in the family the rest of the sidebar's air
already lives in (tab_bar_pad, tab_gap, newtab_pad), taking the same 1/2/4
CSS-order values as `padding` -- one reader for both, so they cannot drift
into meaning different things -- but counted in cells.
The default `0 1` is what the sidebar already drew: that one column is where
every label's leading space has been hard-coded since the sidebar existed, and
moving it into a setting is what makes `tab_bar_padding 0` possible at all --
names flush against the frame, which was not previously a thing anyone could
ask for. The statuses, the `+ new tab` row and the pane count follow the same
indent, since they were each carrying their own copy of that space.
It is air *inside* the plate: a tab's fill still spans the whole row and the
whole row is still the hit, because the row is what you click and a highlight
that stopped short of the frame would read as a highlighted word rather than a
chosen row. The bottom rows are given up when the strip is short of them, like
every other piece of air in here.
tab_bar_pad is the older name for the top component alone. It still works,
still means exactly that, and wins where a config writes both -- it is the
more specific of the two. The dump writes tab_bar_padding and mentions it.
Diffstat (limited to 'src')
| -rw-r--r-- | src/app_draw.c | 59 | ||||
| -rw-r--r-- | src/config.c | 102 |
2 files changed, 116 insertions, 45 deletions
diff --git a/src/app_draw.c b/src/app_draw.c index 85b5b6c..cf3ff39 100644 --- a/src/app_draw.c +++ b/src/app_draw.c @@ -1844,23 +1844,32 @@ static uint16_t draw_tab_cell(app_t *a, screen_t *s, size_t i, uint16_t x, * spare the columns: on a narrow one the name is what you came for, so the * number goes back in front where it costs two cells rather than a column. * Never while renaming, where the whole cell is the editor. */ + /* In a sidebar the label's indent is the strip's own left padding, and its + * right padding is cells the name may not have; in the top strip a tab is + * as wide as its label and the single space each side *is* the plate. */ + uint16_t padl = max_w ? CFG.tab_pad_left : 1; + uint16_t padr = max_w ? CFG.tab_pad_right : 1; + char idx[16] = ""; bool right_idx = false; if (max_w && !editing && CFG.tab_bar_index == TAB_INDEX_RIGHT) { - snprintf(idx, sizeof idx, "%zu ", i + 1); + snprintf(idx, sizeof idx, "%zu%*s", i + 1, (int)padr, ""); uint16_t iw = (uint16_t)cells(idx); right_idx = max_w >= (uint16_t)(iw + 5); if (!right_idx) idx[0] = 0; } if (editing) - snprintf(label, sizeof label, " %s\u2588 ", a->rename_buf); + snprintf(label, sizeof label, "%*s%s\u2588%*s", (int)padl, "", + a->rename_buf, (int)padr, ""); else if (right_idx) - snprintf(label, sizeof label, " %s", nm[0] ? nm : ""); + snprintf(label, sizeof label, "%*s%s", (int)padl, "", nm[0] ? nm : ""); else if (nm[0]) - snprintf(label, sizeof label, " %zu:%s ", i + 1, nm); + snprintf(label, sizeof label, "%*s%zu:%s%*s", (int)padl, "", i + 1, nm, + (int)padr, ""); else - snprintf(label, sizeof label, " %zu ", i + 1); + snprintf(label, sizeof label, "%*s%zu%*s", (int)padl, "", i + 1, (int)padr, + ""); /* A pinned width cuts the label to fit -- bell and number budgeted first, * so neither is what a long name eats -- and then pads it out, so the @@ -1988,10 +1997,13 @@ static uint16_t draw_tab_cell(app_t *a, screen_t *s, size_t i, uint16_t x, static void draw_newtab_button(app_t *a, screen_t *s, uint16_t x, uint16_t y, uint16_t max_w, bool dragging_pane) { char btn[64]; + /* The same indent the labels take, so the button lines up with the list it + * sits under rather than with the frame. */ + int padl = max_w ? (int)CFG.tab_pad_left : 1; if (max_w && (uint16_t)(cells(CFG.newtab_mark) + 10) <= max_w) - snprintf(btn, sizeof btn, " %s new tab ", CFG.newtab_mark); + snprintf(btn, sizeof btn, "%*s%s new tab ", padl, "", CFG.newtab_mark); else - snprintf(btn, sizeof btn, " %s ", CFG.newtab_mark); + snprintf(btn, sizeof btn, "%*s%s ", padl, "", CFG.newtab_mark); /* Padded out on a sidebar row for the same reason a tab's label is: the * hover and the drop fill are the row, not the word sitting on it. */ if (max_w) pad_cells(btn, sizeof btn, max_w); @@ -2217,7 +2229,22 @@ void draw_tab_sidebar(app_t *a, screen_t *s) { * above the list. The pad is where the drawing starts, not a layout fact: * the panes' rect is the same with pad 0 and pad 5, so tuning it repaints * without reflowing anyone. */ - uint16_t y = (uint16_t)(top + (chrome ? 1 : 0) + CFG.tab_bar_pad); + uint16_t y = (uint16_t)(top + (chrome ? 1 : 0) + CFG.tab_pad_top); + + /* The indent everything on a row starts at, and the width it has to work + * in. Inside the plate, not outside it: a tab's fill still spans the whole + * row, because the row is what you click and a highlight that stopped short + * of the frame would read as a highlighted word. */ + uint16_t ix = (uint16_t)(cx + (cw > CFG.tab_pad_left ? CFG.tab_pad_left : 0)); + + /* Air at the bottom, taken before anything is placed against the bottom + * edge -- and given up when the rows are not there to spare, like every + * other piece of air in this strip. */ + { + uint16_t pad = CFG.tab_pad_bottom; + while (pad && limit <= (uint16_t)(y + pad)) pad--; + limit = (uint16_t)(limit - pad); + } /* Bottom rows first: what the horizontal strip keeps on its right. Indented * one cell to sit under the labels' own leading space -- at cx exactly, the @@ -2228,15 +2255,14 @@ void draw_tab_sidebar(app_t *a, screen_t *s) { snprintf(info, sizeof info, "%zu pane%s", np, np == 1 ? "" : "s"); if (limit > y && (uint16_t)(strlen(info) + 1) <= cw) { limit--; - screen_text(s, (uint16_t)(cx + 1), limit, info, TAB_COUNT, NO_COLOR, 0); + screen_text(s, ix, limit, info, TAB_COUNT, NO_COLOR, 0); } if (a->prefix) { /* the prefix is a mode: say so, and say which key */ char pfx[24]; config_chord_name(CFG.prefix_key, CFG.prefix_mods, pfx, sizeof pfx); if (limit > y && (uint16_t)(cells(pfx) + 1) <= cw) { limit--; - screen_text(s, (uint16_t)(cx + 1), limit, pfx, PREFIX_FG, PREFIX_BG, - ATTR_BOLD); + screen_text(s, ix, limit, pfx, PREFIX_FG, PREFIX_BG, ATTR_BOLD); } } @@ -2298,8 +2324,10 @@ void draw_tab_sidebar(app_t *a, screen_t *s) { size_t lines = CFG.tab_bar_status_lines ? CFG.tab_bar_status_lines : 1; if (lines > STATUS_WRAP_MAX) lines = STATUS_WRAP_MAX; if (lines > allow) lines = allow; - /* The text column: everything but the leading space below. */ - uint16_t tw = cw > 1 ? (uint16_t)(cw - 1) : 1; + /* The text column: the row minus the strip's own padding, which is what + * puts a status in the column its tab's name starts in. */ + uint16_t inset = (uint16_t)(CFG.tab_pad_left + CFG.tab_pad_right); + uint16_t tw = cw > inset ? (uint16_t)(cw - inset) : 1; /* Lay the statuses out before drawing any, because whether the last row * has to become an ellipsis depends on what did not fit -- and with @@ -2360,7 +2388,8 @@ void draw_tab_sidebar(app_t *a, screen_t *s) { * sidebar, where three columns is a fifth of everything a status has * to say itself in. The slant below says the same thing in none of * them, and the band says which pane said it. */ - snprintf(text, sizeof text, " %s", rows[j][k]); + snprintf(text, sizeof text, "%*s%s", (int)CFG.tab_pad_left, "", + rows[j][k]); pad_cells(text, sizeof text, cw); /* so a themed band covers the row */ /* Italic, and ambient in colour: these are annotations until pointed * at. The slant is what separates them from the labels, rather than @@ -2377,7 +2406,7 @@ void draw_tab_sidebar(app_t *a, screen_t *s) { } if (ell && y < limit && used < allow) { char text[STATUS_ROW_CAP + 8]; - snprintf(text, sizeof text, " \u2026"); + snprintf(text, sizeof text, "%*s\u2026", (int)CFG.tab_pad_left, ""); pad_cells(text, sizeof text, cw); screen_text(s, cx, y, text, TAB_STATUS_FG, TAB_STATUS_BG, ATTR_ITALIC); y++; /* an ellipsis is not a door: no hit */ diff --git a/src/config.c b/src/config.c index a1d8880..81f23d5 100644 --- a/src/config.c +++ b/src/config.c @@ -311,6 +311,40 @@ static void complain(config_t *c, char *err, size_t errcap, int line, if (err && errcap && !err[0]) snprintf(err, errcap, "%s", full); } +/* `x 1`, `x 0 2`, or `x 1 2 1 2` -- one value for every side, two for + * vertical and horizontal, four in CSS order (top, right, bottom, left), + * because that is the order everybody who has written a stylesheet already + * knows. Three is refused rather than guessed at: CSS says + * top/horizontal/bottom and a reader who has to look that up is a reader who + * cannot see what the line does. + * + * One reader for every setting shaped like this, so `padding` and + * `tab_bar_padding` cannot drift into meaning two different things. */ +static void parse_sides(config_t *c, const kdl_node_t *n, const char *name, + uint16_t *top, uint16_t *right, uint16_t *bottom, + uint16_t *left, char *err, size_t errcap) { + if (!n) return; + long v[4] = {0}; + size_t k = n->nargs < 4 ? n->nargs : 4; + for (size_t i = 0; i < k; i++) v[i] = kdl_arg_int(n, i, 0); + if (n->nargs == 1) { + *top = *right = *bottom = *left = (uint16_t)v[0]; + } else if (n->nargs == 2) { + *top = *bottom = (uint16_t)v[0]; + *right = *left = (uint16_t)v[1]; + } else if (n->nargs == 4) { + *top = (uint16_t)v[0]; + *right = (uint16_t)v[1]; + *bottom = (uint16_t)v[2]; + *left = (uint16_t)v[3]; + } else { + complain(c, err, errcap, n->line, + "%s takes 1, 2 or 4 values (all, vertical horizontal, or top " + "right bottom left), not %zu", + name, n->nargs); + } +} + size_t config_messages(const config_t *c, const char **out, size_t max) { size_t n = c->nmsgs < max ? c->nmsgs : max; for (size_t i = 0; i < n; i++) out[i] = c->msgs[i]; @@ -959,7 +993,11 @@ void config_defaults(config_t *c) { * standing in for, and a blank row inside the frame on top of it reads as * a gap nobody asked for. Still there for a bare sidebar, or for anyone * who wants room at the top for a borderless window's own buttons. */ - c->tab_bar_pad = 0; + /* No rows of air by default, and one column: that column is where every + * label's leading space used to be hard-coded, so the default draws what it + * always drew and 0 is now a thing you can ask for. */ + c->tab_pad_top = c->tab_pad_bottom = 0; + c->tab_pad_left = c->tab_pad_right = 1; c->tab_gap = 0; /* flush, the way the list has always read */ /* Three is enough for "the build, the tests, and one more thing" without a * chatty tab pushing the list off the bottom. */ @@ -1716,8 +1754,24 @@ char *config_render(const config_t *c) { c->tab_bar_index == TAB_INDEX_RIGHT ? "right" : "prefix"); cb_add(&b, "tab_bar_width %u // columns a sidebar takes\n", c->tab_bar_width); - cb_add(&b, "tab_bar_pad %u // blank rows above a sidebar's tabs\n", - c->tab_bar_pad); + if (c->tab_pad_top == c->tab_pad_right && + c->tab_pad_top == c->tab_pad_bottom && c->tab_pad_top == c->tab_pad_left) + cb_add(&b, "tab_bar_padding %u // air inside a sidebar, in cells\n", + c->tab_pad_top); + else if (c->tab_pad_top == c->tab_pad_bottom && + c->tab_pad_right == c->tab_pad_left) + cb_add(&b, + "tab_bar_padding %u %u // air inside a sidebar: rows, then " + "columns\n", + c->tab_pad_top, c->tab_pad_right); + else + cb_add(&b, "tab_bar_padding %u %u %u %u // top right bottom left\n", + c->tab_pad_top, c->tab_pad_right, c->tab_pad_bottom, + c->tab_pad_left); + cb_add(&b, + "// tab_bar_pad %u // the top of that alone, the older " + "name for it\n", + c->tab_pad_top); cb_add(&b, "tab_gap %u // blank rows between a sidebar's tabs\n", c->tab_gap); cb_add(&b, @@ -2200,6 +2254,7 @@ static const char *const KNOWN_TOP[] = { "status_pad", "tab_bar_chrome", "tab_bar_pad", + "tab_bar_padding", "tab_bar_index", "tab_bar_side", "tab_bar_status", @@ -2571,31 +2626,20 @@ static bool load_into(config_t *c, const char *path, int depth, char *err, * stylesheet already knows. Three is refused rather than guessed at: CSS says * top/horizontal/bottom and a reader who has to look that up is a reader who * cannot see what the line does. */ - { - const kdl_node_t *pn = kdl_child(root, "padding"); - if (pn) { - long v[4]; - size_t n = pn->nargs < 4 ? pn->nargs : 4; - for (size_t i = 0; i < n; i++) v[i] = kdl_arg_int(pn, i, 0); - if (pn->nargs == 1) { - c->pad_top = c->pad_right = c->pad_bottom = c->pad_left = - (uint16_t)v[0]; - } else if (pn->nargs == 2) { - c->pad_top = c->pad_bottom = (uint16_t)v[0]; - c->pad_right = c->pad_left = (uint16_t)v[1]; - } else if (pn->nargs == 4) { - c->pad_top = (uint16_t)v[0]; - c->pad_right = (uint16_t)v[1]; - c->pad_bottom = (uint16_t)v[2]; - c->pad_left = (uint16_t)v[3]; - } else { - complain(c, err, errcap, pn->line, - "padding takes 1, 2 or 4 values (all, vertical horizontal, or " - "top right bottom left), not %zu", - pn->nargs); - } - } - } + parse_sides(c, kdl_child(root, "padding"), "padding", &c->pad_top, + &c->pad_right, &c->pad_bottom, &c->pad_left, err, errcap); + /* The same shape for the sidebar's own air, so one form is learned once. + * In cells rather than rows-times-gap_aspect: a sidebar's columns and rows + * are doing different jobs (an indent, and air above a list), and scaling + * one of them by the cell aspect would make `2` mean four columns in a + * sixteen-column strip. */ + parse_sides(c, kdl_child(root, "tab_bar_padding"), "tab_bar_padding", + &c->tab_pad_top, &c->tab_pad_right, &c->tab_pad_bottom, + &c->tab_pad_left, err, errcap); + /* After it, because it is the more specific of the two: a config that says + * both means the narrow one where they overlap. */ + c->tab_pad_top = + (uint16_t)kdl_arg_int(kdl_child(root, "tab_bar_pad"), 0, c->tab_pad_top); c->rounded = kdl_arg_bool(kdl_child(root, "rounded"), 0, c->rounded); c->compact = kdl_arg_bool(kdl_child(root, "compact"), 0, c->compact); c->status_bar = kdl_arg_bool(kdl_child(root, "status_bar"), 0, c->status_bar); @@ -2624,8 +2668,6 @@ static bool load_into(config_t *c, const char *path, int depth, char *err, } c->tab_bar_width = (uint16_t)kdl_arg_int(kdl_child(root, "tab_bar_width"), 0, c->tab_bar_width); - c->tab_bar_pad = - (uint16_t)kdl_arg_int(kdl_child(root, "tab_bar_pad"), 0, c->tab_bar_pad); c->tab_gap = (uint16_t)kdl_arg_int(kdl_child(root, "tab_gap"), 0, c->tab_gap); c->tab_bar_status = (uint16_t)kdl_arg_int(kdl_child(root, "tab_bar_status"), 0, c->tab_bar_status); |
