this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

vxfw: add FlexColumn widget

FlexColumn is similar to a flexbox in HTML/CSS. It is a column that has
flexible height for it's children. It expands to fill the vertical space

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