an experimental irc client

ui: draw scrollbar

+60 -10
+40 -9
src/Scrollbar.zig
··· 1 1 const std = @import("std"); 2 2 const vaxis = @import("vaxis"); 3 + const vxfw = vaxis.vxfw; 3 4 4 5 const Scrollbar = @This(); 5 6 ··· 10 11 style: vaxis.Style = .{}, 11 12 12 13 /// The index of the bottom-most item, with 0 being "at the bottom" 13 - bottom: usize = 0, 14 + bottom: u16 = 0, 14 15 15 16 /// total items in the list 16 - total: usize, 17 + total: u16, 17 18 18 19 /// total items that fit within the view area 19 - view_size: usize, 20 + view_size: u16, 21 + 22 + fn widget(self: *Scrollbar) vxfw.Widget { 23 + return .{ 24 + .userdata = self, 25 + .drawFn = drawFn, 26 + }; 27 + } 28 + 29 + fn drawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface { 30 + const self: *Scrollbar = @ptrCast(@alignCast(ptr)); 31 + return self.draw(ctx); 32 + } 33 + 34 + pub fn draw(self: *Scrollbar, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface { 35 + const max = ctx.max.size(); 36 + if (max.width == 0 or max.height == 0) { 37 + return .{ 38 + .size = .{ .width = 0, .height = 0 }, 39 + .widget = self.widget(), 40 + .buffer = &.{}, 41 + .children = &.{}, 42 + }; 43 + } 20 44 21 - pub fn draw(self: Scrollbar, win: vaxis.Window) void { 45 + const surface = try vxfw.Surface.init( 46 + ctx.arena, 47 + self.widget(), 48 + .{ .width = 2, .height = max.height }, 49 + ); 50 + 22 51 // don't draw when 0 items 23 - if (self.total < 1) return; 52 + if (self.total < 1) return surface; 24 53 25 54 // don't draw when all items can be shown 26 - if (self.view_size >= self.total) return; 55 + if (self.view_size >= self.total) return surface; 27 56 28 57 // (view_size / total) * window height = size of the scroll bar 29 - const bar_height = @max(std.math.divCeil(usize, self.view_size * win.height, self.total) catch unreachable, 1); 58 + const bar_height = @max(std.math.divCeil(usize, self.view_size * max.height, self.total) catch unreachable, 1); 30 59 31 60 // The row of the last cell of the bottom of the bar 32 - const bar_bottom = (win.height - 1) -| (std.math.divCeil(usize, self.bottom * win.height, self.total) catch unreachable); 61 + const bar_bottom = (max.height - 1) -| (std.math.divCeil(usize, self.bottom * max.height, self.total) catch unreachable); 33 62 34 63 var i: usize = 0; 35 64 while (i <= bar_height) : (i += 1) 36 - win.writeCell(0, bar_bottom -| i, .{ .char = self.character, .style = self.style }); 65 + surface.writeCell(0, @intCast(bar_bottom -| i), .{ .char = self.character, .style = self.style }); 66 + 67 + return surface; 37 68 }
+20 -1
src/irc.zig
··· 6 6 const zeit = @import("zeit"); 7 7 const bytepool = @import("pool.zig"); 8 8 9 + const Scrollbar = @import("Scrollbar.zig"); 9 10 const testing = std.testing; 10 11 const mem = std.mem; 11 12 const vxfw = vaxis.vxfw; ··· 463 464 464 465 const msg_view_ctx = ctx.withConstraints(.{ .height = 0, .width = 0 }, .{ 465 466 .height = max.height - 4, 466 - .width = max.width, 467 + .width = max.width - 1, 467 468 }); 468 469 const message_view = try self.drawMessageView(msg_view_ctx); 469 470 try children.append(.{ 470 471 .origin = .{ .row = 2, .col = 0 }, 471 472 .surface = message_view, 473 + }); 474 + 475 + const scrollbar_ctx = ctx.withConstraints( 476 + ctx.min, 477 + .{ .width = 1, .height = max.height - 4 }, 478 + ); 479 + 480 + var scrollbars: Scrollbar = .{ 481 + // Estimate number of lines per message 482 + .total = @intCast(self.messages.items.len * 3), 483 + .view_size = max.height - 4, 484 + .bottom = self.scroll.offset, 485 + }; 486 + const scrollbar_surface = try scrollbars.draw(scrollbar_ctx); 487 + // Draw the text field 488 + try children.append(.{ 489 + .origin = .{ .col = max.width - 1, .row = 2 }, 490 + .surface = scrollbar_surface, 472 491 }); 473 492 474 493 // Draw the text field