this repo has no description
13
fork

Configure Feed

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

widgets: add ScrollView widget

The ScrollView widget can be used to introduce scrollable elements into
existing widgets.

To use the ScrollView, you must use the ScrollView's writeCell and
readCell functions rather than the ones from Window.

authored by

Jari Vetoniemi and committed by
Tim Culverhouse
f25b8ab4 fbaa6ca8

+130
+1
src/widgets.zig
··· 6 6 pub const Table = @import("widgets/Table.zig"); 7 7 pub const TextInput = @import("widgets/TextInput.zig"); 8 8 pub const nvim = @import("widgets/nvim.zig"); 9 + pub const ScrollView = @import("widgets/ScrollView.zig");
+129
src/widgets/ScrollView.zig
··· 1 + const std = @import("std"); 2 + const vaxis = @import("../main.zig"); 3 + 4 + pub 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 + 14 + pub const VerticalScrollbar = struct { 15 + character: vaxis.Cell.Character = .{ .grapheme = "▐", .width = 1 }, 16 + fg: vaxis.Style = .{}, 17 + bg: vaxis.Style = .{ .fg = .{ .index = 8 } }, 18 + }; 19 + 20 + scroll: Scroll = .{}, 21 + vertical_scrollbar: ?VerticalScrollbar = .{}, 22 + 23 + /// Standard input mappings. 24 + /// It is not neccessary to use this, you can set `scroll` manually. 25 + pub 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. 50 + pub fn draw(self: *@This(), parent: vaxis.Window, content_size: struct { 51 + cols: usize, 52 + rows: usize, 53 + }) void { 54 + var content_cols = content_size.cols; 55 + if (self.vertical_scrollbar) |opts| { 56 + const vbar: vaxis.widgets.Scrollbar = .{ 57 + .character = opts.character, 58 + .style = opts.fg, 59 + .total = content_size.rows, 60 + .view_size = parent.height, 61 + .top = self.scroll.y, 62 + }; 63 + const bg = parent.child(.{ 64 + .x_off = parent.width -| opts.character.width, 65 + .width = .{ .limit = opts.character.width }, 66 + .height = .{ .limit = parent.height }, 67 + }); 68 + bg.fill(.{ .char = opts.character, .style = opts.bg }); 69 + vbar.draw(bg); 70 + content_cols +|= 1; 71 + } 72 + const max_scroll_x = content_cols -| parent.width; 73 + const max_scroll_y = content_size.rows -| parent.height; 74 + self.scroll.restrictTo(max_scroll_x, max_scroll_y); 75 + } 76 + 77 + pub const BoundingBox = struct { 78 + x1: usize, 79 + y1: usize, 80 + x2: usize, 81 + y2: usize, 82 + 83 + pub inline fn below(self: @This(), row: usize) bool { 84 + return row < self.y1; 85 + } 86 + 87 + pub inline fn above(self: @This(), row: usize) bool { 88 + return row >= self.y2; 89 + } 90 + 91 + pub inline fn rowInside(self: @This(), row: usize) bool { 92 + return row >= self.y1 and row < self.y2; 93 + } 94 + 95 + pub inline fn colInside(self: @This(), col: usize) bool { 96 + return col >= self.x1 and col < self.x2; 97 + } 98 + 99 + pub inline fn inside(self: @This(), col: usize, row: usize) bool { 100 + return self.rowInside(row) and self.colInside(col); 101 + } 102 + }; 103 + 104 + /// Boundary of the content, useful for culling to improve draw performance. 105 + pub fn bounds(self: *@This(), parent: vaxis.Window) BoundingBox { 106 + const right_pad: usize = if (self.vertical_scrollbar != null) 1 else 0; 107 + return .{ 108 + .x1 = self.scroll.x, 109 + .y1 = self.scroll.y, 110 + .x2 = self.scroll.x +| parent.width -| right_pad, 111 + .y2 = self.scroll.y +| parent.height, 112 + }; 113 + } 114 + 115 + /// Use this function instead of `Window.writeCell` to draw your cells and they will magically scroll. 116 + pub fn writeCell(self: *@This(), parent: vaxis.Window, col: usize, row: usize, cell: vaxis.Cell) void { 117 + const b = self.bounds(parent); 118 + if (!b.inside(col, row)) return; 119 + const win = parent.child(.{ .width = .{ .limit = b.x2 - b.x1 }, .height = .{ .limit = b.y2 - b.y1 } }); 120 + win.writeCell(col -| self.scroll.x, row -| self.scroll.y, cell); 121 + } 122 + 123 + /// Use this function instead of `Window.readCell` to read the correct cell in scrolling context. 124 + pub fn readCell(self: *@This(), parent: vaxis.Window, col: usize, row: usize) ?vaxis.Cell { 125 + const b = self.bounds(parent); 126 + if (!b.inside(col, row)) return; 127 + const win = parent.child(.{ .width = .{ .limit = b.width }, .height = .{ .limit = b.height } }); 128 + return win.readCell(col -| self.scroll.x, row -| self.scroll.y); 129 + }