lib/gui/src/paint/regions.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const paint = @import("root.zig");
2
3 const Bounds = paint.cpu.Bounds;
4 const Rect = @import("../root.zig").layout.Rect;
5 const Region = paint.cpu.Region;
6
7 pub const capacity: usize = 8;
8
9 pub const Set = struct {
10 items: [capacity]Region = undefined,
11 len: usize = 0,
12
13 pub fn clear(self: *Set) void {
14 self.len = 0;
15 }
16
17 pub fn append(self: *Set, region: Region) void {
18 if (self.len < self.items.len) {
19 self.items[self.len] = region;
20 self.len += 1;
21 return;
22 }
23 const collapsed = unite(self.bounding().?, region);
24 self.items[0] = collapsed;
25 self.len = 1;
26 }
27
28 pub fn slice(self: *const Set) []const Region {
29 return self.items[0..self.len];
30 }
31
32 pub fn pixelCount(self: Set) usize {
33 var count: usize = 0;
34 for (self.items[0..self.len]) |region| {
35 count +|= region.pixelCount();
36 }
37 return count;
38 }
39
40 pub fn bounding(self: Set) ?Region {
41 if (self.len == 0) return null;
42 var result = self.items[0];
43 for (self.items[1..self.len]) |region| {
44 result = unite(result, region);
45 }
46 return result;
47 }
48 };
49
50 pub fn fromBounds(
51 bounds: Bounds,
52 margin: u32,
53 width: u32,
54 height: u32,
55 ) Region {
56 const x0 = @min(bounds.x0 -| margin, width);
57 const y0 = @min(bounds.y0 -| margin, height);
58 const x1 = @min(bounds.x1 +| margin, width);
59 const y1 = @min(bounds.y1 +| margin, height);
60 if (x0 >= x1 or y0 >= y1) return .{ .x = x0, .y = y0 };
61 return .{
62 .x = x0,
63 .y = y0,
64 .width = x1 - x0,
65 .height = y1 - y0,
66 };
67 }
68
69 pub fn fromRect(
70 rect: Rect,
71 margin: u32,
72 width: u32,
73 height: u32,
74 ) ?Region {
75 if (rect.width <= 0 or rect.height <= 0) return null;
76 const geometry = paint.cpu.geometry;
77 const x0 = geometry.floorClamp(rect.x, width);
78 const y0 = geometry.floorClamp(rect.y, height);
79 const x1 = geometry.ceilClamp(rect.x + rect.width, width);
80 const y1 = geometry.ceilClamp(rect.y + rect.height, height);
81 if (x0 >= x1 or y0 >= y1) return null;
82 return fromBounds(.{ .x0 = x0, .y0 = y0, .x1 = x1, .y1 = y1 }, margin, width, height);
83 }
84
85 pub fn unite(left: Region, right: Region) Region {
86 const x0 = @min(left.x, right.x);
87 const y0 = @min(left.y, right.y);
88 const x1 = @max(left.x +| left.width, right.x +| right.width);
89 const y1 = @max(left.y +| left.height, right.y +| right.height);
90 return .{
91 .x = x0,
92 .y = y0,
93 .width = x1 - x0,
94 .height = y1 - y0,
95 };
96 }
97
98 pub fn intersect(left: Region, right: Region) Region {
99 const x0 = @max(left.x, right.x);
100 const y0 = @max(left.y, right.y);
101 const x1 = @min(left.x +| left.width, right.x +| right.width);
102 const y1 = @min(left.y +| left.height, right.y +| right.height);
103 if (x0 >= x1 or y0 >= y1) return .{ .x = x0, .y = y0 };
104 return .{
105 .x = x0,
106 .y = y0,
107 .width = x1 - x0,
108 .height = y1 - y0,
109 };
110 }
111
112 test "region set collapses overflow without losing damage" {
113 var regions = Set{};
114 for (0..capacity + 1) |index| {
115 regions.append(.{
116 .x = @intCast(index * 2),
117 .width = 1,
118 .height = 1,
119 });
120 }
121
122 try @import("std").testing.expectEqual(@as(usize, 1), regions.len);
123 try @import("std").testing.expectEqual(
124 Region{ .x = 0, .width = capacity * 2 + 1, .height = 1 },
125 regions.bounding().?,
126 );
127 }
128
129 test "bounds outside the target collapse to an empty edge region" {
130 const std = @import("std");
131 try std.testing.expectEqual(
132 Region{ .x = 16, .y = 4 },
133 fromBounds(
134 .{ .x0 = 20, .y0 = 4, .x1 = 30, .y1 = 8 },
135 0,
136 16,
137 16,
138 ),
139 );
140 try std.testing.expectEqual(
141 Region{ .x = 12, .y = 4 },
142 fromBounds(
143 .{ .x0 = 12, .y0 = 4, .x1 = 8, .y1 = 8 },
144 0,
145 16,
146 16,
147 ),
148 );
149 }