diff --git a/examples/library/ui_pointer_through.ludic b/examples/library/ui_pointer_through.ludic
new file mode 100644
index 00000000..a2c5e69e
--- /dev/null
+++ b/examples/library/ui_pointer_through.ludic
@@ -0,0 +1,55 @@
+# ui_pointer_through.ludic - a screen whose root
holds only a positioned panel (a component's
+# root round a modal) has no size of its own; the pointer looks through it, so the panel's button is
+# pressed and hovered - once nothing under it was. A select's < and > answer :hover as elements do.
+import "ludic.ui"
+program UiPointerThrough {
+ numbers float
+ state ThroughState {
+ got: int = 0
+ v: int = 1
+ }
+ view V (through_st: mut ThroughState) {
+ v = through_st.v
+ on hit() { through_st.got += 1 }
+ on set(x: int) { through_st.v = x }
+ }
+ const PAGE: string = "
"
+ function frame(ui_st: mut UiState, x: float, y: float, down: bool) -> UiNode {
+ let i = new UiInput
+ i.x = x
+ i.y = y
+ i.down = down
+ ui_input(ui_st, i)
+ ui_show(ui_st, "s", view_v(), 0.0, 0.0, 400.0, 400.0)
+ let root: UiNode = ui_nodes(ui_st, "s", view_v())
+ ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
+ return root
+ }
+ function find(n: UiNode, tag: string, cls: string) -> UiNode {
+ var has = cls == ""
+ for i in 0 .. len(n.classes) { if n.classes[i] == cls { has = true } }
+ if n.tag == tag and has { return n }
+ for i in 0 .. len(n.children) {
+ let f = find(n.children[i], tag, cls)
+ if f != null { return f }
+ }
+ return null
+ }
+ entry (ui_st: mut UiState, through_st: ThroughState) {
+ ui_backend(ui_st, new UiBackend)
+ ui_load_text(ui_st, PAGE, "s.xml")
+ var root = frame(ui_st, -1.0, -1.0, false)
+ let b = find(root, "button", "")
+ let x = b.x + b.cw / 2.0
+ let y = b.y + b.ch / 2.0
+ frame(ui_st, x, y, false)
+ root = frame(ui_st, x, y, false)
+ let hover = ui_hex(find(root, "button", "").bg)
+ frame(ui_st, x, y, true)
+ frame(ui_st, x, y, false)
+ let nx = find(root, "span", "ui-next")
+ frame(ui_st, nx.x + nx.cw / 2.0, nx.y + nx.ch / 2.0, false)
+ root = frame(ui_st, nx.x + nx.cw / 2.0, nx.y + nx.ch / 2.0, false)
+ print(`pressed {through_st.got}, hovered {hover}; > hovered {find(root, "span", "ui-next").hovered}`)
+ }
+}
diff --git a/packages/ludic.ui/controls_build.ludic b/packages/ludic.ui/controls_build.ludic
index f87c4d0d..bf02a159 100644
--- a/packages/ludic.ui/controls_build.ludic
+++ b/packages/ludic.ui/controls_build.ludic
@@ -7,6 +7,7 @@
function ct_part(ui_st: mut UiState, n: UiNode, cls: string, text: string) -> UiNode {
let p = nd_take(ui_st)
p.key = mm_join(ui_st, n.key, "/", cls)
+ p.hovered = fr_was_hovered(ui_st, p.key) # a part answers :hover as an element does (a select's < and >)
p.tag = "span"
p.env = n.env
p.sheet = n.sheet
diff --git a/packages/ludic.ui/pointer.ludic b/packages/ludic.ui/pointer.ludic
index e19b2ca6..b280d613 100644
--- a/packages/ludic.ui/pointer.ludic
+++ b/packages/ludic.ui/pointer.ludic
@@ -42,8 +42,9 @@ function fr_active(ui_st: UiState, n: UiNode) -> bool {
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) }
+ let out = px < n.x or py < n.y or px >= n.x + n.cw or py >= n.y + n.ch
+ if out and not fr_empty(n) { return }
+ if not out and 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
diff --git a/packages/ludic.ui/pointer_pick.ludic b/packages/ludic.ui/pointer_pick.ludic
index 4692ba67..901db97e 100644
--- a/packages/ludic.ui/pointer_pick.ludic
+++ b/packages/ludic.ui/pointer_pick.ludic
@@ -36,10 +36,17 @@ function fr_pick(n: UiNode, px: float, py: float, kind: int) -> UiNode {
while k >= 0 {
let c = n.children[k]
if c.pos == 0 and not c.popover and fr_in(c, px, py) { return fr_pick_in(n, c, px, py, kind) }
+ if c.pos == 0 and not c.popover and fr_empty(c) {
+ let r = fr_pick(c, px, py, kind)
+ if r != null and r != c { return r }
+ }
k -= 1
}
return fr_wants(n, kind)
}
+# a box with no size of its own - a component's root
round a positioned screen - holds no point
+# and clips nothing: what it holds is looked through it (a screen in one was pressed and hovered never)
+function fr_empty(n: UiNode) -> bool { return n.children != null and (n.cw <= 0.0 or n.ch <= 0.0) }
function fr_pick_in(n: UiNode, c: UiNode, px: float, py: float, kind: int) -> UiNode {
let r = fr_pick(c, px, py, kind)
if r != null { return r }
diff --git a/tools/ludic-cli/test.ludic b/tools/ludic-cli/test.ludic
index 657e7afa..a27000c6 100644
--- a/tools/ludic-cli/test.ludic
+++ b/tools/ludic-cli/test.ludic
@@ -1246,6 +1246,7 @@ function cmd_dev_test() -> int {
controller_case("library/ui_box", "", "scale 1.5 | panel 35,75 150x90 | missing 1", "ui_box.ludic (L11: ui_scale and ui_box, the interface's scale and an element's laid-out box, read from outside)")
controller_case("library/ui_emit_click", "", "got 2 | pressed: got 1", "ui_emit_click.ludic (L11: a component's `emit click` reaches its user's on-click, by the mouse and by a press)")
controller_case("library/ui_select_arrows", "", "prev 0 next next 2", "ui_select_arrows.ludic (L11: a select's < steps back and its > forward)")
+ controller_case("library/ui_pointer_through", "", "pressed 1, hovered #4a5058; > hovered 1", "ui_pointer_through.ludic (L11: the pointer looks through a root with no size to a positioned panel's button; a select's > answers :hover)")
controller_case("library/ui_submit", "", "[hi] chat '' | [hi][yo][Ada] chat '' name 'Ada'", "ui_submit.ludic (L11: on-submit on a text field by Enter or A, clear-on-submit, the focus kept)")
controller_case("library/ui_zoom", "", "plain 100 pad 10 text 16 h 20 | half 50 pad 5 text 8 h 10 | half of twice 100 | fit 50 em 16", "ui_zoom.ludic (L11: zoom scales a subtree's lengths, text and layout, multiplies, and takes min() and a length over a length)")
controller_case("library/ui_hold", "", "31 frames 500 ms t 500 | 121 frames 500 ms t 500 | pad 16 frames 250 ms t 250 | dududu", "ui_hold.ludic (L11: on-hold every frame a button is held, by the pointer or A, with dt and t on the clock at any rate)")