a modern tui library written in zig
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(.internal_screen);
9
10const InternalScreen = @This();
11
12pub const InternalCell = struct {
13 char: std.ArrayList(u8) = undefined,
14 style: Style = .{},
15 uri: std.ArrayList(u8) = undefined,
16 uri_id: std.ArrayList(u8) = undefined,
17 // if we got skipped because of a wide character
18 skipped: bool = false,
19 default: bool = true,
20
21 pub fn eql(self: InternalCell, cell: Cell) bool {
22 // fastpath when both cells are default
23 if (self.default and cell.default) return true;
24 // this is actually faster than std.meta.eql on the individual items.
25 // Our strings are always small, usually less than 4 bytes so the simd
26 // usage in std.mem.eql has too much overhead vs looping the bytes
27 if (!std.mem.eql(u8, self.char.items, cell.char.grapheme)) return false;
28 if (!Style.eql(self.style, cell.style)) return false;
29 if (!std.mem.eql(u8, self.uri.items, cell.link.uri)) return false;
30 if (!std.mem.eql(u8, self.uri_id.items, cell.link.params)) return false;
31 return true;
32 }
33};
34
35width: usize = 0,
36height: usize = 0,
37
38buf: []InternalCell = undefined,
39
40cursor_row: usize = 0,
41cursor_col: usize = 0,
42cursor_vis: bool = false,
43cursor_shape: CursorShape = .default,
44
45mouse_shape: MouseShape = .default,
46
47/// sets each cell to the default cell
48pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !InternalScreen {
49 var screen = InternalScreen{
50 .buf = try alloc.alloc(InternalCell, w * h),
51 };
52 for (screen.buf, 0..) |_, i| {
53 screen.buf[i] = .{
54 .char = try std.ArrayList(u8).initCapacity(alloc, 1),
55 .uri = std.ArrayList(u8).init(alloc),
56 .uri_id = std.ArrayList(u8).init(alloc),
57 };
58 try screen.buf[i].char.append(' ');
59 }
60 screen.width = w;
61 screen.height = h;
62 return screen;
63}
64
65pub fn deinit(self: *InternalScreen, alloc: std.mem.Allocator) void {
66 for (self.buf, 0..) |_, i| {
67 self.buf[i].char.deinit();
68 self.buf[i].uri.deinit();
69 self.buf[i].uri_id.deinit();
70 }
71
72 alloc.free(self.buf);
73}
74
75/// writes a cell to a location. 0 indexed
76pub fn writeCell(
77 self: *InternalScreen,
78 col: usize,
79 row: usize,
80 cell: Cell,
81) void {
82 if (self.width < col) {
83 // column out of bounds
84 return;
85 }
86 if (self.height < row) {
87 // height out of bounds
88 return;
89 }
90 const i = (row * self.width) + col;
91 assert(i < self.buf.len);
92 self.buf[i].char.clearRetainingCapacity();
93 self.buf[i].char.appendSlice(cell.char.grapheme) catch {
94 log.warn("couldn't write grapheme", .{});
95 };
96 self.buf[i].uri.clearRetainingCapacity();
97 self.buf[i].uri.appendSlice(cell.link.uri) catch {
98 log.warn("couldn't write uri", .{});
99 };
100 self.buf[i].uri_id.clearRetainingCapacity();
101 self.buf[i].uri_id.appendSlice(cell.link.params) catch {
102 log.warn("couldn't write uri_id", .{});
103 };
104 self.buf[i].style = cell.style;
105 self.buf[i].default = cell.default;
106}
107
108pub fn readCell(self: *InternalScreen, col: usize, row: usize) ?Cell {
109 if (self.width < col) {
110 // column out of bounds
111 return null;
112 }
113 if (self.height < row) {
114 // height out of bounds
115 return null;
116 }
117 const i = (row * self.width) + col;
118 assert(i < self.buf.len);
119 return .{
120 .char = .{ .grapheme = self.buf[i].char.items },
121 .style = self.buf[i].style,
122 };
123}