lib/arrange/src/types.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const Size = struct {
4 width: f32 = 0,
5 height: f32 = 0,
6 };
7
8 pub const Rect = struct {
9 x: f32 = 0,
10 y: f32 = 0,
11 width: f32 = 0,
12 height: f32 = 0,
13 };
14
15 pub const Dimensions = struct {
16 width: ?f32 = null,
17 height: ?f32 = null,
18 };
19
20 pub const Insets = struct {
21 top: ?f32 = null,
22 left: ?f32 = null,
23 right: ?f32 = null,
24 bottom: ?f32 = null,
25 };
26
27 pub const Constraints = struct {
28 min_width: ?f32 = null,
29 min_height: ?f32 = null,
30 max_width: ?f32 = null,
31 max_height: ?f32 = null,
32 };
33
34 pub const FlexDirection = enum {
35 row,
36 column,
37 };
38
39 pub const Wrap = enum {
40 no_wrap,
41 wrap,
42 };
43
44 pub const JustifyContent = enum {
45 flex_start,
46 flex_end,
47 center,
48 space_between,
49 space_around,
50 space_evenly,
51 };
52
53 pub const AlignItems = enum {
54 flex_start,
55 flex_end,
56 center,
57 stretch,
58 };
59
60 pub const AlignSelf = enum {
61 auto,
62 flex_start,
63 flex_end,
64 center,
65 stretch,
66 };
67
68 pub const Position = enum {
69 relative,
70 absolute,
71 };
72
73 pub const Style = struct {
74 flex_direction: FlexDirection = .row,
75 wrap: Wrap = .no_wrap,
76 justify_content: JustifyContent = .flex_start,
77 align_items: AlignItems = .stretch,
78 align_self: AlignSelf = .auto,
79 gap: f32 = 0,
80 padding: Insets = .{},
81 constraints: Constraints = .{},
82 flex_grow: f32 = 0,
83 flex_shrink: f32 = 1,
84 flex_basis: ?f32 = null,
85 position: Position = .relative,
86 inset: Insets = .{},
87 };
88
89 pub const AvailableSpace = union(enum) {
90 definite: f32,
91 min_content,
92 max_content,
93 };
94
95 pub const Available = struct {
96 width: AvailableSpace = .max_content,
97 height: AvailableSpace = .max_content,
98 };
99
100 pub const LayoutInput = struct {
101 known: Dimensions = .{},
102 available: Available = .{},
103 };
104
105 pub const Measure = struct {
106 context: ?*anyopaque = null,
107 call: *const fn (context: ?*anyopaque, token: u32, available: Available) Size,
108
109 pub fn size(self: Measure, token: u32, available: Available) Size {
110 validateAvailable(available);
111 const measured = self.call(self.context, token, available);
112 std.debug.assert(std.math.isFinite(measured.width));
113 std.debug.assert(std.math.isFinite(measured.height));
114 std.debug.assert(measured.width >= 0);
115 std.debug.assert(measured.height >= 0);
116 return measured;
117 }
118 };
119
120 pub const MeasureMemo = struct {
121 valid: bool = false,
122 token: u32 = 0,
123 available: Available = .{},
124 size: Size = .{},
125 };
126
127 pub const Measurer = struct {
128 measure: Measure,
129 memos: []MeasureMemo = &.{},
130 used: usize = 0,
131 calls: usize = 0,
132 probes: usize = 0,
133
134 pub fn reset(self: *Measurer) void {
135 for (self.memos) |*memo| memo.* = .{};
136 self.used = 0;
137 self.calls = 0;
138 self.probes = 0;
139 }
140
141 pub fn size(self: *Measurer, token: u32, available: Available) Size {
142 if (self.memos.len == 0) {
143 self.calls += 1;
144 return self.measure.size(token, available);
145 }
146 const index = measureHash(token, available) % self.memos.len;
147 const memo = &self.memos[index];
148 self.probes += 1;
149 if (memo.valid and memo.token == token and sameAvailable(memo.available, available)) {
150 return memo.size;
151 }
152
153 const measured = self.measure.size(token, available);
154 self.calls += 1;
155 if (!memo.valid) {
156 self.used += 1;
157 }
158 memo.* = .{
159 .valid = true,
160 .token = token,
161 .available = available,
162 .size = measured,
163 };
164 return measured;
165 }
166 };
167
168 fn validateAvailable(available: Available) void {
169 validateSpace(available.width);
170 validateSpace(available.height);
171 }
172
173 fn validateSpace(space: AvailableSpace) void {
174 switch (space) {
175 .definite => |value| {
176 std.debug.assert(std.math.isFinite(value));
177 std.debug.assert(value >= 0);
178 },
179 else => {},
180 }
181 }
182
183 fn measureHash(token: u32, available: Available) usize {
184 var hash = @as(u64, token) *% 0x9e3779b97f4a7c15;
185 hash = mixHash(hash, spaceHash(available.width));
186 hash = mixHash(hash, spaceHash(available.height));
187 return @intCast(hash ^ (hash >> 32));
188 }
189
190 fn mixHash(hash: u64, value: u64) u64 {
191 return (hash ^ value) *% 0xbf58476d1ce4e5b9;
192 }
193
194 fn spaceHash(space: AvailableSpace) u64 {
195 return switch (space) {
196 .min_content => 0x243f6a8885a308d3,
197 .max_content => 0x13198a2e03707344,
198 .definite => |value| 0xa4093822299f31d0 ^ normalizedFloatBits(value),
199 };
200 }
201
202 fn normalizedFloatBits(value: f32) u64 {
203 if (value == 0) return 0;
204 const bits: u32 = @bitCast(value);
205 return bits;
206 }
207
208 fn sameAvailable(left: Available, right: Available) bool {
209 return sameSpace(left.width, right.width) and sameSpace(left.height, right.height);
210 }
211
212 fn sameSpace(left: AvailableSpace, right: AvailableSpace) bool {
213 return switch (left) {
214 .definite => |value| switch (right) {
215 .definite => |other| value == other,
216 else => false,
217 },
218 .min_content => right == .min_content,
219 .max_content => right == .max_content,
220 };
221 }
222
223 pub const Node = struct {
224 id: usize = 0,
225 style: Style = .{},
226 size: Dimensions = .{},
227 intrinsic_size: Size = .{},
228 measure_token: ?u32 = null,
229 clip_x: bool = false,
230 clip_y: bool = false,
231 children: []const Node = &.{},
232 };
233
234 pub const LayoutResult = struct {
235 id: usize,
236 rect: Rect,
237 children: []LayoutResult,
238 };