# scroll.ludic - a box with overflow (or ) scrolls itself: the wheel over it, its scrollbar # held and dragged, and the focused control pulled into view. Its content is shifted by how far it is # scrolled and clipped to it, and the offset is kept by its key from frame to frame. function sc_get(ui_st: UiState, key: string) -> float { for i in 0 .. len(ui_st.sc_keys) { if ui_st.sc_keys[i] == key { return ui_st.sc_offs[i] } } return 0.0 } function sc_put(ui_st: mut UiState, key: string, v: float) -> void { for i in 0 .. len(ui_st.sc_keys) { if ui_st.sc_keys[i] == key { ui_st.sc_offs[i] = v return } } ui_kept(ui_st.sc_keys, key) ui_kept(ui_st.sc_offs, v) } # every scroll box, outermost first: its offset (from input, when `live`), then its content moved function sc_update(ui_st: mut UiState, n: UiNode) -> void { ui_st.sc_scope = pp_top(n) if not ui_st.in_now.down { ui_st.sc_drag = "" } sc_walk(ui_st, n, true) } function sc_apply(ui_st: mut UiState, n: UiNode) -> void { sc_walk(ui_st, n, false) } function sc_walk(ui_st: mut UiState, n: UiNode, live: bool) -> void { if n.kind == UI_SCROLL { let room = n.ch - ly_vin(n) let most = Math.max(n.content_h - ly_vin(n) - room, 0.0) let held = sc_held(ui_st, n, sc_get(ui_st, n.key), live) var off = held if live { off = sc_input(ui_st, n, off, room, most) } off = Math.clamp(off, 0.0, most) if live and off != Math.clamp(held, 0.0, most) { ui_fire_float(ui_st, n, "scroll", off / ui_px(ui_st, 1.0)) } sc_put(ui_st, n.key, off) n.scroll_y = off if off != 0.0 { for k in 0 .. len(n.children) { ly_offset(n.children[k], 0.0, 0.0 - off) } } } for k in 0 .. len(n.children) { sc_walk(ui_st, n.children[k], live) } } # the wheel over it, its bar held and dragged (scroll_bar.ludic), the keyboard's focus pulled into # view; while a popover is up, only a box inside it answers the pointer function sc_input(ui_st: mut UiState, n: UiNode, off0: float, room: float, most: float) -> float { var off = off0 if ui_st.sc_scope != null and not sc_within(n, ui_st.sc_scope) { return sc_into_view(ui_st, n, off, room) } let i = ui_st.in_now let over = i.x >= n.x and i.y >= n.y and i.x < n.x + n.cw and i.y < n.y + n.ch if over and i.wheel != 0.0 and not pe_takes_wheel(n, i.x, i.y + off0) { off = off - i.wheel * ui_px(ui_st, 40.0) } off = sb_input(ui_st, n, off, room, most) return sc_into_view(ui_st, n, off, room) } function sc_within(n: UiNode, top: UiNode) -> bool { var at = n while at != null { if at == top { return true } at = at.parent } return false } # the focused control, moved by the keyboard, is brought into the box's view function sc_into_view(ui_st: UiState, n: UiNode, off: float, room: float) -> float { if not ui_st.fc_visible or ui_st.fc_key == "" or not lc_mine(ui_st.fc_key, n.key) { return off } let f = fr_node(n, ui_st.fc_key) if f == null { return off } let top = f.y - n.y - n.pt if top < off { return top - ui_px(ui_st, 8.0) } if top + f.ch > off + room { return top + f.ch - room + ui_px(ui_st, 8.0) } return off } # ui_place moved every box by its stored offset; the frame takes that back before moving it again function sc_undo(n: UiNode) -> void { if n.kind == UI_SCROLL and n.scroll_y != 0.0 { for k in 0 .. len(n.children) { ly_offset(n.children[k], 0.0, n.scroll_y) } } for k in 0 .. len(n.children) { sc_undo(n.children[k]) } }