an experimental irc client

channel: improve name drawing

+51 -25
+1 -12
src/app.zig
··· 287 287 } 288 288 i += 1; 289 289 for (client.channels.items) |channel| { 290 - if (i == idx and i == cursor) { 291 - return .{ 292 - .userdata = channel, 293 - .drawFn = irc.Channel.drawNameSelected, 294 - }; 295 - } 296 - if (i == idx) { 297 - return .{ 298 - .userdata = channel, 299 - .drawFn = irc.Channel.drawName, 300 - }; 301 - } 290 + if (i == idx) return channel.nameWidget(i == cursor); 302 291 i += 1; 303 292 } 304 293 }
+50 -13
src/irc.zig
··· 117 117 has_unread: bool = false, 118 118 has_unread_highlight: bool = false, 119 119 120 + has_mouse: bool = false, 121 + 120 122 pub const Member = struct { 121 123 user: *User, 122 124 ··· 168 170 return l < r; 169 171 } 170 172 171 - pub fn drawName(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 173 + pub fn nameWidget(self: *Channel, selected: bool) vxfw.Widget { 174 + return .{ 175 + .userdata = self, 176 + .eventHandler = Channel.typeErasedEventHandler, 177 + .drawFn = if (selected) 178 + Channel.typeErasedDrawNameSelected 179 + else 180 + Channel.typeErasedDrawName, 181 + }; 182 + } 183 + 184 + fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { 172 185 const self: *Channel = @ptrCast(@alignCast(ptr)); 186 + switch (event) { 187 + .mouse => { 188 + try ctx.setMouseShape(.pointer); 189 + }, 190 + .mouse_enter => { 191 + try ctx.setMouseShape(.pointer); 192 + self.has_mouse = true; 193 + }, 194 + .mouse_leave => { 195 + try ctx.setMouseShape(.default); 196 + self.has_mouse = false; 197 + }, 198 + else => {}, 199 + } 200 + } 201 + 202 + 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 + .{}; 173 209 const text: vxfw.RichText = .{ 174 210 .text = &.{ 175 - .{ .text = " " }, 176 - .{ .text = self.name }, 211 + .{ .text = " ", .style = style }, 212 + .{ .text = self.name, .style = style }, 177 213 }, 178 214 .softwrap = false, 179 215 }; 180 - return text.draw(ctx); 216 + var surface = try text.draw(ctx); 217 + // Replace the widget reference so we can handle the events 218 + surface.widget = self.nameWidget(selected); 219 + return surface; 220 + } 221 + 222 + fn typeErasedDrawName(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 223 + const self: *Channel = @ptrCast(@alignCast(ptr)); 224 + return self.drawName(ctx, false); 181 225 } 182 226 183 - pub fn drawNameSelected(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 227 + fn typeErasedDrawNameSelected(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 184 228 const self: *Channel = @ptrCast(@alignCast(ptr)); 185 - const text: vxfw.RichText = .{ 186 - .text = &.{ 187 - .{ .text = " ", .style = .{ .reverse = true } }, 188 - .{ .text = self.name, .style = .{ .reverse = true } }, 189 - }, 190 - .softwrap = false, 191 - }; 192 - return text.draw(ctx); 229 + return self.drawName(ctx, true); 193 230 } 194 231 195 232 pub fn sortMembers(self: *Channel) void {