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 }