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