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)")