a modern tui library written in zig
at main 4.5 kB view raw
1const std = @import("std"); 2const assert = std.debug.assert; 3const Style = @import("Cell.zig").Style; 4const Cell = @import("Cell.zig"); 5const MouseShape = @import("Mouse.zig").Shape; 6const CursorShape = Cell.CursorShape; 7 8const log = std.log.scoped(.vaxis); 9 10const InternalScreen = @This(); 11 12pub const InternalCell = struct { 13 char: std.ArrayListUnmanaged(u8) = .empty, 14 style: Style = .{}, 15 uri: std.ArrayListUnmanaged(u8) = .empty, 16 uri_id: std.ArrayListUnmanaged(u8) = .empty, 17 // if we got skipped because of a wide character 18 skipped: bool = false, 19 default: bool = true, 20 21 // If we should skip rendering *this* round due to being printed over previously (from a scaled 22 // cell, for example) 23 skip: bool = false, 24 25 scale: Cell.Scale = .{}, 26 27 pub fn eql(self: InternalCell, cell: Cell) bool { 28 29 // fastpath when both cells are default 30 if (self.default and cell.default) return true; 31 32 return std.mem.eql(u8, self.char.items, cell.char.grapheme) and 33 Style.eql(self.style, cell.style) and 34 std.mem.eql(u8, self.uri.items, cell.link.uri) and 35 std.mem.eql(u8, self.uri_id.items, cell.link.params); 36 } 37}; 38 39arena: *std.heap.ArenaAllocator, 40width: u16 = 0, 41height: u16 = 0, 42 43buf: []InternalCell, 44 45cursor_row: u16 = 0, 46cursor_col: u16 = 0, 47cursor_vis: bool = false, 48cursor_shape: CursorShape = .default, 49 50mouse_shape: MouseShape = .default, 51 52/// sets each cell to the default cell 53pub fn init(alloc: std.mem.Allocator, w: u16, h: u16) !InternalScreen { 54 const arena = try alloc.create(std.heap.ArenaAllocator); 55 arena.* = .init(alloc); 56 var screen = InternalScreen{ 57 .arena = arena, 58 .buf = try arena.allocator().alloc(InternalCell, @as(usize, @intCast(w)) * h), 59 }; 60 for (screen.buf, 0..) |_, i| { 61 screen.buf[i] = .{ 62 .char = try std.ArrayListUnmanaged(u8).initCapacity(arena.allocator(), 1), 63 .uri = .empty, 64 .uri_id = .empty, 65 }; 66 screen.buf[i].char.appendAssumeCapacity(' '); 67 } 68 screen.width = w; 69 screen.height = h; 70 return screen; 71} 72 73pub fn deinit(self: *InternalScreen, alloc: std.mem.Allocator) void { 74 self.arena.deinit(); 75 alloc.destroy(self.arena); 76 self.* = undefined; 77} 78 79/// writes a cell to a location. 0 indexed 80pub fn writeCell( 81 self: *InternalScreen, 82 col: u16, 83 row: u16, 84 cell: Cell, 85) void { 86 if (self.width <= col) { 87 // column out of bounds 88 return; 89 } 90 if (self.height <= row) { 91 // height out of bounds 92 return; 93 } 94 const i = (@as(usize, @intCast(row)) * self.width) + col; 95 assert(i < self.buf.len); 96 self.buf[i].char.clearRetainingCapacity(); 97 self.buf[i].char.appendSlice(self.arena.allocator(), cell.char.grapheme) catch { 98 log.warn("couldn't write grapheme", .{}); 99 }; 100 self.buf[i].uri.clearRetainingCapacity(); 101 self.buf[i].uri.appendSlice(self.arena.allocator(), cell.link.uri) catch { 102 log.warn("couldn't write uri", .{}); 103 }; 104 self.buf[i].uri_id.clearRetainingCapacity(); 105 self.buf[i].uri_id.appendSlice(self.arena.allocator(), cell.link.params) catch { 106 log.warn("couldn't write uri_id", .{}); 107 }; 108 self.buf[i].style = cell.style; 109 self.buf[i].default = cell.default; 110} 111 112pub fn readCell(self: *InternalScreen, col: u16, row: u16) ?Cell { 113 if (self.width <= col) { 114 // column out of bounds 115 return null; 116 } 117 if (self.height <= row) { 118 // height out of bounds 119 return null; 120 } 121 const i = (row * self.width) + col; 122 assert(i < self.buf.len); 123 const cell = self.buf[i]; 124 return .{ 125 .char = .{ .grapheme = cell.char.items }, 126 .style = cell.style, 127 .link = .{ 128 .uri = cell.uri.items, 129 .params = cell.uri_id.items, 130 }, 131 .default = cell.default, 132 }; 133} 134 135test "InternalScreen: out-of-bounds read/write are ignored" { 136 var screen = try InternalScreen.init(std.testing.allocator, 2, 2); 137 defer screen.deinit(std.testing.allocator); 138 139 const sentinel: Cell = .{ .char = .{ .grapheme = "A", .width = 1 } }; 140 screen.writeCell(0, 1, sentinel); 141 142 const oob_cell: Cell = .{ .char = .{ .grapheme = "X", .width = 1 } }; 143 screen.writeCell(2, 0, oob_cell); 144 const read_back = screen.readCell(0, 1) orelse return error.TestUnexpectedResult; 145 try std.testing.expect(std.mem.eql(u8, read_back.char.grapheme, "A")); 146 try std.testing.expect(screen.readCell(2, 0) == null); 147}