╭ changed ───────────╮ │src/ui/Repo/Refs.zig│ ╰────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │269 │ │296 - // the active column's row count, used as a clamp-to-end delta for home/end. │ │297 - fn columnRowCount(self: *View) isize { │ │298 - const col = self.activeColumn() orelse return 0; │ │299 - return @intCast(col.children.count()); │ │270 + fn activeIndex(self: *View) ?usize { │ │271 + const id = self.box.getFocus().child_id orelse return null; │ │272 + return self.box.children.getIndex(id); │ │273 + } │ │274 + │ │275 + // [0] = the fixed label, [1] = the scrolling flow │ │276 + fn scrollAt(self: *View, index: usize) *wgt.Scroll(ui.Widget) { │ │277 + return &self.box.children.values()[index].widget.box.children.values()[1].widget.scroll; │ │278 + } │ │279 + │ │280 + fn flowAt(self: *View, index: usize) *ui.widget.TagFlow { │ │281 + return &self.scrollAt(index).child.tag_flow; │ │282 + } │ │283 + │ │284 + fn rowStart(flow: *ui.widget.TagFlow, item: usize) bool { │ │285 + const rects = flow.rects.items; │ │286 + return item == 0 or rects[item - 1].y != rects[item].y; │ │287 } │ │288 │ │302 - fn activeScroll(self: *View) ?*wgt.Scroll(ui.Widget) { │ │303 - const id = self.box.getFocus().child_id orelse return null; │ │304 - const index = self.box.children.getIndex(id) orelse return null; │ │305 - return columnScroll(&self.box.children.values()[index].widget.box); │ │289 + fn rowEnd(flow: *ui.widget.TagFlow, item: usize) bool { │ │290 + const rects = flow.rects.items; │ │291 + return item + 1 == rects.len or rects[item + 1].y != rects[item].y; │ │292 } │ │293 │ │308 - fn activeColumn(self: *View) ?*wgt.Box(ui.Widget) { │ │309 - return &(self.activeScroll() orelse return null).child.box; │ │294 + // land on the other column's row at or above the current one: its first │ │295 + // name when moving right, its last when moving left. names are laid out in │ │296 + // order, so that row ends at the last name no lower than the current one. │ │297 + fn crossTo(self: *View, target: usize, from_y: isize, root_focus: *Focus) void { │ │298 + // the other column may have no names at all │ │299 + const rects = self.flowAt(target).rects.items; │ │300 + if (rects.len == 0) return; │ │301 + var last: usize = 0; │ │302 + for (rects, 0..) |rect, i| { │ │303 + if (rect.y <= from_y) last = i; │ │304 + } │ │305 + var first = last; │ │306 + while (first > 0 and rects[first - 1].y == rects[last].y) first -= 1; │ │307 + self.focusName(target, if (target == right_col) first else last, root_focus); │ │308 } │ │309 │ │312 - fn switchColumn(self: *View, root_focus: *Focus, target: usize) !void { │ │313 - if (target >= self.box.children.count()) return; │ │314 - const cur_col = self.activeColumn() orelse return; │ │315 - const target_scroll = columnScroll(&self.box.children.values()[target].widget.box); │ │316 - const target_col = &target_scroll.child.box; │ │317 - // an empty column has nothing to select; ignore. │ │318 - if (target_col.children.count() == 0) return; │ │319 - // keep the same row offset, clamped to the target column's last row. │ │320 - const cur_id = cur_col.getFocus().child_id orelse return; │ │321 - const cur = cur_col.children.getIndex(cur_id) orelse return; │ │322 - const last = target_col.children.count() - 1; │ │323 - const next = @min(cur, last); │ │324 - root_focus.setFocus(target_col.children.keys()[next]); │ │325 - if (target_col.children.values()[next].rect) |rect| target_scroll.scrollToRect(rect); │ │310 + fn focusName(self: *View, index: usize, item: usize, root_focus: *Focus) void { │ │311 + const flow = self.flowAt(index); │ │312 + root_focus.setFocus(flow.text_boxes.items[item].getFocus().id); │ │313 + // the browser scrolls natively; the terminal brings the name into view │ │314 + if (self.session.is_terminal and item < flow.rects.items.len) self.scrollAt(index).scrollToRect(flow.rects.items[item]);│ │315 } │ │316 │ │317 pub fn clearGrid(self: *View) void { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │326 return self.box.getFocus(); │ │327 } │ │328 │ │329 + // on the active flow's first row, where up leaves the tab │ │330 pub fn atTop(self: *View) bool { │ │341 - const col = self.activeColumn() orelse return false; │ │342 - const cur_id = col.getFocus().child_id orelse return false; │ │343 - return col.children.getIndex(cur_id) == 0; │ │331 + const index = self.activeIndex() orelse return false; │ │332 + const flow = self.flowAt(index); │ │333 + const cur = flow.indexOfFocusId(flow.focus.child_id orelse return false) orelse return false;│ │334 + return flow.rowStep(cur, false) == null; │ │335 } │ │336 }; │ │337 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯