feat(ui): scroll-top holds a scroll box at an offset, on-scroll, and ui_scroll_set
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cf8b9e425e
commit
abf45ef9db
10 changed files with 85 additions and 1 deletions
|
|
@ -457,6 +457,9 @@ applies to both:
|
||||||
anywhere) or by Enter or the pad's A held on it; it is `:active` in between.
|
anywhere) or by Enter or the pad's A held on it; it is `:active` in between.
|
||||||
- A scroll box (`overflow: auto` or `<scroll>`) takes the wheel, has a scrollbar that can be
|
- A scroll box (`overflow: auto` or `<scroll>`) takes the wheel, has a scrollbar that can be
|
||||||
dragged (`scrollbar-color`), clips what it holds and pulls the focused control into view.
|
dragged (`scrollbar-color`), clips what it holds and pulls the focused control into view.
|
||||||
|
- `scroll-top="{px}"` holds a scroll box at that offset every frame; the wheel, the bar and the
|
||||||
|
focus still move it for the frame and say so with `on-scroll` (`event.value`), for the program
|
||||||
|
to keep. `ui_scroll_set("id", px)` (an id or a key) moves a box once.
|
||||||
- The scrollbar is `.ui-scrollbar` with a `.ui-thumb`, styled like a control's parts. A press on
|
- The scrollbar is `.ui-scrollbar` with a `.ui-thumb`, styled like a control's parts. A press on
|
||||||
the thumb holds it where it was taken and the pointer moves it in proportion; a press on the
|
the thumb holds it where it was taken and the pointer moves it in proportion; a press on the
|
||||||
track jumps the thumb's middle there and holds it. A press on the bar presses nothing under it.
|
track jumps the thumb's middle there and holds it. A press on the bar presses nothing under it.
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,8 @@ What a game's screens found missing, now in `ludic.ui`:
|
||||||
as a percentage or a `calc()` of one, of the containing block;
|
as a percentage or a `calc()` of one, of the containing block;
|
||||||
- a nine-slice's corners are clamped to half the box in each direction on its own and cut on whole
|
- a nine-slice's corners are clamped to half the box in each direction on its own and cut on whole
|
||||||
pixels (`ui_nine_cuts`), so a small key cap has no seam;
|
pixels (`ui_nine_cuts`), so a small key cap has no seam;
|
||||||
|
- `scroll-top="{px}"` holds a scroll box at an offset, with `on-scroll` when the player moves it;
|
||||||
|
`ui_scroll_set(id, px)` moves one once;
|
||||||
- a transition lands exactly on its end value (it had stopped a rounding error short of it, at every
|
- a transition lands exactly on its end value (it had stopped a rounding error short of it, at every
|
||||||
frame rate).
|
frame rate).
|
||||||
|
|
||||||
|
|
|
||||||
32
examples/library/ui_scroll_hold.ludic
Normal file
32
examples/library/ui_scroll_hold.ludic
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# ui_scroll_hold.ludic — scroll-top holds a box at an offset: one follows its state, which on-scroll
|
||||||
|
# keeps in step with the wheel; one stays where it is held; and ui_scroll_set moves another once.
|
||||||
|
import "ludic.ui"
|
||||||
|
import "ui_scroll_hold_parts/Held.ludic"
|
||||||
|
program UiScrollHold {
|
||||||
|
numbers float
|
||||||
|
function frame(x: float, y: float, wheel: float) -> UiNode {
|
||||||
|
let i = new UiInput
|
||||||
|
i.x = x
|
||||||
|
i.y = y
|
||||||
|
i.wheel = wheel
|
||||||
|
ui_input(i)
|
||||||
|
ui_show("Held", null, 0.0, 0.0, 400.0, 600.0)
|
||||||
|
let root: UiNode = ui_nodes("Held", null)
|
||||||
|
ui_place(root, 0.0, 0.0, 400.0, 600.0)
|
||||||
|
return root.children[0]
|
||||||
|
}
|
||||||
|
function at(p: UiNode) -> string { return `{int(p.children[0].scroll_y)} {int(p.children[1].scroll_y)} {int(p.children[2].scroll_y)} {p.children[3].text}` }
|
||||||
|
entry {
|
||||||
|
ui_backend(new UiBackend)
|
||||||
|
let p = frame(-1.0, -1.0, 0.0)
|
||||||
|
let first = at(frame(-1.0, -1.0, 0.0))
|
||||||
|
frame(p.children[0].x + 5.0, p.children[0].y + 5.0, 1.0)
|
||||||
|
let followed = at(frame(-1.0, -1.0, 0.0))
|
||||||
|
frame(p.children[1].x + 5.0, p.children[1].y + 5.0, -1.0)
|
||||||
|
let fixed = at(frame(-1.0, -1.0, 0.0))
|
||||||
|
ui_scroll_set("free", 70.0)
|
||||||
|
let set = at(frame(-1.0, -1.0, 0.0))
|
||||||
|
frame(p.children[2].x + 5.0, p.children[2].y + 5.0, 1.0)
|
||||||
|
print(`{first} | {followed} | {fixed} | {set} | {at(frame(-1.0, -1.0, 0.0))}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
3
examples/library/ui_scroll_hold_parts/Held.lss
Normal file
3
examples/library/ui_scroll_hold_parts/Held.lss
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.page { width: 400px; gap: 4px }
|
||||||
|
.list { overflow: auto; width: 200px; height: 100px }
|
||||||
|
p { height: 40px }
|
||||||
5
examples/library/ui_scroll_hold_parts/Held.ludic
Normal file
5
examples/library/ui_scroll_hold_parts/Held.ludic
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Held.ludic - three scroll boxes: one held at its state's offset and told when it moves, one held
|
||||||
|
# at a fixed offset, and one the program moves once
|
||||||
|
component Held {
|
||||||
|
state top: float = 120.0
|
||||||
|
}
|
||||||
6
examples/library/ui_scroll_hold_parts/Held.xml
Normal file
6
examples/library/ui_scroll_hold_parts/Held.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div class="page">
|
||||||
|
<div class="list" id="log" scroll-top="{top}" on-scroll="set top = event.value"><each in="{range(10)}" as="r"><p>{r}</p></each></div>
|
||||||
|
<div class="list" id="fixed" scroll-top="50"><each in="{range(10)}" as="r"><p>{r}</p></each></div>
|
||||||
|
<div class="list" id="free"><each in="{range(10)}" as="r"><p>{r}</p></each></div>
|
||||||
|
<p>top {top}</p>
|
||||||
|
</div>
|
||||||
|
|
@ -25,6 +25,7 @@ import "input_pad.ludic"
|
||||||
import "focus.ludic"
|
import "focus.ludic"
|
||||||
import "scroll.ludic"
|
import "scroll.ludic"
|
||||||
import "scroll_bar.ludic"
|
import "scroll_bar.ludic"
|
||||||
|
import "scroll_held.ludic"
|
||||||
import "popover.ludic"
|
import "popover.ludic"
|
||||||
import "popover_anchor.ludic"
|
import "popover_anchor.ludic"
|
||||||
import "popover_within.ludic"
|
import "popover_within.ludic"
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,11 @@ function sc_walk(n: UiNode, live: bool) -> void {
|
||||||
if n.kind == UI_SCROLL {
|
if n.kind == UI_SCROLL {
|
||||||
let room = n.ch - ly_vin(n)
|
let room = n.ch - ly_vin(n)
|
||||||
let most = Math.max(n.content_h - ly_vin(n) - room, 0.0)
|
let most = Math.max(n.content_h - ly_vin(n) - room, 0.0)
|
||||||
var off = sc_get(n.key)
|
let held = sc_held(n, sc_get(n.key), live)
|
||||||
|
var off = held
|
||||||
if live { off = sc_input(n, off, room, most) }
|
if live { off = sc_input(n, off, room, most) }
|
||||||
off = Math.clamp(off, 0.0, most)
|
off = Math.clamp(off, 0.0, most)
|
||||||
|
if live and off != Math.clamp(held, 0.0, most) { ui_fire(n, "scroll", Value.float(off / ui_px(1.0))) }
|
||||||
sc_put(n.key, off)
|
sc_put(n.key, off)
|
||||||
n.scroll_y = off
|
n.scroll_y = off
|
||||||
if off != 0.0 {
|
if off != 0.0 {
|
||||||
|
|
|
||||||
29
packages/ludic.ui/scroll_held.ludic
Normal file
29
packages/ludic.ui/scroll_held.ludic
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# scroll_held.ludic - a scroll box held at an offset: scroll-top="{px}" puts it there every frame
|
||||||
|
# (the wheel, the bar and the focus still move it, and say so with on-scroll and event.value, for
|
||||||
|
# the program to keep or not), and ui_scroll_set(id or key, px) moves it once
|
||||||
|
var sc_want_keys: []string = new []string
|
||||||
|
var sc_want_px: []float = new []float
|
||||||
|
export function ui_scroll_set(key: string, px: float) -> void {
|
||||||
|
push(sc_want_keys, key)
|
||||||
|
push(sc_want_px, px)
|
||||||
|
}
|
||||||
|
# how far a box is scrolled before this frame's input: what it was, what ui_scroll_set asked for,
|
||||||
|
# or what its scroll-top says
|
||||||
|
function sc_held(n: UiNode, off: float, live: bool) -> float {
|
||||||
|
var at = off
|
||||||
|
if live {
|
||||||
|
let keys = new []string
|
||||||
|
let pxs = new []float
|
||||||
|
for i in 0 .. len(sc_want_keys) {
|
||||||
|
if sc_want_keys[i] == n.key or (n.id != "" and sc_want_keys[i] == n.id) { at = ui_px(sc_want_px[i]) } else {
|
||||||
|
push(keys, sc_want_keys[i])
|
||||||
|
push(pxs, sc_want_px[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sc_want_keys = keys
|
||||||
|
sc_want_px = pxs
|
||||||
|
}
|
||||||
|
let v = ui_attr(n, "scroll-top")
|
||||||
|
if Value.kind(v) != 0 { at = ui_px(el_num(vl_text(v))) }
|
||||||
|
return at
|
||||||
|
}
|
||||||
|
|
@ -769,6 +769,7 @@ function cmd_dev_test() -> int {
|
||||||
controller_case("library/ui_text", "", "Photograph the Be...@100 y0 | Short@160 y20 | Medium words@133 y40 | lines 5 160 one two@160 y66 three@160 y98 | tall 30 | em 20 24 | tie #00ff00", "ui_text.ludic (L11: text-fit shrinks then cuts, line-height, em against the final font size, a sheet over its imports on a tie)")
|
controller_case("library/ui_text", "", "Photograph the Be...@100 y0 | Short@160 y20 | Medium words@133 y40 | lines 5 160 one two@160 y66 three@160 y98 | tall 30 | em 20 24 | tie #00ff00", "ui_text.ludic (L11: text-fit shrinks then cuts, line-height, em against the final font size, a sheet over its imports on a tie)")
|
||||||
controller_case("library/ui_calc", "", "min 200 max 82 clamp 50 21 | p 150,60 q 255,80 r 30", "ui_calc.ludic (L11: min(), max() and clamp(), and insets as percentages and calc() of the containing block)")
|
controller_case("library/ui_calc", "", "min 200 max 82 clamp 50 21 | p 150,60 q 255,80 r 30", "ui_calc.ludic (L11: min(), max() and clamp(), and insets as percentages and calc() of the containing block)")
|
||||||
controller_case("library/ui_nine", "", "cap 0 12 18 30/ 0 8 8 16 | wide 0 12 88 100/ 0 12 28 40 | odd 10 21 21 31/ 4 8 8 13", "ui_nine.ludic (L11: a nine-slice's corners clamped to half the box each way, on whole pixels)")
|
controller_case("library/ui_nine", "", "cap 0 12 18 30/ 0 8 8 16 | wide 0 12 88 100/ 0 12 28 40 | odd 10 21 21 31/ 4 8 8 13", "ui_nine.ludic (L11: a nine-slice's corners clamped to half the box each way, on whole pixels)")
|
||||||
|
controller_case("library/ui_scroll_hold", "", "120 50 0 top 120 | 80 50 0 top 80 | 80 50 0 top 80 | 80 50 70 top 80 | 80 50 30 top 80", "ui_scroll_hold.ludic (L11: scroll-top holds a box at an offset, on-scroll follows the wheel, ui_scroll_set moves one once)")
|
||||||
controller_case("library/ui_inputs", "", "count 12 12 32 0.5 0 | 20 32 0.5 0 | 19 32 0.5 0 | How many to carry | Space bar listening 1 19 9 0.5 0 | 19 9 0.5 0 | 19 0 0.5 0 | 60% 0.50 s | 19 0 0.6 9 | 7!", "ui_inputs.ludic (L11: number fields, key capture with Esc and Backspace, notes, range formats, an event named set, a string prop given a number)")
|
controller_case("library/ui_inputs", "", "count 12 12 32 0.5 0 | 20 32 0.5 0 | 19 32 0.5 0 | How many to carry | Space bar listening 1 19 9 0.5 0 | 19 9 0.5 0 | 19 0 0.5 0 | 60% 0.50 s | 19 0 0.6 9 | 7!", "ui_inputs.ludic (L11: number fields, key capture with Esc and Backspace, notes, range formats, an event named set, a string prop given a number)")
|
||||||
controller_case("library/ui_boxes", "", "list 120 of 200 | text at 32 152 wide, 3 lines, row 60 | zero 0x0 | half 140x36 pad 4 2 | order abc at 16 | fill 98x8 of 200x12", "ui_boxes.ludic (L11: flex-shrink to the leftover room, text wrapping beside its siblings, zero sizes, calc(), order, the range track as a row)")
|
controller_case("library/ui_boxes", "", "list 120 of 200 | text at 32 152 wide, 3 lines, row 60 | zero 0x0 | half 140x36 pad 4 2 | order abc at 16 | fill 98x8 of 200x12", "ui_boxes.ludic (L11: flex-shrink to the leftover room, text wrapping beside its siblings, zero sizes, calc(), order, the range track as a row)")
|
||||||
controller_case("library/ui_look", "", "root div.in.i.m pad 5 margin 9 color #ff0000 text Hi 2,53 #102030 | text Hi 0,50 #eeeeee | opacity 0.25 | nine 40 #ffffff nine 40 #ff0000 of 2 image photo.png #ffffff | image icon:tent #00ff00 | text first line 29,205 #eeeeee | text second line 29,223 #eeeeee by id at 84,50 | flipped at 246,268 100x32", "ui_look.ludic (L11: nested components styled by specificity, text-shadow, ui_opacity, border-image tints, pictures, tooltip lines, anchored popovers)")
|
controller_case("library/ui_look", "", "root div.in.i.m pad 5 margin 9 color #ff0000 text Hi 2,53 #102030 | text Hi 0,50 #eeeeee | opacity 0.25 | nine 40 #ffffff nine 40 #ff0000 of 2 image photo.png #ffffff | image icon:tent #00ff00 | text first line 29,205 #eeeeee | text second line 29,223 #eeeeee by id at 84,50 | flipped at 246,268 100x32", "ui_look.ludic (L11: nested components styled by specificity, text-shadow, ui_opacity, border-image tints, pictures, tooltip lines, anchored popovers)")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue