an experimental irc client

ui: query yellow, use blended for highlighted bg

rockorager.dev b824f5cf 6e7e619b

verified
+59 -8
+39 -3
src/app.zig
··· 84 84 85 85 fg: ?[3]u8, 86 86 bg: ?[3]u8, 87 + yellow: ?[3]u8, 87 88 88 89 const default_rhs: vxfw.Text = .{ .text = "TODO: update this text" }; 89 90 ··· 126 127 .has_focus = true, 127 128 .fg = null, 128 129 .bg = null, 130 + .yellow = null, 129 131 }; 130 132 131 133 self.lua = try Lua.init(&self.alloc); ··· 212 214 switch (color.kind) { 213 215 .fg => self.fg = color.value, 214 216 .bg => self.bg = color.value, 215 - else => {}, 217 + .index => |index| { 218 + switch (index) { 219 + 3 => self.yellow = color.value, 220 + else => {}, 221 + } 222 + }, 223 + .cursor => {}, 216 224 } 217 225 if (self.fg != null and self.bg != null) { 218 226 for (self.clients.items) |client| { 219 227 for (client.channels.items) |channel| { 220 - channel.text_field.style.bg = self.blend(10); 228 + channel.text_field.style.bg = self.blendBg(10); 221 229 } 222 230 } 223 231 } ··· 288 296 try ctx.tick(8, self.widget()); 289 297 try ctx.queryColor(.fg); 290 298 try ctx.queryColor(.bg); 299 + try ctx.queryColor(.{ .index = 3 }); 291 300 }, 292 301 .tick => { 293 302 for (self.clients.items) |client| { ··· 411 420 412 421 /// Blend fg and bg, otherwise return index 8. amt will be clamped to [0,100]. amt will be 413 422 /// interpreted as percentage of fg to blend into bg 414 - pub fn blend(self: *App, amt: u8) vaxis.Color { 423 + pub fn blendBg(self: *App, amt: u8) vaxis.Color { 415 424 const bg = self.bg orelse return .{ .index = 8 }; 416 425 const fg = self.fg orelse return .{ .index = 8 }; 417 426 // Clamp to (0,100) ··· 432 441 @intCast((fg_r + bg_r) / 100), 433 442 @intCast((fg_g + bg_g) / 100), 434 443 @intCast((fg_b + bg_b) / 100), 444 + }, 445 + }; 446 + } 447 + 448 + /// Blend fg and bg, otherwise return index 8. amt will be clamped to [0,100]. amt will be 449 + /// interpreted as percentage of fg to blend into bg 450 + pub fn blendYellow(self: *App, amt: u8) vaxis.Color { 451 + const bg = self.bg orelse return .{ .index = 3 }; 452 + const yellow = self.yellow orelse return .{ .index = 3 }; 453 + // Clamp to (0,100) 454 + if (amt == 0) return .{ .rgb = bg }; 455 + if (amt >= 100) return .{ .rgb = yellow }; 456 + 457 + const yellow_r: u16 = std.math.mulWide(u8, yellow[0], amt); 458 + const yellow_g: u16 = std.math.mulWide(u8, yellow[1], amt); 459 + const yellow_b: u16 = std.math.mulWide(u8, yellow[2], amt); 460 + 461 + const bg_multiplier: u8 = 100 - amt; 462 + const bg_r: u16 = std.math.mulWide(u8, bg[0], bg_multiplier); 463 + const bg_g: u16 = std.math.mulWide(u8, bg[1], bg_multiplier); 464 + const bg_b: u16 = std.math.mulWide(u8, bg[2], bg_multiplier); 465 + 466 + return .{ 467 + .rgb = .{ 468 + @intCast((yellow_r + bg_r) / 100), 469 + @intCast((yellow_g + bg_g) / 100), 470 + @intCast((yellow_b + bg_b) / 100), 435 471 }, 436 472 }; 437 473 }
+20 -5
src/irc.zig
··· 281 281 .completer = Completer.init(gpa), 282 282 }; 283 283 284 - self.text_field.style = .{ .bg = client.app.blend(10) }; 284 + self.text_field.style = .{ .bg = client.app.blendBg(10) }; 285 285 self.text_field.userdata = self; 286 286 self.text_field.onSubmit = Channel.onSubmit; 287 287 self.text_field.onChange = Channel.onChange; ··· 991 991 // Draw the message so we have it's wrapped height 992 992 const text: vxfw.RichText = .{ .text = spans }; 993 993 const child_ctx = ctx.withConstraints( 994 - .{ .width = 0, .height = 0 }, 994 + .{ .width = max.width -| gutter_width, .height = 1 }, 995 995 .{ .width = max.width -| gutter_width, .height = null }, 996 996 ); 997 997 const surface = try text.draw(child_ctx); 998 + if (self.client.app.yellow != null and msg.containsPhrase(self.client.nickname())) { 999 + const bg = self.client.app.blendYellow(30); 1000 + for (surface.buffer) |*cell| { 1001 + if (cell.style.bg != .default) continue; 1002 + cell.style.bg = bg; 1003 + } 1004 + } 998 1005 999 1006 // See if our message contains the mouse. We'll highlight it if it does 1000 1007 const message_has_mouse: bool = blk: { ··· 1077 1084 }; 1078 1085 1079 1086 // If the message has our nick, we'll highlight the time 1080 - if (std.mem.indexOf(u8, content, self.client.nickname())) |_| { 1087 + if (self.client.app.yellow == null and msg.containsPhrase(self.client.nickname())) { 1081 1088 style.fg = .{ .index = 3 }; 1082 1089 style.reverse = true; 1083 1090 } ··· 1087 1094 .style = style, 1088 1095 .softwrap = false, 1089 1096 }; 1097 + const time_ctx = ctx.withConstraints( 1098 + .{ .width = 0, .height = 1 }, 1099 + .{ .width = max.width -| gutter_width, .height = null }, 1100 + ); 1090 1101 try children.append(.{ 1091 1102 .origin = .{ .row = row, .col = 0 }, 1092 - .surface = try time_text.draw(child_ctx), 1103 + .surface = try time_text.draw(time_ctx), 1093 1104 }); 1094 1105 1095 1106 var printed_sender: bool = false; ··· 1106 1117 .text = user.nick, 1107 1118 .style = .{ .fg = user.color, .bold = true }, 1108 1119 }; 1109 - const sender_surface = try sender_text.draw(child_ctx); 1120 + const sender_ctx = ctx.withConstraints( 1121 + .{ .width = 0, .height = 1 }, 1122 + .{ .width = max.width -| gutter_width, .height = null }, 1123 + ); 1124 + const sender_surface = try sender_text.draw(sender_ctx); 1110 1125 try children.append(.{ 1111 1126 .origin = .{ .row = row, .col = gutter_width }, 1112 1127 .surface = sender_surface,