this repo has no description
1const std = @import("std");
2const vaxis = @import("../main.zig");
3
4const vxfw = @import("vxfw.zig");
5
6const Allocator = std.mem.Allocator;
7
8const FlexRow = @This();
9
10children: []const vxfw.FlexItem,
11
12pub fn widget(self: *const FlexRow) vxfw.Widget {
13 return .{
14 .userdata = @constCast(self),
15 .drawFn = typeErasedDrawFn,
16 };
17}
18
19fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
20 const self: *const FlexRow = @ptrCast(@alignCast(ptr));
21 return self.draw(ctx);
22}
23
24pub fn draw(self: *const FlexRow, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
25 std.debug.assert(ctx.max.height != null);
26 std.debug.assert(ctx.max.width != null);
27 if (self.children.len == 0) return vxfw.Surface.init(ctx.arena, self.widget(), ctx.min);
28
29 // Store the inherent size of each widget
30 const size_list = try ctx.arena.alloc(u16, self.children.len);
31
32 var layout_arena = std.heap.ArenaAllocator.init(ctx.arena);
33
34 const layout_ctx: vxfw.DrawContext = .{
35 .min = .{ .width = 0, .height = 0 },
36 .max = .{ .width = null, .height = ctx.max.height },
37 .arena = layout_arena.allocator(),
38 .cell_size = ctx.cell_size,
39 };
40
41 var first_pass_width: u16 = 0;
42 var total_flex: u16 = 0;
43 for (self.children, 0..) |child, i| {
44 if (child.flex == 0) {
45 const surf = try child.widget.draw(layout_ctx);
46 first_pass_width += surf.size.width;
47 size_list[i] = surf.size.width;
48 }
49 total_flex += child.flex;
50 }
51
52 // We are done with the layout arena
53 layout_arena.deinit();
54
55 // make our children list
56 var children: std.ArrayList(vxfw.SubSurface) = .empty;
57
58 // Draw again, but with distributed widths
59 var second_pass_width: u16 = 0;
60 var max_height: u16 = 0;
61 const remaining_space = ctx.max.width.? -| first_pass_width;
62 for (self.children, 0..) |child, i| {
63 const child_width = if (child.flex == 0)
64 size_list[i]
65 else if (i == self.children.len - 1)
66 // If we are the last one, we just get the remainder
67 ctx.max.width.? -| second_pass_width
68 else
69 (remaining_space * child.flex) / total_flex;
70
71 // Create a context for the child
72 const child_ctx = ctx.withConstraints(
73 .{ .width = child_width, .height = 0 },
74 .{ .width = child_width, .height = ctx.max.height.? },
75 );
76 const surf = try child.widget.draw(child_ctx);
77
78 try children.append(ctx.arena, .{
79 .origin = .{ .col = second_pass_width, .row = 0 },
80 .surface = surf,
81 .z_index = 0,
82 });
83 max_height = @max(max_height, surf.size.height);
84 second_pass_width += surf.size.width;
85 }
86 const size: vxfw.Size = .{ .width = second_pass_width, .height = max_height };
87 return .{
88 .size = size,
89 .widget = self.widget(),
90 .buffer = &.{},
91 .children = children.items,
92 };
93}
94
95test FlexRow {
96 // Create child widgets
97 const Text = @import("Text.zig");
98 // Will be height=1, width=3
99 const abc: Text = .{ .text = "abc" };
100 const def: Text = .{ .text = "def" };
101 const ghi: Text = .{ .text = "ghi" };
102 const jklmno: Text = .{ .text = "jkl\nmno" };
103
104 // Create the flex row
105 const flex_row: FlexRow = .{
106 .children = &.{
107 .{ .widget = abc.widget(), .flex = 0 }, // flex=0 means we are our inherent size
108 .{ .widget = def.widget(), .flex = 1 },
109 .{ .widget = ghi.widget(), .flex = 1 },
110 .{ .widget = jklmno.widget(), .flex = 1 },
111 },
112 };
113
114 // Boiler plate draw context
115 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
116 defer arena.deinit();
117 vxfw.DrawContext.init(.unicode);
118
119 const flex_widget = flex_row.widget();
120 const ctx: vxfw.DrawContext = .{
121 .arena = arena.allocator(),
122 .min = .{},
123 .max = .{ .width = 16, .height = 16 },
124 .cell_size = .{ .width = 10, .height = 20 },
125 };
126
127 const surface = try flex_widget.draw(ctx);
128 // FlexRow expands to max width and tallest child
129 try std.testing.expectEqual(16, surface.size.width);
130 try std.testing.expectEqual(2, surface.size.height);
131 // We have four children
132 try std.testing.expectEqual(4, surface.children.len);
133
134 // We will track the column we are on to confirm the origins
135 var col: u16 = 0;
136 // First child has flex=0, it should be it's inherent width
137 try std.testing.expectEqual(3, surface.children[0].surface.size.width);
138 try std.testing.expectEqual(col, surface.children[0].origin.col);
139 // Add the child height each time
140 col += surface.children[0].surface.size.width;
141 // Let's do some math
142 // - We have 4 children to fit into 16 cols. All children will be 3 wide for a total width of 12
143 // - The first child is 3 cols and no flex. The rest of the width gets distributed evenly among
144 // the remaining 3 children. The remainder width is 16 - 12 = 4, so each child should get 4 /
145 // 3 = 1 extra cols, and the last will receive the remainder
146 try std.testing.expectEqual(1 + 3, surface.children[1].surface.size.width);
147 try std.testing.expectEqual(col, surface.children[1].origin.col);
148 col += surface.children[1].surface.size.width;
149
150 try std.testing.expectEqual(1 + 3, surface.children[2].surface.size.width);
151 try std.testing.expectEqual(col, surface.children[2].origin.col);
152 col += surface.children[2].surface.size.width;
153
154 try std.testing.expectEqual(1 + 3 + 1, surface.children[3].surface.size.width);
155 try std.testing.expectEqual(col, surface.children[3].origin.col);
156}
157
158test "refAllDecls" {
159 std.testing.refAllDecls(@This());
160}