ludic/packages/ludic.ui/pointer.ludic
Orkuncakilkaya 0a7525b125 feat(ui): popovers, tooltips, progress; the render3d backend installs itself; duplicate names caught first
- `popover`: a top layer that keeps the pointer and the keyboard, closed by a press outside or Esc.
- `title` tooltips after half a second's rest, styled by .ui-tooltip.
- `<progress>` and `<meter>`.
- Atlases take rows and number cells, and importing ludic.ui/render3d.ludic is enough to draw with
  render3d.
- The compiler reports two declarations of one name before it type-checks, so a package global
  clashing with a program's reads as that, not as 29 type errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 17:41:02 +03:00

59 lines
2.2 KiB
Text

# pointer.ludic - the pointer: what it is over is :hover (the element and all around it, as in CSS),
# a press on a control focuses it and makes it :active, and letting go over it is a click. The
# control being dragged (a range's thumb, a scrollbar) keeps the pointer until it is let go.
var fr_hover: []string = new []string
var fr_press_key: string = ""
var fr_down_now: bool = false
function fr_was_hovered(key: string) -> bool { return sel_has(fr_hover, key) }
function fr_pointer(root: UiNode) -> void {
let i = in_now
fr_down_now = i.down
let keys = new []string
let top = pp_top(root)
var scope = root
if top != null { scope = top }
fr_under(scope, i.x, i.y, keys)
fr_hover = keys
tt_track(scope, i.x, i.y)
if top != null and pp_pointer(top) { return }
let hit = fr_hit(scope, i.x, i.y)
if i.down and not in_was_down {
fr_press_key = ""
if hit != null {
fr_press_key = hit.key
if hit.focusable { ui_focus(hit.key) }
ct_press(hit, i.x, i.y)
}
}
if i.down and fr_press_key != "" { ct_drag(root, fr_press_key, i.x, i.y) }
if not i.down and in_was_down {
if hit != null and hit.key == fr_press_key and hit.enabled { ct_activate(hit) }
fr_press_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 != "" { push(keys, n.key) }
for i in 0 .. len(n.children) { fr_under(n.children[i], px, py, keys) }
}
# the deepest control (or pressable element) under the pointer
function fr_hit(n: UiNode, px: float, py: float) -> UiNode {
if px < n.x or py < n.y or px >= n.x + n.cw or py >= n.y + n.ch { return null }
var i = len(n.children) - 1
while i >= 0 {
let h = fr_hit(n.children[i], px, py)
if h != null { return h }
i -= 1
}
if n.focusable or n.act != null { return n }
return null
}
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 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
}