╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────╮ │src/ui/widget.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1396 // stays legible over it │ │1397 const art_brightness = 35; // percent │ │1398 │ │1392 - // only truecolor can be dimmed; palette colors pass through │ │1393 - fn dimColor(color: ?Grid.Color) ?Grid.Color { │ │1399 + // the art parser only emits truecolor, so a palette color never occurs │ │1400 + fn artRgb(color: ?Grid.Color) ?Grid.Color.Rgb { │ │1401 const c = color orelse return null; │ │1402 return switch (c) { │ │1396 - .rgb => |v| .{ .rgb = .{ │ │1397 - .r = @intCast(@as(u16, v.r) * art_brightness / 100), │ │1398 - .g = @intCast(@as(u16, v.g) * art_brightness / 100), │ │1399 - .b = @intCast(@as(u16, v.b) * art_brightness / 100), │ │1400 - } }, │ │1401 - else => c, │ │1403 + .rgb => |v| v, │ │1404 + else => null, │ │1405 }; │ │1406 + } │ │1407 + │ │1408 + fn dimColor(color: ?Grid.Color) ?Grid.Color { │ │1409 + const v = artRgb(color) orelse return color; │ │1410 + return .{ .rgb = .{ │ │1411 + .r = @intCast(@as(u16, v.r) * art_brightness / 100), │ │1412 + .g = @intCast(@as(u16, v.g) * art_brightness / 100), │ │1413 + .b = @intCast(@as(u16, v.b) * art_brightness / 100), │ │1414 + } }; │ │1415 } │ │1416 │ │1417 fn buildArt(self: *AnsiBackground, allocator: std.mem.Allocator, size: layout.Size, root_focus: *Focus) !void {│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │1434 // hide text there; the ≤9/255 shift is imperceptible on terminals that │ │1435 // actually render truecolor. │ │1436 fn sgrSafe(color: ?Grid.Color) ?Grid.Color { │ │1425 - const c = color orelse return null; │ │1437 + const v = artRgb(color) orelse return color; │ │1438 const snap = struct { │ │1427 - fn f(v: u8) u8 { │ │1428 - return if (v >= 1 and v <= 9) 10 else v; │ │1439 + fn f(c: u8) u8 { │ │1440 + return if (c >= 1 and c <= 9) 10 else c; │ │1441 } │ │1442 }.f; │ │1431 - return switch (c) { │ │1432 - .rgb => |v| .{ .rgb = .{ .r = snap(v.r), .g = snap(v.g), .b = snap(v.b) } },│ │1433 - else => c, │ │1434 - }; │ │1443 + return .{ .rgb = .{ .r = snap(v.r), .g = snap(v.g), .b = snap(v.b) } }; │ │1444 } │ │1445 │ │1446 fn applyArtBackground(dst: *Grid.Cell, src: Grid.Cell) void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │1448 dst.style.bg = background_maybe; │ │1449 // change unstyled text to contrast with the art behind it │ │1450 if (dst.style.fg == null) { │ │1442 - // the art is parsed as truecolor, so a palette bg never occurs │ │1443 - const background: ?Grid.Color.Rgb = if (background_maybe) |b| switch (b) {│ │1444 - .rgb => |v| v, │ │1445 - else => null, │ │1446 - } else null; │ │1447 - if (background) |v| { │ │1451 + if (artRgb(background_maybe)) |v| { │ │1452 // use near-black and near-white, which is easier on the eyes │ │1453 // while retaining high contrast │ │1454 const luminance = (@as(u32, v.r) * 299 + │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │478 const selected_id = if (self.selectedTab()) |tab| self.tab_ids.keys()[tab] else null;│ │479 for (self.box.children.keys(), self.box.children.values()) |id, *child| { │ │480 switch (child.widget) { │ │481 - .text_box => |*tb| { │ │482 - const selected = selected_id == id; │ │483 - tb.options.border_style = if (selected) .single else .hidden; │ │484 - tb.options.inverted = selected; │ │485 - }, │ │481 + .text_box => |*tb| widget.markSelected(tb, selected_id == id), │ │482 else => {}, │ │483 } │ │484 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2292 const lb = self.listBox(i); │ │2293 for (lb.children.keys(), lb.children.values()) |id, *child| { │ │2294 switch (child.widget) { │ │2299 - .text_box => |*tb| { │ │2300 - const selected = lb.getFocus().child_id == id; │ │2301 - tb.options.border_style = if (selected) .single else .hidden; │ │2302 - tb.options.inverted = selected; │ │2303 - }, │ │2295 + .text_box => |*tb| widget.markSelected(tb, lb.getFocus().child_id == id),│ │2296 else => {}, │ │2297 } │ │2298 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2064 const bg = cell.style.bg; │ │2065 const inverted = !covered_by_scroll and cell.style.inverted; │ │2066 │ │2067 - if (first or cell_id != cur_id or !colorEql(fg, cur_fg) or !colorEql(bg, cur_bg) or inverted != cur_inverted) { │ │2067 + if (first or cell_id != cur_id or !std.meta.eql(fg, cur_fg) or !std.meta.eql(bg, cur_bg) or inverted != cur_inverted) {│ │2068 if (open_tag) |t| try output.appendSlice(allocator, t.closeTag()); │ │2069 │ │2070 var new_tag: ?CellTag = null; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2289 } │ │2290 } │ │2291 │ │2292 -fn colorEql(a: ?Grid.Color, b: ?Grid.Color) bool { │ │2293 - if (a) |av| return if (b) |bv| av.eql(bv) else false; │ │2294 - return b == null; │ │2295 -} │ │2296 - │ │2292 // the 16 ansi colors as the browser shows them (xterm's defaults) │ │2293 const ansi_palette = [16]Grid.Color.Rgb{ │ │2294 .{ .r = 0x00, .g = 0x00, .b = 0x00 }, .{ .r = 0xcd, .g = 0x00, .b = 0x00 }, .{ .r = 0x00, .g = 0xcd, .b = 0x00 }, .{ .r = 0xcd, .g = 0xcd, .b = 0x00 },│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2325 const prefix = " style=\""; │ │2326 @memcpy(buf[0..prefix.len], prefix); │ │2327 var i: usize = prefix.len; │ │2333 - const text = if (inverted) bg else fg; │ │2334 - const back = if (inverted) fg else bg; │ │2335 - if (text) |c| { │ │2336 - const v = colorRgb(c); │ │2337 - const s = std.fmt.bufPrint(buf[i..], "color:#{x:0>2}{x:0>2}{x:0>2};", .{ v.r, v.g, v.b }) catch return buf[0..0]; │ │2338 - i += s.len; │ │2339 - } else if (inverted) { │ │2340 - const s = std.fmt.bufPrint(buf[i..], "color:var(--bg);", .{}) catch return buf[0..0]; │ │2341 - i += s.len; │ │2342 - } │ │2343 - if (back) |c| { │ │2344 - const v = colorRgb(c); │ │2345 - const s = std.fmt.bufPrint(buf[i..], "background-color:#{x:0>2}{x:0>2}{x:0>2};", .{ v.r, v.g, v.b }) catch return buf[0..0];│ │2346 - i += s.len; │ │2347 - } else if (inverted) { │ │2348 - const s = std.fmt.bufPrint(buf[i..], "background-color:var(--fg);", .{}) catch return buf[0..0]; │ │2349 - i += s.len; │ │2350 - } │ │2328 + i = cssColor(buf, i, "color", if (inverted) bg else fg, if (inverted) "var(--bg)" else null) orelse return buf[0..0]; │ │2329 + i = cssColor(buf, i, "background-color", if (inverted) fg else bg, if (inverted) "var(--fg)" else null) orelse return buf[0..0];│ │2330 buf[i] = '"'; │ │2352 - i += 1; │ │2353 - return buf[0..i]; │ │2331 + return buf[0 .. i + 1]; │ │2332 +} │ │2333 + │ │2334 +// append `prop:value;` at buf[i..] for the color, or for the fallback when │ │2335 +// the color is unset (nothing when both are). returns the new end, or null │ │2336 +// when it doesn't fit. │ │2337 +fn cssColor(buf: []u8, i: usize, prop: []const u8, color: ?Grid.Color, fallback: ?[]const u8) ?usize { │ │2338 + const s = if (color) |c| blk: { │ │2339 + const v = colorRgb(c); │ │2340 + break :blk std.fmt.bufPrint(buf[i..], "{s}:#{x:0>2}{x:0>2}{x:0>2};", .{ prop, v.r, v.g, v.b }) catch return null; │ │2341 + } else if (fallback) |f| │ │2342 + std.fmt.bufPrint(buf[i..], "{s}:{s};", .{ prop, f }) catch return null │ │2343 + else │ │2344 + return i; │ │2345 + return i + s.len; │ │2346 } │ │2347 │ │2348 fn appendEscapedHtml(allocator: std.mem.Allocator, out: *std.ArrayList(u8), input: []const u8) !void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯