diff --git a/LANGUAGE.md b/LANGUAGE.md index 8179a05f..5f0189bb 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -509,7 +509,9 @@ applies to both: (the default), `center` or `end` along that side. Where it would leave its bounds it opens to the other side and is kept inside them: `within="panel"` names the element, and by default it is the nearest scroll box (or `overflow` box) around it, else the screen. - - `title="..."` shows a tooltip (`.ui-tooltip`) after the pointer rests for half a second. A + - `title="..."` shows a tooltip (`.ui-tooltip`) after the pointer rests for half a second, or + under the element when the keyboard's focus (`:focus-visible`) has rested on it as long; moving + the pointer gives the tooltip back to the pointer. A newline or a written `\n` in it starts another line (`.ui-tooltip-text` each). - `` and `` fill (`.ui-fill`) as far as their value. - **Natives** are for what markup cannot say. `ui_native("minimap", measure, draw)` makes diff --git a/changes/defaults-views-templates.md b/changes/defaults-views-templates.md index d4d09e30..2212a94f 100644 --- a/changes/defaults-views-templates.md +++ b/changes/defaults-views-templates.md @@ -104,6 +104,8 @@ What a game's screens found missing, now in `ludic.ui`: else line by line; - `` takes a mouse button (`UI_MOUSE_LEFT` / `RIGHT` / `MIDDLE`, 256-258) and is `:capturing` while it listens; +- a `title` shows for the keyboard's focus too, after the same half second; a focus ring drawn + through a renderer with no `rect` no longer crashes; - a transition lands exactly on its end value (it had stopped a rounding error short of it, at every frame rate). diff --git a/examples/library/ui_focus_tip.ludic b/examples/library/ui_focus_tip.ludic new file mode 100644 index 00000000..356de6a9 --- /dev/null +++ b/examples/library/ui_focus_tip.ludic @@ -0,0 +1,39 @@ +# ui_focus_tip.ludic — a title shows for the keyboard's focus as it does for the pointer: after the +# focus has rested half a second on an element with one, under that element; moving the pointer +# hands the tooltip back to the pointer. +import "ludic.ui" +program UiFocusTip { + numbers float + var said: []string = new []string + var now: float = 0.0 + const PAGE: string = "" + function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, `{s} {int(x)},{int(y)}`) } + function clock() -> float { return now } + function frame(i: UiInput, secs: float) -> string { + now = now + secs + said = new []string + ui_input(i) + ui_show("f", null, 0.0, 0.0, 400.0, 400.0) + return said[len(said) - 1] + } + entry { + let b = new UiBackend + b.text = fn rec_text + ui_backend(b) + ui_clock(fn clock) + ui_load_text(PAGE, "f.xml") + frame(new UiInput, 0.0) + let tab = new UiInput + tab.tab = true + frame(tab, 0.0) + let early = frame(new UiInput, 0.2) + let shown = frame(new UiInput, 0.4) + frame(tab, 0.0) + let next = frame(new UiInput, 0.6) + let moved = new UiInput + moved.x = 390.0 + moved.y = 390.0 + let gone = frame(moved, 0.6) + print(`{early} | {shown} | {next} | {gone}`) + } +} diff --git a/packages/ludic.ui/draw.ludic b/packages/ludic.ui/draw.ludic index 1972ed90..7a6cb55b 100644 --- a/packages/ludic.ui/draw.ludic +++ b/packages/ludic.ui/draw.ludic @@ -80,5 +80,5 @@ function fr_outline(n: UiNode) -> void { ui_be.ring(n.x - o, n.y - o, n.cw + o * 2.0, n.ch + o * 2.0, n.radius + o, n.outline_w, c, a) return } - fr_frame(n.x - o, n.y - o, n.cw + o * 2.0, n.ch + o * 2.0, n.outline_w, c, a) + if ui_be.rect != null { fr_frame(n.x - o, n.y - o, n.cw + o * 2.0, n.ch + o * 2.0, n.outline_w, c, a) } } diff --git a/packages/ludic.ui/frame.ludic b/packages/ludic.ui/frame.ludic index feb8d304..053b1a82 100644 --- a/packages/ludic.ui/frame.ludic +++ b/packages/ludic.ui/frame.ludic @@ -14,6 +14,7 @@ export function ui_show(screen: string, view: UiView, x: float, y: float, w: flo sc_update(root) fr_pointer(root) fc_keys(root) + tt_track(root, in_now.x, in_now.y) if ui_be != null and ui_be.text != null { fr_draw(root) pp_draw_top() diff --git a/packages/ludic.ui/pointer.ludic b/packages/ludic.ui/pointer.ludic index 2889af42..28ac3aca 100644 --- a/packages/ludic.ui/pointer.ludic +++ b/packages/ludic.ui/pointer.ludic @@ -15,7 +15,6 @@ function fr_pointer(root: UiNode) -> void { 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 } if sc_drag != "" or ct_key_mouse(root) { return } let hit = fr_hit(scope, i.x, i.y) diff --git a/packages/ludic.ui/tooltip.ludic b/packages/ludic.ui/tooltip.ludic index 79c0c3cd..e47a2e58 100644 --- a/packages/ludic.ui/tooltip.ludic +++ b/packages/ludic.ui/tooltip.ludic @@ -1,13 +1,23 @@ -# tooltip.ludic - an element's title="..." shown beside the pointer once it has rested on the -# element for half a second, in a box styled by .ui-tooltip (the default sheet's, or a theme's); a -# title of several lines (a newline, or \n) is one .ui-tooltip-text a line +# tooltip.ludic - an element's title="..." shown beside the pointer once it has rested on it for half +# a second (or under it, when the keyboard's focus has), in a box styled by .ui-tooltip; a title of +# several lines (a newline, or \n) is one .ui-tooltip-text a line var tt_key: string = "" var tt_since: float = 0.0 var tt_text: string = "" var tt_x: float = 0.0 var tt_y: float = 0.0 -function tt_track(scope: UiNode, x: float, y: float) -> void { - let n = tt_under(scope, x, y) +function tt_track(root: UiNode, x: float, y: float) -> void { + var scope = pp_top(root) + if scope == null { scope = root } + var n = tt_under(scope, x, y) + var px = x + var py = y + let k = tt_focused(scope, x, y) + if k != null { + n = k + px = k.x + py = k.y + k.ch - ui_px(14.0) + } if n == null { tt_key = "" return @@ -17,8 +27,8 @@ function tt_track(scope: UiNode, x: float, y: float) -> void { tt_since = an_now() } tt_text = tt_translate(n, ui_attr_text(n, "title")) - tt_x = x - tt_y = y + tt_x = px + tt_y = py } # the deepest element under the point that has a title function tt_under(n: UiNode, x: float, y: float) -> UiNode { diff --git a/packages/ludic.ui/tooltip_text.ludic b/packages/ludic.ui/tooltip_text.ludic index ff09e417..8ead17fe 100644 --- a/packages/ludic.ui/tooltip_text.ludic +++ b/packages/ludic.ui/tooltip_text.ludic @@ -1,5 +1,17 @@ -# tooltip_text.ludic - a title in the program's language: the whole of it when the translator knows -# it, else line by line; and not at all inside translate="no" (the nearest translate= out from it) +# tooltip_text.ludic - a title in the program's language (whole when the translator knows it whole, +# else line by line, and not inside translate="no"), and a title for the keyboard's focus +var tt_by_keys: bool = false # the focus moved by the keyboard since the pointer last moved +var tt_focus_seen: string = "" +# the focused element, when the keyboard put the focus there and it has a title; else null +function tt_focused(scope: UiNode, x: float, y: float) -> UiNode { + if in_last.x != x or in_last.y != y { tt_by_keys = false } + if fc_visible and fc_key != "" and fc_key != tt_focus_seen { tt_by_keys = true } + tt_focus_seen = fc_key + if not tt_by_keys or not fc_visible { return null } + let n = fr_node(scope, fc_key) + if n == null or n.akeys == null or ui_attr_text(n, "title") == "" { return null } + return n +} function tt_translate(n: UiNode, s: string) -> string { var at = n while at != null { diff --git a/tools/ludic-cli/test.ludic b/tools/ludic-cli/test.ludic index b69a3fc0..47ebba23 100644 --- a/tools/ludic-cli/test.ludic +++ b/tools/ludic-cli/test.ludic @@ -773,6 +773,7 @@ function cmd_dev_test() -> int { controller_case("library/ui_fill", "", "0,0 2x20 #030303 .. 98,0 2x20 #fcfcfc | 0,20 50x2 #f2000d .. 0,38 50x2 #0d00f2 .. 0,58 50x2 #0000ff of 70 | 0,85 100x50 | -50,160 200x100 clipped 1 | ratio 90 knob 60", "ui_fill.ludic (L11: linear-gradient backgrounds, object-fit contain and cover, aspect-ratio for boxes and natives)") controller_case("library/ui_translate", "", "Merhaba Hello Hello Merhaba | c/Kaydet ve cik | Satir bir/Satir iki | Line one/Line two", "ui_translate.ludic (L11: translate=no and yes, a title of several lines translated whole or line by line)") controller_case("library/ui_keycap", "", "listening 16711680 | 257 Right click -2 | 258 Middle click -2 | 256 Left click -2 | pressed 0", "ui_keycap.ludic (L11: a key field takes the left, right and middle mouse buttons, and is :capturing while it listens)") + controller_case("library/ui_focus_tip", "", "b 14,38 | Drop it 27,45 | Give it away 27,77 | b 14,38", "ui_focus_tip.ludic (L11: a title shows for the keyboard's focus after half a second, and goes back to the pointer when it moves)") 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_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)")