feat(ui): a scrollbar held where it was taken, a popover that keeps the mouse, easing that lands
- the scrollbar is .ui-scrollbar with a .ui-thumb, styled by the default sheet or a theme; a press on the thumb holds it at the point it was taken and the pointer moves it in proportion, a press on the track jumps the thumb's middle there and holds it, and neither presses what is under the bar (it used to centre the thumb on the pointer and press the row beneath as well); - while a popover is up only scroll boxes inside it take the pointer, and a press outside it that closes it forgets any press in progress; - a transition ends exactly on its value (it stopped a rounding error short, at every frame rate). Tests: ui_popover_mouse, ui_scrollbar, ui_ease (60/120/180/240 Hz). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9f59000f5b
commit
a4c7a756f0
19 changed files with 299 additions and 29 deletions
|
|
@ -1,9 +1,10 @@
|
|||
# scroll.ludic - a box with overflow (or <scroll>) scrolls itself: the wheel over it, a scrollbar
|
||||
# thumb dragged, and the focused control pulled into view. Its content is shifted by how far it is
|
||||
# scroll.ludic - a box with overflow (or <scroll>) 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.
|
||||
var sc_keys: []string = new []string
|
||||
var sc_offs: []float = new []float
|
||||
var sc_drag: string = ""
|
||||
var sc_scope: UiNode = null # the popover that has the pointer this frame, if one is up
|
||||
function sc_get(key: string) -> float {
|
||||
for i in 0 .. len(sc_keys) {
|
||||
if sc_keys[i] == key { return sc_offs[i] }
|
||||
|
|
@ -21,7 +22,11 @@ function sc_put(key: string, v: float) -> void {
|
|||
push(sc_offs, v)
|
||||
}
|
||||
# every scroll box, outermost first: its offset (from input, when `live`), then its content moved
|
||||
function sc_update(n: UiNode) -> void { sc_walk(n, true) }
|
||||
function sc_update(n: UiNode) -> void {
|
||||
sc_scope = pp_top(n)
|
||||
if not in_now.down { sc_drag = "" }
|
||||
sc_walk(n, true)
|
||||
}
|
||||
function sc_apply(n: UiNode) -> void { sc_walk(n, false) }
|
||||
function sc_walk(n: UiNode, live: bool) -> void {
|
||||
if n.kind == UI_SCROLL {
|
||||
|
|
@ -38,17 +43,25 @@ function sc_walk(n: UiNode, live: bool) -> void {
|
|||
}
|
||||
for k in 0 .. len(n.children) { sc_walk(n.children[k], live) }
|
||||
}
|
||||
# the wheel over it, its thumb dragged, the keyboard's focus pulled into view
|
||||
# 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(n: UiNode, off0: float, room: float, most: float) -> float {
|
||||
var off = off0
|
||||
if sc_scope != null and not sc_within(n, sc_scope) { return sc_into_view(n, off, room) }
|
||||
let i = 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 { off = off - i.wheel * ui_px(40.0) }
|
||||
if i.down and not in_was_down and over and i.x >= n.x + n.cw - ui_px(14.0) and most > 0.0 { sc_drag = n.key }
|
||||
if not i.down { sc_drag = "" }
|
||||
if sc_drag == n.key and room > 0.0 { off = (i.y - n.y) / room * (most + room) - room / 2.0 }
|
||||
off = sb_input(n, off, room, most)
|
||||
return sc_into_view(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(n: UiNode, off: float, room: float) -> float {
|
||||
if not fc_visible or fc_key == "" or not lc_mine(fc_key, n.key) { return off }
|
||||
|
|
@ -59,22 +72,6 @@ function sc_into_view(n: UiNode, off: float, room: float) -> float {
|
|||
if top + f.ch > off + room { return top + f.ch - room + ui_px(8.0) }
|
||||
return off
|
||||
}
|
||||
# the scrollbar: a thumb on a track down the box's right edge, when there is more than it shows
|
||||
function sc_bar(n: UiNode) -> void {
|
||||
let room = n.ch - ly_vin(n)
|
||||
let total = n.content_h - ly_vin(n)
|
||||
if total <= room or ui_be.rect == null { return }
|
||||
let w = ui_px(6.0)
|
||||
let x = n.x + n.cw - w - ui_px(2.0)
|
||||
var track = n.bar_track
|
||||
if track == -2 { track = 3355443 }
|
||||
var thumb = n.bar_c
|
||||
if thumb < 0 { thumb = 10066329 }
|
||||
if track >= 0 { ui_be.rect(x, n.y, w, n.ch, track, 0.4 * fr_ga) }
|
||||
let th = Math.max(n.ch * room / total, ui_px(24.0))
|
||||
let ty = n.y + (n.ch - th) * n.scroll_y / (total - room)
|
||||
ui_be.rect(x, ty, w, th, thumb, 0.85 * fr_ga)
|
||||
}
|
||||
# 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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue