a modern tui library written in zig
at main 4.6 kB view raw
1const std = @import("std"); 2const vaxis = @import("../main.zig"); 3 4pub const Scroll = struct { 5 x: usize = 0, 6 y: usize = 0, 7 8 pub fn restrictTo(self: *@This(), w: usize, h: usize) void { 9 self.x = @min(self.x, w); 10 self.y = @min(self.y, h); 11 } 12}; 13 14pub const VerticalScrollbar = struct { 15 character: vaxis.Cell.Character = .{ .grapheme = "", .width = 1 }, 16 fg: vaxis.Style = .{}, 17 bg: vaxis.Style = .{ .fg = .{ .index = 8 } }, 18}; 19 20scroll: Scroll = .{}, 21vertical_scrollbar: ?VerticalScrollbar = .{}, 22 23/// Standard input mappings. 24/// It is not neccessary to use this, you can set `scroll` manually. 25pub fn input(self: *@This(), key: vaxis.Key) void { 26 if (key.matches(vaxis.Key.right, .{})) { 27 self.scroll.x +|= 1; 28 } else if (key.matches(vaxis.Key.right, .{ .shift = true })) { 29 self.scroll.x +|= 32; 30 } else if (key.matches(vaxis.Key.left, .{})) { 31 self.scroll.x -|= 1; 32 } else if (key.matches(vaxis.Key.left, .{ .shift = true })) { 33 self.scroll.x -|= 32; 34 } else if (key.matches(vaxis.Key.up, .{})) { 35 self.scroll.y -|= 1; 36 } else if (key.matches(vaxis.Key.page_up, .{})) { 37 self.scroll.y -|= 32; 38 } else if (key.matches(vaxis.Key.down, .{})) { 39 self.scroll.y +|= 1; 40 } else if (key.matches(vaxis.Key.page_down, .{})) { 41 self.scroll.y +|= 32; 42 } else if (key.matches(vaxis.Key.end, .{})) { 43 self.scroll.y = std.math.maxInt(usize); 44 } else if (key.matches(vaxis.Key.home, .{})) { 45 self.scroll.y = 0; 46 } 47} 48 49/// Must be called before doing any `writeCell` calls. 50pub fn draw(self: *@This(), parent: vaxis.Window, content_size: struct { 51 cols: usize, 52 rows: usize, 53}) void { 54 const content_cols = if (self.vertical_scrollbar) |_| content_size.cols +| 1 else content_size.cols; 55 const max_scroll_x = content_cols -| parent.width; 56 const max_scroll_y = content_size.rows -| parent.height; 57 self.scroll.restrictTo(max_scroll_x, max_scroll_y); 58 if (self.vertical_scrollbar) |opts| { 59 const vbar: vaxis.widgets.Scrollbar = .{ 60 .character = opts.character, 61 .style = opts.fg, 62 .total = content_size.rows, 63 .view_size = parent.height, 64 .top = self.scroll.y, 65 }; 66 const bg = parent.child(.{ 67 .x_off = parent.width -| opts.character.width, 68 .width = opts.character.width, 69 .height = parent.height, 70 }); 71 bg.fill(.{ .char = opts.character, .style = opts.bg }); 72 vbar.draw(bg); 73 } 74} 75 76pub const BoundingBox = struct { 77 x1: usize, 78 y1: usize, 79 x2: usize, 80 y2: usize, 81 82 pub inline fn below(self: @This(), row: usize) bool { 83 return row < self.y1; 84 } 85 86 pub inline fn above(self: @This(), row: usize) bool { 87 return row >= self.y2; 88 } 89 90 pub inline fn rowInside(self: @This(), row: usize) bool { 91 return row >= self.y1 and row < self.y2; 92 } 93 94 pub inline fn colInside(self: @This(), col: usize) bool { 95 return col >= self.x1 and col < self.x2; 96 } 97 98 pub inline fn inside(self: @This(), col: usize, row: usize) bool { 99 return self.rowInside(row) and self.colInside(col); 100 } 101}; 102 103/// Boundary of the content, useful for culling to improve draw performance. 104pub fn bounds(self: *@This(), parent: vaxis.Window) BoundingBox { 105 const right_pad: usize = if (self.vertical_scrollbar != null) 1 else 0; 106 return .{ 107 .x1 = self.scroll.x, 108 .y1 = self.scroll.y, 109 .x2 = self.scroll.x +| parent.width -| right_pad, 110 .y2 = self.scroll.y +| parent.height, 111 }; 112} 113 114/// Use this function instead of `Window.writeCell` to draw your cells and they will magically scroll. 115pub fn writeCell(self: *@This(), parent: vaxis.Window, col: usize, row: usize, cell: vaxis.Cell) void { 116 const b = self.bounds(parent); 117 if (!b.inside(col, row)) return; 118 const win = parent.child(.{ .width = @intCast(b.x2 - b.x1), .height = @intCast(b.y2 - b.y1) }); 119 win.writeCell(@intCast(col -| self.scroll.x), @intCast(row -| self.scroll.y), cell); 120} 121 122/// Use this function instead of `Window.readCell` to read the correct cell in scrolling context. 123pub fn readCell(self: *@This(), parent: vaxis.Window, col: usize, row: usize) ?vaxis.Cell { 124 const b = self.bounds(parent); 125 if (!b.inside(col, row)) return; 126 const win = parent.child(.{ .width = @intCast(b.x2 - b.x1), .height = @intCast(b.y2 - b.y1) }); 127 return win.readCell(@intCast(col -| self.scroll.x), @intCast(row -| self.scroll.y)); 128}