summaryrefslogtreecommitdiff
path: root/src/layout.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/layout.zig')
-rw-r--r--src/layout.zig69
1 files changed, 60 insertions, 9 deletions
diff --git a/src/layout.zig b/src/layout.zig
index 644054ed..cbf0fd96 100644
--- a/src/layout.zig
+++ b/src/layout.zig
@@ -274,7 +274,11 @@ pub fn applyRowSplit(p: *Pardes, cc: usize, k: usize, cur_y: u16) void {
const rb = p.rects[p.col_panes[cc][k + 1]];
const combined: f32 = @floatFromInt(ra.h + rb.h);
var nt: f32 = @floatFromInt(if (p.settings.tag_bottom) cur_y -| ra.y else (cur_y + 1) -| ra.y);
- nt = std.math.clamp(nt, @as(f32, BOX_H), @max(@as(f32, BOX_H), combined - BOX_H));
+ // A terminal keeps its tag and two body rows (floorRows); a text pane
+ // gives up all but its tag, as acme's do.
+ const lo: f32 = @floatFromInt(@max(BOX_H, floorRows(p, p.col_panes[cc][k])));
+ const hi = combined - @as(f32, @floatFromInt(@max(BOX_H, floorRows(p, p.col_panes[cc][k + 1]))));
+ nt = std.math.clamp(nt, @min(lo, @max(@as(f32, BOX_H), hi)), @max(@as(f32, BOX_H), hi));
if (combined <= 0) return;
// Dragging a folded tag open is an explicit resize, so switch this pair
// back to visible-height weights without consuming other folded weights.
@@ -794,16 +798,62 @@ pub fn compute(p: *Pardes) void {
if (vsum <= 0) vsum = 1;
var y: u16 = p.topBarHeight() + p.columnBarHeight();
+ const top = y;
const avail_h = p.screen_h -| y;
- for (0..p.col_n[c]) |k| {
+ var hs: [MAX_PANES]u16 = undefined;
+ const n = p.col_n[c];
+ for (0..n) |k| {
const id = p.col_panes[c][k];
- const pane = p.panes[id] orelse continue;
- const lastk = k + 1 == p.col_n[c];
+ const pane = p.panes[id] orelse {
+ hs[k] = 0;
+ continue;
+ };
+ const lastk = k + 1 == n;
const fh = @as(f32, @floatFromInt(avail_h)) * pane.vweight / vsum;
const room = p.screen_h -| y;
- const ch: u16 = if (lastk) room else @min(room, @max(1, @as(u16, @intFromFloat(@round(fh)))));
- p.rects[id] = .{ .x = x, .y = y, .w = cw, .h = ch };
- y +|= ch;
+ hs[k] = if (lastk) room else @min(room, @max(1, @as(u16, @intFromFloat(@round(fh)))));
+ y +|= hs[k];
+ }
+ keepTerminalFloors(p, c, hs[0..n]);
+ y = top;
+ for (0..n) |k| {
+ const id = p.col_panes[c][k];
+ if (p.panes[id] == null) continue;
+ p.rects[id] = .{ .x = x, .y = y, .w = cw, .h = hs[k] };
+ y +|= hs[k];
+ }
+ }
+}
+
+/// The fewest rows a pane is laid out with: a terminal keeps its tag and
+/// `min_body_rows` (a shorter one loses its prompt and reads busy for
+/// ever), a text pane may shrink to its tag, as acme's windows do.
+fn floorRows(p: *Pardes, id: usize) u16 {
+ const pane = p.panes[id] orelse return 0;
+ return if (pane.isTerminal()) @max(1, pane.tag_rows) + min_body_rows else 1;
+}
+
+/// Raises each terminal in column `c` below its floor to it, a row at a
+/// time from whichever pane has the most rows above its own floor. Where
+/// the column has not the rows, the terminal keeps what it could get.
+// ponytail: a row per step, O(panes x rows); a column holds few of both.
+fn keepTerminalFloors(p: *Pardes, c: usize, hs: []u16) void {
+ for (hs, 0..) |*h, k| {
+ const floor = floorRows(p, p.col_panes[c][k]);
+ while (h.* < floor) {
+ var giver: ?usize = null;
+ var spare: u16 = 0;
+ for (hs, 0..) |other, j| {
+ if (j == k) continue;
+ const extra = other -| floorRows(p, p.col_panes[c][j]);
+ if (extra > spare) {
+ spare = extra;
+ giver = j;
+ }
+ }
+ const g = giver orelse break;
+ hs[g] -= 1;
+ h.* += 1;
}
}
}
@@ -911,9 +961,10 @@ test "collapse local transfers remain bounded through repeated tiny window resiz
test "collapse expansion is limited to space available in one neighbor" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 42 });
defer p.deinit();
+ // Text panes: a terminal would keep its tag and two rows (floorRows).
for (1..4) |id| {
- _ = try p.newShell(id, "");
- insert(p, 0, id, id);
+ p.newScratchBelow(0);
+ try std.testing.expectEqual(id, p.col_panes[0][id]);
}
compute(p);
toggleCollapse(p, 2);