vxfw: add FlexRow widget
This commit is contained in:
parent
6941cd831c
commit
147052b5f2
2 changed files with 160 additions and 0 deletions
159
src/vxfw/FlexRow.zig
Normal file
159
src/vxfw/FlexRow.zig
Normal file
|
@ -0,0 +1,159 @@
|
|||
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),
|
||||
.eventHandler = vxfw.noopEventHandler,
|
||||
.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(),
|
||||
};
|
||||
|
||||
var first_pass_width: u16 = 0;
|
||||
var total_flex: u16 = 0;
|
||||
for (self.children, 0..) |child, i| {
|
||||
const surf = try child.widget.draw(layout_ctx);
|
||||
first_pass_width += surf.size.width;
|
||||
total_flex += child.flex;
|
||||
size_list[i] = surf.size.width;
|
||||
}
|
||||
|
||||
// We are done with the layout arena
|
||||
layout_arena.deinit();
|
||||
|
||||
// make our children list
|
||||
var children = std.ArrayList(vxfw.SubSurface).init(ctx.arena);
|
||||
|
||||
// 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, 1..) |child, i| {
|
||||
const inherent_width = size_list[i - 1];
|
||||
const child_width = if (child.flex == 0)
|
||||
inherent_width
|
||||
else if (i == self.children.len)
|
||||
// If we are the last one, we just get the remainder
|
||||
ctx.max.width.? - second_pass_width
|
||||
else
|
||||
inherent_width + (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(.{
|
||||
.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 = .{ .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();
|
||||
const ucd = try vaxis.Unicode.init(arena.allocator());
|
||||
vxfw.DrawContext.init(&ucd, .unicode);
|
||||
|
||||
const flex_widget = flex_row.widget();
|
||||
const ctx: vxfw.DrawContext = .{
|
||||
.arena = arena.allocator(),
|
||||
.min = .{},
|
||||
.max = .{ .width = 16, .height = 16 },
|
||||
};
|
||||
|
||||
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());
|
||||
}
|
|
@ -14,6 +14,7 @@ pub const App = @import("App.zig");
|
|||
pub const Button = @import("Button.zig");
|
||||
pub const Center = @import("Center.zig");
|
||||
pub const FlexColumn = @import("FlexColumn.zig");
|
||||
pub const FlexRow = @import("FlexRow.zig");
|
||||
pub const ListView = @import("ListView.zig");
|
||||
pub const RichText = @import("RichText.zig");
|
||||
pub const Text = @import("Text.zig");
|
||||
|
|
Loading…
Add table
Reference in a new issue