ludic/tools/ui-preview/calls.ludic
Orkuncakilkaya 49b5e80af5 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>
2026-09-29 19:53:26 +03:00

66 lines
3 KiB
Text

# calls.ludic - a component's functions, answered from the story: `calls <Name> {"pct": [80, 20],
# "label": {"0": "Food", "*": "?"}, "ready": true}`. A list is indexed by the first argument, an
# object keyed by the arguments' text ("," between several; "*" when none matches), anything else is
# the answer whatever the arguments. A call made by an action (a press, not a build) is an event, and
# so is `set`; a name the story does not answer is null, and said once as a miss.
function pv_call(pv: mut PvState, ui_st: UiState, p: pointer, name: string, args: Val, into: Val) -> Val {
let m: PvInst = p
if len(name) > 4 and pv_sat(name, 0, "set:") { return pv_set(pv, m, name, args, into) }
let building = ui_building(ui_st)
if not building { pv_call_event(pv, m, name, args) }
let s = pv_story(pv, m.cls)
if s != null and s.calls != null and Value.kind(s.calls) == 6 and Value.has(s.calls, name) != 0 { return pv_answer(Value.get(s.calls, name), args, into) }
if building { pv_miss(pv, m.cls, name) }
return value_into_null(into)
}
@alloc_ok("an action's call: a press, never a build")
function pv_call_event(pv: mut PvState, m: PvInst, name: string, args: Val) -> void {
pv_s(pv.ev, "call")
pv_q(pv.ev, m.cls)
pv_q(pv.ev, name)
pv_json(pv.ev, args)
pv_nl(pv.ev)
}
# `set name = value` in the template: kept on the instance (a copy: the value is ludic.ui's to reuse)
@alloc_ok("an action's set: a press, never a build")
function pv_set(pv: mut PvState, m: PvInst, name: string, args: Val, into: Val) -> Val {
let key = name[4..len(name)]
let v = pv_copy(Value.at(args, 0))
Value.put(m.sets, key, v)
pv_s(pv.ev, "set")
pv_q(pv.ev, m.cls)
pv_q(pv.ev, key)
pv_json(pv.ev, v)
pv_nl(pv.ev)
return value_into_null(into)
}
@alloc_ok("a miss: said once per class and name")
function pv_miss(pv: mut PvState, cls: string, name: string) -> void {
for i in 0 .. len(pv.miss_cls) {
if pv.miss_cls[i] == cls and pv.miss_fn[i] == name { return }
}
push(pv.miss_cls, cls)
push(pv.miss_fn, name)
pv_s(pv.ev, "miss")
pv_q(pv.ev, cls)
pv_q(pv.ev, name)
pv_nl(pv.ev)
}
# the story's answer to one call; a scalar is written into `into`, as a component's call writes it
@alloc_ok("a story's answer keyed by several arguments: a tool's frame, never the game's")
function pv_answer(spec: Val, args: Val, into: Val) -> Val {
var v = spec
if Value.kind(spec) == 5 { v = Value.at(spec, Value.as_int(Value.at(args, 0))) } else if Value.kind(spec) == 6 {
var key = ""
if Value.count(args) > 0 { key = ui_val_text(Value.at(args, 0)) }
for i in 1 .. Value.count(args) { key = key + "," + ui_val_text(Value.at(args, i)) }
if Value.has(spec, key) != 0 { v = Value.get(spec, key) } else { v = Value.get(spec, "*") }
}
let k = Value.kind(v)
if k == 1 { return value_into_int(into, Value.as_int(v)) }
if k == 7 or k == 2 { return value_into_float(into, Value.as_float(v)) }
if k == 3 { return value_into_bool(into, Value.as_int(v) != 0) }
if k == 4 { return value_into_str(into, Value.as_str(v)) }
if k == 0 { return value_into_null(into) }
return v
}