╭ Title.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const ui = @import("../ui.zig"); 3 const xit = @import("xit"); 4 const xitui = xit.xitui; 5 const wgt = xitui.widget; 6 const layout = xitui.layout; 7 const inp = xitui.input; 8 const Grid = xitui.grid.Grid; 9 const Focus = xitui.focus.Focus; 10 11 content: []const u8, 12 width: usize, 13 14 const Self = @This(); 15 16 pub fn init(arena: *std.heap.ArenaAllocator, orig_content: []const u8) !Self { 17 const rendered = try renderTitle(arena.allocator(), orig_content); 18 // count cells in the first row: one per UTF-8 codepoint start 19 var width: usize = 0; 20 for (rendered) |byte| { 21 if (byte == '\n') break; 22 if (byte & 0xC0 != 0x80) width += 1; 23 } 24 return .{ 25 .content = rendered, 26 .width = width, 27 }; 28 } 29 30 pub const View = struct { 31 text_box: wgt.TextBox(ui.Widget), 32 data: *const Self, 33 34 pub fn init(allocator: std.mem.Allocator, data: *const Self) !View { 35 var text_box = try wgt.TextBox(ui.Widget).init(allocator, data.content, .{ .border_style = null, .wrap_kind = .none }); 36 errdefer text_box.deinit(allocator); 37 return .{ .text_box = text_box, .data = data }; 38 } 39 40 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 41 self.text_box.deinit(allocator); 42 } 43 44 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 45 try self.text_box.build(allocator, constraint, root_focus); 46 } 47 48 pub fn input(self: *View, allocator: std.mem.Allocator, key: inp.Key, root_focus: *Focus) !void { 49 _ = self; 50 _ = allocator; 51 _ = key; 52 _ = root_focus; 53 } 54 55 pub fn clearGrid(self: *View) void { 56 self.text_box.clearGrid(); 57 } 58 59 pub fn getGrid(self: View) ?Grid { 60 return self.text_box.getGrid(); 61 } 62 63 pub fn getFocus(self: *View) *Focus { 64 return self.text_box.getFocus(); 65 } 66 }; 67 68 // 3-row sextant title font with row-aligned dither. 69 // 70 // each glyph is a 6x9 sub-pixel bitmap that renders as a 3x3 grid of 71 // sextant-block characters (U+1FB00-1FB3B). the four 2x3 patterns that 72 // aren't sextants (empty, left column, right column, full) fall back to 73 // U+0020, U+258C, U+2590, U+2588 respectively. 74 75 const Glyph = [9][]const u8; 76 77 fn glyphFor(c: u8) ?Glyph { 78 return switch (std.ascii.toUpper(c)) { 79 ' ' => .{ 80 "......", 81 "......", 82 "......", 83 "......", 84 "......", 85 "......", 86 "......", 87 "......", 88 "......", 89 }, 90 'A' => .{ 91 "..##..", 92 ".####.", 93 ".#..#.", 94 "##..##", 95 "######", 96 "#....#", 97 "#....#", 98 "#....#", 99 "#....#", 100 }, 101 'B' => .{ 102 "#####.", 103 "#....#", 104 "#....#", 105 "#....#", 106 "#####.", 107 "#....#", 108 "#....#", 109 "#....#", 110 "#####.", 111 }, 112 'C' => .{ 113 ".####.", 114 "#....#", 115 "#.....", 116 "#.....", 117 "#.....", 118 "#.....", 119 "#.....", 120 "#....#", 121 ".####.", 122 }, 123 'D' => .{ 124 "#####.", 125 "#....#", 126 "#....#", 127 "#....#", 128 "#....#", 129 "#....#", 130 "#....#", 131 "#....#", 132 "#####.", 133 }, 134 'E' => .{ 135 "######", 136 "#.....", 137 "#.....", 138 "#.....", 139 "#####.", 140 "#.....", 141 "#.....", 142 "#.....", 143 "######", 144 }, 145 'F' => .{ 146 "######", 147 "#.....", 148 "#.....", 149 "#.....", 150 "#####.", 151 "#.....", 152 "#.....", 153 "#.....", 154 "#.....", 155 }, 156 'G' => .{ 157 ".####.", 158 "#....#", 159 "#.....", 160 "#.....", 161 "#..###", 162 "#....#", 163 "#....#", 164 "#....#", 165 ".####.", 166 }, 167 'H' => .{ 168 "#....#", 169 "#....#", 170 "#....#", 171 "#....#", 172 "######", 173 "#....#", 174 "#....#", 175 "#....#", 176 "#....#", 177 }, 178 'I' => .{ 179 "######", 180 "..##..", 181 "..##..", 182 "..##..", 183 "..##..", 184 "..##..", 185 "..##..", 186 "..##..", 187 "######", 188 }, 189 'J' => .{ 190 "....##", 191 "....##", 192 "....##", 193 "....##", 194 "....##", 195 "....##", 196 "#...##", 197 "#..##.", 198 ".##...", 199 }, 200 'K' => .{ 201 "#....#", 202 "#...#.", 203 "#..#..", 204 "#.#...", 205 "##....", 206 "#.#...", 207 "#..#..", 208 "#...#.", 209 "#....#", 210 }, 211 'L' => .{ 212 "#.....", 213 "#.....", 214 "#.....", 215 "#.....", 216 "#.....", 217 "#.....", 218 "#.....", 219 "#.....", 220 "######", 221 }, 222 'M' => .{ 223 "#....#", 224 "##..##", 225 "#.##.#", 226 "#.##.#", 227 "#....#", 228 "#....#", 229 "#....#", 230 "#....#", 231 "#....#", 232 }, 233 'N' => .{ 234 "#....#", 235 "##...#", 236 "##...#", 237 "#.#..#", 238 "#..#.#", 239 "#...##", 240 "#...##", 241 "#....#", 242 "#....#", 243 }, 244 'O' => .{ 245 ".####.", 246 "#....#", 247 "#....#", 248 "#....#", 249 "#....#", 250 "#....#", 251 "#....#", 252 "#....#", 253 ".####.", 254 }, 255 'P' => .{ 256 "#####.", 257 "#....#", 258 "#....#", 259 "#....#", 260 "#####.", 261 "#.....", 262 "#.....", 263 "#.....", 264 "#.....", 265 }, 266 'Q' => .{ 267 ".####.", 268 "#....#", 269 "#....#", 270 "#....#", 271 "#....#", 272 "#....#", 273 "#..#.#", 274 ".####.", 275 ".....#", 276 }, 277 'R' => .{ 278 "#####.", 279 "#....#", 280 "#....#", 281 "#....#", 282 "#####.", 283 "#.#...", 284 "#..#..", 285 "#...#.", 286 "#....#", 287 }, 288 'S' => .{ 289 ".####.", 290 "#....#", 291 "#.....", 292 "#.....", 293 ".####.", 294 ".....#", 295 ".....#", 296 "#....#", 297 ".####.", 298 }, 299 'T' => .{ 300 "######", 301 "..##..", 302 "..##..", 303 "..##..", 304 "..##..", 305 "..##..", 306 "..##..", 307 "..##..", 308 "..##..", 309 }, 310 'U' => .{ 311 "#....#", 312 "#....#", 313 "#....#", 314 "#....#", 315 "#....#", 316 "#....#", 317 "#....#", 318 "#....#", 319 ".####.", 320 }, 321 'V' => .{ 322 "#....#", 323 "#....#", 324 "#....#", 325 ".#..#.", 326 ".#..#.", 327 ".#..#.", 328 ".#..#.", 329 ".#..#.", 330 "..##..", 331 }, 332 'W' => .{ 333 "#....#", 334 "#....#", 335 "#....#", 336 "#....#", 337 "#....#", 338 "#.##.#", 339 "#.##.#", 340 "##..##", 341 ".#..#.", 342 }, 343 'X' => .{ 344 "#....#", 345 ".#..#.", 346 ".#..#.", 347 "..##..", 348 "..##..", 349 "..##..", 350 ".#..#.", 351 ".#..#.", 352 "#....#", 353 }, 354 'Y' => .{ 355 "#....#", 356 ".#..#.", 357 ".#..#.", 358 "..##..", 359 "..##..", 360 "..##..", 361 "..##..", 362 "..##..", 363 "..##..", 364 }, 365 'Z' => .{ 366 "######", 367 "....#.", 368 "....#.", 369 "...#..", 370 "..##..", 371 "..#...", 372 ".#....", 373 ".#....", 374 "######", 375 }, 376 '0' => .{ 377 ".####.", 378 "#...##", 379 "#..#.#", 380 "#..#.#", 381 "#.#..#", 382 "#.#..#", 383 "##...#", 384 "#....#", 385 ".####.", 386 }, 387 '1' => .{ 388 "..##..", 389 ".###..", 390 "..##..", 391 "..##..", 392 "..##..", 393 "..##..", 394 "..##..", 395 "..##..", 396 "######", 397 }, 398 '2' => .{ 399 ".####.", 400 "#....#", 401 ".....#", 402 "....#.", 403 "...#..", 404 "..#...", 405 ".#....", 406 "#.....", 407 "######", 408 }, 409 '3' => .{ 410 ".####.", 411 "#....#", 412 ".....#", 413 ".....#", 414 ".####.", 415 ".....#", 416 ".....#", 417 "#....#", 418 ".####.", 419 }, 420 '4' => .{ 421 "....#.", 422 "...##.", 423 "..#.#.", 424 ".#..#.", 425 "#...#.", 426 "######", 427 "....#.", 428 "....#.", 429 "....#.", 430 }, 431 '5' => .{ 432 "######", 433 "#.....", 434 "#.....", 435 "#.....", 436 "#####.", 437 ".....#", 438 ".....#", 439 "#....#", 440 ".####.", 441 }, 442 '6' => .{ 443 ".####.", 444 "#....#", 445 "#.....", 446 "#.....", 447 "#####.", 448 "#....#", 449 "#....#", 450 "#....#", 451 ".####.", 452 }, 453 '7' => .{ 454 "######", 455 ".....#", 456 "....#.", 457 "....#.", 458 "...#..", 459 "...#..", 460 "..#...", 461 "..#...", 462 ".#....", 463 }, 464 '8' => .{ 465 ".####.", 466 "#....#", 467 "#....#", 468 "#....#", 469 ".####.", 470 "#....#", 471 "#....#", 472 "#....#", 473 ".####.", 474 }, 475 '9' => .{ 476 ".####.", 477 "#....#", 478 "#....#", 479 "#....#", 480 ".#####", 481 ".....#", 482 ".....#", 483 "#....#", 484 ".####.", 485 }, 486 '-' => .{ 487 "......", 488 "......", 489 "......", 490 "......", 491 ".####.", 492 "......", 493 "......", 494 "......", 495 "......", 496 }, 497 '.' => .{ 498 "......", 499 "......", 500 "......", 501 "......", 502 "......", 503 "......", 504 "......", 505 "..##..", 506 "..##..", 507 }, 508 else => null, 509 }; 510 } 511 512 // map a 2x3 sub-pixel pattern to its corresponding sextant codepoint 513 fn sextantCodepoint(pattern: u6) u21 { 514 return switch (pattern) { 515 0b000000 => ' ', 516 0b010101 => 0x258C, // ▌ left half block 517 0b101010 => 0x2590, // ▐ right half block 518 0b111111 => 0x2588, // █ full block 519 else => blk: { 520 var offset: u21 = @as(u21, pattern) - 1; 521 if (pattern > 0b010101) offset -= 1; 522 if (pattern > 0b101010) offset -= 1; 523 break :blk 0x1FB00 + offset; 524 }, 525 }; 526 } 527 528 fn renderTitle(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { 529 var rows: [3]std.ArrayList(u8) = .{ .empty, .empty, .empty }; 530 defer for (&rows) |*r| r.deinit(allocator); 531 532 var first = true; 533 for (input) |c| { 534 const glyph = glyphFor(c) orelse continue; 535 536 // row-aligned dither: keep on-pixels only at even bitmap rows. 537 var kept: [9][6]bool = std.mem.zeroes([9][6]bool); 538 for (0..9) |r| { 539 if (r % 2 != 0) continue; 540 for (0..6) |col| { 541 kept[r][col] = glyph[r][col] == '#'; 542 } 543 } 544 545 if (!first) { 546 for (&rows) |*r| try r.append(allocator, ' '); 547 } 548 first = false; 549 550 for (0..3) |text_row| { 551 for (0..3) |text_col| { 552 var pattern: u6 = 0; 553 for (0..3) |sub_row| { 554 for (0..2) |sub_col| { 555 const row_idx = text_row * 3 + sub_row; 556 const col_idx = text_col * 2 + sub_col; 557 if (kept[row_idx][col_idx]) { 558 pattern |= @as(u6, 1) << @intCast(sub_row * 2 + sub_col); 559 } 560 } 561 } 562 const cp = sextantCodepoint(pattern); 563 var utf8_buf: [4]u8 = undefined; 564 const len = try std.unicode.utf8Encode(cp, &utf8_buf); 565 try rows[text_row].appendSlice(allocator, utf8_buf[0..len]); 566 } 567 } 568 } 569 570 var out: std.ArrayList(u8) = .empty; 571 errdefer out.deinit(allocator); 572 try out.appendSlice(allocator, rows[0].items); 573 try out.append(allocator, '\n'); 574 try out.appendSlice(allocator, rows[1].items); 575 try out.append(allocator, '\n'); 576 try out.appendSlice(allocator, rows[2].items); 577 return out.toOwnedSlice(allocator); 578 } 579