╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ User.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
448 if (data.repos_next_start) |next_start| { 449 var next = route; 450 next.user_repos.start = next_start; 451 try items.append(aa, .{ .text = "next →", .link = try aa.print("a:{s}", .{try next.toUrl(&arena)}) }); 452 } 453 try list.setItems(allocator, items.items); 454 455 try box.children.put(allocator, list.getFocus().id, .{ .widget = .{ .flow_box_scroll = list }, .rect = null, .min_size = null }); 456 } 457 458 // search results start in the box so the term can be refined right away 459 box.getFocus().child_id = box.children.keys()[if (data.repos_search != null) header_index else list_index]; 460 return .{ .box = box, .data = data, .session = session }; 461 } 462 463 pub fn deinit(self: *ReposView, allocator: std.mem.Allocator) void { 464 self.box.deinit(allocator); 465 } 466 467 pub fn build(self: *ReposView, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 468 self.clearGrid(); 469 // clear the incoming min height so the list scrolls within what's left 470 try self.box.build(allocator, .{ 471 .min_size = .{ .width = null, .height = null }, 472 .max_size = constraint.max_size, 473 }, root_focus); 474 } 475 476 pub fn input(self: *ReposView, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 477 const direction = inp.vertDirection(key); 478 if (self.headerActive()) { 479 const search_box = self.searchBox(); 480 if (direction == .down) { 481 // the empty message has nothing to focus 482 if (!self.data.reposEmpty()) root_focus.setFocus(self.listScroll().getFocus().id); 483 return; 484 } 485 if (key == .enter) { 486 const text = try search_box.text(allocator); 487 defer allocator.free(text); 488 return self.submit(text); 489 } 490 return search_box.input(allocator, key, root_focus); 491 } 492 // up from the first row reaches the search box 493 if (direction == .up and self.listScroll().atTop()) return self.focusHeader(root_focus); 494 try self.listScroll().input(allocator, key, root_focus); 495 } 496 497 // the repos narrowed to `text` from the first window. an empty box on an 498 // unsearched page has nothing to clear. 499 fn submit(self: *ReposView, text: []const u8) !void { 500 if (text.len == 0 and self.data.repos_search == null) return; 501 const route = reposRoute(self.data.user.name) orelse return; 502 try self.session.navigate(route.withSearch(text) orelse return); 503 } 504 505 fn searchBox(self: *ReposView) *ui.widget.SearchBox { 506 return &self.box.children.values()[header_index].widget.header_scroll.row().children.values()[0].widget.search_box; 507 } 508 509 fn listScroll(self: *ReposView) *ui.widget.FlowBox.Scroll { 510 return &self.box.children.values()[list_index].widget.flow_box_scroll; 511 } 512 513 fn headerActive(self: *ReposView) bool { 514 return self.box.getFocus().child_id == self.box.children.keys()[header_index]; 515 } 516 517 pub fn clearGrid(self: *ReposView) void { 518 self.box.clearGrid(); 519 } 520 521 pub fn getGrid(self: ReposView) ?Grid { 522 return self.box.getGrid(); 523 } 524 525 pub fn getFocus(self: *ReposView) *Focus { 526 return self.box.getFocus(); 527 } 528 529 // up leaves the tab from the search box 530 pub fn atTop(self: *ReposView) bool { 531 return self.headerActive(); 532 } 533 534 pub fn focusHeader(self: *ReposView, root_focus: *Focus) void { 535 root_focus.setFocus(self.searchBox().getFocus().id); 536 } 537 }; 538 539 // the first window of `name`'s repos 540 fn reposRoute(name: []const u8) ?ui.RoutablePage { 541 return .{ .user_repos = .{ .name = ui.RoutablePage.Array(evt.User.name_max_len).from(name) orelse return null } }; 542 } 543