╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1669 } │ │1670 │ │1671 fn detailInput(self: *View, allocator: std.mem.Allocator, index: usize, key: Key, root_focus: *Focus) !void { │ │1672 + switch (key) { │ │1673 + .page_up => { │ │1674 + self.pageDetail(index, root_focus, -10); │ │1675 + return; │ │1676 + }, │ │1677 + .page_down => { │ │1678 + self.pageDetail(index, root_focus, 10); │ │1679 + return; │ │1680 + }, │ │1681 + .home => { │ │1682 + self.jumpDetail(index, root_focus, false); │ │1683 + return; │ │1684 + }, │ │1685 + .end => { │ │1686 + self.jumpDetail(index, root_focus, true); │ │1687 + return; │ │1688 + }, │ │1689 + else => {}, │ │1690 + } │ │1691 if (self.data.comment_page != null and std.mem.eql(u8, self.window(index).issues[self.detailed_index[index] orelse return].id, self.data.selected_id)) {│ │1692 try self.commentInput(index, key, root_focus); │ │1693 } else if (self.toolRowFocused(index)) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │1774 if (sc.y == before) _ = self.moveCommentVertical(index, root_focus, true);│ │1775 return; │ │1776 }, │ │1758 - .page_up => sc.y -= 10, │ │1759 - .page_down => sc.y += 10, │ │1760 - .home => sc.y = 0, │ │1761 - .end => sc.y = std.math.maxInt(isize), │ │1777 else => return, │ │1778 } │ │1779 sc.clampToContent(); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1785 .arrow_right => _ = self.moveCommentHorizontal(index, root_focus, true), │ │1786 .arrow_up => _ = self.moveCommentVertical(index, root_focus, false), │ │1787 .arrow_down => _ = self.moveCommentVertical(index, root_focus, true), │ │1773 - .page_up => self.detailScroll(index).y -= 10, │ │1774 - .page_down => self.detailScroll(index).y += 10, │ │1775 - .home => self.detailScroll(index).y = 0, │ │1776 - .end => self.detailScroll(index).y = std.math.maxInt(isize), │ │1788 else => return, │ │1789 } │ │1779 - self.detailScroll(index).clampToContent(); │ │1790 + } │ │1791 + │ │1792 + // page the detail scroll, then focus the first or last visible widget. │ │1793 + fn pageDetail(self: *View, index: usize, root_focus: *Focus, delta: isize) void { │ │1794 + const sc = self.detailScroll(index); │ │1795 + sc.y += delta; │ │1796 + sc.clampToContent(); │ │1797 + self.focusVisible(index, root_focus, delta > 0); │ │1798 + } │ │1799 + │ │1800 + // jump to the detail's top or bottom and focus that edge's visible widget. │ │1801 + fn jumpDetail(self: *View, index: usize, root_focus: *Focus, to_end: bool) void { │ │1802 + const sc = self.detailScroll(index); │ │1803 + sc.y = if (to_end) std.math.maxInt(isize) else 0; │ │1804 + sc.clampToContent(); │ │1805 + self.focusVisible(index, root_focus, to_end); │ │1806 + } │ │1807 + │ │1808 + fn detailRectVisible(self: *View, index: usize, rect: layout.IRect) bool { │ │1809 + const sc = self.detailScroll(index); │ │1810 + const viewport = sc.grid orelse return false; │ │1811 + const top = sc.y; │ │1812 + const bottom = top + @as(isize, @intCast(viewport.size.height - sc.bar_h)); │ │1813 + return rect.y + @as(isize, @intCast(rect.size.height)) > top and rect.y < bottom; │ │1814 + } │ │1815 + │ │1816 + // focus the top- or bottom-most focusable widget intersecting the viewport. │ │1817 + fn focusVisible(self: *View, index: usize, root_focus: *Focus, last: bool) void { │ │1818 + const inner = self.detailInner(index); │ │1819 + var offset: usize = 0; │ │1820 + while (offset < inner.children.count()) : (offset += 1) { │ │1821 + const child_index = if (last) inner.children.count() - 1 - offset else offset; │ │1822 + if (self.focusVisibleChild(index, child_index, root_focus, last)) return; │ │1823 + } │ │1824 + } │ │1825 + │ │1826 + fn focusVisibleChild(self: *View, index: usize, child_index: usize, root_focus: *Focus, last: bool) bool { │ │1827 + const child = &self.detailInner(index).children.values()[child_index]; │ │1828 + const child_rect = child.rect orelse return false; │ │1829 + var chosen: ?usize = null; │ │1830 + if (child.widget.getFocus().focusable and self.detailRectVisible(index, child_rect)) { │ │1831 + const id = self.detailInner(index).children.keys()[child_index]; │ │1832 + chosen = id; │ │1833 + if (!last) { │ │1834 + root_focus.setFocus(id); │ │1835 + return true; │ │1836 + } │ │1837 + } │ │1838 + │ │1839 + for (child.widget.getFocus().children.keys(), child.widget.getFocus().children.values()) |id, focus_child| {│ │1840 + if (!focus_child.focus.focusable) continue; │ │1841 + const rect: layout.IRect = .{ │ │1842 + .x = child_rect.x + @as(isize, @intCast(focus_child.rect.x)), │ │1843 + .y = child_rect.y + @as(isize, @intCast(focus_child.rect.y)), │ │1844 + .size = focus_child.rect.size, │ │1845 + }; │ │1846 + if (!self.detailRectVisible(index, rect)) continue; │ │1847 + chosen = id; │ │1848 + if (!last) break; │ │1849 + } │ │1850 + if (chosen) |id| { │ │1851 + root_focus.setFocus(id); │ │1852 + return true; │ │1853 + } │ │1854 + return false; │ │1855 } │ │1856 │ │1857 fn focusedDetailChild(self: *View, index: usize, root_focus: *Focus) ?usize { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯