//! A `RenderBuffer` usable as a screen, plus the width method used to size //! grapheme clusters. Mirrors uv `ScreenBuffer`: the render buffer's grid ops //! (clear/fill/clone/…) are forwarded so `screen` helpers take the fast path. //! //! The embedded `screen` field implements [`screen.Screen`]: its vtable points //! at the `impl` thunks below, which recover the `ScreenBuffer` from the //! embedded interface with `@fieldParentPtr`. The public methods mirror that //! surface on the buffer itself (`bounds`/`cellAt`/`setCell`/…), forwarding to //! the render buffer. const ir = @import("root.zig"); const std = @import("std"); const scr = @import("screen.zig"); const RenderBuffer = ir.RenderBuffer; const Buffer = ir.Buffer; const Cell = ir.Cell; const Rectangle = ir.Rectangle; const Method = ir.width.Method; const Allocator = std.mem.Allocator; const t = std.testing; const Screen = @This(); render_buffer: RenderBuffer, method: Method = .wcwidth, screen: scr.Screen = .{ .vtable = &.{ .bounds = vtableBounds, .cell_at = vtableCellAt, .set_cell = vtableSetCell, .width_method = impl.widthMethod, .clear = impl.clear, .clear_area = impl.clearArea, .fill = impl.fill, .fill_area = impl.fillArea, .clone = impl.clone, .clone_area = impl.cloneArea, }, }, fn vtableBounds(s: *scr.Screen) Rectangle { const buffer: *Screen = @fieldParentPtr("screen", s); return buffer.render_buffer.bounds(); } fn vtableCellAt(s: *scr.Screen, x: usize, y: usize) ?*Cell { const buffer: *Screen = @fieldParentPtr("screen", s); return buffer.render_buffer.cellAt(x, y); } fn vtableSetCell(s: *scr.Screen, x: usize, y: usize, cell: ?Cell) void { const buffer: *Screen = @fieldParentPtr("screen", s); return buffer.render_buffer.setCell(x, y, cell); } const impl = struct { fn widthMethod(s: *scr.Screen) Method { const buffer: *Screen = @fieldParentPtr("screen", s); return buffer.method; } fn clear(s: *scr.Screen) anyerror!void { const buffer: *Screen = @fieldParentPtr("screen", s); buffer.render_buffer.clear(); } fn clearArea(s: *scr.Screen, area: Rectangle) anyerror!void { const buffer: *Screen = @fieldParentPtr("screen", s); buffer.render_buffer.clearArea(area); } fn fill(s: *scr.Screen, cell: ?Cell) anyerror!void { const buffer: *Screen = @fieldParentPtr("screen", s); buffer.render_buffer.fill(cell); } fn fillArea(s: *scr.Screen, cell: ?Cell, area: Rectangle) anyerror!void { const buffer: *Screen = @fieldParentPtr("screen", s); buffer.render_buffer.fillArea(cell, area); } fn clone(s: *scr.Screen, _: Allocator) anyerror!Buffer { const buffer: *Screen = @fieldParentPtr("screen", s); return buffer.render_buffer.buffer.clone(); } fn cloneArea(s: *scr.Screen, _: Allocator, area: Rectangle) anyerror!?Buffer { const buffer: *Screen = @fieldParentPtr("screen", s); return buffer.render_buffer.buffer.cloneArea(area); } }; pub fn init(allocator: Allocator, w: usize, h: usize) !Screen { return .{ .render_buffer = try .init(allocator, w, h) }; } pub fn deinit(self: *Screen) void { self.render_buffer.deinit(); } pub fn bounds(self: *Screen) Rectangle { return self.render_buffer.bounds(); } pub fn cellAt(self: *Screen, x: usize, y: usize) ?*Cell { return self.render_buffer.cellAt(x, y); } pub fn setCell(self: *Screen, x: usize, y: usize, cell: ?Cell) void { self.render_buffer.setCell(x, y, cell); } pub fn widthMethod(self: *Screen) Method { return self.method; } pub fn clear(self: *Screen) void { self.render_buffer.clear(); } pub fn clearArea(self: *Screen, area: Rectangle) void { self.render_buffer.clearArea(area); } pub fn fill(self: *Screen, cell: ?Cell) void { self.render_buffer.fill(cell); } pub fn fillArea(self: *Screen, cell: ?Cell, area: Rectangle) void { self.render_buffer.fillArea(cell, area); } pub fn clone(self: *Screen) !Buffer { return self.render_buffer.buffer.clone(); } pub fn cloneArea(self: *Screen, area: Rectangle) !?Buffer { return self.render_buffer.buffer.cloneArea(area); } test "screen buffer defaults" { var buffer: Screen = try .init(t.allocator, 10, 5); defer buffer.deinit(); try t.expect(buffer.render_buffer.width() == 10); try t.expect(buffer.render_buffer.height() == 5); try t.expect(buffer.method == .wcwidth); try t.expect(buffer.bounds().width() == 10); try t.expect(buffer.bounds().height() == 5); try t.expectEqual(.wcwidth, buffer.widthMethod()); } test "screen passthrough ops participate in drawing" { var buffer: Screen = try .init(t.allocator, 6, 4); defer buffer.deinit(); buffer.fill(.of("x", 1)); try t.expectEqualStrings("x", buffer.cellAt(3, 2).?.content); const clipped: Rectangle = .new(1, 1, 2, 2); buffer.clearArea(clipped); try t.expect(buffer.cellAt(1, 1).?.equal(.space)); try t.expect(buffer.cellAt(2, 2).?.equal(.space)); try t.expectEqualStrings("x", buffer.cellAt(3, 2).?.content); buffer.fillArea(.of("f", 1), clipped); try t.expectEqualStrings("f", buffer.cellAt(2, 2).?.content); var copy: Buffer = try buffer.clone(); defer copy.deinit(); try t.expectEqualStrings("f", copy.cellAt(2, 2).?.content); var sub = (try buffer.cloneArea(.new(1, 1, 2, 2))).?; defer sub.deinit(); try t.expectEqualStrings("f", sub.cellAt(1, 1).?.content); buffer.clear(); try t.expect(buffer.cellAt(0, 0).?.equal(.space)); }