╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Undo.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
576 // active means there is one 577 const copyable_text = self.header() orelse unreachable; 578 if (direction == .down) { 579 // the empty message has nothing to focus 580 if (!self.data.isEmpty(self.session)) root_focus.setFocus(self.contentBox().getFocus().id); 581 return; 582 } 583 return copyable_text.input(allocator, key, root_focus); 584 } 585 // the empty message has nothing to focus 586 if (self.data.isEmpty(self.session)) return; 587 // the way back to the list, by enter or a click 588 if (self.detailActive()) if (ui.widget.listBackId(self.detailFrame())) |id| { 589 if (inp.activated(root_focus, id, key)) return self.focusList(root_focus); 590 if (root_focus.grandchild_id == id) return switch (direction) { 591 // without a sub header, atTop hands it to the page's tabs 592 .up => _ = self.focusHeader(root_focus), 593 .down => root_focus.setFocus(self.detailScroll().getFocus().id), 594 .none => if (key == .arrow_left) self.focusList(root_focus), 595 }; 596 if (direction == .up and self.contentAtTop()) return root_focus.setFocus(id); 597 }; 598 if (direction == .up and self.contentAtTop() and self.focusHeader(root_focus)) return; 599 try self.refreshDetail(allocator); 600 if (self.detailActive()) { 601 const details = &self.detailScroll().child.box; 602 if (self.selected()) |index| { 603 if (inp.activated(root_focus, details.children.keys()[0], key) and self.data.handle.canUndo() and self.data.items[index].index > 0) { 604 self.requestUndo(ui.RoutablePage.repoUndoRoute(self.data.identity, self.data.items[index].index) orelse return error.RouteTooLong); 605 return; 606 } 607 } 608 if (inp.rowDelta(key, @intCast(details.children.count()))) |delta| { 609 ui.widget.moveRowFocus(details, self.detailScroll(), root_focus, delta); 610 } else if (key == .arrow_left) { 611 self.focusList(root_focus); 612 } 613 } else if (inp.rowDelta(key, @intCast(self.listScroll().child.box.children.count()))) |delta| { 614 ui.widget.moveRowFocus(&self.listScroll().child.box, self.listScroll(), root_focus, delta); 615 } else switch (key) { 616 .arrow_right, .enter => if (self.selected() != null) self.focusDetail(root_focus), 617 // when the window is too narrow to lay out the detail pane beside 618 // the list, a click on a row opens it like enter. the detail was 619 // swapped to the just-selected row above. 620 .mouse => |mouse| if (self.selected() != null and self.contentBox().children.values()[1].rect == null and ui.widget.clickOnSelectedRow(&self.listScroll().child.box, root_focus, mouse)) { 621 self.focusDetail(root_focus); 622 }, 623 else => {}, 624 } 625 } 626 627 // enter the detail pane, landing on the way back to the list when it shows 628 fn focusDetail(self: *View, root_focus: *Focus) void { 629 if (ui.widget.listBackId(self.detailFrame()) != null) return ui.widget.focusListBack(self.detailFrame(), root_focus); 630 root_focus.setFocus(self.detailScroll().getFocus().id); 631 } 632 633 fn focusList(self: *View, root_focus: *Focus) void { 634 root_focus.setFocus(self.listScroll().getFocus().id); 635 } 636 637 fn contentAtTop(self: *View) bool { 638 const scroll = if (self.detailActive()) self.detailScroll() else self.listScroll(); 639 const rows = &scroll.child.box; 640 const id = rows.getFocus().child_id orelse return true; 641 return rows.children.getIndex(id) == 0 and scroll.y == 0; 642 } 643 644 // moving up from the sub header returns to the page's tabs, as does moving 645 // up from the top of the content where there is no sub header 646 pub fn atTop(self: *View) bool { 647 if (self.header() != null) return self.headerActive(); 648 if (self.data.isEmpty(self.session)) return true; 649 // the way back to the list sits above the detail while it shows 650 if (self.detailActive()) if (ui.widget.listBackId(self.detailFrame())) |id| return self.detailFrame().getFocus().child_id == id; 651 return self.contentAtTop(); 652 } 653 654 // arriving from the page's tabs lands on the sub header, not the content 655 pub fn focusHeader(self: *View, root_focus: *Focus) bool { 656 const copyable_text = self.header() orelse return false; 657 root_focus.setFocus(copyable_text.getFocus().id); 658 return true; 659 } 660 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 661 self.box.deinit(allocator); 662 } 663 pub fn clearGrid(self: *View) void { 664 self.box.clearGrid(); 665 } 666 pub fn getGrid(self: View) ?Grid { 667 return self.box.getGrid(); 668 } 669 pub fn getFocus(self: *View) *Focus { 670 return self.box.getFocus(); 671 } 672 }; 673