aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-21 00:42:01 +0200
committeruser <user@clank>2026-08-21 00:42:01 +0200
commit54dc47f801a444fe0d710f393b04c684df8fa31d (patch)
tree834c94d6be439cb0e2efa271979156974332e5aa
parentslosh: minimized panes wear buttons in the strip (diff)
downloadslosh-54dc47f801a444fe0d710f393b04c684df8fa31d.tar.gz
slosh-54dc47f801a444fe0d710f393b04c684df8fa31d.tar.xz
slosh-54dc47f801a444fe0d710f393b04c684df8fa31d.zip
slosh: put-away panes are miniature pane frames
Brackets were tried and still read as text. Each min-bar entry is now a three-row chip wearing a real pane frame -- the same corners, because a frame is what a pane *looks like* in this program, so a shrunken one reads as exactly what it is: a pane, clickable. The put-away mark rides inside each chip and the lone legend is gone; a rung pane's bell mark keeps its own colour inside the frame; hover lights the whole chip and the whole chip is the hit. The bar costs three rows instead of one while anything is minimised (and nothing when nothing is), which is what an affordance that works is worth.
-rw-r--r--docs/panes.md13
-rw-r--r--src/app.c59
-rw-r--r--src/app_layout.c19
-rw-r--r--tests/test_bell.py8
-rw-r--r--tests/test_hints.py2
5 files changed, 67 insertions, 34 deletions
diff --git a/docs/panes.md b/docs/panes.md
index 03f2279..52ace45 100644
--- a/docs/panes.md
+++ b/docs/panes.md
@@ -28,9 +28,16 @@ looking at is drawn.
stack is the pane on the right once you turn it. Four turns come back exactly.
A turn that would squeeze a pane under the floor is refused with a word.
- **`C-a z`** fills the tab with one pane and back; **`C-a m`** puts one away
- into a strip along the bottom, still running. Each put-away pane is a
- button there — `[▬ name]`, the brackets meaning what they mean everywhere
- else here — and clicking it brings the pane back.
+ into a strip along the bottom, still running. Each put-away pane sits there
+ as a **miniature pane frame** — a frame is what a pane looks like here, so
+ a shrunken one reads as exactly what it is — and clicking it anywhere
+ brings the pane back:
+
+ ```
+ ╭────────────╮ ╭─────────────────╮
+ │ ▬ server │ │ ▬ agent [!] │
+ ╰────────────╯ ╰─────────────────╯
+ ```
## The mouse
diff --git a/src/app.c b/src/app.c
index 681d292..17e5f6e 100644
--- a/src/app.c
+++ b/src/app.c
@@ -395,42 +395,63 @@ void draw_min_bar(app_t *a, screen_t *s) {
uint16_t x = bar.x;
uint16_t right = (uint16_t)(bar.x + bar.w);
+ /* Each entry is a miniature pane frame: three rows, the same corners a
+ * real pane wears. A frame is what a pane *looks like* in this program,
+ * so a put-away pane wearing one reads as a pane you can click — a row of
+ * dim bare names read as a caption, and nobody clicks a caption; brackets
+ * were tried and still read as text. The put-away mark rides inside each
+ * chip, saying what kind of pane this is, and a rung pane's bell mark
+ * keeps its own colour inside the frame: a pane you cannot see is exactly
+ * the one a bell is for. The whole chip is the hit. */
+ const char *tl = CFG.rounded ? "\u256d" : "\u250c",
+ *tr = CFG.rounded ? "\u256e" : "\u2510";
+ const char *bl = CFG.rounded ? "\u2570" : "\u2514",
+ *br = CFG.rounded ? "\u256f" : "\u2518";
+
for (size_t i = 0; i < nmin && x < right; i++) {
const char *nm = pane_title(mins[i]->pane);
- /* A pane you cannot see is exactly the one a bell is for, so the mark
- * comes along and keeps its own colour rather than the row's. */
bool rang = CFG.bell_indicator && pane_bell(mins[i]->pane);
- /* Each entry wears square brackets, because that is what *clickable*
- * already looks like in this program -- [re-run], [close], the buttons a
- * pane's own row offers. A row of dim bare names read as a caption, and
- * nobody clicks a caption. The put-away mark rides inside each entry
- * rather than standing at the head of the row as a legend: a legend
- * explains the row once, a mark per entry says what each button *is*. */
char label[96];
- snprintf(label, sizeof label, "[%s %s", CFG.min_mark,
+ snprintf(label, sizeof label, " %s %s", CFG.min_mark,
nm && *nm ? nm : "pane");
uint16_t lw = cells(label);
uint16_t bw = rang ? (uint16_t)(1 + cells(CFG.bell_mark)) : 0;
- /* label, the bell when there is one, the closing bracket, and the gap. */
- uint16_t total = (uint16_t)(lw + bw + 2);
- if ((uint16_t)(x + total) > right) break;
+ uint16_t inner = (uint16_t)(lw + bw + 1); /* and a breath before the wall */
+ uint16_t chip = (uint16_t)(inner + 2); /* the frame's two columns */
+ if ((uint16_t)(x + chip) > right) break;
- bool hot = ptr_on(a, x, bar.y, total, 1);
+ bool hot = ptr_on(a, x, bar.y, chip, 3);
color_t fg = hot ? MINBAR_HOVER : MINBAR;
uint16_t attrs = hot ? ATTR_BOLD : 0;
- uint16_t w = screen_text(s, x, bar.y, label, fg, NO_COLOR, attrs);
+ uint16_t x1 = (uint16_t)(x + chip - 1);
+ uint16_t ymid = (uint16_t)(bar.y + 1), ybot = (uint16_t)(bar.y + 2);
+
+ screen_text(s, x, bar.y, tl, fg, NO_COLOR, attrs);
+ screen_text(s, x1, bar.y, tr, fg, NO_COLOR, attrs);
+ screen_text(s, x, ybot, bl, fg, NO_COLOR, attrs);
+ screen_text(s, x1, ybot, br, fg, NO_COLOR, attrs);
+ for (uint16_t cx = (uint16_t)(x + 1); cx < x1; cx++) {
+ screen_text(s, cx, bar.y, "\u2500", fg, NO_COLOR, attrs);
+ screen_text(s, cx, ybot, "\u2500", fg, NO_COLOR, attrs);
+ }
+ screen_text(s, x, ymid, "\u2502", fg, NO_COLOR, attrs);
+ screen_text(s, x1, ymid, "\u2502", fg, NO_COLOR, attrs);
+ uint16_t w =
+ screen_text(s, (uint16_t)(x + 1), ymid, label, fg, NO_COLOR, attrs);
if (rang) {
char mark[40];
snprintf(mark, sizeof mark, " %s", CFG.bell_mark);
- w = (uint16_t)(w + screen_text(s, (uint16_t)(x + w), bar.y, mark, BELL_C,
- NO_COLOR, ATTR_BOLD));
+ w = (uint16_t)(w + screen_text(s, (uint16_t)(x + 1 + w), ymid, mark,
+ BELL_C, NO_COLOR, ATTR_BOLD));
}
- screen_text(s, (uint16_t)(x + w), bar.y, "]", fg, NO_COLOR, attrs);
+ for (uint16_t cx = (uint16_t)(x + 1 + w); cx < x1; cx++)
+ screen_text(s, cx, ymid, " ", fg, NO_COLOR, 0);
+
char action[48];
snprintf(action, sizeof action, "focus:%u", mins[i]->id);
- hit_add(&s->hits, x, bar.y, total, 1, action);
- x = (uint16_t)(x + total);
+ hit_add(&s->hits, x, bar.y, chip, 3, action);
+ x = (uint16_t)(x + chip + 1);
}
}
diff --git a/src/app_layout.c b/src/app_layout.c
index 266775f..b7587fc 100644
--- a/src/app_layout.c
+++ b/src/app_layout.c
@@ -426,20 +426,23 @@ void layout(app_t *a) {
* the same arithmetic — two copies of "does this fit" is exactly how the
* guide and the layout would start disagreeing. */
/* Minimised panes come out of the layout and sit in a strip along the
- * bottom, one row each. The tree is then laid out in what is left, so a
- * minimised pane costs a row rather than a share. */
- /* Minimised panes are listed on one row along the bottom, however many there
- * are. A row each would let putting things away cost more room than having
- * them out, which is the opposite of the point. */
+ * bottom: one bar, however many there are — a row each would let putting
+ * things away cost more room than having them out, which is the opposite
+ * of the point. The bar is three rows, because each entry is drawn as a
+ * miniature pane frame (draw_min_bar): a frame is what a pane looks like
+ * in this program, so a put-away pane wearing one reads as a pane you can
+ * click rather than as a caption — which a row of dim names turned out to
+ * be mistaken for. Fixed cost while anything is minimised, zero when
+ * nothing is. */
node_t *mins[64];
size_t nmin = collect_minimized(root, mins, 64, 0);
rect_t tree_r = r;
bool no_room = false;
cur(a)->min_bar = (rect_t){0, 0, 0, 0};
if (nmin) {
- if (r.h >= (uint16_t)(MIN_PANE_ROWS + 3)) {
- tree_r.h = (uint16_t)(r.h - 1);
- cur(a)->min_bar = (rect_t){r.x, (uint16_t)(r.y + r.h - 1), r.w, 1};
+ if (r.h >= (uint16_t)(MIN_PANE_ROWS + 5)) {
+ tree_r.h = (uint16_t)(r.h - 3);
+ cur(a)->min_bar = (rect_t){r.x, (uint16_t)(r.y + r.h - 3), r.w, 3};
} else {
no_room = true;
}
diff --git a/tests/test_bell.py b/tests/test_bell.py
index db82903..8278bec 100644
--- a/tests/test_bell.py
+++ b/tests/test_bell.py
@@ -340,8 +340,10 @@ RINGER = [
def bar_row(s):
+ """The bar's middle row — each entry is a three-row pane-frame chip now,
+ and the label (mark included) lives between the borders."""
open_panes = [p for p in s.panes() if p["w"] > 1]
- return max(p["y"] + p["h"] for p in open_panes)
+ return max(p["y"] + p["h"] for p in open_panes) + 1
def test_a_minimised_pane_rings_on_the_bar():
@@ -382,9 +384,9 @@ def test_a_minimised_pane_rings_on_the_bar():
entry = [
h
for h in s.snapshot().hits
- if h["action"] == f"focus:{gone}" and h["h"] == 1
+ if h["action"] == f"focus:{gone}" and h["h"] == 3
]
- check("the entry is still one target", len(entry) == 1, str(entry))
+ check("the whole chip is one target", len(entry) == 1, str(entry))
if not entry:
return
s.click(entry[0]["x"] + 1, entry[0]["y"])
diff --git a/tests/test_hints.py b/tests/test_hints.py
index 792c3a0..cf15517 100644
--- a/tests/test_hints.py
+++ b/tests/test_hints.py
@@ -131,7 +131,7 @@ def test_the_gap_and_the_strip_and_the_bar():
e = [
h
for h in s.snapshot().hits
- if h["action"] == f"focus:{gone}" and h["h"] == 1
+ if h["action"] == f"focus:{gone}" and h["h"] == 3
][0]
hover(s, e["x"] + 1, e["y"])
check(