╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Header.zig ───────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
71 .shrink = true, 72 }); 73 } 74 75 // spacer 76 { 77 var text = try wgt.Text(ui.Widget).init(allocator, " "); 78 errdefer text.deinit(allocator); 79 try box.children.put(allocator, text.getFocus().id, .{ 80 .widget = .{ .text = text }, 81 .rect = null, 82 .min_size = .{ .width = 1, .height = null }, 83 }); 84 } 85 86 // every tab link is repo-scoped so selecting one stays on this page — 87 // switching its stack and updating the url — instead of navigating to 88 // the global settings or auth pages. the files tab routes through the 89 // shared helper so the /repo/.../files url format lives in one place. 90 const identity = try std.fmt.allocPrint(aa, "{s}/{s}", .{ data.owner_name, data.name }); 91 const files_route = ui.RoutablePage.repoFilesRoute(identity, "") orelse return error.RouteTooLong; 92 const files_link = try std.fmt.allocPrint(aa, "a:{s}", .{try files_route.urlAlloc(session.page_arena)}); 93 const commits_route = ui.RoutablePage.repoCommitsRoute(identity, "", 0) orelse return error.RouteTooLong; 94 const commits_link = try std.fmt.allocPrint(aa, "a:{s}", .{try commits_route.urlAlloc(session.page_arena)}); 95 const settings_link = try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}/settings", .{ data.owner_name, data.name }); 96 const auth_link = try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}/auth", .{ data.owner_name, data.name }); 97 98 // files tab 99 { 100 var text_box = try wgt.TextBox(ui.Widget).init(allocator, "files", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); 101 errdefer text_box.deinit(allocator); 102 text_box.getFocus().focusable = true; 103 text_box.getFocus().kind = .{ .custom = files_link }; 104 try tab_ids.put(allocator, text_box.getFocus().id, {}); 105 try box.children.put(allocator, text_box.getFocus().id, .{ 106 .widget = .{ .text_box = text_box }, 107 .rect = null, 108 .min_size = .{ .width = "files".len + 2, .height = null }, 109 }); 110 } 111 112 // commits tab 113 { 114 var text_box = try wgt.TextBox(ui.Widget).init(allocator, "commits", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); 115 errdefer text_box.deinit(allocator); 116 text_box.getFocus().focusable = true; 117 text_box.getFocus().kind = .{ .custom = commits_link }; 118 try tab_ids.put(allocator, text_box.getFocus().id, {}); 119 try box.children.put(allocator, text_box.getFocus().id, .{ 120 .widget = .{ .text_box = text_box }, 121 .rect = null, 122 .min_size = .{ .width = "commits".len + 2, .height = null }, 123 }); 124 } 125 126 // spacer pushes settings + auth to the right 127 { 128 var spacer = try ui.Spacer.init(allocator); 129 errdefer spacer.deinit(allocator); 130 try box.children.put(allocator, spacer.getFocus().id, .{ 131 .widget = .{ .spacer = spacer }, 132 .rect = null, 133 .min_size = null, 134 }); 135 } 136 137 // settings tab 138 { 139 var text_box = try wgt.TextBox(ui.Widget).init(allocator, "settings", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); 140 errdefer text_box.deinit(allocator); 141 text_box.getFocus().focusable = true; 142 text_box.getFocus().kind = .{ .custom = settings_link }; 143 try tab_ids.put(allocator, text_box.getFocus().id, {}); 144 try box.children.put(allocator, text_box.getFocus().id, .{ 145 .widget = .{ .text_box = text_box }, 146 .rect = null, 147 .min_size = .{ .width = "settings".len + 2, .height = null }, 148 }); 149 } 150 151 // auth tab (login / logout). AuthTab defaults to the global a:/auth link; 152 // repoint its instance at this repo's auth route so it stays local too. 153 { 154 var auth_tab = try AuthTab.View.init(allocator, &data.auth_tab, session); 155 errdefer auth_tab.deinit(allocator); 156 auth_tab.text_box.getFocus().kind = .{ .custom = auth_link }; 157 try tab_ids.put(allocator, auth_tab.getFocus().id, {}); 158 try box.children.put(allocator, auth_tab.getFocus().id, .{ 159 .widget = .{ .auth_tab = auth_tab }, 160 .rect = null, 161 .min_size = .{ .width = AuthTab.min_width, .height = null }, 162 }); 163 } 164 165 // quit tab 166 if (session.is_terminal) { 167 var text_box = try wgt.TextBox(ui.Widget).init(allocator, ui.Quit.tab_label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); 168 errdefer text_box.deinit(allocator); 169 text_box.getFocus().focusable = true; 170 text_box.getFocus().kind = .{ .custom = ui.Quit.tab_kind }; 171 try tab_ids.put(allocator, text_box.getFocus().id, {}); 172 try box.children.put(allocator, text_box.getFocus().id, .{ 173 .widget = .{ .text_box = text_box }, 174 .rect = null, 175 // the label is a single column; +2 for the border 176 .min_size = .{ .width = 1 + 2, .height = null }, 177 }); 178 } 179 180 var self = View{ .box = box, .data = data, .tab_ids = tab_ids }; 181 // open on the tab named by the current route 182 self.getFocus().child_id = self.tab_ids.keys()[ 183 switch (session.data.current_page) { 184 .repo_commits => 1, 185 .repo_settings => 2, 186 .repo_auth => 3, 187 else => 0, 188 } 189 ]; 190 return self; 191 } 192 193 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 194 self.box.deinit(allocator); 195 self.tab_ids.deinit(allocator); 196 } 197 198 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 199 self.clearGrid(); 200 // only the selected tab shows its border 201 for (self.box.children.keys(), self.box.children.values()) |id, *child| { 202 const tb: ?*wgt.TextBox(ui.Widget) = switch (child.widget) { 203 .text_box => |*x| x, 204 .auth_tab => |*at| &at.text_box, 205 else => null, 206 }; 207 if (tb) |t| { 208 t.options.border_style = if (self.getFocus().child_id == id) .single else .hidden; 209 } 210 } 211 try self.box.build(allocator, constraint, root_focus); 212 } 213 214 pub fn input(self: *View, allocator: std.mem.Allocator, key: inp.Key, root_focus: *Focus) !void { 215 _ = allocator; 216 const current_tab = self.currentTabIndex() orelse return; 217 var new_tab = current_tab; 218 switch (key) { 219 .arrow_left => new_tab -|= 1, 220 .arrow_right => if (new_tab + 1 < self.tab_ids.count()) { 221 new_tab += 1; 222 }, 223 else => {}, 224 } 225 if (new_tab != current_tab) { 226 try root_focus.setFocus(self.tab_ids.keys()[new_tab]); 227 } 228 } 229 230 pub fn clearGrid(self: *View) void { 231 self.box.clearGrid(); 232 } 233 234 pub fn getGrid(self: View) ?Grid { 235 return self.box.getGrid(); 236 } 237 238 pub fn getFocus(self: *View) *Focus { 239 return self.box.getFocus(); 240 } 241 242 pub fn getSelectedIndex(self: View) ?usize { 243 return self.currentTabIndex(); 244 } 245 246 fn currentTabIndex(self: View) ?usize { 247 const child_id = self.box.focus.child_id orelse return null; 248 return self.tab_ids.getIndex(child_id); 249 } 250 }; 251