diff options
| author | user <user@clank> | 2026-08-25 14:23:33 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-25 14:23:33 +0200 |
| commit | b744a90859bf248bfbea97f990668301b5fa9537 (patch) | |
| tree | f1b717507bc24161b7ab41def644289f9457470e /src/app_layout.c | |
| parent | placeholder webbie (diff) | |
| download | slosh-b744a90859bf248bfbea97f990668301b5fa9537.tar.gz slosh-b744a90859bf248bfbea97f990668301b5fa9537.tar.xz slosh-b744a90859bf248bfbea97f990668301b5fa9537.zip | |
compact: shared borders instead of gaps (D23)
One knob, `compact true`: panes pack flush against 1-cell divider lines
that meet in real junctions, one frame rings the tab, and a pane's title
rides the line above it -- a boundary costs one cell where the default
look spends four. The lines are strokes: each write says which edges of
its cell it needs, unions with what is there, and never clobbers text,
so ├ ┬ ┼ fall out of paint order and the focus ring recolours without
inventing arms. The divider *is* the gap (eff_gap, one answer for the
layout, split_fits and the resize clamp), so gap hits, hover hints and
corner drags work unchanged. Interior edges give up click-to-split --
one cell cannot hold two verbs -- and a pane's status row overlays its
last content row. Floats, zoom and the D6 flatten keep classic frames
through place_hidden: zero new tree cases.
Underneath it, mode or no mode: a border drag now moves the boundary on
that side, converted on the first motion so the press that never moves
keeps its old meaning -- the handle's click still splits, the rim's is
still deliberately nothing (drag.rim). Which is also the fix for gap 0,
where flush panes leave no gap cell to grab and the resize gesture had
silently vanished with it.
Diffstat (limited to 'src/app_layout.c')
| -rw-r--r-- | src/app_layout.c | 61 |
1 files changed, 48 insertions, 13 deletions
diff --git a/src/app_layout.c b/src/app_layout.c index 6618a2a..8ba4a3f 100644 --- a/src/app_layout.c +++ b/src/app_layout.c @@ -21,6 +21,16 @@ /* ---- layout: a pure function of the tree and the rect ------------------- */ +/* The space between two siblings, which every piece of gap arithmetic — the + * layout, the fit check, the resize clamp — must agree on. Compact packs + * panes against 1-cell divider lines, so the "gap" is exactly the divider, + * whichever axis: `gap` and `gap_aspect` are about air, and compact has + * none. */ +uint16_t eff_gap(split_dir_t dir) { + if (CFG.compact) return 1; + return dir == SPLIT_COLS ? (uint16_t)(CFG.gap * CFG.gap_aspect) : CFG.gap; +} + /* Every leaf under a node, in tree order. */ size_t collect_leaves(node_t *n, node_t **out, size_t cap, size_t k) { if (!n || k >= cap) return k; @@ -201,12 +211,24 @@ static void layout_node(node_t *n, rect_t r, layout_ctx_t *ctx) { * rect too small for a frame gets neither, and the pane takes the whole * thing. Horizontal padding is aspect-corrected, like the gap: the config's * numbers are rows, so a square-looking ring is one number rather than an - * arithmetic problem. */ - bool framed = r.w >= 3 && r.h >= 3; - uint16_t left = framed ? 1 + CFG.pad_left * CFG.gap_aspect : 0; - uint16_t right = framed ? 1 + CFG.pad_right * CFG.gap_aspect : 0; - uint16_t top = framed ? 1 + CFG.pad_top : 0; - uint16_t bottom = framed ? 1 + CFG.pad_bottom : 0; + * arithmetic problem. + * + * A compact tiled pane spends no border cells at all: the lines live + * outside its rect, between the rects, so content is the rect minus + * padding only. Floats keep the classic frame — they are overlays, and an + * overlay needs its own edge — and so does everything laid out through + * the flatten/zoom paths (`place_hidden`), where the pane on show is not + * packed against anything. */ + bool classic = !CFG.compact || n->floating || ctx->place_hidden; + bool framed = classic && r.w >= 3 && r.h >= 3; + uint16_t bord = framed ? 1 : 0; + bool pads = framed || !classic; + uint16_t left = + (uint16_t)(bord + (pads ? CFG.pad_left * CFG.gap_aspect : 0)); + uint16_t right = + (uint16_t)(bord + (pads ? CFG.pad_right * CFG.gap_aspect : 0)); + uint16_t top = (uint16_t)(bord + (pads ? CFG.pad_top : 0)); + uint16_t bottom = (uint16_t)(bord + (pads ? CFG.pad_bottom : 0)); n->content = (rect_t){ .x = (uint16_t)(r.x + left), .y = (uint16_t)(r.y + top), @@ -238,8 +260,7 @@ static void layout_node(node_t *n, rect_t r, layout_ctx_t *ctx) { return; } - uint16_t gap = - n->dir == SPLIT_COLS ? (uint16_t)(CFG.gap * CFG.gap_aspect) : CFG.gap; + uint16_t gap = eff_gap(n->dir); uint16_t total = n->dir == SPLIT_COLS ? r.w : r.h; /* Does every child clear the floor? If not, this node collapses — a local @@ -363,7 +384,18 @@ static void ensure_tiled(app_t *a) { * and the outer gap. Factored so the float edits can ask the same question * the layout answers, rather than a second copy of this arithmetic. */ static rect_t tab_area(app_t *a) { - uint16_t gx = (uint16_t)(CFG.gap * CFG.gap_aspect), gy = CFG.gap; + /* Compact reserves exactly the outer frame: one cell a side, drawn as one + * ring around the whole tab rather than a border per pane. The ring is + * also where the top row of panes put their titles, so it is not optional + * air the way the gap is. */ + uint16_t gx, gy; + if (CFG.compact) { + gx = a->cols >= 3 ? 1 : 0; + gy = a->rows >= 3 ? 1 : 0; + } else { + gx = (uint16_t)(CFG.gap * CFG.gap_aspect); + gy = CFG.gap; + } uint16_t top = (uint16_t)(gy + STRIP_ROWS); return (rect_t){.x = gx, .y = top, @@ -374,6 +406,11 @@ static rect_t tab_area(app_t *a) { : 1)}; } +/* The same rect for the draw passes: the compact frame ring and the outer + * split affordances are drawn against it, and recomputing it there would be a + * second opinion about where the layout put everything. */ +rect_t app_tab_area(app_t *a) { return tab_area(a); } + void layout(app_t *a) { if (!a->ntabs) return; /* Re-derived below, like everything else about a layout pass: whether this @@ -618,8 +655,7 @@ bool split_fits(node_t *leaf, split_dir_t dir) { uint16_t floor_ = dir == SPLIT_COLS ? CFG.min_split_cols : CFG.min_split_rows; uint16_t hard = dir == SPLIT_COLS ? MIN_PANE_COLS : MIN_PANE_ROWS; if (floor_ < hard) floor_ = hard; - uint16_t gap = - dir == SPLIT_COLS ? (uint16_t)(CFG.gap * CFG.gap_aspect) : CFG.gap; + uint16_t gap = eff_gap(dir); size_t k; uint16_t total; @@ -1531,8 +1567,7 @@ void transfer_weight(node_t *from, node_t *to, int amount) { 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 gap = eff_gap(sp->dir); 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; |
