╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Markdown.zig ─────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
320 self.font_box.deinit(allocator); 321 } 322 323 pub fn build(self: *Heading, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 324 self.clearGrid(); 325 self.focus.clear(); 326 if (try self.wrapFont(allocator, constraint.max_size.width)) { 327 try self.buildShown(allocator, constraint, root_focus, &self.font_box); 328 } else { 329 try self.buildShown(allocator, constraint, root_focus, &self.text); 330 } 331 } 332 333 // fill the font box with the heading wrapped at `max_width`, or return 334 // false when there's no font 335 fn wrapFont(self: *Heading, allocator: std.mem.Allocator, max_width: ?usize) !bool { 336 const font = self.font orelse return false; 337 // a glyph and the gap after it, so n glyphs take n * char_width - 1 columns 338 const char_width: usize = if (font == .title) 4 else 3; 339 const max_chars = if (max_width) |w| (w + 1) / char_width else null; 340 341 var arena = std.heap.ArenaAllocator.init(allocator); 342 defer arena.deinit(); 343 const aa = arena.allocator(); 344 // the fonts only cover ascii, so each byte is one char 345 const chars = try aa.alloc(u21, self.plain.len); 346 for (self.plain, chars) |c, *char| char.* = c; 347 var lines: std.ArrayList(wgt.Line) = .empty; 348 try wgt.wrapLines(aa, &lines, chars, .word, max_chars); 349 350 // a blank row between lines keeps the glyphs from touching 351 var out: std.ArrayList(u8) = .empty; 352 for (lines.items, 0..) |line, i| { 353 if (i > 0) try out.appendSlice(aa, "\n\n"); 354 const text = std.mem.trimEnd(u8, self.plain[line.start..line.end], " "); 355 try out.appendSlice(aa, switch (font) { 356 .title => (try ui.Title.init(&arena, text, .solid)).content, 357 .sub_title => (try ui.SubTitle.init(&arena, text)).content, 358 }); 359 } 360 try self.font_box.setContent(allocator, out.items); 361 return true; 362 } 363 364 fn buildShown(self: *Heading, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus, widget: anytype) !void { 365 try widget.build(allocator, constraint, root_focus); 366 const grid = widget.getGrid() orelse return; 367 try self.focus.addChild(allocator, widget.getFocus(), grid.size, 0, 0); 368 } 369 370 pub fn input(self: *Heading, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 371 _ = self; 372 _ = allocator; 373 _ = key; 374 _ = root_focus; 375 } 376 377 pub fn clearGrid(self: *Heading) void { 378 self.text.clearGrid(); 379 self.font_box.clearGrid(); 380 } 381 382 pub fn getGrid(self: Heading) ?Grid { 383 // only the one last built has a grid 384 return self.font_box.getGrid() orelse self.text.getGrid(); 385 } 386 387 pub fn getFocus(self: *Heading) *Focus { 388 return self.focus; 389 } 390 }; 391 392 // a marker column beside a child, so wrapped lines hang under the child's 393 // first line: a list item's bullet, or a quote's bar on every row. 394 pub const Gutter = struct { 395 focus: *Focus, 396 grid: ?Grid, 397 child: wgt.Box(ui.Widget), 398 marker: []const u8, 399 // draw the marker on every row rather than only the first 400 repeat: bool, 401 style: Grid.Style, 402 403 // takes ownership of `child` on success 404 fn init(allocator: std.mem.Allocator, marker: []const u8, repeat: bool, style: Grid.Style, child: wgt.Box(ui.Widget)) !Gutter { 405 return .{ .focus = try Focus.create(allocator, .container), .grid = null, .child = child, .marker = marker, .repeat = repeat, .style = style }; 406 } 407 408 pub fn deinit(self: *Gutter, allocator: std.mem.Allocator) void { 409 self.focus.destroy(allocator); 410 self.clearGrid(); 411 self.child.deinit(allocator); 412 } 413 414 pub fn build(self: *Gutter, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 415 self.clearGrid(); 416 self.focus.clear(); 417 const marker_width = try xitui.width.displayWidth(self.marker); 418 if (constraint.max_size.width) |w| if (w <= marker_width) return; 419 if (constraint.max_size.height == 0) return; 420 421 try self.child.build(allocator, .{ 422 .min_size = .{ .width = null, .height = null }, 423 .max_size = .{ .width = if (constraint.max_size.width) |w| w - marker_width else null, .height = constraint.max_size.height }, 424 }, root_focus); 425 const child_grid = self.child.getGrid(); 426 const child_size: layout.Size = if (child_grid) |g| g.size else .{ .width = 0, .height = 0 }; 427 428 var grid = try Grid.init(allocator, .{ .width = marker_width + child_size.width, .height = @max(1, child_size.height) }); 429 errdefer grid.deinit(); 430 for (0..if (self.repeat) grid.size.height else 1) |y| { 431 var x: usize = 0; 432 var utf8 = (try std.unicode.Utf8View.init(self.marker)).iterator(); 433 while (utf8.nextCodepoint()) |rune| { 434 (try grid.cell(x, y)).style = self.style; 435 try grid.setRune(x, y, rune); 436 x += xitui.width.cellWidth(rune); 437 } 438 } 439 if (child_grid) |g| { 440 try grid.drawGrid(g, marker_width, 0); 441 try self.focus.addChild(allocator, self.child.getFocus(), g.size, marker_width, 0); 442 } 443 self.grid = grid; 444 } 445 446 pub fn input(self: *Gutter, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 447 _ = self; 448 _ = allocator; 449 _ = key; 450 _ = root_focus; 451 } 452 453 pub fn clearGrid(self: *Gutter) void { 454 if (self.grid) |*grid| { 455 grid.deinit(); 456 self.grid = null; 457 } 458 self.child.clearGrid(); 459 } 460 461 pub fn getGrid(self: Gutter) ?Grid { 462 return self.grid; 463 } 464 465 pub fn getFocus(self: *Gutter) *Focus { 466 return self.focus; 467 } 468 }; 469