a modern tui library written in zig
1const std = @import("std");
2const vaxis = @import("../main.zig");
3
4const Allocator = std.mem.Allocator;
5
6const vxfw = @import("vxfw.zig");
7
8const Padding = @This();
9const PadValues = struct {
10 left: u16 = 0,
11 right: u16 = 0,
12 top: u16 = 0,
13 bottom: u16 = 0,
14};
15
16child: vxfw.Widget,
17padding: PadValues = .{},
18
19/// Vertical padding will be divided by 2 to approximate equal padding
20pub fn all(padding: u16) PadValues {
21 return .{
22 .left = padding,
23 .right = padding,
24 .top = padding / 2,
25 .bottom = padding / 2,
26 };
27}
28
29pub fn horizontal(padding: u16) PadValues {
30 return .{
31 .left = padding,
32 .right = padding,
33 };
34}
35
36pub fn vertical(padding: u16) PadValues {
37 return .{
38 .top = padding,
39 .bottom = padding,
40 };
41}
42
43pub fn widget(self: *const Padding) vxfw.Widget {
44 return .{
45 .userdata = @constCast(self),
46 .drawFn = typeErasedDrawFn,
47 };
48}
49
50fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
51 const self: *const Padding = @ptrCast(@alignCast(ptr));
52 return self.draw(ctx);
53}
54
55pub fn draw(self: *const Padding, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
56 const pad = self.padding;
57 if (pad.left > 0 or pad.right > 0)
58 std.debug.assert(ctx.max.width != null);
59 if (pad.top > 0 or pad.bottom > 0)
60 std.debug.assert(ctx.max.height != null);
61 const inner_min: vxfw.Size = .{
62 .width = ctx.min.width -| (pad.right + pad.left),
63 .height = ctx.min.height -| (pad.top + pad.bottom),
64 };
65
66 const max_width: ?u16 = if (ctx.max.width) |max|
67 max -| (pad.right + pad.left)
68 else
69 null;
70 const max_height: ?u16 = if (ctx.max.height) |max|
71 max -| (pad.top + pad.bottom)
72 else
73 null;
74
75 const inner_max: vxfw.MaxSize = .{
76 .width = max_width,
77 .height = max_height,
78 };
79
80 const child_surface = try self.child.draw(ctx.withConstraints(inner_min, inner_max));
81
82 const children = try ctx.arena.alloc(vxfw.SubSurface, 1);
83 children[0] = .{
84 .surface = child_surface,
85 .z_index = 0,
86 .origin = .{ .row = pad.top, .col = pad.left },
87 };
88
89 const size: vxfw.Size = .{
90 .width = child_surface.size.width + (pad.right + pad.left),
91 .height = child_surface.size.height + (pad.top + pad.bottom),
92 };
93
94 // Create the padding surface
95 return .{
96 .size = size,
97 .widget = self.widget(),
98 .buffer = &.{},
99 .children = children,
100 };
101}
102
103test Padding {
104 const Text = @import("Text.zig");
105 // Will be height=1, width=3
106 const text: Text = .{ .text = "abc" };
107
108 const padding: Padding = .{
109 .child = text.widget(),
110 .padding = horizontal(1),
111 };
112
113 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
114 defer arena.deinit();
115 vxfw.DrawContext.init(.unicode);
116
117 // Center expands to the max size. It must therefore have non-null max width and max height.
118 // These values are asserted in draw
119 const ctx: vxfw.DrawContext = .{
120 .arena = arena.allocator(),
121 .min = .{},
122 .max = .{ .width = 10, .height = 10 },
123 .cell_size = .{ .width = 10, .height = 20 },
124 };
125
126 const pad_widget = padding.widget();
127
128 const surface = try pad_widget.draw(ctx);
129 // Padding does not produce any drawable cells
130 try std.testing.expectEqual(0, surface.buffer.len);
131 // Padding has 1 child
132 try std.testing.expectEqual(1, surface.children.len);
133 const child = surface.children[0];
134 // Padding is the child size + padding
135 try std.testing.expectEqual(child.surface.size.width + 2, surface.size.width);
136 try std.testing.expectEqual(0, child.origin.row);
137 try std.testing.expectEqual(1, child.origin.col);
138}
139
140test "refAllDecls" {
141 std.testing.refAllDecls(@This());
142}