╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭─────────────────────────────────────────────────────────────╮ │670 .arrow_right => { │ │671 if (on_content) { │ │672 sc.x += 1; │ │672 - self.clampDetailScroll(); │ │673 + ui.clampScroll(self.detailScroll());│ │674 } else _ = self.moveNav(root_focus, 1); │ │675 }, │ │676 .arrow_up => { │ ╰─────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │677 if (on_content) { │ │678 if (self.session.is_terminal and sc.y > 0) { │ │679 sc.y -= 1; │ │679 - self.clampDetailScroll(); │ │680 + ui.clampScroll(self.detailScroll()); │ │681 } else self.focusNav(root_focus); // cross up to the links│ │682 } │ │683 }, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │685 if (on_content) { │ │686 if (self.session.is_terminal) { │ │687 sc.y += 1; │ │687 - self.clampDetailScroll(); │ │688 + ui.clampScroll(self.detailScroll()); │ │689 } │ │690 } else self.focusContent(root_focus); // links -> content│ │691 }, │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │692 .page_up => if (on_content and self.session.is_terminal) { │ │693 sc.y -= 10; │ │693 - self.clampDetailScroll(); │ │694 + ui.clampScroll(self.detailScroll()); │ │695 }, │ │696 .page_down => if (on_content and self.session.is_terminal) {│ │697 sc.y += 10; │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────╮ │697 - self.clampDetailScroll(); │ │698 + ui.clampScroll(self.detailScroll()); │ │699 }, │ │700 .home => if (on_content and self.session.is_terminal) {│ │701 sc.y = 0; │ ╰────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │701 - self.clampDetailScroll(); │ │702 + ui.clampScroll(self.detailScroll()); │ │703 }, │ │704 .end => if (on_content and self.session.is_terminal) {│ │705 sc.y = std.math.maxInt(isize); │ ╰───────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │705 - self.clampDetailScroll(); │ │706 + ui.clampScroll(self.detailScroll()); │ │707 }, │ │708 .mouse => |mouse| switch (mouse.action) { │ │709 .scroll => |dir| if (on_content and self.session.is_terminal) {│ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────╮ │710 sc.y += if (dir == .up) -5 else 5; │ │710 - self.clampDetailScroll(); │ │711 + ui.clampScroll(self.detailScroll());│ │712 }, │ │713 else => {}, │ │714 }, │ ╰─────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │743 return true; │ │744 } │ │745 │ │745 - // keep the detail scroll within its content, using the last build's grids. │ │746 - // the scroll bar's reserved column/row isn't part of the content viewport, │ │747 - // so exclude it or the last column/row stays unreachable. │ │748 - fn clampDetailScroll(self: *View) void { │ │749 - const sc = self.detailScroll(); │ │750 - const vp = sc.grid orelse return; │ │751 - const content = sc.child.box.grid orelse return; │ │752 - const view_w = vp.size.width - sc.bar_w; │ │753 - const view_h = vp.size.height - sc.bar_h; │ │754 - const max_y: isize = if (content.size.height > view_h) @intCast(content.size.height - view_h) else 0;│ │755 - const max_x: isize = if (content.size.width > view_w) @intCast(content.size.width - view_w) else 0; │ │756 - sc.y = std.math.clamp(sc.y, 0, max_y); │ │757 - sc.x = std.math.clamp(sc.x, 0, max_x); │ │758 - } │ │759 - │ │746 // enter the detail pane, landing on the "next" link when there is one (so │ │747 // right-arrow reaches it), else on the content. a directory or ".." row has │ │748 // neither, so there's nothing to enter. selecting the frame's child (then │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │761 // return to the list. │ │762 fn focusList(self: *View, root_focus: *Focus) !void { │ │763 root_focus.setFocus(self.listScroll().getFocus().id); │ │778 - } │ │779 - │ │780 - fn moveSelection(self: *View, root_focus: *Focus, delta: isize) !void { │ │781 - const lb = self.listBox(); │ │782 - const keys = lb.children.keys(); │ │783 - if (keys.len == 0) return; │ │784 - const cur_id = lb.getFocus().child_id orelse return; │ │785 - const cur: isize = @intCast(lb.children.getIndex(cur_id) orelse return); │ │786 - const last: isize = @intCast(keys.len - 1); │ │787 - const next: usize = @intCast(std.math.clamp(cur + delta, 0, last)); │ │788 - if (next == @as(usize, @intCast(cur))) return; │ │789 - root_focus.setFocus(keys[next]); │ │790 - if (lb.children.values()[next].rect) |rect| self.listScroll().scrollToRect(rect);│ │764 } │ │765 │ │766 pub fn clearGrid(self: *View) void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯