const std = @import("std"); const vaxis = @import("../main.zig"); const vxfw = @import("vxfw.zig"); const Allocator = std.mem.Allocator; const FlexRow = @This(); children: []const vxfw.FlexItem, pub fn widget(self: *const FlexRow) vxfw.Widget { return .{ .userdata = @constCast(self), .drawFn = typeErasedDrawFn, }; } fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { const self: *const FlexRow = @ptrCast(@alignCast(ptr)); return self.draw(ctx); } pub fn draw(self: *const FlexRow, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { std.debug.assert(ctx.max.height != null); std.debug.assert(ctx.max.width != null); if (self.children.len == 0) return vxfw.Surface.init(ctx.arena, self.widget(), ctx.min); // Store the inherent size of each widget const size_list = try ctx.arena.alloc(u16, self.children.len); var layout_arena = std.heap.ArenaAllocator.init(ctx.arena); const layout_ctx: vxfw.DrawContext = .{ .min = .{ .width = 0, .height = 0 }, .max = .{ .width = null, .height = ctx.max.height }, .arena = layout_arena.allocator(), .cell_size = ctx.cell_size, }; var first_pass_width: u16 = 0; var total_flex: u16 = 0; for (self.children, 0..) |child, i| { if (child.flex == 0) { const surf = try child.widget.draw(layout_ctx); first_pass_width += surf.size.width; size_list[i] = surf.size.width; } total_flex += child.flex; } // We are done with the layout arena layout_arena.deinit(); // make our children list var children: std.ArrayList(vxfw.SubSurface) = .empty; // Draw again, but with distributed widths var second_pass_width: u16 = 0; var max_height: u16 = 0; const remaining_space = ctx.max.width.? -| first_pass_width; for (self.children, 0..) |child, i| { const child_width = if (child.flex == 0) size_list[i] else if (i == self.children.len - 1) // If we are the last one, we just get the remainder ctx.max.width.? -| second_pass_width else (remaining_space * child.flex) / total_flex; // Create a context for the child const child_ctx = ctx.withConstraints( .{ .width = child_width, .height = 0 }, .{ .width = child_width, .height = ctx.max.height.? }, ); const surf = try child.widget.draw(child_ctx); try children.append(ctx.arena, .{ .origin = .{ .col = second_pass_width, .row = 0 }, .surface = surf, .z_index = 0, }); max_height = @max(max_height, surf.size.height); second_pass_width += surf.size.width; } const size: vxfw.Size = .{ .width = second_pass_width, .height = max_height }; return .{ .size = size, .widget = self.widget(), .buffer = &.{}, .children = children.items, }; } test FlexRow { // Create child widgets const Text = @import("Text.zig"); // Will be height=1, width=3 const abc: Text = .{ .text = "abc" }; const def: Text = .{ .text = "def" }; const ghi: Text = .{ .text = "ghi" }; const jklmno: Text = .{ .text = "jkl\nmno" }; // Create the flex row const flex_row: FlexRow = .{ .children = &.{ .{ .widget = abc.widget(), .flex = 0 }, // flex=0 means we are our inherent size .{ .widget = def.widget(), .flex = 1 }, .{ .widget = ghi.widget(), .flex = 1 }, .{ .widget = jklmno.widget(), .flex = 1 }, }, }; // Boiler plate draw context var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); vxfw.DrawContext.init(.unicode); const flex_widget = flex_row.widget(); const ctx: vxfw.DrawContext = .{ .arena = arena.allocator(), .min = .{}, .max = .{ .width = 16, .height = 16 }, .cell_size = .{ .width = 10, .height = 20 }, }; const surface = try flex_widget.draw(ctx); // FlexRow expands to max width and tallest child try std.testing.expectEqual(16, surface.size.width); try std.testing.expectEqual(2, surface.size.height); // We have four children try std.testing.expectEqual(4, surface.children.len); // We will track the column we are on to confirm the origins var col: u16 = 0; // First child has flex=0, it should be it's inherent width try std.testing.expectEqual(3, surface.children[0].surface.size.width); try std.testing.expectEqual(col, surface.children[0].origin.col); // Add the child height each time col += surface.children[0].surface.size.width; // Let's do some math // - We have 4 children to fit into 16 cols. All children will be 3 wide for a total width of 12 // - The first child is 3 cols and no flex. The rest of the width gets distributed evenly among // the remaining 3 children. The remainder width is 16 - 12 = 4, so each child should get 4 / // 3 = 1 extra cols, and the last will receive the remainder try std.testing.expectEqual(1 + 3, surface.children[1].surface.size.width); try std.testing.expectEqual(col, surface.children[1].origin.col); col += surface.children[1].surface.size.width; try std.testing.expectEqual(1 + 3, surface.children[2].surface.size.width); try std.testing.expectEqual(col, surface.children[2].origin.col); col += surface.children[2].surface.size.width; try std.testing.expectEqual(1 + 3 + 1, surface.children[3].surface.size.width); try std.testing.expectEqual(col, surface.children[3].origin.col); } test "refAllDecls" { std.testing.refAllDecls(@This()); }