╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Header.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │355 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ │356 self.clearGrid(); │ │357 const box = &self.scroll.child.box; │ │358 + const back_visible = self.session.back == .available; │ │359 + ui.widget.setBackButtonVisible(box, back_visible); │ │360 const tabs_child = self.tabsChild(); │ │361 const tabs_box = &tabs_child.widget.box; │ │362 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │383 // the outer box's hidden border occupies two columns. keep the title │ │384 // and tabs together if they fit; otherwise tab strip on its own row. │ │385 const content_width = if (viewport_width) |width| width -| 2 else null; │ │380 - const wrap = if (content_width) |width| self.first_group_width + tabs_width > width else false; │ │386 + const back_width: usize = if (back_visible) ui.widget.back_button_width else 0; │ │387 + const wrap = if (content_width) |width| self.first_group_width + back_width + tabs_width > width else false;│ │388 box.options.direction = if (wrap) .vert else .horiz; │ │389 tabs_child.min_size = if (wrap) .{ .width = content_width, .height = null } else null; │ │390 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/User/Header.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │26 pub const View = struct { │ │27 scroll: wgt.Scroll(ui.Widget), │ │28 tab_ids: std.AutoArrayHashMapUnmanaged(usize, void), │ │29 + session: *ui.Session, │ │30 │ │31 pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !View { │ │32 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = .hidden, .rounded_corners = true, .direction = .horiz });│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │34 │ │35 var tab_ids: std.AutoArrayHashMapUnmanaged(usize, void) = .empty; │ │36 errdefer tab_ids.deinit(allocator); │ │37 + │ │38 + try ui.widget.addBackButton(allocator, &box, session); │ │39 │ │40 + { │ │41 + var text = try wgt.Text.init(allocator, " "); │ │42 + errdefer text.deinit(allocator); │ │43 + try box.children.put(allocator, text.getFocus().id, .{ │ │44 + .widget = .{ .text = text }, │ │45 + .rect = null, │ │46 + .min_size = .{ .width = 1, .height = null }, │ │47 + }); │ │48 + } │ │49 + │ │50 // title sits to the left of the tabs │ │51 { │ │52 var title_view = try ui.Title.View.init(allocator, &data.title);│ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │189 var self = View{ │ │190 .scroll = try wgt.Scroll(ui.Widget).init(allocator, .{ .box = box }, .{ .direction = .horiz, .show_bar = false, .web_native = !session.is_terminal }),│ │191 .tab_ids = tab_ids, │ │192 + .session = session, │ │193 }; │ │194 self.getFocus().child_id = selected_tab orelse self.tab_ids.keys()[0]; │ │195 return self; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │204 self.clearGrid(); │ │205 // only the selected tab shows its border │ │206 const box = &self.scroll.child.box; │ │207 + ui.widget.setBackButtonVisible(box, self.session.back == .available);│ │208 for (box.children.keys(), box.children.values()) |id, *child| { │ │209 const tb: ?*wgt.TextBox = switch (child.widget) { │ │210 .text_box => |*x| x, │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/ui/widget.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 99 } │ │100 } │ │101 }; │ │102 + │ │103 +pub const back_button_width = 3; │ │104 +const back_button_height = 3; │ │105 +const back_kind = "back"; │ │106 + │ │107 +pub fn addBackButton(allocator: std.mem.Allocator, box: *wgt.Box(Widget), session: *ui.Session) !void { │ │108 + if (!session.is_terminal) return; │ │109 + var back = try wgt.TextBox.init(allocator, "←", .{ .border_style = .hidden, .wrap_kind = .none }); │ │110 + errdefer back.deinit(allocator); │ │111 + back.getFocus().kind = .{ .custom = back_kind }; │ │112 + try box.children.put(allocator, back.getFocus().id, .{ │ │113 + .widget = .{ .text_box = back }, │ │114 + .rect = null, │ │115 + .min_size = .{ .width = back_button_width, .height = back_button_height }, │ │116 + .max_size = .{ .width = back_button_width, .height = back_button_height }, │ │117 + .hidden = session.back != .available, │ │118 + }); │ │119 +} │ │120 + │ │121 +pub fn setBackButtonVisible(box: *wgt.Box(Widget), visible: bool) void { │ │122 + for (box.children.values()) |*child| { │ │123 + if (isBackButton(child.widget.getFocus())) { │ │124 + child.hidden = !visible; │ │125 + continue; │ │126 + } │ │127 + switch (child.widget) { │ │128 + .box => |*nested| setBackButtonVisible(nested, visible), │ │129 + else => {}, │ │130 + } │ │131 + } │ │132 +} │ │133 │ │134 +pub fn isBackButton(focus: *Focus) bool { │ │135 + return switch (focus.kind) { │ │136 + .custom => |kind| std.mem.eql(u8, kind, back_kind), │ │137 + else => false, │ │138 + }; │ │139 +} │ │140 + │ │141 // move the focused row in `box` by `delta`, clamped to the ends, scrolling │ │142 // `scroll` to keep it visible. │ │143 pub fn moveRowFocus(box: *wgt.Box(Widget), scroll: *wgt.Scroll(Widget), root_focus: *Focus, delta: isize) void {│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯