╭ changed ──────────╮ │src/embed/script.js│ ╰───────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │66 return container ? { focused, container } : null; │ │67 } │ │68 │ │69 -// scroll one row while the focused widget continues past the viewport edge. │ │69 +// scroll a step while the focused widget continues past the viewport edge. │ │70 function scrollFocusedWidget(down) { │ │71 + const stepLines = 3; // see step_lines in widget.zig's scrollThroughRow for the terminal equivalent│ │72 const native = focusedNativeScroll(); │ │73 if (!native) return false; │ │74 const viewport = native.container.getBoundingClientRect(); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │76 const overflow = down ? edge.bottom - viewport.bottom : viewport.top - edge.top; │ │77 if (overflow <= 0.5) return false; │ │78 if (!cellHeight) measureCell(); │ │78 - native.container.scrollTop += (down ? 1 : -1) * Math.min(cellHeight, overflow); │ │79 + native.container.scrollTop += (down ? 1 : -1) * Math.min(stepLines * cellHeight, overflow);│ │80 return true; │ │81 } │ │82 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │281 // vertical is the priority axis. keep the viewport in place when it │ │282 // already overlaps a tall widget; otherwise reveal its nearest edge. │ │283 if (bottom - top > container.clientHeight) { │ │283 - container.scrollTop = Math.min(Math.max(container.scrollTop, top), bottom - container.clientHeight);│ │284 + if (bottom <= container.scrollTop) { │ │285 + container.scrollTop = bottom - container.clientHeight; │ │286 + } else if (top >= container.scrollTop + container.clientHeight) { │ │287 + container.scrollTop = top; │ │288 + } │ │289 } else if (top < container.scrollTop) { │ │290 container.scrollTop = top; │ │291 } else if (bottom > container.scrollTop + container.clientHeight) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/ui/widget.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │178 const cur: isize = @intCast(box.children.getIndex(cur_id) orelse return); │ │179 // home and end jump straight to the ends │ │180 if (@abs(delta) < keys.len) if (box.children.values()[@intCast(cur)].rect) |rect| if (scrollThroughRow(scroll, rect, delta)) return;│ │181 - const view_h = viewHeight(scroll); │ │181 const last: isize = @intCast(keys.len - 1); │ │182 const next: usize = @intCast(std.math.clamp(cur + delta, 0, last)); │ │183 if (next == @as(usize, @intCast(cur))) return; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │184 root_focus.setFocus(keys[next]); │ │186 - const rect = box.children.values()[next].rect orelse return; │ │187 - // a tall row shows the end it was entered from │ │188 - if (view_h) |h| if (@as(isize, @intCast(rect.size.height)) > h) { │ │189 - scroll.y = if (delta > 0) rect.y else rectBottom(rect) - h; │ │190 - return; │ │191 - }; │ │192 - scroll.scrollToRect(rect); │ │185 + if (box.children.values()[next].rect) |rect| scrollToEnteredRow(scroll, rect, delta > 0);│ │186 +} │ │187 + │ │188 +// bring `rect`, a row in `scroll`'s content just entered from above (`down`) │ │189 +// or below, into view. a row taller than the view keeps the view while the │ │190 +// edge it was entered from is in it, so the following steps scroll through it, │ │191 +// and otherwise shows that edge. │ │192 +pub fn scrollToEnteredRow(scroll: *wgt.Scroll(Widget), rect: layout.IRect, down: bool) void {│ │193 + const grid = scroll.grid orelse return; │ │194 + const h: isize = @intCast(grid.size.height - scroll.bar_h); │ │195 + if (@as(isize, @intCast(rect.size.height)) <= h) return scroll.scrollToRect(rect); │ │196 + const edge = if (down) rect.y else rectBottom(rect) - 1; │ │197 + if (edge >= scroll.y and edge < scroll.y + h) return; │ │198 + scroll.y = if (down) rect.y else rectBottom(rect) - h; │ │199 } │ │200 │ │201 // step across the boxes of `box`'s selected row, false when it isn't a row or │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │210 │ │211 // scroll a step through `rect`, a row in `scroll`'s content, when it extends │ │212 // past the view in `delta`'s direction. false when it doesn't, and on the web, │ │207 -// which scrolls natively. │ │213 +// where scrollFocusedWidget in script.js handles scrolling. │ │214 pub fn scrollThroughRow(scroll: *wgt.Scroll(Widget), rect: layout.IRect, delta: isize) bool { │ │209 - // how far one step scrolls │ │210 - const step_lines = 5; │ │215 + const step_lines = 3; // how far a step scrolls (see stepLines in script.js for the web equivalent)│ │216 const h = viewHeight(scroll) orelse return false; │ │217 const hidden = if (delta > 0) rectBottom(rect) - (scroll.y + h) else scroll.y - rect.y; │ │218 if (hidden <= 0) return false; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1155 return false; │ │1156 } │ │1157 │ │1158 - fn focusChild(self: *This, child_index: usize, last: bool, root_focus: *Focus) void { │ │1158 + // focus child `child_index`, its last part when `last`, entered from above │ │1159 + // when `down` │ │1160 + fn focusChild(self: *This, child_index: usize, last: bool, down: bool, root_focus: *Focus) void {│ │1161 const inner_box = self.inner(); │ │1162 const child = &inner_box.children.values()[child_index]; │ │1163 var target = inner_box.children.keys()[child_index]; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1179 root_focus.setFocus(target); │ │1180 }, │ │1181 else => root_focus.setFocus(target), │ │1180 - } │ │1181 - const detail_scroll = &self.scroll; │ │1182 - if (detail_scroll.grid) |viewport| { │ │1183 - const viewport_height = viewport.size.height - detail_scroll.bar_h; │ │1184 - if (rect.size.height > viewport_height) { │ │1185 - const bottom_aligned = rect.y + @as(isize, @intCast(rect.size.height - viewport_height));│ │1186 - detail_scroll.y = std.math.clamp(detail_scroll.y, rect.y, bottom_aligned); │ │1187 - detail_scroll.clampToContent(); │ │1188 - return; │ │1189 - } │ │1182 } │ │1191 - detail_scroll.scrollToRect(rect); │ │1183 + widget.scrollToEnteredRow(&self.scroll, rect, down); │ │1184 } │ │1185 │ │1186 fn moveVertical(self: *This, root_focus: *Focus, down: bool) bool { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │1192 if (inner_box.children.values()[current].widget == .repo_comment) { │ │1193 const comment = &inner_box.children.values()[current].widget.repo_comment;│ │1194 if (down and !comment.bodyFocused()) { │ │1203 - self.focusChild(current, true, root_focus); │ │1195 + self.focusChild(current, true, true, root_focus); │ │1196 return true; │ │1197 } │ │1198 if (!down and comment.bodyFocused()) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │1207 - self.focusChild(current, false, root_focus); │ │1199 + self.focusChild(current, false, false, root_focus);│ │1200 return true; │ │1201 } │ │1202 } │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯