╭ changed ─────────────╮ │src/ui/Home/Header.zig│ ╰──────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │150 text_box.getFocus().kind = .{ .custom = settings_link }; │ │151 try tab_ids.put(allocator, text_box.getFocus().id, {}); │ │152 if (std.mem.eql(u8, settings_link, current_link)) selected_tab = text_box.getFocus().id;│ │141 - try box.children.put(allocator, text_box.getFocus().id, .{ │ │153 + try tabs_box.children.put(allocator, text_box.getFocus().id, .{ │ │154 .widget = .{ .text_box = text_box }, │ │155 .rect = null, │ │156 .min_size = .{ .width = settings_tab_label.len + 2, .height = null }, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │164 auth_tab.text_box.getFocus().kind = .{ .custom = auth_link }; │ │165 try tab_ids.put(allocator, auth_tab.getFocus().id, {}); │ │166 if (std.mem.eql(u8, auth_link, current_link)) selected_tab = auth_tab.getFocus().id;│ │155 - try box.children.put(allocator, auth_tab.getFocus().id, .{ │ │167 + try tabs_box.children.put(allocator, auth_tab.getFocus().id, .{ │ │168 .widget = .{ .auth_tab = auth_tab }, │ │169 .rect = null, │ │170 .min_size = .{ .width = auth_tab.minWidth(), .height = null }, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │178 text_box.getFocus().mode = .all; │ │179 text_box.getFocus().kind = .{ .custom = ui.Quit.tab_kind }; │ │180 try tab_ids.put(allocator, text_box.getFocus().id, {}); │ │169 - try box.children.put(allocator, text_box.getFocus().id, .{ │ │181 + try tabs_box.children.put(allocator, text_box.getFocus().id, .{│ │182 .widget = .{ .text_box = text_box }, │ │183 .rect = null, │ │184 // the label is a single column; +2 for the border │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │186 }); │ │187 } │ │188 │ │177 - var self = View{ │ │189 + tabs_box.getFocus().child_id = selected_tab orelse tab_ids.keys()[0]; │ │190 + const title_id = title_box.getFocus().id; │ │191 + const tabs_id = tabs_box.getFocus().id; │ │192 + try box.children.put(allocator, title_id, .{ .widget = .{ .box = title_box }, .rect = null, .min_size = null }); │ │193 + title_box_owned = true; │ │194 + // a blank row between the title and the tabs, shown only when they wrap │ │195 + const gap_id = blk: { │ │196 + var gap = try wgt.Text.init(allocator, " "); │ │197 + errdefer gap.deinit(allocator); │ │198 + const id = gap.getFocus().id; │ │199 + try box.children.put(allocator, id, .{ .widget = .{ .text = gap }, .rect = null, .min_size = null, .hidden = true }); │ │200 + break :blk id; │ │201 + }; │ │202 + try box.children.put(allocator, tabs_id, .{ .widget = .{ .box = tabs_box }, .rect = null, .min_size = null }); │ │203 + tabs_box_owned = true; │ │204 + box.getFocus().child_id = tabs_id; │ │205 + │ │206 + return .{ │ │207 .scroll = try wgt.Scroll(ui.Widget).init(allocator, .{ .box = box }, .{ .direction = .horiz, .show_bar = false, .web_native = !session.is_terminal }),│ │208 .tab_ids = tab_ids, │ │209 + .tabs_id = tabs_id, │ │210 + .gap_id = gap_id, │ │211 + .first_group_width = first_group_width, │ │212 .session = session, │ │213 }; │ │182 - self.getFocus().child_id = selected_tab orelse self.tab_ids.keys()[0]; │ │183 - return self; │ │214 } │ │215 │ │216 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │221 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ │222 self.clearGrid(); │ │223 const box = &self.scroll.child.box; │ │194 - ui.widget.setBackButtonVisible(box, self.session.back == .available); │ │195 - for (box.children.keys(), box.children.values()) |id, *child| { │ │224 + const back_visible = self.session.back == .available; │ │225 + ui.widget.setBackButtonVisible(box, back_visible); │ │226 + const tabs_child = self.tabsChild(); │ │227 + const tabs_box = &tabs_child.widget.box; │ │228 + │ │229 + // only the selected tab shows its border │ │230 + const selected_tab = if (box.getFocus().child_id == self.tabs_id) tabs_box.getFocus().child_id else null; │ │231 + var tabs_width: usize = 0; │ │232 + for (tabs_box.children.keys(), tabs_box.children.values()) |id, *child| { │ │233 const tb: ?*wgt.TextBox = switch (child.widget) { │ │234 .text_box => |*x| x, │ │235 .auth_tab => |*at| blk: { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │239 }, │ │240 else => null, │ │241 }; │ │205 - if (tb) |t| ui.widget.markSelected(t, self.getFocus().child_id == id); │ │242 + if (tb) |t| ui.widget.markSelected(t, selected_tab == id); │ │243 + tabs_width += if (child.min_size) |min_size| min_size.width orelse 0 else 0; │ │244 } │ │245 + │ │246 + // the outer box's hidden border occupies two columns. keep the title │ │247 + // and tabs together if they fit; otherwise tab strip on its own row. │ │248 + const viewport_width = constraint.max_size.width orelse constraint.min_size.width; │ │249 + const content_width = if (viewport_width) |width| width -| 2 else null; │ │250 + const back_width: usize = if (back_visible) ui.widget.back_button_width else 0; │ │251 + const wrap = if (content_width) |width| self.first_group_width + back_width + tabs_width > width else false;│ │252 + box.options.direction = if (wrap) .vert else .horiz; │ │253 + tabs_child.min_size = if (wrap) .{ .width = content_width, .height = null } else null; │ │254 + (box.children.getPtr(self.gap_id) orelse unreachable).hidden = !wrap; │ │255 + │ │256 var scroll_constraint = constraint; │ │208 - scroll_constraint.min_size.width = constraint.max_size.width orelse constraint.min_size.width; │ │257 + scroll_constraint.min_size.width = viewport_width; │ │258 try self.scroll.build(allocator, scroll_constraint, root_focus); │ │259 } │ │260 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │264 if (inp.moveTab(key, current_tab, self.tab_ids.count())) |new_tab| { │ │265 const tab_id = self.tab_ids.keys()[new_tab]; │ │266 root_focus.setFocus(tab_id); │ │218 - const tab = self.scroll.child.box.children.get(tab_id) orelse return;│ │219 - if (tab.rect) |rect| self.scroll.scrollToRect(rect); │ │267 + const tabs_child = self.tabsChild(); │ │268 + const tabs_rect = tabs_child.rect orelse return; │ │269 + const tab = tabs_child.widget.box.children.get(tab_id) orelse return;│ │270 + var rect = tab.rect orelse return; │ │271 + rect.x += tabs_rect.x; │ │272 + rect.y += tabs_rect.y; │ │273 + self.scroll.scrollToRect(rect); │ │274 } │ │275 } │ │276 │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │291 } │ │292 │ │293 fn currentTabIndex(self: View) ?usize { │ │240 - const child_id = self.scroll.child.box.focus.child_id orelse return null; │ │294 + if (self.scroll.child.box.focus.child_id != self.tabs_id) return null; │ │295 + const child_id = self.tabsChild().widget.box.focus.child_id orelse return null;│ │296 return self.tab_ids.getIndex(child_id); │ │297 + } │ │298 + │ │299 + fn tabsChild(self: *const View) *wgt.Box(ui.Widget).Child { │ │300 + return self.scroll.child.box.children.getPtr(self.tabs_id) orelse unreachable; │ │301 } │ │302 }; │ │303 │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯