╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Header.zig ───────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
269 text_box.getFocus().focusable = true; 270 text_box.getFocus().kind = .{ .custom = settings_link }; 271 try tab_ids.put(allocator, text_box.getFocus().id, {}); 272 if (std.mem.eql(u8, settings_link, current_link)) selected_tab = text_box.getFocus().id; 273 try tabs_box.children.put(allocator, text_box.getFocus().id, .{ 274 .widget = .{ .text_box = text_box }, 275 .rect = null, 276 .min_size = .{ .width = "settings".len + 2, .height = null }, 277 }); 278 } 279 280 // auth tab (login / logout). AuthTab defaults to the global ai:/auth 281 // link; repoint its instance at this repo's auth route so it stays on 282 // this page. local mode has no accounts, so it has no auth tab. 283 if (!session.data.is_local) { 284 var auth_tab = try AuthTab.View.init(allocator, session); 285 errdefer auth_tab.deinit(allocator); 286 auth_tab.text_box.getFocus().kind = .{ .custom = auth_link }; 287 try tab_ids.put(allocator, auth_tab.getFocus().id, {}); 288 if (std.mem.eql(u8, auth_link, current_link)) selected_tab = auth_tab.getFocus().id; 289 try tabs_box.children.put(allocator, auth_tab.getFocus().id, .{ 290 .widget = .{ .auth_tab = auth_tab }, 291 .rect = null, 292 .min_size = .{ .width = auth_tab.minWidth(), .height = null }, 293 }); 294 } 295 296 // quit tab 297 if (session.is_terminal) { 298 var text_box = try wgt.TextBox(ui.Widget).init(allocator, ui.Quit.tab_label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); 299 errdefer text_box.deinit(allocator); 300 text_box.getFocus().focusable = true; 301 text_box.getFocus().kind = .{ .custom = ui.Quit.tab_kind }; 302 try tab_ids.put(allocator, text_box.getFocus().id, {}); 303 try tabs_box.children.put(allocator, text_box.getFocus().id, .{ 304 .widget = .{ .text_box = text_box }, 305 .rect = null, 306 // the label is a single column; +2 for the border 307 .min_size = .{ .width = 1 + 2, .height = null }, 308 }); 309 } 310 311 tabs_box.getFocus().child_id = selected_tab orelse tab_ids.keys()[0]; 312 const title_id = title_box.getFocus().id; 313 const tabs_id = tabs_box.getFocus().id; 314 try box.children.put(allocator, title_id, .{ 315 .widget = .{ .box = title_box }, 316 .rect = null, 317 .min_size = null, 318 }); 319 title_box_owned = true; 320 try box.children.put(allocator, tabs_id, .{ 321 .widget = .{ .box = tabs_box }, 322 .rect = null, 323 .min_size = null, 324 }); 325 tabs_box_owned = true; 326 box.getFocus().child_id = tabs_id; 327 328 return .{ 329 .scroll = try wgt.Scroll(ui.Widget).init(allocator, .{ .box = box }, .{ .direction = .horiz, .show_bar = false, .web_native = !session.is_terminal }), 330 .data = data, 331 .tab_ids = tab_ids, 332 .tabs_id = tabs_id, 333 .first_group_width = first_group_width, 334 }; 335 } 336 337 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 338 self.scroll.deinit(allocator); 339 self.tab_ids.deinit(allocator); 340 } 341 342 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 343 self.clearGrid(); 344 const box = &self.scroll.child.box; 345 const tabs_child = self.tabsChild(); 346 const tabs_box = &tabs_child.widget.box; 347 348 const viewport_width = constraint.max_size.width orelse constraint.min_size.width; 349 // the outer box's hidden border occupies two columns 350 const content_width = if (viewport_width) |width| width -| 2 else null; 351 const wrap = if (content_width) |width| self.first_group_width >= width / 2 else false; 352 box.options.direction = if (wrap) .vert else .horiz; 353 tabs_child.min_size = if (wrap) .{ .width = content_width, .height = null } else null; 354 355 // only the selected tab shows its border 356 const selected_tab = if (box.getFocus().child_id == self.tabs_id) tabs_box.getFocus().child_id else null; 357 for (tabs_box.children.keys(), tabs_box.children.values()) |id, *child| { 358 const tb: ?*wgt.TextBox(ui.Widget) = switch (child.widget) { 359 .text_box => |*x| x, 360 .auth_tab => |*at| blk: { 361 // the label tracks login state per frame, so the width must too 362 child.min_size = .{ .width = at.minWidth(), .height = null }; 363 break :blk &at.text_box; 364 }, 365 else => null, 366 }; 367 if (tb) |t| { 368 t.options.border_style = if (selected_tab == id) .single else .hidden; 369 } 370 } 371 var scroll_constraint = constraint; 372 scroll_constraint.min_size.width = viewport_width; 373 try self.scroll.build(allocator, scroll_constraint, root_focus); 374 } 375 376 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 377 _ = allocator; 378 const current_tab = self.currentTabIndex() orelse return; 379 if (inp.moveTab(key, current_tab, self.tab_ids.count())) |new_tab| { 380 const tab_id = self.tab_ids.keys()[new_tab]; 381 root_focus.setFocus(tab_id); 382 const tabs_child = self.tabsChild(); 383 const tabs_rect = tabs_child.rect orelse return; 384 const tab = tabs_child.widget.box.children.get(tab_id) orelse return; 385 var rect = tab.rect orelse return; 386 rect.x += tabs_rect.x; 387 rect.y += tabs_rect.y; 388 self.scroll.scrollToRect(rect); 389 } 390 } 391 392 pub fn clearGrid(self: *View) void { 393 self.scroll.clearGrid(); 394 } 395 396 pub fn getGrid(self: View) ?Grid { 397 return self.scroll.getGrid(); 398 } 399 400 pub fn getFocus(self: *View) *Focus { 401 return self.scroll.getFocus(); 402 } 403 404 pub fn getSelectedIndex(self: View) ?usize { 405 return self.currentTabIndex(); 406 } 407 408 fn currentTabIndex(self: View) ?usize { 409 if (self.scroll.child.box.focus.child_id != self.tabs_id) return null; 410 const child_id = self.tabsChild().widget.box.focus.child_id orelse return null; 411 return self.tab_ids.getIndex(child_id); 412 } 413 414 fn tabsChild(self: *const View) *wgt.Box(ui.Widget).Child { 415 return self.scroll.child.box.children.getPtr(self.tabs_id) orelse unreachable; 416 } 417 }; 418