╭ changed ─────────────╮ │src/ui/Home/Header.zig│ ╰──────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────╮ │ 8 const Grid = xitui.grid.Grid; │ │ 9 const Focus = xitui.focus.Focus; │ │10 │ │11 -const Self = @This(); │ │11 +pub const AuthTab = @import("./AuthTab.zig");│ │12 │ │13 -const Tab = enum { users, repos }; │ │13 +auth_tab: AuthTab, │ │14 + │ │15 +const Self = @This(); │ │16 │ │17 pub fn init() Self { │ │16 - return .{}; │ │18 + return .{ .auth_tab = AuthTab.init() }; │ │19 } │ │20 │ │21 pub const View = struct { │ ╰─────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │22 box: wgt.Box(ui.Widget), │ │23 data: *const Self, │ │24 + tab_ids: [3]usize, │ │25 │ │23 - pub fn init(allocator: std.mem.Allocator, data: *const Self) !View { │ │26 + const auth_tab_index: usize = 2; │ │27 + // the auth slot must fit either "login" or "logout" │ │28 + const auth_tab_min_width: usize = @as(usize, "logout".len) + 2; │ │29 + │ │30 + pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !View { │ │31 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz });│ │32 errdefer box.deinit(); │ │33 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │27 - inline for (@typeInfo(Tab).@"enum".fields) |tab_field| { │ │28 - const tab: Tab = @enumFromInt(tab_field.value); │ │29 - const name = switch (tab) { │ │30 - .users => "users", │ │31 - .repos => "repos", │ │32 - }; │ │34 + var tab_ids: [3]usize = undefined; │ │35 + │ │36 + // fixed tabs: users, repos │ │37 + const fixed_tab_names = [_][]const u8{ "users", "repos" }; │ │38 + for (fixed_tab_names, 0..) |name, i| { │ │39 var text_box = try wgt.TextBox(ui.Widget).init(allocator, name, .{ .border_style = .single, .wrap_kind = .none });│ │40 errdefer text_box.deinit(); │ │41 text_box.getFocus().focusable = true; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │36 - try box.children.put(box.allocator, text_box.getFocus().id, .{ .widget = .{ .text_box = text_box }, .rect = null, .min_size = null });│ │42 + tab_ids[i] = text_box.getFocus().id; │ │43 + try box.children.put(box.allocator, text_box.getFocus().id, .{ │ │44 + .widget = .{ .text_box = text_box }, │ │45 + .rect = null, │ │46 + .min_size = .{ .width = name.len + 2, .height = null }, │ │47 + }); │ │48 + } │ │49 + │ │50 + // spacer pushes the auth tab to the right │ │51 + { │ │52 + var spacer = ui.Spacer.init(allocator); │ │53 + errdefer spacer.deinit(); │ │54 + try box.children.put(box.allocator, spacer.getFocus().id, .{ │ │55 + .widget = .{ .spacer = spacer }, │ │56 + .rect = null, │ │57 + .min_size = .{ .width = 1, .height = null }, │ │58 + }); │ │59 } │ │60 │ │39 - var self = View{ .box = box, .data = data }; │ │40 - self.getFocus().child_id = box.children.keys()[0]; │ │61 + // auth tab — knows how to swap its own label based on session │ │62 + { │ │63 + var auth_tab = try AuthTab.View.init(allocator, &data.auth_tab, session); │ │64 + errdefer auth_tab.deinit(); │ │65 + tab_ids[auth_tab_index] = auth_tab.getFocus().id; │ │66 + try box.children.put(box.allocator, auth_tab.getFocus().id, .{ │ │67 + .widget = .{ .home_auth_tab = auth_tab }, │ │68 + .rect = null, │ │69 + .min_size = .{ .width = auth_tab_min_width, .height = null }, │ │70 + }); │ │71 + } │ │72 + │ │73 + var self = View{ .box = box, .data = data, .tab_ids = tab_ids }; │ │74 + self.getFocus().child_id = tab_ids[0]; │ │75 return self; │ │76 } │ │77 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │81 │ │82 pub fn build(self: *View, constraint: layout.Constraint, root_focus: *Focus) !void { │ │83 self.clearGrid(); │ │50 - for (self.box.children.keys(), self.box.children.values()) |id, *tab| { │ │51 - tab.widget.text_box.options.border_style = if (self.getFocus().child_id == id)│ │52 - (if (root_focus.grandchild_id == id) .double else .single) │ │53 - else │ │54 - .hidden; │ │84 + for (self.box.children.keys(), self.box.children.values()) |id, *child| { │ │85 + const tb: ?*wgt.TextBox(ui.Widget) = switch (child.widget) { │ │86 + .text_box => |*x| x, │ │87 + .home_auth_tab => |*at| &at.text_box, │ │88 + else => null, │ │89 + }; │ │90 + if (tb) |t| { │ │91 + t.options.border_style = if (self.getFocus().child_id == id) │ │92 + (if (root_focus.grandchild_id == id) .double else .single) │ │93 + else │ │94 + .hidden; │ │95 + } │ │96 } │ │97 try self.box.build(constraint, root_focus); │ │98 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │ 99 │ │100 pub fn input(self: *View, key: inp.Key, root_focus: *Focus) !void {│ │ 60 - if (self.getFocus().child_id) |child_id| { │ │ 61 - const children = &self.box.children; │ │ 62 - if (children.getIndex(child_id)) |current_index| { │ │ 63 - var index = current_index; │ │ 64 - │ │ 65 - switch (key) { │ │ 66 - .arrow_left => { │ │ 67 - index -|= 1; │ │ 68 - }, │ │ 69 - .arrow_right => { │ │ 70 - if (index + 1 < children.count()) { │ │ 71 - index += 1; │ │ 72 - } │ │ 73 - }, │ │ 74 - else => {}, │ │ 75 - } │ │ 76 - │ │ 77 - if (index != current_index) { │ │ 78 - try root_focus.setFocus(children.keys()[index]); │ │ 79 - } │ │ 80 - } │ │101 + const current_tab = self.currentTabIndex() orelse return; │ │102 + var new_tab = current_tab; │ │103 + switch (key) { │ │104 + .arrow_left => new_tab -|= 1, │ │105 + .arrow_right => if (new_tab + 1 < self.tab_ids.len) { │ │106 + new_tab += 1; │ │107 + }, │ │108 + else => {}, │ │109 + } │ │110 + if (new_tab != current_tab) { │ │111 + try root_focus.setFocus(self.tab_ids[new_tab]); │ │112 } │ │113 } │ │114 │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────╮ │125 } │ │126 │ │127 pub fn getSelectedIndex(self: View) ?usize { │ │ 97 - if (self.box.focus.child_id) |child_id| { │ │ 98 - return self.box.children.getIndex(child_id); │ │ 99 - } else { │ │100 - return null; │ │128 + return self.currentTabIndex(); │ │129 + } │ │130 + │ │131 + fn currentTabIndex(self: View) ?usize { │ │132 + const child_id = self.box.focus.child_id orelse return null;│ │133 + for (self.tab_ids, 0..) |id, i| { │ │134 + if (id == child_id) return i; │ │135 } │ │136 + return null; │ │137 } │ │138 }; │ │139 │ ╰─────────────────────────────────────────────────────────────────────────╯