diff --git a/src/layout/Rectangle.zig b/src/layout/Rectangle.zig index 58f9414..1651ccb 100644 --- a/src/layout/Rectangle.zig +++ b/src/layout/Rectangle.zig @@ -23,3 +23,23 @@ pub inline fn contains(self: Rectangle, pos: Position) bool { pos.x <= self.pos.x + self.size.width and pos.y <= self.pos.y + self.size.height; } + +pub fn intersection(self: Rectangle, other: Rectangle) ?Rectangle { + const pos1 = Position{ + .x = @max(self.pos.x, other.pos.x), + .y = @max(self.pos.y, other.pos.y), + }; + + const pos2 = Position{ + .x = @min(self.pos.x + self.size.width, other.pos.x + other.size.width), + .y = @min(self.pos.y + self.size.height, other.pos.y + other.size.height), + }; + + if (pos1.x < pos2.x and pos1.y < pos2.y) { + return .{ + .pos = pos1, + .size = pos2.sub(pos1).size(), + }; + } + return null; +} diff --git a/src/layout/Size.zig b/src/layout/Size.zig index 4003930..d904940 100644 --- a/src/layout/Size.zig +++ b/src/layout/Size.zig @@ -38,3 +38,11 @@ pub inline fn sub(self: Size, other: Size) Size { pub inline fn position(self: Size) Position { return .{ .x = self.width, .y = self.height }; } + +/// Scales this size by a fraction given as a numerator and denominator. +pub inline fn scaleFrac(self: Size, numerator: u31, denominator: u31) Size { + return .{ + .width = self.width * numerator / denominator, + .height = self.height * numerator / denominator, + }; +} diff --git a/src/painter.zig b/src/painter.zig index 1c62f8f..604aba8 100644 --- a/src/painter.zig +++ b/src/painter.zig @@ -38,7 +38,7 @@ fn Prototype(comptime Self: type) type { .ptr, "rect", anyerror!void, - .{ rectangle, color }, + .{ self, rectangle, color }, ); } @@ -63,7 +63,7 @@ fn Prototype(comptime Self: type) type { .self, "strokeRect", anyerror!void, - .{ rectangle, line_width, stroke_color, fill_color }, + .{ self, rectangle, line_width, stroke_color, fill_color }, )) |ret| try ret else { // TODO: draw as 2 rects if fill_color is set const ud_size = Size{ @@ -135,7 +135,7 @@ fn Prototype(comptime Self: type) type { .ptr, "texturedRect", anyerror!void, - .{ src, dest, texture }, + .{ self, src, dest, texture }, ); } @@ -182,7 +182,13 @@ fn Prototype(comptime Self: type) type { Color.fromInt(0x00ff00ff), ); } - return statspatch.implcall(self, .ptr, "span", anyerror!void, .{ pos, text_span }); + return statspatch.implcall( + self, + .ptr, + "span", + anyerror!void, + .{ self, pos, text_span }, + ); } /// Draw the given chunk of text at the given position. @@ -212,7 +218,7 @@ fn Prototype(comptime Self: type) type { .ptr, "chunk", anyerror!void, - .{ pos, text_chunk }, + .{ self, pos, text_chunk }, )) |ret| try ret else { for (text_chunk.spans.items) |ss| { try self.span(pos.add(ss.position), ss.span); @@ -222,4 +228,39 @@ fn Prototype(comptime Self: type) type { }; } -pub const Painter = statspatch.StatspatchType(Prototype, void, &zenolith.painter_impls); +pub const PainterData = struct { + /// A stack of stencils to apply to the rendered shapes. This is used to render partial widgets. + /// The topmost stencil should be applied by the painter, if present. + sstack: std.ArrayList(Rectangle), + + /// Create a new PainterData. Caller must call deinit. + pub fn init(alloc: std.mem.Allocator) PainterData { + return .{ .sstack = std.ArrayList(Rectangle).init(alloc) }; + } + + pub fn deinit(self: *PainterData) void { + self.sstack.deinit(); + } + + /// Pushes a rectangular stencil onto the stencil stack. This would typically be called by a + /// interested in drawing partial children in the Draw treevent handler. + pub fn pushStencil(self: *PainterData, rect: Rectangle) !void { + try self.sstack.append(rect); + } + + /// Removes the topmost stencil from the stencil stack. The caller asserts that the stencil + /// stack is not empty. + pub fn popStencil(self: *PainterData) void { + _ = self.sstack.pop(); + } + + /// Returns the topmost stencil of the stencil stack or null if it is empty. + /// Painter implementations should call this when drawing shapes and perform clipping. + pub fn peekStencil(self: PainterData) ?Rectangle { + if (self.sstack.items.len == 0) + return null; + return self.sstack.items[self.sstack.items.len - 1]; + } +}; + +pub const Painter = statspatch.StatspatchType(Prototype, PainterData, &zenolith.painter_impls);