7f85da50
refactor: one weight split per axis, and a leaf root inserts by splitting
a73x 2026-08-29 10:01
Commit message
src/client/layout.zig
| Old | New | ||
|---|---|---|---|
| @@ -193,27 +193,14 @@ pub const Tree = struct { | |||
| 193 | /// `.stacked` container holding `[old, new]` — matching today's stripes. | 193 | /// `.stacked` container holding `[old, new]` — matching today's stripes. |
| 194 | pub fn insert(self: *Tree, focus: u8, tile: u8) !void { | 194 | pub fn insert(self: *Tree, focus: u8, tile: u8) !void { |
| 195 | const found = self.findLeaf(focus) orelse return error.NotFound; | 195 | const found = self.findLeaf(focus) orelse return error.NotFound; |
| 196 | if (found.parent) |p| { | 196 | // A leaf root is a stacked split of itself, which is what `split` |
| 197 | const new_node = try self.alloc.create(Node); | 197 | // already does when the focused leaf has no parent. |
| 198 | new_node.* = .{ .leaf = tile }; | 198 | const p = found.parent orelse return self.split(focus, tile, .stacked); |
| 199 | const w = p.weights.items[found.index]; | 199 | const new_node = try self.alloc.create(Node); |
| 200 | try p.children.insert(self.alloc, found.index + 1, new_node); | 200 | new_node.* = .{ .leaf = tile }; |
| 201 | try p.weights.insert(self.alloc, found.index + 1, w); | 201 | const w = p.weights.items[found.index]; |
| 202 | } else { | 202 | try p.children.insert(self.alloc, found.index + 1, new_node); |
| 203 | // Root is a leaf — wrap it in a stacked container. The old | 203 | try p.weights.insert(self.alloc, found.index + 1, w); |
| 204 | // node stays a leaf child; a new node holds the container. | ||
| 205 | const c = try self.alloc.create(Container); | ||
| 206 | c.* = .{ .orient = .stacked, .children = .empty, .weights = .empty }; | ||
| 207 | try c.children.append(self.alloc, found.node); | ||
| 208 | const new_node = try self.alloc.create(Node); | ||
| 209 | new_node.* = .{ .leaf = tile }; | ||
| 210 | try c.children.append(self.alloc, new_node); | ||
| 211 | try c.weights.append(self.alloc, 1); | ||
| 212 | try c.weights.append(self.alloc, 1); | ||
| 213 | const c_node = try self.alloc.create(Node); | ||
| 214 | c_node.* = .{ .container = c }; | ||
| 215 | self.root = c_node; | ||
| 216 | } | ||
| 217 | } | 204 | } |
| 218 | 205 | ||
| 219 | /// Replace the focused leaf with a two-child container of the forced | 206 | /// Replace the focused leaf with a two-child container of the forced |
| @@ -821,6 +808,25 @@ fn flattenFullscreen( | |||
| 821 | } | 808 | } |
| 822 | } | 809 | } |
| 823 | 810 | ||
| 811 | /// One axis of a container, cut by weight: `avail` cells across `weights`, | ||
| 812 | /// the division's remainder handed out a cell at a time from the first | ||
| 813 | /// child, so the parts always add back to `avail`. Caller frees. | ||
| 814 | fn weightSplit(alloc: std.mem.Allocator, weights: []const u32, avail: u16, total: u64) ![]u16 { | ||
| 815 | const base = try alloc.alloc(u16, weights.len); | ||
| 816 | var sum: u64 = 0; | ||
| 817 | for (weights, 0..) |w, i| { | ||
| 818 | base[i] = @intCast(@as(u64, avail) * w / total); | ||
| 819 | sum += base[i]; | ||
| 820 | } | ||
| 821 | var rem = avail - @as(u16, @intCast(sum)); | ||
| 822 | var i: usize = 0; | ||
| 823 | while (rem > 0 and i < weights.len) : (i += 1) { | ||
| 824 | base[i] += 1; | ||
| 825 | rem -= 1; | ||
| 826 | } | ||
| 827 | return base; | ||
| 828 | } | ||
| 829 | |||
| 824 | fn flattenNode( | 830 | fn flattenNode( |
| 825 | alloc: std.mem.Allocator, | 831 | alloc: std.mem.Allocator, |
| 826 | placed: *std.ArrayList(Placed), | 832 | placed: *std.ArrayList(Placed), |
| @@ -849,19 +855,8 @@ fn flattenNode( | |||
| 849 | 855 | ||
| 850 | switch (c.orient) { | 856 | switch (c.orient) { |
| 851 | .stacked => { | 857 | .stacked => { |
| 852 | var base = try alloc.alloc(u16, n); | 858 | const base = try weightSplit(alloc, c.weights.items, rows, total_weight); |
| 853 | defer alloc.free(base); | 859 | defer alloc.free(base); |
| 854 | var sum: u64 = 0; | ||
| 855 | for (c.weights.items, 0..) |w, i| { | ||
| 856 | base[i] = @intCast(@as(u64, rows) * w / total_weight); | ||
| 857 | sum += base[i]; | ||
| 858 | } | ||
| 859 | var rem = rows - @as(u16, @intCast(sum)); | ||
| 860 | var i: usize = 0; | ||
| 861 | while (rem > 0 and i < n) : (i += 1) { | ||
| 862 | base[i] += 1; | ||
| 863 | rem -= 1; | ||
| 864 | } | ||
| 865 | var cur_top = top; | 860 | var cur_top = top; |
| 866 | for (c.children.items, 0..) |child, ci| { | 861 | for (c.children.items, 0..) |child, ci| { |
| 867 | try flattenNode(alloc, placed, rails, child, cur_top, left, base[ci], cols, floors); | 862 | try flattenNode(alloc, placed, rails, child, cur_top, left, base[ci], cols, floors); |
| @@ -872,20 +867,8 @@ fn flattenNode( | |||
| 872 | // Reserve n-1 columns for rails, then split cols by weight. | 867 | // Reserve n-1 columns for rails, then split cols by weight. |
| 873 | const rail_count: u16 = @intCast(n - 1); | 868 | const rail_count: u16 = @intCast(n - 1); |
| 874 | if (cols < rail_count) return error.TooSmall; | 869 | if (cols < rail_count) return error.TooSmall; |
| 875 | const avail = cols - rail_count; | 870 | const base = try weightSplit(alloc, c.weights.items, cols - rail_count, total_weight); |
| 876 | var base = try alloc.alloc(u16, n); | ||
| 877 | defer alloc.free(base); | 871 | defer alloc.free(base); |
| 878 | var sum: u64 = 0; | ||
| 879 | for (c.weights.items, 0..) |w, i| { | ||
| 880 | base[i] = @intCast(@as(u64, avail) * w / total_weight); | ||
| 881 | sum += base[i]; | ||
| 882 | } | ||
| 883 | var rem = avail - @as(u16, @intCast(sum)); | ||
| 884 | var i: usize = 0; | ||
| 885 | while (rem > 0 and i < n) : (i += 1) { | ||
| 886 | base[i] += 1; | ||
| 887 | rem -= 1; | ||
| 888 | } | ||
| 889 | var cur_left = left; | 872 | var cur_left = left; |
| 890 | for (c.children.items, 0..) |child, ci| { | 873 | for (c.children.items, 0..) |child, ci| { |
| 891 | try flattenNode(alloc, placed, rails, child, top, cur_left, rows, base[ci], floors); | 874 | try flattenNode(alloc, placed, rails, child, top, cur_left, rows, base[ci], floors); |