╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────╮ │src/ui/widget.zig│ ╰─────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │949 const effective_background = if (style.inverted) style.fg else style.bg; │ │950 const transparent = end - i == 1 and rune[0] == ' ' and effective_background == null; │ │951 try row.append(allocator, .{ │ │950 - .rune = if (transparent) null else rune, │ │952 + .rune = if (transparent) null else codepoint, │ │953 .style = style, │ │954 }); │ │955 + if (!transparent and xitui.width.cellWidth(codepoint) == 2) { │ │956 + try row.append(allocator, .{ .rune = null, .style = style, .continuation = true });│ │957 + } │ │958 i = end; │ │959 } │ │960 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │978 for (rows.items[0..clamped_h], 0..) |r, y| { │ │979 const n = @min(r.items.len, clamped_w); │ │980 for (r.items[0..n], 0..) |cell, x| { │ │976 - grid.cells.items[try grid.cells.at(.{ y, x })] = cell;│ │981 + if (cell.continuation) continue; │ │982 + (try grid.cell(x, y)).style = cell.style; │ │983 + try grid.setRune(x, y, cell.rune); │ │984 } │ │985 } │ │986 self.grid = grid; │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │1113 │ │1114 // a foreground cell counts as blank if it's empty or a bare space without styling │ │1115 fn cellIsBlank(cell: Grid.Cell) bool { │ │1116 + if (cell.continuation) return false; │ │1117 if (cell.rune) |rune| { │ │1110 - return std.mem.eql(u8, rune, " ") and │ │1118 + return rune == ' ' and │ │1119 cell.style.fg == null and cell.style.bg == null and !cell.style.inverted;│ │1120 } │ │1121 return true; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │1140 .max_size = .{ .width = size.width, .height = size.height },│ │1141 }, root_focus); │ │1142 if (self.art.grid) |*grid| { │ │1135 - for (grid.cells.items) |*cell| { │ │1143 + for (grid.cells) |*cell| { │ │1144 cell.style.fg = dimColor(cell.style.fg); │ │1145 cell.style.bg = dimColor(cell.style.bg); │ │1146 } │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │1163 return .{ .r = snap(c.r), .g = snap(c.g), .b = snap(c.b) }; │ │1164 } │ │1165 │ │1166 + fn applyArtBackground(dst: *Grid.Cell, src: Grid.Cell) void { │ │1167 + const background_maybe = sgrSafe(src.style.bg orelse src.style.fg); │ │1168 + dst.style.bg = background_maybe; │ │1169 + // change unstyled text to contrast with the art behind it │ │1170 + if (dst.style.fg == null) { │ │1171 + if (background_maybe) |background| { │ │1172 + // use near-black and near-white, which is easier on the eyes │ │1173 + // while retaining high contrast │ │1174 + const luminance = (@as(u32, background.r) * 299 + │ │1175 + @as(u32, background.g) * 587 + │ │1176 + @as(u32, background.b) * 114) / 1000; │ │1177 + dst.style.fg = if (luminance >= 128) │ │1178 + .{ .r = 16, .g = 16, .b = 16 } │ │1179 + else │ │1180 + .{ .r = 240, .g = 240, .b = 240 }; │ │1181 + } │ │1182 + } │ │1183 + } │ │1184 + │ │1185 // composites ANSI art behind a foreground grid │ │1186 fn artBehind(allocator: std.mem.Allocator, foreground: Grid, art_grid: Grid) !Grid {│ │1187 var out = try Grid.initFromGrid(allocator, foreground, foreground.size, 0, 0); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1190 const anchor_x = foreground.size.width -| art_grid.size.width; │ │1191 for (0..art_grid.size.height) |y| { │ │1192 for (0..art_grid.size.width) |x| { │ │1166 - const src = art_grid.cells.items[try art_grid.cells.at(.{ y, x })]; │ │1193 + const src = (try art_grid.cell(x, y)).*; │ │1194 if (src.rune == null) continue; │ │1168 - const idx = try out.cells.at(.{ y, anchor_x + x }); │ │1169 - const dst = &out.cells.items[idx]; │ │1195 + const dst = try out.cell(anchor_x + x, y); │ │1196 + const wide = x + 1 < art_grid.size.width and (try art_grid.cell(x + 1, y)).continuation;│ │1197 + const next_dst = if (wide) try out.cell(anchor_x + x + 1, y) else null; │ │1198 // blank cells take the art; occupied cells take its background │ │1171 - if (cellIsBlank(dst.*)) { │ │1172 - dst.* = src; │ │1199 + if (cellIsBlank(dst.*) and (next_dst == null or cellIsBlank(next_dst.?.*))) { │ │1200 + dst.style = src.style; │ │1201 dst.style.fg = sgrSafe(dst.style.fg); │ │1202 dst.style.bg = sgrSafe(dst.style.bg); │ │1203 + try out.setRune(anchor_x + x, y, src.rune); │ │1204 } else { │ │1176 - const background_maybe = sgrSafe(src.style.bg orelse src.style.fg); │ │1177 - dst.style.bg = background_maybe; │ │1178 - // change unstyled text to contrast with the art behind it │ │1179 - if (dst.style.fg == null) { │ │1180 - if (background_maybe) |background| { │ │1181 - // use near-black and near-white, which is easier on the eyes │ │1182 - // while retaining high contrast │ │1183 - const luminance = (@as(u32, background.r) * 299 + │ │1184 - @as(u32, background.g) * 587 + │ │1185 - @as(u32, background.b) * 114) / 1000; │ │1186 - dst.style.fg = if (luminance >= 128) │ │1187 - .{ .r = 16, .g = 16, .b = 16 } │ │1188 - else │ │1189 - .{ .r = 240, .g = 240, .b = 240 }; │ │1190 - } │ │1191 - } │ │1205 + applyArtBackground(dst, src); │ │1206 + if (next_dst) |next| applyArtBackground(next, src); │ │1207 } │ │1208 } │ │1209 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1235 errdefer box.deinit(allocator); │ │1236 │ │1237 if (choices.len > 1) for (choices) |choice| { │ │1223 - var selector_box = try wgt.TextBox(Widget).init(allocator, choice.selector, .{ .border_style = .hidden, .wrap_kind = .none }); │ │1238 + var selector_box = try wgt.TextBox.init(allocator, choice.selector, .{ .border_style = .hidden, .wrap_kind = .none }); │ │1239 errdefer selector_box.deinit(allocator); │ │1240 selector_box.getFocus().focusable = true; │ │1241 try box.children.put(allocator, selector_box.getFocus().id, .{ .widget = .{ .text_box = selector_box }, .rect = null, .min_size = .{ .width = choice.selector.len + 2, .height = 3 } });│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │1242 }; │ │1243 │ │1229 - var text_input = try wgt.TextInput(Widget).init(allocator, .{│ │1244 + var text_input = try wgt.TextInput.init(allocator, .{ │ │1245 .border_style = .single, │ │1246 .label = choices[0].label, │ │1247 .bottom_label = choices[0].bottom_label, │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1365 self.session.host_request = .{ .show_copyable_text = choice.copyable_text orelse choice.text }; │ │1366 } │ │1367 │ │1353 - fn textInput(self: *CopyableText) *wgt.TextInput(Widget) { │ │1368 + fn textInput(self: *CopyableText) *wgt.TextInput { │ │1369 return &self.box.children.values()[if (self.choices.len > 1) self.choices.len else 0].widget.text_input;│ │1370 } │ │1371 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │1357 - fn selector(self: *CopyableText, index: usize) *wgt.TextBox(Widget) {│ │1372 + fn selector(self: *CopyableText, index: usize) *wgt.TextBox { │ │1373 return &self.box.children.values()[index].widget.text_box; │ │1374 } │ │1375 │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯