ludic ui-preview: ludic.ui components previewed for a studio over stdio (R5)
A prebuilt tool, bin/ludic-ui-preview (built by ludic-dev build, shipped beside ludic-lsp, run as `ludic ui-preview [--font DIR]`): ludic.ui with a backend that records every draw call as a line, and mock component classes the studio describes (load / model / calls). frame t runs ui_show at interface time t; text is measured on the CPU with the game's font.json metrics; locale goes through ludic.i18n; tree / box / rules inspect the frame. No game code, no GPU, no network. The wire protocol is frozen as tools/ui-preview/protocol-v1.md; smoke.txt is a transcript to run. ludic.ui gains, all additive: UiClass.make_of, UiBackend.said / emitted, UiRule.text / at (with comment line breaks kept so rule lines count true, and a class's styles read under its .lss path), and ui_root, ui_find, ui_rules_of, ui_rule_value, ui_building, ui_class, ui_class_load, ui_file_forget, ui_errors_clear; ui_nine_cuts_into exported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
28f7b4666b
commit
49b5e80af5
38 changed files with 1986 additions and 16 deletions
71
packages/ludic.ui/inspect.ludic
Normal file
71
packages/ludic.ui/inspect.ludic
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# inspect.ludic - what an inspector asks of the last frame shown (ludic ui-preview, a developer's
|
||||
# tools): a screen's root, an element by id, the rules that style it, and whether a build is running
|
||||
# the root of the screen `screen` as it was last shown, or null
|
||||
export function ui_root(ui_st: UiState, screen: string) -> UiNode {
|
||||
for i in 0 .. len(ui_st.fb_roots) {
|
||||
if ui_st.fb_roots[i].key == screen { return ui_st.fb_roots[i] }
|
||||
}
|
||||
return null
|
||||
}
|
||||
# the element with that id (or key) on any screen shown, or null
|
||||
export function ui_find(ui_st: UiState, id: string) -> UiNode {
|
||||
for i in 0 .. len(ui_st.fb_roots) {
|
||||
let n = fb_find(ui_st.fb_roots[i], id)
|
||||
if n != null { return n }
|
||||
}
|
||||
return null
|
||||
}
|
||||
# true while a frame's tree is being built (a model read), false while an action runs (a press)
|
||||
export function ui_building(ui_st: UiState) -> bool { return ui_st.uv_on }
|
||||
# the rules that match the element now, in the order the cascade applies them: the default sheet's,
|
||||
# then its file's (merged with each user's sheet for a component's root); its style="..." is not one
|
||||
@alloc_ok("an inspector's question, never a frame")
|
||||
export function ui_rules_of(ui_st: mut UiState, n: UiNode, out: []UiRule) -> void {
|
||||
List.clear(out)
|
||||
ir_sheet(ui_st, n, ua_sheet(ui_st), out)
|
||||
if n.ups == null {
|
||||
if n.sheet != null { ir_sheet(ui_st, n, n.sheet, out) }
|
||||
return
|
||||
}
|
||||
let at = len(out)
|
||||
if n.sheet != null { ir_sheet(ui_st, n, n.sheet, out) }
|
||||
for i in 0 .. len(n.ups) {
|
||||
if n.ups[i].sheet != null and n.ups[i].sheet != n.sheet { ir_sheet(ui_st, n, n.ups[i].sheet, out) }
|
||||
}
|
||||
for i in at + 1 .. len(out) { # one order by specificity, stable, as cs_merged applies them
|
||||
let r = out[i]
|
||||
var j = i
|
||||
while j > at and out[j - 1].spec > r.spec {
|
||||
out[j] = out[j - 1]
|
||||
j -= 1
|
||||
}
|
||||
out[j] = r
|
||||
}
|
||||
}
|
||||
@alloc_ok("an inspector's question, never a frame")
|
||||
function ir_sheet(ui_st: mut UiState, n: UiNode, sh: UiSheet, out: []UiRule) -> void {
|
||||
for i in 0 .. len(sh.rules) {
|
||||
let r = sh.rules[i]
|
||||
if md_ok(ui_st, r) and sel_match(ui_st, r.sel, n) { push(out, r) }
|
||||
}
|
||||
}
|
||||
# a declaration's value as written, when it is plain text (else "" - an expression, read per element)
|
||||
export function ui_rule_value(r: UiRule, k: int) -> string {
|
||||
let x = r.vals[k]
|
||||
if x.op == X_LIT { return vl_text(x.v) }
|
||||
return ""
|
||||
}
|
||||
# a class's template and styles read now (else at its first show), so a host hears their errors at once
|
||||
export function ui_class_load(ui_st: mut UiState, k: UiClass) -> void { uc_load(ui_st, k) }
|
||||
# a file read again the next time anything imports it (a host changed its text): every class too
|
||||
@alloc_ok("an inspector's edit, never a frame")
|
||||
export function ui_file_forget(ui_st: mut UiState, path: string) -> void {
|
||||
let keep = new []UiFile
|
||||
for i in 0 .. len(ui_st.lib_files) {
|
||||
if ui_st.lib_files[i].path != path { push(keep, ui_st.lib_files[i]) }
|
||||
}
|
||||
ui_st.lib_files = keep
|
||||
uc_invalidate(ui_st)
|
||||
}
|
||||
# every error forgotten, so each is said again the next time it is made (a host that reloads a class)
|
||||
export function ui_errors_clear(ui_st: mut UiState) -> void { List.clear(ui_st.ui_errs) }
|
||||
Loading…
Add table
Add a link
Reference in a new issue