ludic/packages/ludic.ui/pointer.ludic
Orkuncakilkaya e476a9d975 ludic.ui: emit click reaches on-click, and a select's < steps back
From the user's play: clicking an item in the pack opened nothing, and the wardrobe's picks did
not respond - both are a component whose button says `emit click` (ItemCell, LookCell, ListRow),
answered by its user's on-click. An on-* attribute's name is kept as a browser would have it, so
on-click is on-press (tpl_event), and the emit looked for "click" and found nothing. An emitted
name now goes through the same tpl_event.

And a settings cycler's left arrow did the right one's job: any press let go on a select stepped it
forward. ct_activate_at steps back when it is let go over the select's .ui-prev; Enter and the rest
of the select still step forward.

Goldens ui_emit_click (the mouse and a press both reach on-click; with the toolchain before this,
neither does) and ui_select_arrows (1 -> 0 on <, then 2 on > >; before this, 1 -> 2 on <). The 33
ui examples pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:32:45 +03:00

63 lines
3 KiB
Text

# pointer.ludic - the pointer: what it is over is :hover (and all around it, as in CSS), a press on a
# control focuses it and makes it :active, letting go over it is a click. A dragged thumb or scrollbar
# keeps the pointer until let go; a press that takes a scrollbar or closes a popover presses nothing else.
function fr_was_hovered(ui_st: UiState, key: string) -> bool { return sel_has(ui_st.fr_hover, key) }
function fr_pointer(ui_st: mut UiState, root: UiNode) -> void {
let i = ui_st.in_now
ui_st.fr_down_now = i.down
let keys = ui_st.fr_hover_sp # the spare list, emptied; last frame's goes spare
List.clear(keys)
let top = pp_top(root)
var scope = root
if top != null { scope = top }
fr_under(scope, i.x, i.y, keys)
ui_st.fr_hover_sp = ui_st.fr_hover
ui_st.fr_hover = keys
if top != null and pp_pointer(ui_st, top) { return }
if ui_st.sc_drag != "" or ct_key_mouse(ui_st, root) { return }
let hit = fr_hit(scope, i.x, i.y)
pe_frame(ui_st, root, scope)
if i.down and not ui_st.in_was_down { fr_press(ui_st, hit, i.x, i.y) }
if i.down and ui_st.fr_press_key != "" { ct_drag(ui_st, root, ui_st.fr_press_key, i.x, i.y) }
if not i.down and ui_st.in_was_down {
let was = fr_node(root, ui_st.fr_press_key)
if was != null { ui_fire(ui_st, was, "up", ui_st.fc_none) }
if hit != null and hit.key == ui_st.fr_press_key and hit.enabled { ct_activate_at(ui_st, hit, i.x, i.y) }
ui_st.fr_press_key = ""
}
}
function fr_press(ui_st: mut UiState, hit: UiNode, x: float, y: float) -> void {
ct_key_pressed_away(ui_st, hit)
ui_st.fr_press_key = ""
if hit == null { return }
ui_st.fr_press_key = hit.key
if hit.focusable { ui_focus(ui_st, hit.key) }
if hit.enabled { ui_fire(ui_st, hit, "down", ui_st.fc_none) }
ct_press(ui_st, hit, x, y)
}
# :active - the press began on it, or inside it, and is still held (by the pointer, Enter or A)
function fr_active(ui_st: UiState, n: UiNode) -> bool {
if ui_st.fr_down_now and ui_st.fr_press_key != "" and lc_mine(ui_st.fr_press_key, n.key) { return true }
if ui_st.pe_key != "" and lc_mine(ui_st.pe_key, n.key) { return true }
return ui_st.fr_key_press != "" and lc_mine(ui_st.fr_key_press, n.key)
}
function fr_under(n: UiNode, px: float, py: float, keys: []string) -> void {
if px < n.x or py < n.y or px >= n.x + n.cw or py >= n.y + n.ch { return }
if n.key != "" { ui_kept(keys, n.key) }
for i in 0 .. len(n.children) { fr_under(n.children[i], px, py, keys) }
}
# the control (or pressable element) the pointer is on: out from the topmost element under it
function fr_hit(n: UiNode, px: float, py: float) -> UiNode {
if not fr_in(n, px, py) { return null }
return fr_pick(n, px, py, PK_CONTROL)
}
export function ui_hit(root: UiNode, px: float, py: float) -> UiNode { return fr_hit(root, px, py) }
function fr_node(n: UiNode, key: string) -> UiNode {
if key == "" { return null }
if n.key == key { return n }
for i in 0 .. len(n.children) {
let f = fr_node(n.children[i], key)
if f != null { return f }
}
return null
}