an experimental irc client

buffers: implement clicking

+101 -34
+2 -2
build.zig.zon
··· 7 7 .hash = "1220affeb3fe37ef09411b5a213b5fdf9bb6568e9913bade204694648983a8b2776d", 8 8 }, 9 9 .vaxis = .{ 10 - .url = "git+https://github.com/rockorager/libvaxis#d8e5ec0d7bbeb6e70efc95734cd06082c3d518ff", 11 - .hash = "1220717a916024027282ea35359f8348858f3f49601af55f817c44d9806d08a6e06b", 10 + .url = "git+https://github.com/rockorager/libvaxis#4e07fb905ef40e3bd544a563914c769c1cf285d3", 11 + .hash = "1220c42eb95ef849c0d4c131ed2042bfffd392cb61db2f30dbb41148e866b5756610", 12 12 }, 13 13 .zeit = .{ 14 14 .url = "git+https://github.com/rockorager/zeit?ref=main#d943bc4bfe9e18490460dfdd64f48e997065eba8",
+31 -12
src/app.zig
··· 273 273 const self: *const App = @ptrCast(@alignCast(ptr)); 274 274 var i: usize = 0; 275 275 for (self.clients.items) |client| { 276 - if (i == idx and i == cursor) { 277 - return .{ 278 - .userdata = client, 279 - .drawFn = irc.Client.drawNameSelected, 280 - }; 281 - } 282 - if (i == idx) { 283 - return .{ 284 - .userdata = client, 285 - .drawFn = irc.Client.drawName, 286 - }; 287 - } 276 + if (i == idx) return client.nameWidget(i == cursor); 288 277 i += 1; 289 278 for (client.channels.items) |channel| { 290 279 if (i == idx) return channel.nameWidget(i == cursor); ··· 1173 1162 } 1174 1163 } 1175 1164 return null; 1165 + } 1166 + 1167 + pub fn selectBuffer(self: *App, buffer: irc.Buffer) void { 1168 + var i: u32 = 0; 1169 + switch (buffer) { 1170 + .client => |target| { 1171 + for (self.clients.items) |client| { 1172 + if (client == target) { 1173 + self.buffer_list.cursor = i; 1174 + self.buffer_list.ensureScroll(); 1175 + return; 1176 + } 1177 + i += 1; 1178 + for (client.channels.items) |_| i += 1; 1179 + } 1180 + }, 1181 + .channel => |target| { 1182 + for (self.clients.items) |client| { 1183 + i += 1; 1184 + for (client.channels.items) |channel| { 1185 + if (channel == target) { 1186 + self.buffer_list.cursor = i; 1187 + self.buffer_list.ensureScroll(); 1188 + return; 1189 + } 1190 + i += 1; 1191 + } 1192 + } 1193 + }, 1194 + } 1176 1195 } 1177 1196 1178 1197 fn draw(self: *App, input: *TextInput) !void {
+68 -20
src/irc.zig
··· 184 184 fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { 185 185 const self: *Channel = @ptrCast(@alignCast(ptr)); 186 186 switch (event) { 187 - .mouse => { 187 + .mouse => |mouse| { 188 188 try ctx.setMouseShape(.pointer); 189 + if (mouse.type == .press and mouse.button == .left) { 190 + self.client.app.selectBuffer(.{ .channel = self }); 191 + return ctx.consumeAndRedraw(); 192 + } 189 193 }, 190 194 .mouse_enter => { 191 195 try ctx.setMouseShape(.pointer); ··· 200 204 } 201 205 202 206 pub fn drawName(self: *Channel, ctx: vxfw.DrawContext, selected: bool) Allocator.Error!vxfw.Surface { 203 - const style: vaxis.Style = if (selected) 204 - .{ .reverse = true } 205 - else if (self.has_mouse) 206 - .{ .bg = .{ .index = 8 } } 207 - else 208 - .{}; 207 + var style: vaxis.Style = .{}; 208 + if (selected) style.reverse = true; 209 + if (self.has_mouse) style.bg = .{ .index = 8 }; 210 + 209 211 const text: vxfw.RichText = .{ 210 212 .text = &.{ 211 - .{ .text = " ", .style = style }, 213 + .{ .text = " " }, 212 214 .{ .text = self.name, .style = style }, 213 215 }, 214 216 .softwrap = false, ··· 566 568 fifo: std.fifo.LinearFifo(Event, .Dynamic), 567 569 fifo_mutex: std.Thread.Mutex, 568 570 571 + has_mouse: bool, 572 + 569 573 pub fn init( 570 574 alloc: std.mem.Allocator, 571 575 app: *comlink.App, ··· 585 589 .redraw = std.atomic.Value(bool).init(false), 586 590 .fifo = std.fifo.LinearFifo(Event, .Dynamic).init(alloc), 587 591 .fifo_mutex = .{}, 592 + .has_mouse = false, 588 593 }; 589 594 } 590 595 ··· 627 632 self.fifo.deinit(); 628 633 } 629 634 630 - pub fn drawName(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 631 - const self: *Client = @ptrCast(@alignCast(ptr)); 632 - const text: vxfw.Text = .{ 633 - .text = self.config.name orelse self.config.server, 634 - .softwrap = false, 635 + pub fn nameWidget(self: *Client, selected: bool) vxfw.Widget { 636 + return .{ 637 + .userdata = self, 638 + .eventHandler = Client.typeErasedEventHandler, 639 + .drawFn = if (selected) 640 + Client.typeErasedDrawNameSelected 641 + else 642 + Client.typeErasedDrawName, 635 643 }; 636 - return text.draw(ctx); 637 644 } 638 645 639 - pub fn drawNameSelected(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 640 - const self: *Client = @ptrCast(@alignCast(ptr)); 641 - const text: vxfw.Text = .{ 642 - .text = self.config.name orelse self.config.server, 646 + pub fn drawName(self: *Client, ctx: vxfw.DrawContext, selected: bool) Allocator.Error!vxfw.Surface { 647 + var style: vaxis.Style = .{}; 648 + if (selected) style.reverse = true; 649 + if (self.has_mouse) style.bg = .{ .index = 8 }; 650 + 651 + const name = self.config.name orelse self.config.server; 652 + 653 + const text: vxfw.RichText = .{ 654 + .text = &.{ 655 + .{ .text = name, .style = style }, 656 + }, 643 657 .softwrap = false, 644 - .style = .{ .reverse = true }, 645 658 }; 646 - return text.draw(ctx); 659 + var surface = try text.draw(ctx); 660 + // Replace the widget reference so we can handle the events 661 + surface.widget = self.nameWidget(selected); 662 + return surface; 663 + } 664 + 665 + fn typeErasedDrawName(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 666 + const self: *Client = @ptrCast(@alignCast(ptr)); 667 + return self.drawName(ctx, false); 668 + } 669 + 670 + fn typeErasedDrawNameSelected(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 671 + const self: *Client = @ptrCast(@alignCast(ptr)); 672 + return self.drawName(ctx, true); 673 + } 674 + 675 + fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { 676 + const self: *Client = @ptrCast(@alignCast(ptr)); 677 + switch (event) { 678 + .mouse => |mouse| { 679 + try ctx.setMouseShape(.pointer); 680 + if (mouse.type == .press and mouse.button == .left) { 681 + self.app.selectBuffer(.{ .client = self }); 682 + return ctx.consumeAndRedraw(); 683 + } 684 + }, 685 + .mouse_enter => { 686 + try ctx.setMouseShape(.pointer); 687 + self.has_mouse = true; 688 + }, 689 + .mouse_leave => { 690 + try ctx.setMouseShape(.default); 691 + self.has_mouse = false; 692 + }, 693 + else => {}, 694 + } 647 695 } 648 696 649 697 pub fn drainFifo(self: *Client, ctx: *vxfw.EventContext) void {