╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │509 focus: Focus, │ │510 grid: ?Grid, │ │511 text_boxes: std.ArrayList(wgt.TextBox(Widget)), │ │299 - lines: std.ArrayList([]const u8), │ │512 + // backs the per-item strings the text boxes borrow: each box's `content` │ │513 + // and, for link items, its `.custom` focus kind (e.g. "a:/user/foo"). reset│ │514 + // wholesale on each setItems so there's no per-string ownership to track. │ │515 + arena: std.heap.ArenaAllocator, │ │516 // column count from the last build — FlowBox.Scroll.input uses it so arrow │ │517 // up/down can step by a row's worth of items. │ │518 last_cols: usize, │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │525 cell_height: usize = 3, │ │526 }; │ │527 │ │312 - pub fn init(options: Options) FlowBox { │ │528 + pub fn init(allocator: std.mem.Allocator, options: Options) FlowBox {│ │529 return .{ │ │530 .focus = Focus.init(.container), │ │531 .grid = null, │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │532 .text_boxes = .empty, │ │317 - .lines = .empty, │ │533 + .arena = std.heap.ArenaAllocator.init(allocator),│ │534 .last_cols = 1, │ │535 .options = options, │ │536 }; │ ╰──────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │544 } │ │545 for (self.text_boxes.items) |*tb| tb.deinit(allocator); │ │546 self.text_boxes.deinit(allocator); │ │331 - for (self.lines.items) |line| allocator.free(line); │ │332 - self.lines.deinit(allocator); │ │547 + self.arena.deinit(); │ │548 } │ │549 │ │335 - pub fn setItems(self: *FlowBox, allocator: std.mem.Allocator, items: []const []const u8) !void { │ │550 + // `links`, when given, is parallel to `items`: a non-empty entry sets that │ │551 + // item's focus kind to `.{ .custom = link }` (e.g. "a:/user/foo"), which the │ │552 + // web renderer turns into an anchor. pass null when items aren't links. │ │553 + // both the item text and the link are copied into the arena, so the caller's │ │554 + // slices needn't outlive this call. │ │555 + pub fn setItems(self: *FlowBox, allocator: std.mem.Allocator, items: []const []const u8, links: ?[]const []const u8) !void {│ │556 for (self.text_boxes.items) |*tb| tb.deinit(allocator); │ │557 self.text_boxes.clearAndFree(allocator); │ │558 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │339 - for (self.lines.items) |line| allocator.free(line); │ │340 - self.lines.clearAndFree(allocator); │ │559 + // the old text boxes (and their borrowed content/links) are gone now, so│ │560 + // the arena that backed those strings can be reclaimed. │ │561 + _ = self.arena.reset(.retain_capacity); │ │562 + const aa = self.arena.allocator(); │ │563 │ │564 self.focus.clear(); │ │565 self.focus.child_id = null; │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │566 │ │345 - for (items) |item| { │ │346 - const line = try allocator.dupe(u8, item); │ │347 - { │ │348 - errdefer allocator.free(line); │ │349 - try self.lines.append(allocator, line); │ │350 - } │ │567 + for (items, 0..) |item, i| { │ │568 + const line = try aa.dupe(u8, item); │ │569 │ │570 var text_box = try wgt.TextBox(Widget).init(allocator, line, .{ .border_style = .hidden, .rounded_corners = true, .wrap_kind = .word });│ │571 errdefer text_box.deinit(allocator); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │572 text_box.getFocus().focusable = true; │ │573 + │ │574 + if (links) |link_items| { │ │575 + if (i < link_items.len and link_items[i].len > 0) { │ │576 + text_box.getFocus().kind = .{ .custom = try aa.dupe(u8, link_items[i]) };│ │577 + } │ │578 + } │ │579 + │ │580 try self.text_boxes.append(allocator, text_box); │ │581 } │ │582 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │675 scroll: wgt.Scroll(Widget), │ │676 │ │677 pub fn init(allocator: std.mem.Allocator, options: FlowBox.Options) !Scroll { │ │453 - var layout_inner = FlowBox.init(options); │ │678 + var layout_inner = FlowBox.init(allocator, options); │ │679 errdefer layout_inner.deinit(allocator); │ │680 var scroll = try wgt.Scroll(Widget).init(allocator, .{ .flow_box = layout_inner }, .vert);│ │681 errdefer scroll.deinit(allocator); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │686 self.scroll.deinit(allocator); │ │687 } │ │688 │ │464 - pub fn setItems(self: *Scroll, allocator: std.mem.Allocator, items: []const []const u8) !void { │ │689 + pub fn setItems(self: *Scroll, allocator: std.mem.Allocator, items: []const []const u8, links: ?[]const []const u8) !void {│ │690 self.scroll.x = 0; │ │691 self.scroll.y = 0; │ │467 - try self.inner().setItems(allocator, items); │ │692 + try self.inner().setItems(allocator, items, links); │ │693 } │ │694 │ │695 pub fn build(self: *Scroll, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯