an experimental irc client

channel: improve name drawing

+51 -25
+1 -12
src/app.zig
··· 287 } 288 i += 1; 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 - } 302 i += 1; 303 } 304 }
··· 287 } 288 i += 1; 289 for (client.channels.items) |channel| { 290 + if (i == idx) return channel.nameWidget(i == cursor); 291 i += 1; 292 } 293 }
+50 -13
src/irc.zig
··· 117 has_unread: bool = false, 118 has_unread_highlight: bool = false, 119 120 pub const Member = struct { 121 user: *User, 122 ··· 168 return l < r; 169 } 170 171 - pub fn drawName(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 172 const self: *Channel = @ptrCast(@alignCast(ptr)); 173 const text: vxfw.RichText = .{ 174 .text = &.{ 175 - .{ .text = " " }, 176 - .{ .text = self.name }, 177 }, 178 .softwrap = false, 179 }; 180 - return text.draw(ctx); 181 } 182 183 - pub fn drawNameSelected(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 184 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); 193 } 194 195 pub fn sortMembers(self: *Channel) void {
··· 117 has_unread: bool = false, 118 has_unread_highlight: bool = false, 119 120 + has_mouse: bool = false, 121 + 122 pub const Member = struct { 123 user: *User, 124 ··· 170 return l < r; 171 } 172 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 { 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 + .{}; 209 const text: vxfw.RichText = .{ 210 .text = &.{ 211 + .{ .text = " ", .style = style }, 212 + .{ .text = self.name, .style = style }, 213 }, 214 .softwrap = false, 215 }; 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); 225 } 226 227 + fn typeErasedDrawNameSelected(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface { 228 const self: *Channel = @ptrCast(@alignCast(ptr)); 229 + return self.drawName(ctx, true); 230 } 231 232 pub fn sortMembers(self: *Channel) void {