aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-14 17:01:47 +0200
committeruser <user@clank>2026-08-14 17:01:47 +0200
commit954989dc58c6f98593cc97cb1cc43a3833cb8d22 (patch)
tree4f7414b153d1dfca1f473015ee4e4e0f572b78cd /src
parentsl0ppty: the tab strip answers the pointer (diff)
downloadslosh-954989dc58c6f98593cc97cb1cc43a3833cb8d22.tar.gz
slosh-954989dc58c6f98593cc97cb1cc43a3833cb8d22.tar.xz
slosh-954989dc58c6f98593cc97cb1cc43a3833cb8d22.zip
sl0ppty: a pane has to clear the floor in both directions
Two panes stacked on top of each other could be squeezed to four columns wide and nothing collapsed, at any width. The fit check lived only on split nodes, and a split node only ever asks about the dimension it divides: a row split asks about height and never about width. A leaf was asked nothing at all. So for a stacked layout there was no node in the tree responsible for noticing that its panes had become unusably narrow — the check existed, it was just looking the other way. A leaf is the only node that can see both of its own sides, so it now checks both. The floor is per pane and the same in every arrangement, which is what made this a hole rather than a missing feature. That immediately made resizing dangerous, which is the more interesting half. Dragging a divider one cell too far could push a pane under the floor and collapse the entire tab into a list — a drastic answer to a nudge, and it looked like the session falling over rather than a limit being reached. So the transfer stops at the floor instead, the way every other resizable thing stops. The clamp went into transfer_weight rather than into the caller, because the mouse reaches it through drag_edge and the keyboard through resize_focus, and my first attempt clamped only the mouse. A limit that one of two paths respects is worse than none: it looks fixed until you use the other hand. It also has to round the weight *up*. The weight that buys exactly `floor_` cells is a fraction, and rounding it down buys one cell fewer — which is precisely the cell that puts the pane under the floor. The first version rounded down and collapsed anyway, one drag later than before. Two tests changed with real content rather than arithmetic: one used a seventy-six by eight screen whose panes were four rows tall, which is under the floor and should always have collapsed, and one asserted a squeezed pane stayed "at least 3 columns" when it now stops at 24 exactly.
Diffstat (limited to 'src')
-rw-r--r--src/app.c44
1 files changed, 42 insertions, 2 deletions
diff --git a/src/app.c b/src/app.c
index cc650bd..e7cc7fd 100644
--- a/src/app.c
+++ b/src/app.c
@@ -754,6 +754,14 @@ static void layout_node(node_t *n, rect_t r, layout_ctx_t *ctx) {
};
if (ctx->apply && !n->hidden)
pane_resize(n->pane, n->content.w, n->content.h);
+
+ /* A pane has to clear the floor in *both* directions, whatever arrangement
+ * produced it. The check on a split node only ever asks about the
+ * dimension that node divides — a row split asks about height and never
+ * about width — so a column of stacked panes could be squeezed to four
+ * cells wide with nothing in the tree responsible for noticing. The leaf
+ * is the only place that can see both of its own sides. */
+ if (r.w < MIN_PANE_COLS || r.h < MIN_PANE_ROWS) ctx->overflow = true;
return;
}
@@ -1324,9 +1332,40 @@ static size_t index_in_parent(node_t *n) {
}
/* Move weight between two adjacent siblings, refusing to squeeze either out. */
+/* `from` always shrinks. It stops at the floor rather than being pushed under
+ * one: a pane below the floor collapses the whole tab into a list, and having
+ * that happen because you nudged a divider one cell too far would read as the
+ * session falling over rather than as a limit being reached. Every other
+ * resizable thing simply stops, so this does too.
+ *
+ * The clamp lives here because both ways of resizing end up here — the mouse
+ * through drag_edge and the keyboard through resize_focus — and a limit that
+ * only one of them respected would be worse than none. */
static void transfer_weight(node_t *from, node_t *to, int amount) {
- if (from->weight - amount < WEIGHT_MIN) amount = from->weight - WEIGHT_MIN;
- if (amount <= 0) return;
+ long min_weight = WEIGHT_MIN;
+
+ node_t *sp = from->parent;
+ if (sp && sp->nkids >= 2) {
+ uint16_t floor_ = sp->dir == SPLIT_COLS ? MIN_PANE_COLS : MIN_PANE_ROWS;
+ uint16_t gap = sp->dir == SPLIT_COLS ? (uint16_t)(CFG.gap * CFG.gap_aspect)
+ : CFG.gap;
+ uint16_t span = sp->dir == SPLIT_COLS ? sp->rect.w : sp->rect.h;
+ uint16_t gaps = (uint16_t)(gap * (sp->nkids - 1));
+ uint16_t avail = span > gaps ? (uint16_t)(span - gaps) : span;
+ long total = 0;
+ for (size_t k = 0; k < sp->nkids; k++) total += sp->kids[k]->weight;
+ /* Rounded up: the weight that buys exactly `floor_` cells is a fraction,
+ * and rounding down buys one cell fewer — which is the cell that puts the
+ * pane under the floor. */
+ if (avail && total > 0) {
+ long need = ((long)floor_ * total + avail - 1) / avail;
+ if (need > min_weight) min_weight = need;
+ }
+ }
+
+ if (from->weight - amount < min_weight)
+ amount = (int)(from->weight - min_weight);
+ if (amount <= 0) return; /* already at the floor: the nudge does nothing */
from->weight -= amount;
to->weight += amount;
}
@@ -2369,6 +2408,7 @@ static void drag_edge(app_t *a, node_t *sp, size_t i, int cells) {
int amount = (int)((long)labs(cells) * total / (span ? span : 1));
if (amount <= 0) amount = 1;
+
if (cells > 0) transfer_weight(sp->kids[i + 1], sp->kids[i], amount);
else transfer_weight(sp->kids[i], sp->kids[i + 1], amount);
layout(a);