diff --git a/changes/ui-mounted.md b/changes/ui-mounted.md new file mode 100644 index 00000000..d029d68f --- /dev/null +++ b/changes/ui-mounted.md @@ -0,0 +1,6 @@ +bump: minor +type: feature +**`ui_mounted` and `ui_model_of`: an editor's questions of a running interface.** `ui_mounted(out)` fills the +caller's list with the classes of the components the last frame showed, in tree order and each once; +`ui_model_of(cls)` is the first mounted instance's model, read in place (the next frame fills it again), or +null. For devlink's ui_screen and ui_model; asked on request, never a frame. diff --git a/examples/library/ui_mounted.ludic b/examples/library/ui_mounted.ludic new file mode 100644 index 00000000..083468b4 --- /dev/null +++ b/examples/library/ui_mounted.ludic @@ -0,0 +1,18 @@ +# ui_mounted.ludic - an editor's questions of a running interface: which components the last frame +# showed (in tree order, each once) and one's model, read in place. App shows two Counters: the list +# is App then Counter, a Counter has a model, and a class not shown has none. Prints `App|Counter| 1 1`. +import "ludic.ui" +import "ui_component_parts/Counter.ludic" +import "ui_component_parts/App.ludic" +program UiMounted { + numbers float + entry (ui_st: mut UiState) { + let root: UiNode = ui_nodes(ui_st, "App", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) + let names = new []string + ui_mounted(ui_st, names) + var joined = "" + for i in 0 .. len(names) { joined = joined + names[i] + "|" } + print(`{joined} {ui_model_of(ui_st, "Counter") != null} {ui_model_of(ui_st, "Nothing") == null}`) + } +} diff --git a/packages/ludic.ui/index.ludic b/packages/ludic.ui/index.ludic index 022c474d..39dcb84e 100644 --- a/packages/ludic.ui/index.ludic +++ b/packages/ludic.ui/index.ludic @@ -111,3 +111,4 @@ import "dump.ludic" import "inspect.ludic" import "held.ludic" import "override.ludic" +import "mounted.ludic" diff --git a/packages/ludic.ui/mounted.ludic b/packages/ludic.ui/mounted.ludic new file mode 100644 index 00000000..e9324f7a --- /dev/null +++ b/packages/ludic.ui/mounted.ludic @@ -0,0 +1,39 @@ +# mounted.ludic - which components the last frame showed, and one's model: an editor's questions of a +# running game's interface (devlink's ui_screen and ui_model), asked on request and never a frame. + +# the class names of the components mounted in the last build, in tree order (every root the frame +# drew, a node at a time) and each once, into the caller's list +@alloc_ok("an inspector's question: the caller's list grows to the most classes shown at once") +export function ui_mounted(ui_st: UiState, out: []string) -> void { + List.clear(out) + for r in 0 .. len(ui_st.fb_roots) { mt_walk(ui_st, ui_st.fb_roots[r], out) } + for i in 0 .. len(ui_st.in_list) { # a mounted one the walk did not meet, in the order made + let it = ui_st.in_list[i] + if it.seen == ui_st.in_build { mt_add(out, it.cls.name) } + } +} +@alloc_ok("an inspector's question: the caller's list grows to the most classes shown at once") +function mt_walk(ui_st: UiState, n: UiNode, out: []string) -> void { + if n == null { return } + for i in 0 .. len(ui_st.in_list) { + let it = ui_st.in_list[i] + if it.seen == ui_st.in_build and it.key == n.key { mt_add(out, it.cls.name) } + } + for c in 0 .. len(n.children) { mt_walk(ui_st, n.children[c], out) } +} +@alloc_ok("an inspector's question: the caller's list grows to the most classes shown at once") +function mt_add(out: []string, name: string) -> void { + for i in 0 .. len(out) { + if out[i] == name { return } + } + push(out, name) +} +# the model of the first mounted instance of `cls` (the Val its class's model() fills), or null: read +# it now, as the next frame fills the same object again +export function ui_model_of(ui_st: UiState, cls: string) -> Val { + for i in 0 .. len(ui_st.in_list) { + let it = ui_st.in_list[i] + if it.seen == ui_st.in_build and it.cls != null and it.cls.name == cls { return it.model } + } + return null +} diff --git a/tools/ludic-cli/test.ludic b/tools/ludic-cli/test.ludic index 7699683d..59440ba3 100644 --- a/tools/ludic-cli/test.ludic +++ b/tools/ludic-cli/test.ludic @@ -1296,6 +1296,7 @@ function cmd_dev_test() -> int { controller_case("library/ui_reload", "", "ui: templates reloaded note 1 | reloaded 0 then 1 | note again 1", "ui_reload.ludic (L11: a template changed on disk is read again, and its state kept)") controller_case("library/ui_component", "", "counters|a: 1 (2)|+|reset|b: 10 (20) big|+|reset|outside| b margin 9; b text 16711680; outside -1; button 30 b: 0 (0)", "ui_component.ludic (L11: components as NAME.ludic + .xml + .lss - props, state, fields, events, scoped styles, @import, class passed down)") controller_case("library/ui_override", "", "counters|a: 1 (2)|+|reset|b: 0 (0)|+|reset|outside| counters|[a=1]|+|[b=0]|+|outside| 1 counters|a: 1 (2)|+|reset|b: 0 (0)|+|reset|outside| 0", "ui_override.ludic (a template given from outside: shown with the state kept, and the file back when let go)") + controller_case("library/ui_mounted", "", "App|Counter| 1 1", "ui_mounted.ludic (the components the last frame showed, in tree order and each once, and one's model)") reject_case("rejected/component_no_template", "there is no Lonely.xml beside it", "a component needs its template beside it") controller_case("library/ui_remount", "", "c: 2 (4) | c: 0 (0) | heap 0", "ui_remount.ludic (a component unmounted and mounted again starts as new, and two thousand comings and goings hold the heap flat)") controller_case("library/ui_controls", "", "1 false 7 2 Adalove 70 1 false 8 2 Adalove 70; scrolled 40", "ui_controls.ludic (L11: focus and keys, the built-in controls, a range dragged, a box scrolled)")