an experimental irc client
1const std = @import("std");
2const vaxis = @import("vaxis");
3const vxfw = vaxis.vxfw;
4
5const Scrollbar = @This();
6
7/// character to use for the scrollbar
8const character: vaxis.Cell.Character = .{ .grapheme = "▐", .width = 1 };
9const empty: vaxis.Cell = .{ .char = character, .style = .{ .fg = .{ .index = 8 } } };
10
11/// style to draw the bar character with
12style: vaxis.Style = .{},
13
14/// The index of the bottom-most item, with 0 being "at the bottom"
15bottom: u16 = 0,
16
17/// total items in the list
18total: u16,
19
20/// total items that fit within the view area
21view_size: u16,
22
23fn widget(self: *Scrollbar) vxfw.Widget {
24 return .{
25 .userdata = self,
26 .drawFn = drawFn,
27 };
28}
29
30fn drawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface {
31 const self: *Scrollbar = @ptrCast(@alignCast(ptr));
32 return self.draw(ctx);
33}
34
35pub fn draw(self: *Scrollbar, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface {
36 const max = ctx.max.size();
37 if (max.width == 0 or max.height == 0) {
38 return .{
39 .size = .{ .width = 0, .height = 0 },
40 .widget = self.widget(),
41 .buffer = &.{},
42 .children = &.{},
43 };
44 }
45
46 const surface = try vxfw.Surface.init(
47 ctx.arena,
48 self.widget(),
49 .{ .width = 2, .height = max.height },
50 );
51
52 // don't draw when 0 items
53 if (self.total < 1) return surface;
54
55 // don't draw when all items can be shown
56 if (self.view_size >= self.total) return surface;
57
58 @memset(surface.buffer, empty);
59
60 // (view_size / total) * window height = size of the scroll bar
61 const premul1 = std.math.mulWide(u16, self.view_size, max.height);
62 const bar_height = @max(std.math.divCeil(usize, premul1, self.total) catch unreachable, 1);
63
64 // Premultiply. We use mulWide to ensure we never overflow
65 const premul2 = std.math.mulWide(u16, self.bottom, max.height);
66 // The row of the last cell of the bottom of the bar
67 const bar_bottom = (max.height - 1) -| (std.math.divCeil(usize, premul2, self.total) catch unreachable);
68
69 var i: usize = 0;
70 while (i <= bar_height) : (i += 1)
71 surface.writeCell(0, @intCast(bar_bottom -| i), .{ .char = character, .style = self.style });
72
73 return surface;
74}