a modern tui library written in zig
at v0.2.1 6.1 kB view raw
1const std = @import("std"); 2const vaxis = @import("../main.zig"); 3const grapheme = @import("grapheme"); 4const DisplayWidth = @import("DisplayWidth"); 5const ScrollView = vaxis.widgets.ScrollView; 6 7pub const BufferWriter = struct { 8 pub const Error = error{OutOfMemory}; 9 pub const Writer = std.io.GenericWriter(@This(), Error, write); 10 11 allocator: std.mem.Allocator, 12 buffer: *Buffer, 13 gd: *const grapheme.GraphemeData, 14 wd: *const DisplayWidth.DisplayWidthData, 15 16 pub fn write(self: @This(), bytes: []const u8) Error!usize { 17 try self.buffer.append(self.allocator, .{ 18 .bytes = bytes, 19 .gd = self.gd, 20 .wd = self.wd, 21 }); 22 return bytes.len; 23 } 24 25 pub fn writer(self: @This()) Writer { 26 return .{ .context = self }; 27 } 28}; 29 30pub const Buffer = struct { 31 const StyleList = std.ArrayListUnmanaged(vaxis.Style); 32 const StyleMap = std.HashMapUnmanaged(usize, usize, std.hash_map.AutoContext(usize), std.hash_map.default_max_load_percentage); 33 34 pub const Content = struct { 35 bytes: []const u8, 36 gd: *const grapheme.GraphemeData, 37 wd: *const DisplayWidth.DisplayWidthData, 38 }; 39 40 pub const Style = struct { 41 begin: usize, 42 end: usize, 43 style: vaxis.Style, 44 }; 45 46 pub const Error = error{OutOfMemory}; 47 48 grapheme: std.MultiArrayList(grapheme.Grapheme) = .{}, 49 content: std.ArrayListUnmanaged(u8) = .{}, 50 style_list: StyleList = .{}, 51 style_map: StyleMap = .{}, 52 rows: usize = 0, 53 cols: usize = 0, 54 // used when appending to a buffer 55 last_cols: usize = 0, 56 57 pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { 58 self.style_map.deinit(allocator); 59 self.style_list.deinit(allocator); 60 self.grapheme.deinit(allocator); 61 self.content.deinit(allocator); 62 self.* = undefined; 63 } 64 65 /// Clears all buffer data. 66 pub fn clear(self: *@This(), allocator: std.mem.Allocator) void { 67 self.deinit(allocator); 68 self.* = .{}; 69 } 70 71 /// Replaces contents of the buffer, all previous buffer data is lost. 72 pub fn update(self: *@This(), allocator: std.mem.Allocator, content: Content) Error!void { 73 self.clear(allocator); 74 errdefer self.clear(allocator); 75 try self.append(allocator, content); 76 } 77 78 /// Appends content to the buffer. 79 pub fn append(self: *@This(), allocator: std.mem.Allocator, content: Content) Error!void { 80 var cols: usize = self.last_cols; 81 var iter = grapheme.Iterator.init(content.bytes, content.gd); 82 const dw: DisplayWidth = .{ .data = content.wd }; 83 while (iter.next()) |g| { 84 try self.grapheme.append(allocator, .{ 85 .len = g.len, 86 .offset = @as(u32, @intCast(self.content.items.len)) + g.offset, 87 }); 88 const cluster = g.bytes(content.bytes); 89 if (std.mem.eql(u8, cluster, "\n")) { 90 self.cols = @max(self.cols, cols); 91 cols = 0; 92 continue; 93 } 94 cols +|= dw.strWidth(cluster); 95 } 96 try self.content.appendSlice(allocator, content.bytes); 97 self.last_cols = cols; 98 self.cols = @max(self.cols, cols); 99 self.rows +|= std.mem.count(u8, content.bytes, "\n"); 100 } 101 102 /// Clears all styling data. 103 pub fn clearStyle(self: *@This(), allocator: std.mem.Allocator) void { 104 self.style_list.deinit(allocator); 105 self.style_map.deinit(allocator); 106 } 107 108 /// Update style for range of the buffer contents. 109 pub fn updateStyle(self: *@This(), allocator: std.mem.Allocator, style: Style) Error!void { 110 const style_index = blk: { 111 for (self.style_list.items, 0..) |s, i| { 112 if (std.meta.eql(s, style.style)) { 113 break :blk i; 114 } 115 } 116 try self.style_list.append(allocator, style.style); 117 break :blk self.style_list.items.len - 1; 118 }; 119 for (style.begin..style.end) |i| { 120 try self.style_map.put(allocator, i, style_index); 121 } 122 } 123 124 pub fn writer( 125 self: *@This(), 126 allocator: std.mem.Allocator, 127 gd: *const grapheme.GraphemeData, 128 wd: *const DisplayWidth.DisplayWidthData, 129 ) BufferWriter.Writer { 130 return .{ 131 .context = .{ 132 .allocator = allocator, 133 .buffer = self, 134 .gd = gd, 135 .wd = wd, 136 }, 137 }; 138 } 139}; 140 141scroll_view: ScrollView = .{}, 142 143pub fn input(self: *@This(), key: vaxis.Key) void { 144 self.scroll_view.input(key); 145} 146 147pub fn draw(self: *@This(), win: vaxis.Window, buffer: Buffer) void { 148 self.scroll_view.draw(win, .{ .cols = buffer.cols, .rows = buffer.rows }); 149 const Pos = struct { x: usize = 0, y: usize = 0 }; 150 var pos: Pos = .{}; 151 var byte_index: usize = 0; 152 const bounds = self.scroll_view.bounds(win); 153 for (buffer.grapheme.items(.len), buffer.grapheme.items(.offset), 0..) |g_len, g_offset, index| { 154 if (bounds.above(pos.y)) { 155 break; 156 } 157 158 const cluster = buffer.content.items[g_offset..][0..g_len]; 159 defer byte_index += cluster.len; 160 161 if (std.mem.eql(u8, cluster, "\n")) { 162 if (index == buffer.grapheme.len - 1) { 163 break; 164 } 165 pos.y +|= 1; 166 pos.x = 0; 167 continue; 168 } else if (bounds.below(pos.y)) { 169 continue; 170 } 171 172 const width = win.gwidth(cluster); 173 defer pos.x +|= width; 174 175 if (!bounds.colInside(pos.x)) { 176 continue; 177 } 178 179 const style: vaxis.Style = blk: { 180 if (buffer.style_map.get(byte_index)) |style_index| { 181 break :blk buffer.style_list.items[style_index]; 182 } 183 break :blk .{}; 184 }; 185 186 self.scroll_view.writeCell(win, pos.x, pos.y, .{ 187 .char = .{ .grapheme = cluster, .width = width }, 188 .style = style, 189 }); 190 } 191}