feat(lang): UI components as files - component Name { ... } beside Name.xml and Name.lss
The compiler turns a component declaration into: - a record of its props and state; - a constructor, a props setter, a model and a call; - a class, registered with ludic.ui at start. Its template and styles are read from beside it and compiled in, with @import inlined, and a missing template fails the build. In the runtime: - each mounted instance keeps its own props and state, and `set` in a template writes the state; - styles are scoped to the component; - class, style and id on a component's tag land on its root, styled by the parent's sheet too; - ui_reload re-reads a component's files from disk and keeps instances. The package's own module is now ludic_ui, so a program may call a directory of its own ui. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
637d07400e
commit
20d011f8e9
40 changed files with 62224 additions and 49996 deletions
37
examples/library/ui_component.ludic
Normal file
37
examples/library/ui_component.ludic
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# ui_component.ludic — components as files: Counter.ludic declares props, state, a field, a function
|
||||
# and an event; Counter.xml is its template; Counter.lss its scoped styles (which @import a theme).
|
||||
# Two counters keep their own state, a parent's class reaches the child's root, and the child's
|
||||
# `p { color }` does not leak into the parent.
|
||||
import "ludic.ui"
|
||||
import "ui_component_parts/Counter.ludic"
|
||||
import "ui_component_parts/App.ludic"
|
||||
program UiComponent {
|
||||
numbers float
|
||||
function texts(n: UiNode, out: []string) -> void {
|
||||
if n.text != "" { push(out, n.text) }
|
||||
for i in 0 .. len(n.children) { texts(n.children[i], out) }
|
||||
}
|
||||
function frame() -> UiNode {
|
||||
let root: UiNode = ui_nodes("App", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
return root
|
||||
}
|
||||
entry {
|
||||
var root = frame()
|
||||
let b = root.children[0].children[2]
|
||||
ui_press(b.children[1])
|
||||
ui_press(b.children[1])
|
||||
ui_press(root.children[0].children[1].children[1])
|
||||
root = frame()
|
||||
let out = new []string
|
||||
texts(root, out)
|
||||
var joined = ""
|
||||
for i in 0 .. len(out) { joined = joined + out[i] + "|" }
|
||||
print(joined)
|
||||
let b2 = root.children[0].children[2]
|
||||
print(`b margin {int(b2.mt)}; b text {b2.children[0].fg}; outside {root.children[0].children[3].fg}; button {int(b2.children[1].ch)}`)
|
||||
ui_press(b2.children[2])
|
||||
root = frame()
|
||||
print(root.children[0].children[2].children[0].text)
|
||||
}
|
||||
}
|
||||
1
examples/library/ui_component_parts/App.lss
Normal file
1
examples/library/ui_component_parts/App.lss
Normal file
|
|
@ -0,0 +1 @@
|
|||
.wide { margin: 9px }
|
||||
4
examples/library/ui_component_parts/App.ludic
Normal file
4
examples/library/ui_component_parts/App.ludic
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# App.ludic — a screen is a component too: this one has no props, only what it shows
|
||||
component App {
|
||||
title: string = "counters"
|
||||
}
|
||||
6
examples/library/ui_component_parts/App.xml
Normal file
6
examples/library/ui_component_parts/App.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<div>
|
||||
<h1>{title}</h1>
|
||||
<Counter label="a"/>
|
||||
<Counter label="b" step="{5}" class="wide"/>
|
||||
<p>outside</p>
|
||||
</div>
|
||||
3
examples/library/ui_component_parts/Counter.lss
Normal file
3
examples/library/ui_component_parts/Counter.lss
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@import "theme.lss";
|
||||
.counter { padding: 4px }
|
||||
p { color: #ff0000 }
|
||||
9
examples/library/ui_component_parts/Counter.ludic
Normal file
9
examples/library/ui_component_parts/Counter.ludic
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Counter.ludic — a component: what it takes (props), keeps (state), works out and does
|
||||
component Counter {
|
||||
prop label: string
|
||||
prop step: int = 1
|
||||
state count: int = 0
|
||||
doubled: int = count * 2
|
||||
function big() -> bool { return count > 3 }
|
||||
on bump() { count += step }
|
||||
}
|
||||
5
examples/library/ui_component_parts/Counter.xml
Normal file
5
examples/library/ui_component_parts/Counter.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<div class="counter">
|
||||
<p>{label}: {count} ({doubled}){big() ? ' big' : ''}</p>
|
||||
<button on-click="bump()">+</button>
|
||||
<button on-click="set count = 0">reset</button>
|
||||
</div>
|
||||
2
examples/library/ui_component_parts/theme.lss
Normal file
2
examples/library/ui_component_parts/theme.lss
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/* theme.lss - shared by whoever imports it */
|
||||
button { height: 30px }
|
||||
8
examples/rejected/component_no_template.ludic
Normal file
8
examples/rejected/component_no_template.ludic
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# a component's template is NAME.xml beside it; this one has none
|
||||
import "ludic.ui"
|
||||
program ComponentNoTemplate {
|
||||
component Lonely {
|
||||
prop name: string
|
||||
}
|
||||
entry { print("never") }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue