╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1111 } │ │1112 } │ │1113 }; │ │1114 + │ │1115 +// move the focused row in `box` by `delta`, clamped to the ends, scrolling │ │1116 +// `scroll` to keep it visible. │ │1117 +pub fn moveRowFocus(box: *wgt.Box(Widget), scroll: *wgt.Scroll(Widget), root_focus: *Focus, delta: isize) void {│ │1118 + const keys = box.children.keys(); │ │1119 + if (keys.len == 0) return; │ │1120 + const cur_id = box.getFocus().child_id orelse return; │ │1121 + const cur: isize = @intCast(box.children.getIndex(cur_id) orelse return); │ │1122 + const last: isize = @intCast(keys.len - 1); │ │1123 + const next: usize = @intCast(std.math.clamp(cur + delta, 0, last)); │ │1124 + if (next == @as(usize, @intCast(cur))) return; │ │1125 + root_focus.setFocus(keys[next]); │ │1126 + if (box.children.values()[next].rect) |rect| scroll.scrollToRect(rect); │ │1127 +} │ │1128 + │ │1129 +// keep a scroll within its content, using the last build's grids. the scroll │ │1130 +// bar's reserved column/row isn't part of the content viewport, so exclude it │ │1131 +// (as scrollToRect does) or the last column/row stays unreachable. │ │1132 +pub fn clampScroll(sc: *wgt.Scroll(Widget)) void { │ │1133 + const vp = sc.grid orelse return; │ │1134 + const content = sc.child.box.grid orelse return; │ │1135 + const view_w = vp.size.width - sc.bar_w; │ │1136 + const view_h = vp.size.height - sc.bar_h; │ │1137 + const max_y: isize = if (content.size.height > view_h) @intCast(content.size.height - view_h) else 0; │ │1138 + const max_x: isize = if (content.size.width > view_w) @intCast(content.size.width - view_w) else 0; │ │1139 + sc.y = std.math.clamp(sc.y, 0, max_y); │ │1140 + sc.x = std.math.clamp(sc.x, 0, max_x); │ │1141 +} │ │1142 │ │1143 pub const FlowBox = struct { │ │1144 focus: *Focus, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯