this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

render: use different internal model of screen

We use two screens: one which the user provides a slice of bytes for the
graphemes, and the user owns the bytes. We copy those bytes to our
internal model so that we can compare between frames

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>

+179 -35
+3 -2
examples/text_input.zig
··· 31 31 // We'll adjust the color index every keypress for the border 32 32 var color_idx: u8 = 0; 33 33 34 - var text_input: TextInput = .{}; 34 + var text_input = TextInput.init(alloc); 35 + defer text_input.deinit(); 35 36 36 37 // The main event loop. Vaxis provides a thread safe, blocking, buffered 37 38 // queue which can serve as the primary event queue for an application ··· 47 48 255 => 0, 48 49 else => color_idx + 1, 49 50 }; 50 - text_input.update(.{ .key_press = key }); 51 + try text_input.update(.{ .key_press = key }); 51 52 if (key.codepoint == 'c' and key.mods.ctrl) { 52 53 break :outer; 53 54 }
+73
src/InternalScreen.zig
··· 1 + const std = @import("std"); 2 + const assert = std.debug.assert; 3 + const Style = @import("cell.zig").Style; 4 + const Cell = @import("cell.zig").Cell; 5 + 6 + const log = std.log.scoped(.internal_screen); 7 + 8 + const InternalScreen = @This(); 9 + 10 + pub const InternalCell = struct { 11 + char: std.ArrayList(u8) = undefined, 12 + style: Style = .{}, 13 + 14 + pub fn eql(self: InternalCell, cell: Cell) bool { 15 + return std.mem.eql(u8, self.char.items, cell.char.grapheme) and std.meta.eql(self.style, cell.style); 16 + } 17 + }; 18 + 19 + width: usize = 0, 20 + height: usize = 0, 21 + 22 + buf: []InternalCell = undefined, 23 + 24 + cursor_row: usize = 0, 25 + cursor_col: usize = 0, 26 + cursor_vis: bool = false, 27 + 28 + /// sets each cell to the default cell 29 + pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !InternalScreen { 30 + var screen = InternalScreen{}; 31 + screen.buf = try alloc.alloc(InternalCell, w * h); 32 + for (screen.buf, 0..) |_, i| { 33 + screen.buf[i] = .{ 34 + .char = try std.ArrayList(u8).initCapacity(alloc, 1), 35 + }; 36 + } 37 + screen.width = w; 38 + screen.height = h; 39 + return screen; 40 + } 41 + 42 + pub fn deinit(self: *InternalScreen, alloc: std.mem.Allocator) void { 43 + for (self.buf, 0..) |_, i| { 44 + self.buf[i].char.deinit(); 45 + } 46 + 47 + alloc.free(self.buf); 48 + } 49 + 50 + /// writes a cell to a location. 0 indexed 51 + pub fn writeCell( 52 + self: *InternalScreen, 53 + col: usize, 54 + row: usize, 55 + char: []const u8, 56 + style: Style, 57 + ) void { 58 + if (self.width < col) { 59 + // column out of bounds 60 + return; 61 + } 62 + if (self.height < row) { 63 + // height out of bounds 64 + return; 65 + } 66 + const i = (row * self.width) + col; 67 + assert(i < self.buf.len); 68 + self.buf[i].char.clearRetainingCapacity(); 69 + self.buf[i].char.appendSlice(char) catch { 70 + log.warn("couldn't write grapheme", .{}); 71 + }; 72 + self.buf[i].style = style; 73 + }
+7 -10
src/Screen.zig
··· 16 16 cursor_col: usize = 0, 17 17 cursor_vis: bool = false, 18 18 19 - /// sets each cell to the default cell 20 - pub fn init(self: *Screen) void { 19 + pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen { 20 + var self = Screen{ 21 + .buf = try alloc.alloc(Cell, w * h), 22 + .width = w, 23 + .height = h, 24 + }; 21 25 for (self.buf, 0..) |_, i| { 22 26 self.buf[i] = .{}; 23 27 } 28 + return self; 24 29 } 25 - 26 30 pub fn deinit(self: *Screen, alloc: std.mem.Allocator) void { 27 31 alloc.free(self.buf); 28 - } 29 - 30 - pub fn resize(self: *Screen, alloc: std.mem.Allocator, w: usize, h: usize) !void { 31 - alloc.free(self.buf); 32 - self.buf = try alloc.alloc(Cell, w * h); 33 - self.width = w; 34 - self.height = h; 35 32 } 36 33 37 34 /// writes a cell to a location. 0 indexed
+10 -6
src/vaxis.zig
··· 6 6 const Winsize = Tty.Winsize; 7 7 const Key = @import("Key.zig"); 8 8 const Screen = @import("Screen.zig"); 9 + const InternalScreen = @import("InternalScreen.zig"); 9 10 const Window = @import("Window.zig"); 10 11 const Options = @import("Options.zig"); 11 12 const Style = @import("cell.zig").Style; ··· 38 39 screen: Screen, 39 40 // The last screen we drew. We keep this so we can efficiently update on 40 41 // the next render 41 - screen_last: Screen, 42 + screen_last: InternalScreen = undefined, 42 43 43 44 alt_screen: bool, 44 45 ··· 113 114 /// freed when resizing 114 115 pub fn resize(self: *Self, alloc: std.mem.Allocator, winsize: Winsize) !void { 115 116 log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows }); 116 - try self.screen.resize(alloc, winsize.cols, winsize.rows); 117 + self.screen.deinit(alloc); 118 + self.screen = try Screen.init(alloc, winsize.cols, winsize.rows); 119 + // try self.screen.int(alloc, winsize.cols, winsize.rows); 117 120 // we only init our current screen. This has the effect of redrawing 118 121 // every cell 119 - self.screen.init(); 120 - try self.screen_last.resize(alloc, winsize.cols, winsize.rows); 122 + self.screen_last.deinit(alloc); 123 + self.screen_last = try InternalScreen.init(alloc, winsize.cols, winsize.rows); 124 + // try self.screen_last.resize(alloc, winsize.cols, winsize.rows); 121 125 } 122 126 123 127 /// returns a Window comprising of the entire terminal screen ··· 214 218 } 215 219 // If cell is the same as our last frame, we don't need to do 216 220 // anything 217 - if (std.meta.eql(cell, self.screen_last.buf[i])) { 221 + if (self.screen_last.buf[i].eql(cell)) { 218 222 reposition = true; 219 223 // Close any osc8 sequence we might be in before 220 224 // repositioning ··· 225 229 } 226 230 defer cursor = cell.style; 227 231 // Set this cell in the last frame 228 - self.screen_last.buf[i] = cell; 232 + self.screen_last.writeCell(col, row, cell.char.grapheme, cell.style); 229 233 230 234 // reposition the cursor, if needed 231 235 if (reposition) {
+86 -17
src/widgets/TextInput.zig
··· 16 16 17 17 // Index of our cursor 18 18 cursor_idx: usize = 0, 19 + grapheme_count: usize = 0, 19 20 20 - // the actual line of input 21 - buffer: [4096]u8 = undefined, 22 - buffer_idx: usize = 0, 21 + buf: std.ArrayList(u8), 22 + 23 + pub fn init(alloc: std.mem.Allocator) TextInput { 24 + return TextInput{ 25 + .buf = std.ArrayList(u8).init(alloc), 26 + }; 27 + } 23 28 24 - pub fn update(self: *TextInput, event: Event) void { 29 + pub fn deinit(self: *TextInput) void { 30 + self.buf.deinit(); 31 + } 32 + 33 + pub fn update(self: *TextInput, event: Event) !void { 25 34 switch (event) { 26 35 .key_press => |key| { 27 36 if (key.text) |text| { 28 - @memcpy(self.buffer[self.buffer_idx .. self.buffer_idx + text.len], text); 29 - self.buffer_idx += text.len; 30 - self.cursor_idx += strWidth(text, .full) catch 1; 37 + try self.buf.insertSlice(self.byteOffsetToCursor(), text); 38 + self.cursor_idx += 1; 39 + self.grapheme_count += 1; 31 40 } 32 41 switch (key.codepoint) { 33 42 Key.backspace => { 34 - // TODO: this only works at the end of the array. Then 35 - // again, we don't have any means to move the cursor yet 36 - // This also doesn't work with graphemes yet 37 - if (self.buffer_idx == 0) return; 38 - self.buffer_idx -= 1; 39 - self.cursor_idx -= 1; 43 + if (self.cursor_idx == 0) return; 44 + // Get the grapheme behind our cursor 45 + self.deleteBeforeCursor(); 46 + }, 47 + Key.delete => { 48 + if (self.cursor_idx == self.grapheme_count) return; 49 + self.deleteAtCursor(); 50 + }, 51 + Key.left => { 52 + if (self.cursor_idx > 0) self.cursor_idx -= 1; 53 + }, 54 + Key.right => { 55 + if (self.cursor_idx < self.grapheme_count) self.cursor_idx += 1; 40 56 }, 41 57 else => {}, 42 58 } ··· 45 61 } 46 62 47 63 pub fn draw(self: *TextInput, win: Window) void { 48 - const input = self.buffer[0..self.buffer_idx]; 49 - var iter = GraphemeIterator.init(input); 64 + var iter = GraphemeIterator.init(self.buf.items); 50 65 var col: usize = 0; 66 + var i: usize = 0; 67 + var cursor_idx: usize = 0; 51 68 while (iter.next()) |grapheme| { 52 - const g = grapheme.slice(input); 69 + const g = grapheme.slice(self.buf.items); 53 70 const w = strWidth(g, .full) catch 1; 54 71 win.writeCell(col, 0, .{ 55 72 .char = .{ ··· 58 75 }, 59 76 }); 60 77 col += w; 78 + i += 1; 79 + if (i == self.cursor_idx) cursor_idx = col; 61 80 } 62 - win.showCursor(self.cursor_idx, 0); 81 + win.showCursor(cursor_idx, 0); 82 + } 83 + 84 + // returns the number of bytes before the cursor 85 + fn byteOffsetToCursor(self: TextInput) usize { 86 + var iter = GraphemeIterator.init(self.buf.items); 87 + var offset: usize = 0; 88 + var i: usize = 0; 89 + while (iter.next()) |grapheme| { 90 + if (i == self.cursor_idx) break; 91 + offset += grapheme.len; 92 + i += 1; 93 + } 94 + return offset; 95 + } 96 + 97 + fn deleteBeforeCursor(self: *TextInput) void { 98 + var iter = GraphemeIterator.init(self.buf.items); 99 + var offset: usize = 0; 100 + var i: usize = 1; 101 + while (iter.next()) |grapheme| { 102 + if (i == self.cursor_idx) { 103 + var j: usize = 0; 104 + while (j < grapheme.len) : (j += 1) { 105 + _ = self.buf.orderedRemove(offset); 106 + } 107 + self.cursor_idx -= 1; 108 + self.grapheme_count -= 1; 109 + return; 110 + } 111 + offset += grapheme.len; 112 + i += 1; 113 + } 114 + } 115 + 116 + fn deleteAtCursor(self: *TextInput) void { 117 + var iter = GraphemeIterator.init(self.buf.items); 118 + var offset: usize = 0; 119 + var i: usize = 1; 120 + while (iter.next()) |grapheme| { 121 + if (i == self.cursor_idx + 1) { 122 + var j: usize = 0; 123 + while (j < grapheme.len) : (j += 1) { 124 + _ = self.buf.orderedRemove(offset); 125 + } 126 + self.grapheme_count -= 1; 127 + return; 128 + } 129 + offset += grapheme.len; 130 + i += 1; 131 + } 63 132 }