diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 759f9796..00000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -packages/*/lib/** filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index ec21dc03..cdbad763 100644 --- a/.gitignore +++ b/.gitignore @@ -55,8 +55,3 @@ __pycache__/ assets/polyhaven/hdri/ assets/polyhaven/textures/ assets/polyhaven/models/ -# `ludic run` beside an example writes its binary into a build/ there -examples/**/build/ - -# a package native/build.sh writes its objects under the package (phase 15) -packages/*/build/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 33ed5947..7fafbe8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,39 +7,6 @@ change type. Preview the next one with `ludic dev release --dry-run`. A released section may carry a hand-written summary paragraph above its groups (v0.2.0 has one); the generated bullets below it are not edited by hand. -## v0.22.0 — 2026-09-18 - -### Features - -- **Every key on the keyboard is bindable** — the F-row, Home/End/PageUp/PageDown, - Insert/Delete, Caps Lock and the numpad reach a game as codes 132-166. - - Until now the platform gave them no code at all, so a game's rebinding screen could - not take one and nothing said why. Windows asked the active layout what they type and - got nothing back (`w_vk_char` answers 0 for a key with no character); macOS let them - fall through to `charactersIgnoringModifiers`, which reports `NSF1FunctionKey` and its - neighbours at 0xF704 and up - outside the 256-bit held set either way. - - - **The codes are the same on both platforms**: 132-143 are F1-F12, 144-149 are Home, - End, PageUp, PageDown, Insert and Delete, 150 is Caps Lock, 152-161 are the numpad - digits and 162-166 its `*`, `+`, `-`, `.` and `/`. The numpad's Enter is Enter. - - **`Key.F1`, `Key.Home`, `Key.Numpad0`** and the rest fold at compile time like the - other named keys. - - **`Input.key_label`** names them - "F1", "Home", "CapsLock", "Num 0", "Num /" - and - does *not* ask the layout, because a key that types nothing is called the same thing - on every layout. -## v0.21.1 — 2026-09-17 - -### Fixes - -- A windowed game opens under its package's `app name`. `ludic build`, `ludic run` and - `ludic bundle` pass it to the compiler (`ludicc --title `), so `game_title()` - the - window's first title - is the name the player knows rather than the `program` name, which - showed for up to two seconds before the renderer retitled the window. Without an `app name` - it is the `program` name, as before. -- `Crypto.random_bytes`, `Crypto.random_hex`, `Crypto.random_u32` and `Uuid.*` draw from the - system CSPRNG on Windows (`RtlGenRandom`, advapi32). They read `/dev/urandom`, which Windows - does not have, and every byte came back zero. ## v0.21.0 — 2026-09-17 ### Features diff --git a/COMPILING.md b/COMPILING.md index 8f03d7d3..2fd42133 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -25,7 +25,7 @@ > ```bash > # one-time bootstrap: clang assembles the seed, then ludicc compiles bin/ludic > mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc -> bin/ludicc --unsafe --globals tools/ludic-cli/dev.ludic -o bin/ludic-dev +> bin/ludicc tools/ludic-cli/dev.ludic -o bin/ludic-dev > bin/ludic-dev build # the whole toolchain into bin/ > # (ludicc, ludic, ludic-fmt, ludic-lsp) > bin/ludicc examples/games/snake.ludic -o bin/snake # the compiler, directly @@ -73,9 +73,6 @@ point at a different LLVM toolchain if you have one. | a windowed native executable | `ludicc game.ludic -o build/game` | | a headless executable | `ludicc game.ludic --headless -o build/game` | | the IR, to read | `ludicc src.ludic --emit-llvm -o src.ll` | -| the schema an editor reads (records, registries and their entries, consts) | `ludicc src.ludic --emit-schema schema.json` | -| every error, as a JSON array on stdout | `ludicc src.ludic --check --diagnostics=json` | -| the same, with an unsaved buffer on stdin standing for one of its files | `ludicc src.ludic --check --diagnostics=json --stdin-file lib/a.ludic < buf` | | a shared library † | `ludicc lib.ludic --shared -o build/liblib.dylib` | | a game that runs in a browser † | `ludicc game.ludic --target wasm32-unknown-unknown -o build/web/game.wasm` | | an object file † | `ludicc src.ludic -c -o src.o` | @@ -353,7 +350,6 @@ The self-hosted `ludicc`/`ludic` (built with `bin/ludic-dev build-cli`) accept: --windowed force a windowed (Cocoa) build --headless force a headless build (stdin input, out.ppm output) --emit-llvm stop at LLVM IR — write it and exit, no clang - --check every check a build makes (types, modules, uses, layers, ports, binds); write nothing --fmt lex + parse only; exit 0 if it parses, 1 on a parse error (the check-docs gate; canonical formatting not yet restored) --save-temps keep the intermediate .ll diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6d0c971..d1c8128a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ From a clean checkout, one line lifts the toolchain off the seed: ```bash mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc -bin/ludicc --unsafe --globals tools/ludic-cli/dev.ludic -o bin/ludic-dev +bin/ludicc tools/ludic-cli/dev.ludic -o bin/ludic-dev ``` That gives you `bin/ludic-dev`, the contributor tool: it replaces every diff --git a/LANGUAGE.md b/LANGUAGE.md index eff6abe7..427c4e8b 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -62,1395 +62,6 @@ parse: chronorift/world.ludic:1: error: expected expression ``` -All of a program's files share one namespace, so **a name is defined once**: two functions, two -`var`s or `const`s (or an `enum` and a `const`), or two `property` / `event` records with one name -are an error that names both places. Declarations the runtime splices in are its own and are not -checked against each other. -The same holds inside a function: a `let` or `var` declares its name once per block (another -block, a loop variable or a parameter may reuse it), and a function with a result type must -`return` one on every path - running off the end of its body is an error, not a zero. - -### Modules (`module`, `export`, `friend module`) - -One namespace is not the same as one room. A barrel that says `module bank` makes its -directory a **module**: the barrel, every file it imports, and every file those import - until -one says `module` of its own - belong to `bank`. Inside a module every name is visible as -before. From anywhere else, a module's `function`, `var`, `const`, `property` or `event` is -reachable only if its declaration says `export`: - -```ludic -# doc-check: skip — a module spans files -# bank/index.ludic -module bank -import "ledger.ludic" - -# bank/ledger.ludic -export state Bank { balance: int = 0 } # its fields are bank's to change: only bank's functions do -export event Deposited { amount: int } -function add(b: mut Bank, n: int) -> void { b.balance += n } # private: only module bank sees it -export function deposit(b: mut Bank, n: int) -> void { - add(b, n) - emit Deposited(amount: n) -} -``` - -A program that imports `bank` may call `deposit` and listen to `Deposited`; calling `add` is - -``` -visible.ludic:5: error: add is private to module bank; mark it 'export' where it is declared (bank/ledger.ludic) -``` - -A file in no module - the program's own file, the runtime - is public, and a package found -through `ludic_modules` or the toolchain keeps its own module rather than its importer's. A -program that says `friend module lab` sees every module's private names: that is for a test -harness, which has to reach inside what it tests. `friend module lab of fishing, data` narrows -that to the modules named: `lab` sees their private names, and every other module's exports only. `export` is a keyword before a declaration -and is not the `@export` annotation, which names a C symbol. - -**A module's private names are its own.** A function, `var` or `const` a module does not export -may share its spelling with a name in another module, in a file in no module, or in the engine's -runtime: `shop` and `weather` may each have a private `seed`, and each module's code reaches its -own (a local of the same name still shadows it). There is no need to prefix a package's privates. -Exported names are one namespace across the program, so two modules that both `export function -seed` are still refused (`function 'seed' is defined twice`). Where two names meet, the private -one is compiled under its module's name (`seed$shop`), which is the spelling a message about it may -show. - -The same holds for a `property` and an `event`: `fishing` and `hunting` may each have a private -`Catch` record and a private `Landed` event with different fields, and each module's types, `new`, -`emit` and `@On` reach its own (compiled as `Catch__fishing`). Two exported ones of one spelling are -refused (`'Catch' is defined twice`, `event 'Landed' is defined twice`). When one of the two is a package's export the message -says so (`'UiNode' is defined twice (...): ludic_ui exports it, and exported names are one namespace - -rename this one, or declare it without export inside a module of your own`); a package exports only -what a program uses, so ludic.ui's `UiAct` is its own and a game's may take the name. Three kinds of record stay -one namespace, because other code names them by spelling: a generic record, a property that is an -entity's component (a `model` names it), and the record a component or a view generates. - -To move an existing codebase onto modules, build it once with `LUDIC_VIS_REPORT=1`: every -reference that would be refused is printed as `vis: :: . used from -` and the build goes on, so a script can add the `export`s the program already relies on. - -### What a module may reach (`uses`) - -`export` says what a module offers; `uses` says what a module takes. A module line may name the -only other modules its files reach: - -```ludic -# doc-check: skip — a module spans files -# fishing/index.ludic -module fishing uses base, data -``` - -From then on a reference from `fishing` into any other module is refused, even to a name that -module exports: - -``` -fishing/land.ludic:4: error: fishing uses items.inv_add (items/index.ludic:3): add 'uses items' to fishing's module line, or take it through a port -``` - -- A module with no `uses` clause keeps the rule before it - anything exported - so the rule can be - switched on one module at a time. Two module lines for one module add their lists together. -- A package's module counts like any other: a mechanic that says `uses ludic_base` and calls - into `ludic_inventory`, `ludic_ui` or the renderer is refused. A package with no `module` line - of its own (`ludic.render3d`) is named for its directory - `ludic_render3d` - for this rule; its - names stay public to the export rule. Only the engine's own runtime - `Value`, `Json`, - `Random` and the rest, which is in no module and no package - needs no naming. -- A file in no module (the program's root) is unaffected either way. -- `module ludic_base uses` with nothing after it is a module that reaches no other module at all. -- A friend of a module (`friend module lab`, or `friend module lab of fishing`) is not held to its - `uses` for that module. -- The declared graph may not go round: `module a uses b` beside `module b uses a` is refused - (`the modules' uses go round in a circle: a -> b -> a`) - one of them takes the other through a - port instead. -- **Layers.** `module flow in layer app uses base, items` puts `flow` in layer `app`. The modules of - one layer use each other freely, without naming each other, and may go round - a game's app - modules (the flow, the menus, the HUD) reach each other by design. Everything outside the layer is - still held to the module's `uses`, and a layered module with no `uses` may reach nothing outside - its layer. A cycle is allowed only inside a layer: `items uses hud` with `hud` in layer `app` - using `items` back is `items -> layer app -> items`, refused. A module is in one layer. -- `LUDIC_VIS_REPORT=1` lists these too, as `uses: :: . used from - (module )`, and builds. - -### Ports (`port`, `bind`) - -A module that needs something from outside itself - the time, a save, a sound - declares a -**port** instead of naming (and `uses`-ing) the module that answers. A port is a record of -function values; a member with no default is required: - -```ludic -# doc-check: skip — a module spans files -# clock/index.ludic -module clock uses units -export port Clock { - now: fn() -> int - day: fn() -> int = fn first_day -} -export function hour_of_day() -> int { return Clock.now() - (Clock.day() - 1) * HOURS } - -# app/index.ludic - where the program is put together -module app uses clock -bind Clock { now: fn game_hours } -function game_hours() -> int { return g_hours } -``` - -A member that takes nothing can be bound to a variable instead: `bind Purse { money: g_money }` for a -`money: fn() -> int` writes the getter (`bind_Purse_money`, returning `g_money` as it is at each call) -in the bind's own file, so the one-line wrapper function is not needed. A member that takes something -is refused a variable (`bind Purse: price is a fn(int)->int, and only a member that takes nothing can -be bound to a variable`). - -Calls go through the port by name, `Clock.now()`. `clock` never names `app`, so it needs no -`uses app`; the binder must be able to see the port - it is `export`ed, and a binder that says -`uses` names the port's module. What the bind names (`fn game_hours`) is checked from the bind's -own file. The compiler refuses: - -``` -app.ludic:5: error: port Clock is used here but never bound: the program has to say 'bind Clock { ... }' once, where it is put together -app.ludic:9: error: bind Clock leaves out now, which has no default -app.ludic:11: error: port Clock is bound twice (first at app.ludic:10) -app.ludic:9: error: port Clock has no member later -``` - -and a member of the wrong type is a type error like any other (`field now of Clock_port wants a -fn()->int and this is a fn(int)->float`). A port nobody uses may stay unbound, and so may a port -whose every member has a default: unbound, it answers with its defaults - which is how a -package's fallbacks ("unbound: this machine runs the world") are written. - -### State: no function writes a global (`state`, `mut`) - -A function that changes a module variable it was not given is a hidden coupling: nothing in its -signature says what it touches, and it cannot run without the whole program around it. So a -module-level `var` is refused, and a module's changing data is a **state** record instead: - -```ludic -# doc-check: skip — a fragment -state Hiker { - hips: int = -1 - spine: int = -1 -} - -function hiker_bind(h: mut Hiker, sk: Skin) -> void { - h.hips = skin_joint(sk, "hips") - h.spine = skin_joint(sk, "spine") -} -function hips_of(h: Hiker) -> int { return h.hips } -``` - -- **One instance, which no code names.** The program holds exactly one of each `state`, made - before any code runs. It reaches code only as a parameter: `h: mut Hiker` may change it, `h: Hiker` - may only read it. So a function's signature is everything it reads and writes, and a test hands - it a plain value. -- **Read-only is checked where it is written.** Through a read-only state the compiler refuses an - assignment whose target starts at it (`h.hips = 1`, `h.list[i] = x`), a `push` onto something in - it, and passing it where a `mut` one is wanted (`bump changes Tally (c: mut Tally), and c is - read-only here`). A reference read out of it into a local (`let l = h.list`, `let r = h.rows[0]`) - is read-only too, so a write through that is refused the same way; a value read out (`let n = - h.count`) is a copy and the local's own. `machine h.mode` writes its store on every `become`. - `mut` is for a state parameter only. -- **The runtime supplies it at the entry points** - the only code nothing in the program calls: - - a body that declares it: `entry (h: mut Hiker) { ... }`, `handler Draw(h: Hiker) phase Render - { ... }`, `@On(Ping) handler Heard(h: mut Hiker) { ... }`, `@OnSpawn(M) handler Made(h: mut - Hiker) { ... }`, a scene's `on enter (h: mut Hiker) { ... }`, `test "name" (h: mut Hiker) { ... }`; - - a retained `ui` block, which names a state's instance by the state's name: `font: Menu.title_font`; - - a component, whose header names its states: `component Tally (score: mut Score) { ... }`; - - a function value: `fn tick` of `function tick(h: mut Hiker, t: Tick)` is `tick` with its - leading states supplied, a `fn(Tick) -> void` - so a system's functions, a port's bind and any - callback a package calls are entry points without saying so; - - a port member bound to a state's field, `bind Purse { money: Wallet.cash }`; - - a call the compiler writes: a namespace method's target (`Weapon.def(...)` of `function - weapon_def(w: mut Weapons, ...)`), an engine system, a runtime built-in. - Every other call passes its states explicitly. -- **Everything else module-level is immutable all the way down.** `let LIMITS: []int = [1, 2]`, - a `const`, a registry: an assignment or a `push` that starts at one is refused, and so is one - through a local that holds part of it (`let r = LIMITS; push(r, 3)`). -- **Tests get fresh states.** Each test block starts from states made new, in its own process under - `ludic test` and in the runner run directly. -- The toolchain's own programs (the compiler, the CLI) are not part of this yet: they build with - `ludicc --globals`, which lets a module-level `var` through. - -The errors: - -``` -counter.ludic:5: error: this assignment: c is read-only here (c: Tally); take it as c: mut Tally to change it -counter.ludic:3: error: a module-level var is refused: a module's changing data is its state (state Name { ... }), passed to the functions that use it - or, if it never changes, a let -counter.ludic:5: error: this assignment: LIMITS is module-level and immutable all the way down; changing data belongs in a state, passed as a mut parameter -counter.ludic:3: error: x: mut int - mut is for a state parameter, and int is not a state -``` - -**`ludic migrate state [file|dir...] [--prune] [--runtime] [--dry-run]` moves programs there.** It compiles -each program and, from the compiler's own view of every name: - -1. a var nothing writes, holding a value (an int, a string, an enum...), becomes a module-level - `let` where it stands; -2. each module's other vars become one state, `state State { ... }`, where the first of - them was - a module's by its name (`module fishing`: `FishingState`), a directory with no module - line by its path (`ludic.render3d`: `Render3dState`), the program's own file by the program's - name (`program SceneDemo`: `SceneDemoState`); the fields keep their comments; -3. every reference to one is rewritten to `_st.` (`scene_demo_st.counter`), and in a - `ui` block to `.`; -4. each function's states - those it touches, and those of everything it calls, to a fixed point - - become its leading parameters, `mut` where it or something it calls writes; a state a run before - declared read-only becomes `mut` where it is now changed; -5. each call passes them on, each entry point declares them (after any it declares already), and - each component's header declares what all its members need. - -Give it every program at once - a directory stands for the test programs under it - and it merges -their plans before it edits anything: programs that share a package agree about it, a module two of -them see different files of is one state, and a path reached as `../../packages/x` is the same file -as `packages/x`. A program's own file keeps a state of its own (named for the program) whatever module it says, and -a `friend module`'s other files go by their directory, since each program declares that module for -itself. A package's var nothing in the given programs writes stays changing data (a game may set -`r3d_dem_path`); only a private one of a package's module becomes a `let`. A name the program uses -that a package migrated earlier moved into its state (`cam_pos`, now `Render3dState`'s) is rewritten -through that state, and a read of the runtime's own var from outside it through the runtime function -that answers it (`gl_w` is `gl_width()`). A program's own module named like a package (a game's `module fishing` beside -`ludic.fishing`) gets a state of its own, `FishingAppState` / `fishing_app_st`, never the package's. -It writes only under the programs and directories it is given (and `runtime/` with `--runtime`): -when the programs need a change in a file anywhere else - an installed package, a module imported -from a neighbouring directory - it says which and changes nothing. It prints what it cannot decide -(a var read in another global's initializer, a reference in generated code), for a person to finish. -A later run finds the states an earlier one made and adds to them. `--runtime` moves the runtime's -own vars too. gpp's packages and examples were moved with one command: - -``` -ludic migrate state packages packages/ludic.lab/example/plate.ludic -migrate: 1804 vars into 126 states, 64 into lets; 23498 edits in 460 files -``` - -**`--prune` takes out what a function no longer uses:** each state parameter that neither the -function nor anything it calls uses, and the argument that fills it at every call. An argument for a -parameter the callee no longer has goes too - a package's verb that dropped a state leaves its callers -passing one too many, and `ludic migrate state --prune ` puts them right. A reducer keeps its -state, and a state declared after a plain parameter (`home_keep(r: Records, save_st: mut Save)`) is -kept where it is. When ludic.base's queues began keeping their own counts, every package verb lost its -`base_st` this way (`wallet_earn(wallet_st, n)`, not `wallet_earn(base_st, wallet_st, n)`): - -``` -ludic migrate state --prune packages packages/ludic.lab/example/plate.ludic -migrate: 0 vars into 0 states, 0 into lets; 1889 edits in 132 files -``` - -### Actions and reducers (`action`, `reducer`, `dispatch`) - -Threading states makes a function's signature say what it touches, and it shows where one function -does everything: an input handler that reads the keys and then changes the world itself takes every -state the world has. An action separates the two. The input says WHAT happened; each module decides -what that means for its own state, and nothing else: - -```ludic -program Pack { - state Bag { - items: []int = new []int - weight: int = 0 - } - state Log { lines: []string = new []string } - action PickUp { item: int, kg: int = 1 } # what happened: a typed record - - reducer Bag on PickUp(b: mut Bag, a: PickUp) { # in the module that owns Bag - push(b.items, a.item) - b.weight += a.kg - } - reducer Log on PickUp(l: mut Log, a: PickUp) { push(l.lines, `picked {a.item}`) } - - handler Keys phase Input { - if Input.key() == 'e' { dispatch PickUp { item: 7 } } # a translator: keys to actions - } -} -``` - -- **`action Name { fields }`** is a record, with defaults like any. `export action` for other - modules to dispatch it. -- **`reducer State on Action(s: mut State, a: Action) { ... }`** WRITES exactly its state, and takes - the action last. Between the two it may declare states it only READS - `reducer Wallet on Buy(w: - mut Wallet, s: Shop, a: Buy)` - supplied like its own, for what is only known inside the drain (a - price another reducer just set, the map in play, where the save lives). A second state to write - is refused (`reducer Pack on Buy: a reducer writes one state, and w is a mut Wallet - read it (w: - Wallet), or dispatch an action Wallet's own reducer takes`), and so is a call inside it to a - function that writes another. What the dispatcher knows rides in the action. Several reducers may - handle one action, one per state; a reducer is not called by name. -- **`dispatch Action { fields }`** queues the action, from anywhere: a handler, a function, a - listener, a reducer. The queue is the runtime's, supplied like an entry point's state, so - dispatching needs no state parameter. -- **When the queue is drained:** at the end of every phase of the frame loop (so what the `Input` - phase dispatches is reduced before `Update`); after every phase of the ludic.base system runner - (`core_tick_all`); when ludic.ui has run a frame's presses (`ui_show`, `ui_press`), so a button's - action is reduced before the host presents the frame, not a frame later; and wherever the program - calls `drain_actions()` (an `entry` program, a test, a loop of its own - and a host that runs UI - presses some other way calls it before it presents). Draining runs the actions in the order they were dispatched, and each action's - reducers in the order of their states' names - never the order of imports - so the same actions - make the same changes on every machine and in a replay. -- **An action a reducer dispatches** is queued behind the rest and reduced in the same drain, never - re-entrantly. A queue still growing after 64 rounds of that stops the program, naming the action: - `actions: Ping is still being dispatched after 64 rounds of reducers - a reducer dispatches what - dispatches it`. -- **Events stay** for what changes no state - a sound, a notice, telemetry - and `@On` listeners run - as the event is emitted. Actions are for changes. - -**A reducer on a row** (27.3). Many of a kind are rows of a ludic.base `Table` (Things, animals, -vehicles), and an action may name ONE of them: its field marked `@Target` holds the row's handle, -and a reducer written `in` the table runs for that row alone. - -```ludic -import "ludic.base" -program Herds { - numbers float - property Deer { - @Column x: float = 0.0 # a table column mirrors it - fear: int = 0 - } - state Herd { deer: Table = null } - state Noise { loud: int = 2 } - action Spook { @Target who: int = -1, by: int = 1 } - action Bolt { @Target who: int = -1, dx: float = 0.0 } - - reducer Deer in Herd.deer on Spook(r: mut Row, n: Noise, a: Spook) { - r.rec.fear += a.by * n.loud - if r.rec.fear > 3 { dispatch Bolt { who: r.h, dx: 5.0 } } - } - reducer Deer in Herd.deer on Bolt(r: mut Row, a: Bolt) { deer_move(r, r.rec.x + a.dx) } - - @RowVerb function deer_move(r: mut Row, x: float) -> void { - r.rec.x = x - tb_set_f(r.tb, 0, r.row, x) # the column and its indexes, with the field - } - entry (h: mut Herd) { - let tb: Table = table_new(1, 0) - h.deer = tb - dispatch Spook { who: tb_add(tb, new Deer), by: 2 } - drain_actions() - } -} -``` - -- **`reducer Record in State.table on Action(r: mut Row, reads..., a: Action)`** - the - table is a field of the state (a path through its records is fine: `WildState.w.tab`), of type - `Table`, and only the module that owns the state declares a row reducer on it. The row - comes first and `mut`, the action last, and between them states it only READS, as any reducer's. -- **`@Target`** marks the action's one field holding the handle (an `int`, what `tb_add` returned). - One to an action: several rows are the dispatcher's loop, one `dispatch` per handle. -- **The drain resolves the handle** (`tb_row`) and hands the reducer a `Row` (ludic.base: - `tb`, `row`, `h`, `rec`) it keeps, one per row reducer and filled in place, so a targeted action - allocates nothing. A handle whose row is gone - the animal removed since the dispatch - runs - nothing, which is not an error; `LUDIC_ACTIONS_LOG=1` prints a line for it. Row reducers take - their place among the action's reducers by their state's name, then the table's. -- **The row goes no further.** Inside a row reducer `r.rec` and `r.h` are all it reaches: `r.tb` - and `r.row` are refused, the view is not assigned, stored, copied or handed to anything but a - **`@RowVerb`** - a function of the record's own module that takes the row first - and a field - marked **`@Column`** (one a table column mirrors: a position, a kind, whether it is alive) is not - written through `r.rec`: `reducer Deer in Herd.deer on Push: Deer.x is mirrored by a column of the - table (@Column) - write it through a @RowVerb, which keeps the column and its indexes with it`. - What `things_verify` catches at run time is a compile error inside a row reducer. -- **Dice** a row reducer rolls come from the row's own `Rng` or its owner's, never `Random.*`: it - runs in the drain, outside its owner's tick, and the world's stream is co-op's shared order. - -**A machine as data** (27.1). A row's state - an animal idling, fleeing, drinking - is an enum field -of its record, and the machine that moves it is a registry marked `@Machine(Record.field)`: one row -a transition, which the compiler turns into the reducers and the tick. The table is the whole -machine, and a studio edits it as a graph. - -```ludic -import "ludic.base" -program Moods { - enum Mood { Calm, Wary, Fled } - property Deer { - mood: Mood = Mood.Calm # the start: the field's default - fear: int = 0 - } - state Herd { deer: Table = null } - action Spook { @Target who: int = -1 } - property DeerStep { - from: Mood = Mood.Calm - to: Mood = Mood.Calm - on: string = "" # an action's name, or "" for a transition the tick asks - guard: fn(Row, Herd) -> bool = null - enter: fn(Row, Herd) -> void = null - } - @Machine(Deer.mood) registry DeerSteps of DeerStep - def DeerSteps startled { from: Mood.Calm, to: Mood.Wary, on: "Spook", enter: fn deer_startle } - def DeerSteps bolts { from: Mood.Wary, to: Mood.Fled, on: "Spook", guard: fn deer_afraid } - def DeerSteps settles { from: Mood.Wary, to: Mood.Calm, guard: fn deer_settled } - def DeerSteps home { from: Mood.Fled, to: Mood.Calm } - - function deer_afraid(r: Row, h: Herd) -> bool { return r.rec.fear > 1 } - function deer_settled(r: Row, h: Herd) -> bool { return r.rec.fear == 0 } - function deer_startle(r: mut Row, h: Herd) -> void { r.rec.fear += 1 } - entry (h: mut Herd, m: mut DeerStepsMachine) { - h.deer = table_new(0, 0) - dispatch Spook { who: tb_add(h.deer, new Deer) } - drain_actions() - deer_steps_tick(m, h) - } -} -``` - -- **The registry's record** has `from` and `to` (variants of the field's enum), `on: string` (an - action's name, `""` for a transition the tick asks), and may have `guard: fn(Row, reads...) - -> bool` and `enter: fn(Row, reads...) -> void`, each taking the row first and then the - states it reads. Its rows may live in an `.lres` (`from "deer_steps.lres"`), where a value is - written as in code: `bolts { from: Mood.Wary, to: Mood.Fled, on: "Spook", guard: fn deer_afraid }`. - The states are the enum's variants; the record is held in one state's `Table`, and the - registry is that state's module's. -- **What the compiler writes.** For each action an `on` names (it must have a `@Target`), a row - reducer: the row's current state, the first transition from it on that action - in the table's - order - whose guard passes (no guard passes always), the field set, then `enter`. When a row - leaves a state on a guard alone, `state DeerStepsMachine` (the tick's kept row view) and - `deer_steps_tick(m: mut DeerStepsMachine, s: mut Herd, reads...)`, which takes every row of the - table through the same first match, one transition a row a tick; the program calls it from its - system. A program's own row reducer on the same action runs before the machine's, so an enter - that needs the action's payload finds it on the row. Nothing is allocated: the row views are kept. -- **The table is the whole machine.** Its field is written by nothing else - an assignment or a - `machine` block's `become` anywhere else is refused: `this assignment: Deer.mood is the machine - DeerSteps's (@Machine(Deer.mood)) - it changes only by a transition in its table`. A new row takes - its state in its `new` (a save's load does too). A guard and an enter are functions of the - record's module and keep a row reducer's rules - the row reaches `r.rec` and `r.h`, goes only to a - `@RowVerb` or another of the machine's functions, and a `@Column` field is not written; a guard - asks and writes nothing through its row. -- **The graph is checked**, each an error at its row: a state never reached from the start; a state - with no way out; an `on` naming no action, or an action naming no row; a transition from a state - to itself with no guard; and two ways out of one state on one trigger behind an unguarded first - (`settles and stays both leave Wary on Spook, and settles has no guard - stays could never be - taken`). -- `ludic schema`'s code section lists each machine (`machines`: `registry`, `record`, `field`, - `enum`, `table`, `start`, `states`, `actions`, `tick`, `module`, `at`), and `ludic deps` names its - reducers `reducer Deer in Herd.deer on Spook (machine DeerSteps)`. The `machine` block stays for - a machine that is only code. - -`ludic deps` reports the widest function - the most states any function or entry point of the -program's own takes - and `--check` holds it as a ratchet like its other numbers -(`widest_function 12` in the baseline file). A function value's states are supplied where it is called, so a -step list - `let STEPS = [fn a, fn b]` walked by a function that takes nothing - hides what it -touches; `widest_reach` is the most states any function can come to, through its calls, the `fn f` -it writes and the globals holding fn values it reads (a registry of systems), and the report says how -many of them it does not take itself. `ludic deps --widest N` lists the N functions that take the -most states, each with what it reaches; `--reach N` lists them by reach. - -### Types are checked before anything is emitted - -Between the parse and the emitter a checker walks every function, the entry, the tests, the -globals' initializers and every `@On` listener, and refuses a program whose types do not agree - -all of its mix-ups at once, each at its own line: - -``` -trip.ludic:12: error: metres wants an int and this is a float -trip.ludic:14: error: area takes 2 argument(s) and this call gives 1 -trip.ludic:20: error: + of a string and an int: text joins text only - write string(x) for a number -3 type error(s) -``` - -What it holds apart: `int`, `float`, `fixed` and `bool` (a float or a fixed into an int is -`int(x)`; a float and a fixed never meet but by a literal, which takes whichever kind its slot is); -text and numbers (`string(n)` or a template); one record type and another; slices of different -elements; functions of different types. A call gives exactly as many arguments as there are -parameters, a `return` gives the declared result, and `push` gives the slice's own element. A -function names each parameter once (`add names two parameters n`). A type written in a parameter, a -result or a field names a declared type (or one of the declaration's type parameters): a misspelling -is refused there (`kind_of's parameter f: there is no type CharFact`), not later as a member access -that makes no sense. - -`pointer` is untyped, as `void *` is in C: it goes wherever a reference is wanted and takes any -reference, and a `[]pointer` any slice of references. Restricting what a raw pointer may reach is -`unsafe`'s job, not the checker's. Bits cross between kinds by name - `as_int(x)` / `as_fixed(n)` -reinterpret a word, `float_bits(f)` / `float_from_bits(i)` a float's - never by a slot's type. - -A name the checker cannot type (an engine namespace's arguments, a query's bindings) agrees with -everything, so it only ever reports what it can prove; the emitter keeps its own checks behind -it. `LUDIC_CHECK_REPORT=1` lists every mix-up by category and fails nothing, which is how an -existing program is measured before it has to pass. - -### Generic records and functions - -A record or a function can take type parameters, written after its name: - -```ludic -program Pools { - property Pool { - items: []T = null - n: int = 0 - } - function pool_new() -> Pool { - let p = new Pool - p.items = new []T - return p - } - function pool_add(p: Pool, x: T) -> void { - push(p.items, x) - p.n += 1 - } - function map(xs: []T, f: fn(T) -> U) -> []U { - let out = new []U - var i = 0 - while i < len(xs) { - push(out, f(xs[i])) - i += 1 - } - return out - } - entry { - let names: Pool = pool_new() - pool_add(names, "Crater Lake") - print(names.n) - } -} -``` - -A type names an instance with its arguments - `Pool`, `Pair`, -`Pool>`, `[]Pool` - and two instances of one generic are two types. A call's -type arguments are worked out from its arguments (`pool_add(names, "x")` is `pool_add` at -`string`), a literal deciding only what nothing else did; a call with nothing to say it, like -`pool_new()`, takes them from the slot its result is written into - a `let` with a declared type, -an assignment, an argument, a `return`. Where neither decides, the call is refused and says which -parameter it could not tell. - -Generics are compiled by instantiation: each instance the program uses is an ordinary record or -function, made and checked once, so it costs exactly what writing it out by hand would. A generic -nothing instantiates is not compiled at all. - -### Namespaces declared in Ludic (`alias`) - -A namespace method can be a name for a function. Inside a `namespace` block, - -```ludic -program Trails { - namespace Trail { - export alias length(from, to) = trail_distance - alias km = trail_km - } - function trail_distance(a: int, b: int) -> int { return b - a } - function trail_km(metres: int) -> int { return metres / 1000 } - entry { print(Trail.length(to: 12, from: 2) + Trail.km(metres: 5000)) } -} -``` - -makes `Trail.length(...)` a call to `trail_distance`: the list after the method is the labels a call -may name its arguments by, in the target's parameter order, and without one the target's own -parameter names are the labels (`alias show() = present` takes none). The call is the target's - -same arguments, same checks, same code - so a namespace costs nothing over calling the function. - -This is how the engine declares its own namespaces: `Http`, `Udp`, `Process`, `Json`, `Value`, -`Screen`, `Input`, `Audio`, `World`, `Tiled` and the rest are `alias` blocks in -`runtime/native/namespaces.ludic`, not branches in the compiler, and a package owns an API the same -way in its own files. What is still built into the compiler is the namespaces that compute inline - -`Math`, `Text`, `List`, `Vector`, `Color`, `Time`, `Date` - and the few methods that choose their -target by an argument's type (`Audio.play` of a handle or a name). - -A function of the program's own that is named like the target of one of the engine's namespace -methods would take that method's calls - `Random.range` is `rng_range`, so a package's -`rng_range(a, b, c)` would receive every `Random.range(1, 6)`. Where the program calls that method, -the function is refused (`rng_range is the engine's Random.range, which this program calls (...), -and every such call would reach this function instead; choose another name`), as a function named -like a compiler built-in (`run`, `exit`) or like one of the engine runtime's own functions is. So -is a type - a property, record, enum, event or state - named like one the runtime declares in a file -the program uses: `property PadButton` beside the runtime's `enum PadButton` used to compile and -then fail in clang, where the two layouts met (`PadButton is the runtime's enum -(runtime/native/input.ludic); choose another name for this property`). - -### Registries (`registry`, `def`) - -A table of records that code used to fill with calls in an init function is declared instead: - -```ludic -program Camp { - property Furnishing { - key: string = "" - name: string = "" - cost: int = 0 - } - registry Furnishings of Furnishing as HF - def Furnishings chair { name: "Camp chair", cost: 60 } - def Furnishings crate { name: "Crate", cost: 30 } - entry { print(Furnishings[HF_CRATE].cost + HF_COUNT) } -} -``` - -`registry NAME of RECORD [as PREFIX]` is a global `[]RECORD`, and every `def NAME key { ... }` is one -entry of it - in any file, collected in source order, and in the table before any code runs. Each -entry gets an index constant, `PREFIX_KEY` (the prefix defaults to the registry's name in capitals), -in declaration order, and the registry a count, `PREFIX_COUNT`. When the record has a `key: string` -field it is filled with the entry's key, and `name_find(key)` (the registry's name in lower case) -returns its index or -1. A def's fields are checked against the record like any record literal, a -key is declared once, and `export registry` exports the table, its constants and its lookup. - -Because the index is the order of the defs, a table stored by position - a save, a setting - keeps -the rule it always had: add an entry at the end, never between two. The order is the order the -compiler reads them in, and an `import` is read where it stands: a file's imported defs come before -the defs written after the import line. - -A registry belongs to its module, and a `def` written in another module is refused - unless the -registry says `open`: - -```ludic -# doc-check: skip — a module spans files -# core/index.ludic -module core -export property System { key: string = "", run: fn() -> int = null } -export open registry Systems of System as SY -def Systems clock { run: fn clock_run } - -# weather/index.ludic -module weather uses core -def Systems weather { run: fn weather_run } # weather_run may stay private to weather -``` - -A def into an open registry goes through visibility like any other reference: the registry must be -exported, and a module that says `uses` names the registry's module. What the entry itself names -(`fn weather_run`) is seen from the def's own module. The errors: - -``` -game.ludic:4: error: def Tools saw: registry Tools is not open to other modules; declare it 'open registry Tools' in module kit, or write the def there -game.ludic:4: error: Tools is private to module kit; mark it 'export' where it is declared (kit/index.ludic) -``` - -**The index order of an open registry** is the declaring module's own entries first, in the order -they are read, then every other module's: the modules in the order of their names, each one's -entries in the order they are read. `SY_CLOCK` is 0 however the program imports things, and an -entry from `alpha` comes before one from `zeta` even when `zeta` is imported first - so a table -saved by position keeps its meaning when a barrel's imports are reordered. The rule for a saved -table is still to append: a new entry goes at the end of its own module's list, and a new module -whose name sorts before an existing one moves that one's entries along. A registry whose defs are -all in its own module (or in no module) keeps exactly the order it always had. - -### Resource files (`registry ... from`) - -A registry's entries can live in a data file instead of the source: - -```ludic -# doc-check: skip — the file it names is beside the example -registry Tools of Tool as TL from "data/tools.lres" -``` - -``` -# tools.lres -axe { - name: "Axe" - weight: 1.5 - uses: [{ verb: "Chop", minutes: 20 }, { verb: "Split", minutes: 10 }] -} -lantern { name: "Lantern", weight: 0.75, uses: [] } -``` - -The file is a list of entries, each a key and a record, with `#` comments. It is read when the -program is compiled: the registry's record is its schema, so every entry is checked as a `def` is -- a field the record does not have, a string where it wants a number, a key twice - and the error -names the line in the resource file. A field whose type is a record takes a bare `{ ... }`, and one -typed as a list of records takes `[{ ... }, ...]`; the schema supplies the type. Values are Ludic -expressions in the registry's module, so an entry refers to another table's entry by its constant. -The entries are compiled in: nothing is parsed at start-up, and a build that succeeds has checked -every resource it uses. The path is the project's (where the build runs), else beside the file that -declares the registry. - - -A module that extends an open registry can bring its entries from a resource file of its own: - -```ludic -# doc-check: skip — the file it names is beside the example -import "crafting" # module crafting: export open registry Recipes of Recipe as RC -def Recipes from "recipes.lres" # the game's recipes, after crafting's own -``` - -`def REGISTRY from "file.lres"` reads the file as `registry ... from` does - the project's path, else -beside the file that says it - and checks every entry against the registry's record, an error -naming the resource file's own line (`bad_recipes.lres:3: error: field minutes of Recipe wants an -int and this is a string`). Its entries are defs of the module that wrote the line, so the registry -must be open (and exported) to it, and they take that module's place in the order: the declaring -module's entries, then each other module's by module name, and within a file, file order. - -### Map-scoped tables (`@PerMap`, `@Chunked`) - -A registry can hold what is on a map rather than what is in the game: its rows are not compiled in, -they are read when a map loads, from that map's own directory. - -```ludic -# doc-check: skip — its rows are files under each map's directory -property PropRow { - key: string = "" # the entry's key, as every registry record - @Ref(Models) model: int = 0 # a literal, or any constant by name: MDL_TENT2 - x: float = 0.0 - @Unit("deg") yaw: float = 0.0 - @Ref(Spots) home: string = "" # a row of another map table, by its key -} -@PerMap @ByKey registry Props of PropRow from "props.lres" -@PerMap @Chunked(64) registry Instances of InstRow from "instances/{cx}_{cz}.lres" -``` - -The maps are the directories under the maps root, which `package.ludic` names (`maps "assets/maps"`, -the default; taken relative to the package's directory): `assets/maps/maroon/props.lres`, -`assets/maps/maroon/instances/12_-3.lres`. `from` is relative to the map's directory; in a -`@Chunked(n)` registry, `{cx}` and `{cz}` are the chunk's integer coordinates, `floor(x / n)` and -`floor(z / n)`, written as decimals (`-3`), and they go in the file's own name, not a directory above -it. The files are ordinary `.lres` entries (`key { field: value, ... }`, see Resource files), read -through the same open every asset takes, so a mounted pack serves them and a dev run reads the -directory. A map's row may hold an `int`, a `float`, a `bool`, a `string`, a nested record, a list of -any of those (not a list of lists or of `fn` values), or a `fn` value written `fn name` (resolved -among the functions of the field's type the registry's module can name). An `int` field takes a -literal or any `int` constant by name, a `float` field any number or number constant; the program -carries its constants by name for that, when a `@PerMap` registry exists. The record's first field -is `key: string`, filled from the entry's key. Rows are in file order. - -A `@PerMap` registry has no `as PREFIX` and no `_KEY` constants - its keys are not known when the -game compiles - takes no `def`, and is never `open`. The compiler writes, in the declaring module and -exported with the registry, a `state` named after it and its verbs, prefixed with the registry's name -in snake case (`GroundLayers` -> `ground_layers_`): - -```ludic -# doc-check: skip — what the compiler writes for the two registries above -state Props { rows: []PropRow, map: string, err: string, ... } -props_load(st: mut Props, map: string) -> bool # //props.lres; false + st.err if missing or wrong -props_clear(st: mut Props) -> void -props_find(st: Props, key: string) -> int # the row's index, or -1 -props_path(st: Props, rel: string) -> string # "//", for an asset a row names - -property InstancesChunk { cx: int, cz: int, on: bool, rows: []InstRow, ... } -state Instances { chunks: []InstancesChunk, map: string, err: string, ... } -instances_in(st: mut Instances, map: string, cx: int, cz: int) -> int # its slot; no file is an empty chunk, a wrong one -1 + st.err -instances_out(st: mut Instances, cx: int, cz: int) -> void -instances_slot(st: Instances, cx: int, cz: int) -> int # -1 when that chunk is not in -instances_find(st: Instances, slot: int, key: string) -> int # a row of that slot, or -1 -instances_clear(st: mut Instances) -> void -instances_path(st: Instances, rel: string) -> string -``` - -Read `props.rows[i]` and `len(props.rows)`, `st.chunks[s].rows`. An error is `"file:line:col: what"` -(`maps/alpha/props.lres:2:18: PropRow has no field colour`, `unknown constant MDL_TENT3`), and a -failed load leaves the table empty, never half filled. `_find` is a hash over the row keys, rebuilt -in place on every load: O(1) and allocation-free. `_in` for a chunk that is already in hands back its -slot; `_in` with another map than the one the chunks came from puts every chunk out first. `_path` -makes one string: it is for load time, not a frame. A row's id for co-op is `(map, key)` for a -whole-map table and `(cx, cz, key)` for a chunked one - both stable, because they are data. - -**The table owns its rows, and everything in them.** Every row record, every list inside a row and -every record in such a list is pooled: a reload of the map, or a chunk slot refilled, resets and -refills them in place - the rows list and each row's lists are emptied and refilled, each record set -back to its record's defaults (a template made once) - and a record is made only when a load needs -more of its type than any load before it. Nothing is allocated past that high water, so a frame may -call `instances_in`. Never keep a row, a row's list or a chunk's rows across a load or an `_out`: keep -the key or the index, or copy the numbers. A string field, and a whole-map table's key, is interned -and safe to keep. A CHUNKED table's keys are unique across its map (`t0` .. `t91842`: a row's id is -`(map, key)`, and a tree moved into another chunk keeps it; `ludicc --check` refuses a key written in -two chunk files, naming both), so they are NOT interned - interning every key a player walks past would -fill the bounded intern table and keep them all: a slot's keys live in the slot's own buffers, -rewritten when the slot is refilled. Keep such a key past `_out` with `intern(row.key)`. A record may hold a record of its -own type only through a list. The reader -(the runtime's `lres.ludic`) keeps its own buffers the same way: the file's bytes in one that grows -only for a bigger file, its tree as parallel lists reused from file to file. - -`@Ref(T)` where `T` is a `@PerMap` registry goes on a `string` field holding the row's key (an `int` -field is refused: "use a string key"); its schema attribute says `"scope": "map"`. - -**Every map is checked with the program.** `ludicc --check` (and `ludic build --check`) reads every -directory under the maps root as a map and checks, with the compiler's own resource parser, each -registry's file in it (each file a chunked pattern matches) against the record: the field exists, -its value has the field's type, a constant it names exists, `fn name` names a function of the -field's type, `@OneOf` and `@Range` hold, an `@Ref` into a game registry is in range, and an `@Ref` -into a `@PerMap` registry names a row of that table in the same map (in any of its chunks; `""` is -none). A key is written once in a file. Errors carry the map file's line and column, through the -diagnostics as any other (`--diagnostics=json`); `--no-maps` leaves the maps alone, and no maps -root is nothing to check. `ludic build --check` reads them only for the package's `entry` (the game): -a partial program - a unit test, a molecule, a bake's runner - lacks the game's constants and cannot -judge them, so they are left alone there unless `--maps` asks. `LUDIC_PERMAP_SRC=` appends what the compiler wrote for each table. - -### Editor attributes and the schema (`@Ref`, `@Range`, ..., `ludic schema`) - -A field can say what an editor of the data should offer for it, and a registry how its entries may -change, on the same `@` a field's `@max(64)` is written with - one or several, on the field's line or -the lines above it: - -```ludic -# doc-check: skip — the registries it names are declared elsewhere -property Tool { - @Ref(Vendors) seller: int = 0 # an index into that registry: its entries are offered - @OneOf(GR_) grade: int = 0 # one of the constants whose names start GR_ - @OneOf(GR_GOLD, GR_SILVER) medal: int = 0 # or one of these constants - @Range(0, 20.5) @Unit("kg") weight: float = 1.0 - @Asset("gltf") model: string = "" # a file of that kind (any string) - @Asset("png", map) density: string = "" # a path under EACH map's directory (assets/maps//...) - @Color tint: int = 0 - @Node(model) grip: string = "" # a node inside the glTF that `model` names - @Clip(model) swing: string = "" # a clip inside it - @Material(model) finish: string = "" # a material inside it - @OneOf("box", "hull") shape: string = "" # a string field: one of these words - @Color @Tint(TSLOT_SHELL) shell: int = 0 # a colour for that tint slot - @Derived reach: float = 0.0 # worked out at boot: anything written is overwritten - @Text @Multiline blurb: string = "" # read by the player (so translated), and prose - @Key bind: int = 0 # a key code -} -@AppendOnly @ByKey -registry Tools of Tool as TL from "data/tools.lres" -``` - -`@Unit` takes one of the canonical ASCII spellings - `m`, `m/s`, `m/s2`, `s`, `min`, `h`, `d`, `deg`, -`rad`, `rad/s`, `kg`, `N`, `N.m`, `%`, `px` - so one word means one unit to an editor and a converter: -the angle is `"deg"`, never `"°"`. Any other spelling is a warning naming the canonical one where -there is an obvious one (`"°"`, `"degrees"` -> `deg`, `"sec"` -> `s`, `"m/s^2"` -> `m/s2`, `"Nm"` -> -`N.m`), else listing them; the schema carries the list as `"units"`. - -They change nothing the program does. A target that no part of the program declares - the registry -an `@Ref` names, the constant of an `@Tint` or a listed `@OneOf` - is a warning, and the schema marks -the attribute `"unresolved": true`: a package can name the game's registry without importing it. -Everything else is an error, every one reported: a target that exists but is another kind -(`@Ref(Vendor)` on a record); `@OneOf` of the wrong kind for its field - on a string field the -arguments are words (`@OneOf("box", "hull")`) and every registry row's value must be one of them, on -any other field a prefix ending in `_` or constants; and `@Node(f)`, `@Clip(f)` or `@Material(f)` -naming anything but a field `f` of the same record that is `@Asset("gltf")`, or an `@Ref` to a -registry whose record has exactly one `@Asset("gltf")` field (the model is then the row's). `@Asset(kind, map)` names a file under each map's -directory, not the game's root: `ludicc --check` looks for it in every map - a @PerMap row's in its own -map, a game-wide row's in all of them - and refuses a map that lacks it, unless the field says -`@Asset(kind, map, optional)`; the schema marks it `"scope": "map"`. A plain `@Asset(kind)` is not -looked for. - -`ludicc app.ludic --emit-schema out.json` (or `ludic schema [file] [-o out.json]`) writes what the -compiler resolved, once the types are checked and every open registry has its entries, as one JSON -object with `"schema_version": 1`: - -- `records` - every `property`, `state` and `event`: its module, file, line and column, its doc - comment (the comment lines above it, else the one ending its line), and its fields, each with its - type as text, its default as written (or null), its doc, its place and its attributes - (`[{"name": "Range", "args": [0, 20.5]}]`); -- `registries` - every registry: its record, prefix (null for a `@PerMap` one), resource file, - whether it is open, its `"scope"` (`"map"` for `@PerMap`, else `"game"`), its `"chunk"` size (or - null) and, for a map-scoped one, the `"maps"` root; its own attributes, and its entries in their final index order (`{"key": "axe", "constant": "TL_AXE", - "index": 0, "file": ..., "line": ..., "col": ..., "fields": [{"name", "value", "file", "line", - "col"}]}`), with `contributors`: which resource file or file of `def`s brought which keys in; -- `consts` - every const: its type, its value as written, its module and doc; -- `functions` - every function a `fn` value can name, exported or not: its module, return type, - `params` (the arguments a caller passes), `states` (what the runtime supplies), `signature`, and - `fn_type` - the type a `fn` field sees, the states stripped, spelled as a field's type is - (`fn(NpcPerson,float)->bool`), so matching a function to a field is comparing two strings. -- `components` - every UI component (see Components): its module, place, doc, `xml` and `lss` (the - template's and the stylesheet's paths, `lss` null when it has none); `props` and `state`, the - instance's own fields in the order written, each with its type, its default as written (or null), - its place and doc (a prop's `attributes` are `[]`: a component's members take none); `states_read`, - the program's states its header names, which the runtime supplies and the template never sees; - `derived`, the fields worked out each frame, with their types (an inferred one resolved); - `functions` (`{"name", "params": [["i", "int"]], "result"}`) and `events` (its `on` handlers, - `{"name", "params"}`) as the template calls them, the header's states and the instance stripped; - and `natives`, the registered native tags its template uses as elements; -- `natives` - every `ui_native` / `ui_native_input` call whose tag is a string literal: - `{"tag", "via", "handler", "module", "file", "line", "col"}`, `via` the function called and - `handler` the `fn` value it passes last (else null). A tag worked out at run time cannot be known - and is left out. -- `units` - `@Unit`'s canonical spellings. -- `lang` - the text keys and the languages (see Text keys), or null without a `lang` line. -- `code` - the program's code map, so an editor needs no scan of its own. Every place is - `"file:line:col"` (`"at"`); what the runtime declares and what the compiler writes itself are left - out. It holds: - - `modules` - `{"name", "package", "layer", "uses", "uses_at", "friend", "files"}`: `uses` null - without a `uses` line, `friend` null, or `{"of": null}` for a friend of every module and - `{"of": [...]}` for `friend module lab of a, b`; `files` its source files and the resource files - read into its registries; - - `states` (`{"name", "module", "at"}`), `actions` (`{"name", "module", "at", "target"}`, - `target` its `@Target` field or null), `reducers` (`{"state", "action", "module", "at"}`, a - state's), `row_reducers` (`{"record", "table", "state", "action", "target", "predicted", "net", - "module", "at"}`: `table` the path as written, `State.field`; `predicted` false and `net` null - until `@Predicted` and `@Net` reach reducers), `row_verbs` (`{"name", "record", "module", "at"}`) - and `dispatch` (every `dispatch`: `{"action", "module", "at"}`); - - `events` - `{"name", "module", "at", "cancellable", "net"}` (`net` `"toserver"` for - `@ToServer`, `"toclients"` for `@ToClients`, else null), its `emits` (every `emit`'s place) and - its `listeners` (the `@On` handlers: `{"handler", "module", "at"}`); - - `ports` - `{"name", "module", "at", "bound", "members"}`, each member `{"name", "type", "default", - "required"}` (its `fn` type as a field's is spelled, its default as written); and `binds` - one - row per member a `bind` gives: `{"port", "member", "fn", "value", "module", "at"}`, `fn` the - function a `fn name` names (null for a variable bound to a member that takes nothing) and - `value` as written; - - `handlers` - `{"name", "module", "at", "phase", "hook", "target", "public", "net", "queries", - "scene", "layer"}`: `phase` null for a hook, `hook` null or `"On"`, `"OnSpawn"`, `"OnDespawn"`, - `"OnAttach"`, `"OnDetach"`, `"OnEnable"`, `"OnDisable"`, `"OnStart"`, `"OnQuit"` with `target` - the event, model or property it names; `net` `"server"` (`@Server`), `"predicted"` - (`@Predicted`) or null; `queries` null or `{"these": [{"property", "filter"}], "on"}` (a - filter as written, or null); a scene's handler has its name as written and its `scene` and - `layer`; - - `models` (`{"name", "module", "at", "owned", "properties": [{"name", "sync"}]}`), `prefabs` - (`{"name", "model", "module", "at", "components": [{"property", "fields": [{"name", - "value"}]}]}`) and `scenes` (`{"name", "module", "at", "start", "public", "shows", "lasts", - "then", "loads", "enter", "exit", "layers"}`: `lasts` as written, `then` the scene it goes on to, - `loads` the scene a `loads then` one goes on to, `enter` / `exit` whether it has the block); - - `fn_refs` - every `fn name` written, in code or in a resource file: `{"fn", "module", "at", - "slot_kind", "slot", "entry"}`, the slot it fills: `"registry"` (`"Registry.field"`, `entry` the - row's key), `"port"` (a bind's `"Port.member"`), `"port_default"`, `"default"` (a record - field's default, `"Type.field"`), `"record"` (a `new` or a spawn's `"Type.field"`), `"event"` - (an `emit`'s `"Event.field"`), `"arg"` (`"callee(i)"`, the i-th argument written), `"assign"` - (`s.tick = fn f`: `"s.tick"`), `"let"` (the name), or `"value"` with a null slot. - - Named lists are sorted by name (then file and line), sites by place. - -Each list is sorted by name (then file and line), a registry's entries are in index order, and the -paths are the ones the compiler read, so two runs over the same source write the same file. The -engine's runtime is left out. - -`Build.schema_hash()` is a `long`: FNV-1a 64 of exactly those bytes, for the program being built, -worked out once and only when the program names it - so a tool talking to a running build can tell -whether it was compiled from the data in front of it. It is 0 in a release (`ludicc --release`, -which `ludic bundle` passes). - -### Text keys (`Key`, `k"..."`, `lang`) - -A text the player reads is named by a KEY, and the key's English is a language file like any -other's. `package.ludic` says where the languages are and which is the source: - -``` -lang "assets/lang" en # the directory, and the source language: assets/lang/en.po -``` - -In code a key is a literal of the builtin type `Key` - `k"module.purpose"`, or `kn"..."` for a key -whose text has plural forms - written against its quote (`k "x"` is a name and a string, as ever): - -```ludic -# doc-check: skip — tr / trf / trn are ludic.i18n's -let title = tr(k"pause.resume") # tr(key: Key) -> string -let day = trf(k"hud.day", txt_num(n)) # the English's holes {1}..{4}, in order -let got = trn(kn"catch.count", n, fish) # {1} is the count, the rest follow -``` - -A `Key` is not a `string`, and a `string` is not a `Key`: giving one where the other is wanted is an -error either way, and so is `+` on a key. Keys compare with `==` and `!=`, and are fields, parameters, -results, list elements and registry values like any other type (`null` is none). At run time a key is -its text after a marker byte - byte 1, or 2 for `kn"..."` - so `k"pause.resume"` is the string -`"\x01pause.resume"`: the runtime's `tr` is a cast, and a translator knows a key from English. -`string(key)` gives that marked text, for a runtime that needs it; a program itself makes text with -`tr`, and a key in a template literal's hole (`` `at {k}` ``) is an error - write `trf(k"...", ...)`. -`trn` takes a plural key and `tr` / `trf` refuse one, with or without a `lang` line. (A program that -declares its own type named `Key` keeps it; the builtin is then out of reach.) - -**A package's own words** are keys too. ludic.ui's key field says "Right click" as `ui_tk(ui_st, -k"ui.right_click", "Right click")`: translated through the program's translator when one is bound, the -plain text otherwise (a program with no languages). A program with a `lang` line that imports ludic.ui -therefore carries `ui.right_click`, `ui.middle_click`, `ui.left_click` and `ui.press_key` in its -source `.po`, and the check says so if it does not. - -**The data.** A registry's `@Text` field is text by derivation: its key is -`..` - the registry's name in snake case (`Items` -> `items`, `GearKinds` -> -`gear_kinds`), a list field adding `.` and a nested record `.` - or, with -`@TextKey("steps")` on the registry, `steps..`. A `@PerMap` table's is -`maps....`. When the field's type is `Key` and a row of a compiled registry -gives it no value, the compiler fills in the derived key (marked, as a literal is), so code writes -`tr(Items[i].name)` and the `.lres` never spells a key. The same holds below the row: a nested -record's field adds `.` and a list item `.` (`disruptions..arrive.steps.0.text`), and a -`@Text []Key` a row leaves out takes `<...>.0`, `.1`, ... for as many as the source `.po` has; `field: -null` is no text, and is not checked; a row may still name one, `name: -k"items.lamp.name"`, in a resource file or a map's file alike. - -```ludic -# doc-check: skip — the data file is the game's -property Item { - key: string = "" - @Text name: Key = null # items..name, filled in when the row gives none -} -registry Items of Item as IT from "data/items.lres" -@TextKey("steps") registry StepKinds of StepKind from "steps.lres" # steps.. -``` - -**The templates.** A component's text is a key too: `{t('pause.resume')}`, `title="{t('pause.close')}"`, -`{t('hud.day', day)}`; `t(expr)` takes a key worked out at run time (a `Key` field reaches a template -as its marked text). Words outside an element with `translate="no"` are English still waiting for a -key. - -**The checks.** With a `lang` line and its source `.po` there, the compiler holds every key to it, -each diagnostic at its `file:line:col`: - -- a `k"..."` literal, a template's `t('...')` literal, or a key a data row names or derives that the - source `.po` does not have is an **error**; so is a `kn"..."` whose entry has no `msgid_plural`; -- a call of `trf` or `trn` with a key literal, and a template's `t('key', ...)`, gives as many values - after the key as the English's highest hole `{n}` (`trn`'s count is `{1}`; trailing `""` literals - are padding and not counted), or it is a **warning**; -- **English left** - a template's words outside `translate="no"`, a text attribute's words (`title`, - `label`, `hint`, `text`, `caption`, and any other whose value is not a keyword), a quoted choice - inside a hole that reads as words, and a `@Text` row whose value is still English - is an - **error** (a warning until the migration was done; `ludic deps` still counts it as `english_left`); -- one English under several keys (a split) wants a `#.` description on each, for a translator to - tell them apart: a **warning** at the `.po`'s line. - -With no `lang` line nothing is checked and nothing is said (a package test uses key literals freely); a -`lang` line whose source `.po` is missing checks nothing, and the first key literal says so once. -The source `.po` is gettext's: `msgid` (the key), `msgid_plural` and `msgstr[n]`, `#,` flags -(`fuzzy`), `#.` descriptions, `msgctxt` (read and ignored: the key is the context), strings over several lines; `#~` -entries are obsolete. `ludic schema` carries a `"lang"` object (null without a `lang` line): every -key used with its kind (`code`, `template`, `data`), its sites, its English, whether it is a plural, -and its description; `unused` (the source's keys used nowhere), `undescribed`, `split`, and per other -language in the directory its `missing`, `fuzzy` and `extra` keys. - -### Default parameters, and calls that name what they change - -A parameter can have a default, and a call leaves out what it does not change - the last ones when -it passes arguments by position, or any it does not name. A call can pass its first arguments by -position and the rest by name: - -```ludic -program Boxes { - numbers float - function box(label: string, w: float = 300.0, pad: float = 8.0, bold: bool = false) -> string { - return `{label} {w} {pad} {bold}` - } - entry { - print(box("a")) # every default - print(box("b", 120.0)) # the first two by position - print(box("c", bold: true)) # the subject by position, a prop by name - } -} -``` - -A default is an expression written with the function and evaluated for each call that leaves it -out. A call that leaves out a parameter with no default, names one the function does not have, or -puts a positional argument after a named one is refused with that said. - -### Components and templates (`ludic.ui`) - -A UI is components, and a component is three files side by side: -- `Name.ludic` declares what it takes, keeps and does; -- `Name.xml` is its HTML-shaped template; -- `Name.lss` holds its CSS-shaped styles, which apply to its own elements only. - -```ludic -# doc-check: skip — a fragment of a program that imports ludic.ui -component Counter { - prop label: string # its parent passes it: - prop step: int = 1 # with a default - state count: int = 0 # the instance's own, kept while it is on screen - doubled: int = count * 2 # worked out every frame - function big() -> bool { return count > 3 } # callable from the template - on bump() { count += step } # an event: on-click="bump()" -} -``` - -- **Props and state** are plain names in the component's code; each mounted instance is a record - of them. A prop or state is an `int`, a `float`, a `bool`, a `string` or a `Val`. A field is - anything a template can read, and records and lists become objects and lists. -- **The template** has one root (use `` for several). A component is used by its name as - a tag. `set count = 0` in an action sets its state, and `emit close` runs what its parent passed - as `on-close`. `class`, `style` and `id` on a component's tag land on its root element, styled by - the parent's sheet as well as its own; when that root is itself a component, every user in turn - passes theirs down (the outermost's `id` wins). The rules of all those sheets that match the root - are weighed together by specificity, as one sheet's are, and a user's rule wins a tie. -- **A component's names.** An event may be called anything, `set` included (`on set(v: int)`, - pressed as `set(4)`; `set x = ...` is still the action). A `string` prop given a number reads it - as text (`label="{3}"` is `"3"`). Two components of one name are an error that names both files. -- **Compiled in.** The compiler reads the template and the styles from beside the declaration and - inlines each `@import` (a path starting with `/` is from the project's root), so a missing - template fails the build and nothing has to ship beside the program. `ui_reload()` reads the - files again when they change on disk and keeps every instance's state. -- **A screen is a component** with no props: `ui_show("Counter", null, x, y, w, h)`, or - `ui_nodes("Counter", null)` for a test. -- **States in the header.** A component that reads the program's data names the states it reads - and changes after its name, as an entry point does: - - ```ludic - # doc-check: skip — a fragment of a program that imports ludic.ui - component Tally (score: mut Score, look: Look) { - total: int = score.points - function big() -> bool { return total > look.big } - on add(n: int) { score.points += n } - } - ``` - - Every getter, default, function and event takes them before the instance; a member's call to - another passes them on; ludic.ui's calls into the component are given the instances; and the - template never sees them - `on-click="add(5)"` passes only `5`. A header state without `mut` is - read-only in every member. - -An older, lighter bridge remains: `view Name { ... }` gives a whole template file of ``s -and ``s (loaded with `ui_load`) one model and one call, and the rest of this section -applies to both: - -```xml - - - - - The purse: ${purse} - - - - - - Nothing bought yet - {owned} bought - - -``` - -- **Elements.** A template is HTML-shaped: - - `div`, `section`, `header`, `footer`, `nav`, `main`, `article`, `aside`, `ul`, `ol`, `li` and - `form` are boxes laid out in a column; `row` is one laid out in a row. - - `p`, `span`, `label`, `h1`-`h6`, `strong`, `em`, `small`, `b`, `i` and `a` are text; words - inside a box become a text of their own. - - `button`, `img src`, `hr` and `spacer`; `scroll` is a column that scrolls. - - A default stylesheet, like a browser's, sizes the headings and pads the buttons. -- **Attributes.** `id`, `class` (which may be bound: `class="{picked ? 'on' : ''} row"`), - `style="padding: 4px; color: gold"`, `hidden`, `disabled`, `onclick` or `on-click` (also - `on-press`), and any attribute a property is named after (`width="300"`). Any other attribute is - kept for `[attr=value]` selectors, as HTML's are. -- **The box model and flex.** Sizes are border boxes. - - `padding` and `margin` take one to four lengths, or one side by name (`padding-left`). - - `border` is `2px solid #ffcc00`, or `border-width` and `border-color`. - - A length is `12`, `12px`, `50%`, `fit`/`auto`, `fill` or `calc()`. `width: 0` and `height: 0` - are 0, not unset. - - `calc()` takes `+`, `-`, `*` and `/` and brackets over `px`, `em`, `rem`, `vw`, `vh`, plain - numbers and `var()`; in a `width`, `height`, `top`, `right`, `bottom` or `left` it may also hold - a percentage of the room or of the containing block (`calc(50% - 10px)`). `min()`, `max()` and - `clamp(least, want, most)` pick among lengths, inside `calc()` or on their own - (`width: min(300px, 50vw)`); a percentage cannot be compared there. - - `top`, `right`, `bottom` and `left` take a percentage of the containing block: its width for - left and right, its height for top and bottom. - - `flex-grow` (or `flex`) shares out the spare room along `flex-direction`, and `fill` is a share - of 1. `justify-content` takes `flex-start`/`start`, `center`, `end`, `space-between`, - `space-around` or `space-evenly`. - - `align-items` and `align-self` take `start`, `center`, `end` or `stretch`. `flex-wrap: wrap` - breaks a row into lines, and `min-`/`max-width`/`-height` bound it. - - `flex-shrink` gives up room when a line's children want more than it has, each in proportion - to its shrink times its size, and none below its min size, a fixed size or its content. A scroll - box shrinks (and scrolls) and a box in a column shrinks as far as the scroll boxes in it let it, - so a list in a column takes the room its siblings leave with no height of its own. `flex: 1 0` - is the grow and the shrink. `order` rearranges a box's children without touching the tree. - - `gap`, `text-align`, `display: none`, `background(-color)`, `color`, `opacity` and `font-size` - are CSS's. - - Every property also has a short name (`w`, `h`, `pad`, `bg`, `size`, `grow`, `align`, - `justify`, `self`, `dir`, `wrap`, `alpha`). Rounded corners, font weight and a few more are - things the renderer does not draw, and the runtime says so rather than ignoring them. A colour - is the renderer's name for one, `#rrggbb` or `#rgb`. -- **Stylesheets.** Rules go in a `

a

b

c

" - function at(ui_st: mut UiState) -> string { - ui_show(ui_st, "a", view_anim(), 0.0, 0.0, 200.0, 200.0) - let r: UiNode = ui_nodes(ui_st, "a", view_anim()) - return `{int(r.children[0].alpha * 100.0)} {int(r.children[1].alpha * 100.0)} {int(r.children[2].alpha * 100.0)}` - } - entry (ui_anim_st: mut UiAnimState, ui_st: mut UiState) { - ui_load_text(ui_st, PAGE, "a.xml") - var out = at(ui_st) - for i in 0 .. 29 { at(ui_st) } - out = out + " | " + at(ui_st) - ui_anim_st.dim = true - for i in 0 .. 29 { at(ui_st) } - out = out + " | " + at(ui_st) - print(out) - } -} diff --git a/examples/library/ui_box.ludic b/examples/library/ui_box.ludic deleted file mode 100644 index 2f8f29ff..00000000 --- a/examples/library/ui_box.ludic +++ /dev/null @@ -1,19 +0,0 @@ -# ui_box.ludic — what a host reads from outside the interface: ui_scale(), the scale it is drawn at -# (the renderer's), and ui_box("id"), where an element was laid out when its screen was last shown, -# in screen pixels (null for one that is not there) - to frame a 3D view beside a panel. -import "ludic.ui" -program UiBox { - numbers float - const PAGE: string = "

title

" - function scale() -> float { return 1.5 } - entry (ui_st: mut UiState) { - let b = new UiBackend - b.scale = fn scale - ui_backend(ui_st, b) - ui_load_text(ui_st, PAGE, "b.xml") - ui_show(ui_st, "b", null, 20.0, 30.0, 800.0, 600.0) - let p = ui_box(ui_st, "panel") - let none = ui_box(ui_st, "nothing") - print(`scale {ui_scale(ui_st)} | panel {int(p.x)},{int(p.y)} {int(p.w)}x{int(p.h)} | missing {none == null}`) - } -} diff --git a/examples/library/ui_boxes.ludic b/examples/library/ui_boxes.ludic deleted file mode 100644 index 7242d520..00000000 --- a/examples/library/ui_boxes.ludic +++ /dev/null @@ -1,25 +0,0 @@ -# ui_boxes.ludic — ludic.ui's flex and sizes: a scroll box in a column shrinks to the room its -# siblings leave (flex-shrink) and scrolls what it cannot show; text in a row wraps in the room left -# after the icon beside it; width and height 0 are 0; calc() mixes a percentage with lengths; order -# rearranges a row; and a range's track is a row, its fill as tall as the track. -import "ludic.ui" -import "ui_boxes_parts/Boxes.ludic" -program UiBoxes { - numbers float - entry (ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - ui_show(ui_st, "Boxes", null, 0.0, 0.0, 400.0, 600.0) - let root: UiNode = ui_nodes(ui_st, "Boxes", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0) - let page = root.children[0] - let list = page.children[0].children[1] - let line = page.children[1] - let text = line.children[1] - let zero = page.children[2] - let half = page.children[3] - let ord = page.children[4] - let track = page.children[5].children[0] - let fill = track.children[0] - print(`list {int(list.ch)} of {int(list.content_h)} | text at {int(text.x)} {int(text.cw)} wide, {len(text.lines)} lines, row {int(line.ch)} | zero {int(zero.cw)}x{int(zero.ch)} | half {int(half.cw)}x{int(half.ch)} pad {int(half.pt)} {int(half.pr)} | order {ord.children[0].text}{ord.children[1].text}{ord.children[2].text} at {int(ord.children[2].x)} | fill {int(fill.cw)}x{int(fill.ch)} of {int(track.cw)}x{int(track.ch)}`) - } -} diff --git a/examples/library/ui_boxes_parts/Boxes.lss b/examples/library/ui_boxes_parts/Boxes.lss deleted file mode 100644 index 30bfda09..00000000 --- a/examples/library/ui_boxes_parts/Boxes.lss +++ /dev/null @@ -1,14 +0,0 @@ -.page { width: 300px; gap: 4px } -.panel { height: 200px; gap: 10px } -.bar { height: 30px } -.list { overflow: auto } -.item { height: 20px } -.line { width: 200px; gap: 8px } -.line img { width: 24px; height: 24px } -.zero { width: 0; height: 0 } -.half { --gap: 10px; width: calc(50% - var(--gap)); height: calc(2 * var(--gap) + 1em); padding: calc(1em / 4) 2px } -.ordered { gap: 0 } -.a { order: 1 } -.b { order: 2 } -.c { order: 3 } -.ui-track { height: 12px; padding: 2px } diff --git a/examples/library/ui_boxes_parts/Boxes.ludic b/examples/library/ui_boxes_parts/Boxes.ludic deleted file mode 100644 index e48029a5..00000000 --- a/examples/library/ui_boxes_parts/Boxes.ludic +++ /dev/null @@ -1,5 +0,0 @@ -# Boxes.ludic - a panel with a list that takes the room its header and footer leave, a line of text -# beside an icon, zero sizes, calc(), order, and a range's track -component Boxes { - state rows: int = 10 -} diff --git a/examples/library/ui_boxes_parts/Boxes.xml b/examples/library/ui_boxes_parts/Boxes.xml deleted file mode 100644 index 1f25e02c..00000000 --- a/examples/library/ui_boxes_parts/Boxes.xml +++ /dev/null @@ -1,12 +0,0 @@ -
-
-

Head

-

{r}

-

Foot

-
-

A line long enough that it has to wrap beside the icon

-

hidden

-
- cab - -
diff --git a/examples/library/ui_calc.ludic b/examples/library/ui_calc.ludic deleted file mode 100644 index 7a4eea33..00000000 --- a/examples/library/ui_calc.ludic +++ /dev/null @@ -1,20 +0,0 @@ -# ui_calc.ludic — min(), max() and clamp() (on their own and inside calc()), and top, right, bottom -# and left as percentages and calc() of the containing block, for absolute and relative elements. -import "ludic.ui" -program UiCalc { - numbers float - const STYLE: string = "" - const BODY: string = "
" - entry (ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - ui_load_text(ui_st, "" + STYLE + BODY + "", "c.xml") - ui_show(ui_st, "c", null, 0.0, 0.0, 400.0, 600.0) - let r: UiNode = ui_nodes(ui_st, "c", null) - ui_place(ui_st, r, 0.0, 0.0, 400.0, 600.0) - let box = r.children[4] - let p = box.children[0] - let q = box.children[1] - let rel = box.children[2] - print(`min {int(r.children[0].cw)} max {int(r.children[1].cw)} clamp {int(r.children[2].cw)} {int(r.children[3].ch)} | p {int(p.x - box.x)},{int(p.y - box.y)} q {int(q.x - box.x)},{int(q.y - box.y)} r {int(rel.x - box.x)}`) - } -} diff --git a/examples/library/ui_component.ludic b/examples/library/ui_component.ludic deleted file mode 100644 index cac2a70b..00000000 --- a/examples/library/ui_component.ludic +++ /dev/null @@ -1,37 +0,0 @@ -# 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(ui_st: mut UiState) -> UiNode { - let root: UiNode = ui_nodes(ui_st, "App", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root - } - entry (ui_st: mut UiState) { - var root = frame(ui_st) - let b = root.children[0].children[2] - ui_press(ui_st, b.children[1]) - ui_press(ui_st, b.children[1]) - ui_press(ui_st, root.children[0].children[1].children[1]) - root = frame(ui_st) - 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(ui_st, b2.children[2]) - root = frame(ui_st) - print(root.children[0].children[2].children[0].text) - } -} diff --git a/examples/library/ui_component_parts/App.lss b/examples/library/ui_component_parts/App.lss deleted file mode 100644 index 29c72305..00000000 --- a/examples/library/ui_component_parts/App.lss +++ /dev/null @@ -1 +0,0 @@ -.wide { margin: 9px } diff --git a/examples/library/ui_component_parts/App.ludic b/examples/library/ui_component_parts/App.ludic deleted file mode 100644 index 788d54e7..00000000 --- a/examples/library/ui_component_parts/App.ludic +++ /dev/null @@ -1,4 +0,0 @@ -# App.ludic — a screen is a component too: this one has no props, only what it shows -component App { - title: string = "counters" -} diff --git a/examples/library/ui_component_parts/App.xml b/examples/library/ui_component_parts/App.xml deleted file mode 100644 index afd44365..00000000 --- a/examples/library/ui_component_parts/App.xml +++ /dev/null @@ -1,6 +0,0 @@ -
-

{title}

- - -

outside

-
diff --git a/examples/library/ui_component_parts/Counter.lss b/examples/library/ui_component_parts/Counter.lss deleted file mode 100644 index 15174c36..00000000 --- a/examples/library/ui_component_parts/Counter.lss +++ /dev/null @@ -1,3 +0,0 @@ -@import "theme.lss"; -.counter { padding: 4px } -p { color: #ff0000 } diff --git a/examples/library/ui_component_parts/Counter.ludic b/examples/library/ui_component_parts/Counter.ludic deleted file mode 100644 index 8a677ed8..00000000 --- a/examples/library/ui_component_parts/Counter.ludic +++ /dev/null @@ -1,9 +0,0 @@ -# Counter.ludic — a component: what it takes (props), keeps (state), works out and does -component Counter { - prop label: string - prop step: int = 1 # how much one press adds - state count: int = 0 - doubled: int = count * 2 - function big() -> bool { return count > 3 } - on bump() { count += step } -} diff --git a/examples/library/ui_component_parts/Counter.xml b/examples/library/ui_component_parts/Counter.xml deleted file mode 100644 index 617de5aa..00000000 --- a/examples/library/ui_component_parts/Counter.xml +++ /dev/null @@ -1,5 +0,0 @@ -
-

{label}: {count} ({doubled}){big() ? ' big' : ''}

- - -
diff --git a/examples/library/ui_component_parts/theme.lss b/examples/library/ui_component_parts/theme.lss deleted file mode 100644 index c7297243..00000000 --- a/examples/library/ui_component_parts/theme.lss +++ /dev/null @@ -1,2 +0,0 @@ -/* theme.lss - shared by whoever imports it */ -button { height: 30px } diff --git a/examples/library/ui_controls.ludic b/examples/library/ui_controls.ludic deleted file mode 100644 index 7e4508a7..00000000 --- a/examples/library/ui_controls.ludic +++ /dev/null @@ -1,71 +0,0 @@ -# ui_controls.ludic — ludic.ui's own controls, driven as a player would drive them: Tab and the -# arrows move the focus, Enter presses and flips, the arrows move a range and step a select, typing -# fills a text field and Backspace takes a letter back, a key field takes the next key, the pointer -# drags a range, and the wheel scrolls a box. Every change reaches the component as an event. -import "ludic.ui" -import "ui_controls_parts/Settings.ludic" -program UiControls { - numbers float - function frame(ui_st: mut UiState, i: UiInput) -> UiNode { - ui_input(ui_st, i) - ui_show(ui_st, "Settings", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "Settings", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root - } - function press(tab: bool, down: bool, right: bool, enter: bool) -> UiInput { - let i = new UiInput - i.tab = tab - i.down_key = down - i.right = right - i.enter = enter - return i - } - function state(root: UiNode) -> string { - let page = root.children[0] - let last = page.children[len(page.children) - 1] - return last.text - } - entry (ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - var root = frame(ui_st, new UiInput) - root = frame(ui_st, press(true, false, false, false)) - root = frame(ui_st, press(false, false, false, true)) - root = frame(ui_st, press(false, true, false, false)) - root = frame(ui_st, press(false, false, false, true)) - root = frame(ui_st, press(false, true, false, false)) - root = frame(ui_st, press(false, false, true, false)) - root = frame(ui_st, press(false, false, true, false)) - root = frame(ui_st, press(false, true, false, false)) - root = frame(ui_st, press(false, false, true, false)) - root = frame(ui_st, press(false, true, false, false)) - let typed = new UiInput - typed.typed = "lovelace" - root = frame(ui_st, typed) - let back = new UiInput - back.backspace = true - root = frame(ui_st, back) - root = frame(ui_st, press(false, true, false, false)) - root = frame(ui_st, press(false, false, false, true)) - let k = new UiInput - k.key = 70 - root = frame(ui_st, k) - print(state(root)) - let range = root.children[0].children[2] - let track = range.children[1] - let at = new UiInput - at.x = track.x + track.cw * 0.8 - at.y = track.y + 2.0 - at.down = true - root = frame(ui_st, at) - root = frame(ui_st, new UiInput) - let list = root.children[0].children[6] - let w = new UiInput - w.x = list.x + 5.0 - w.y = list.y + 5.0 - w.wheel = -1.0 - root = frame(ui_st, w) - root = frame(ui_st, new UiInput) - print(`{state(root)}; scrolled {int(root.children[0].children[6].scroll_y)}`) - } -} diff --git a/examples/library/ui_controls_parts/Settings.lss b/examples/library/ui_controls_parts/Settings.lss deleted file mode 100644 index cb956a07..00000000 --- a/examples/library/ui_controls_parts/Settings.lss +++ /dev/null @@ -1,4 +0,0 @@ -.page { width: 400px; gap: 4px } -input, select { width: fill; height: 30px } -.list { overflow: auto; height: 60px } -p { height: 20px } diff --git a/examples/library/ui_controls_parts/Settings.ludic b/examples/library/ui_controls_parts/Settings.ludic deleted file mode 100644 index 63542b85..00000000 --- a/examples/library/ui_controls_parts/Settings.ludic +++ /dev/null @@ -1,10 +0,0 @@ -# Settings.ludic - a screen of controls: every change comes back as an event with its value -component Settings { - state music: bool = true - state volume: int = 5 - state quality: int = 1 - state name: string = "Ada" - state jump: int = 32 - state pressed: int = 0 - on go() { pressed += 1 } -} diff --git a/examples/library/ui_controls_parts/Settings.xml b/examples/library/ui_controls_parts/Settings.xml deleted file mode 100644 index 61cde68f..00000000 --- a/examples/library/ui_controls_parts/Settings.xml +++ /dev/null @@ -1,12 +0,0 @@ -
- - - - - - -

1

2

3

4

5

6

7

8

-

{pressed} {music} {volume} {quality} {name} {jump}

-
diff --git a/examples/library/ui_css.ludic b/examples/library/ui_css.ludic deleted file mode 100644 index 90a3feac..00000000 --- a/examples/library/ui_css.ludic +++ /dev/null @@ -1,26 +0,0 @@ -# ui_css.ludic — ludic.ui's CSS without a renderer: custom properties and var(), absolute, fixed and -# relative positioning, em, rem and vw, @media, text that wraps, sibling combinators, :nth-child -# formulas, :checked, and overflow making a box scroll. -import "ludic.ui" -program UiCss { - numbers float - view Css { - wide = true - } - const LOOK: string = "screen { --gap: 6px; --accent: #ff8800 }\n.panel { position: relative; width: 300px; height: 200px; padding: 10px }\n.badge { position: absolute; right: 5px; bottom: 5px; width: 40px; height: 20px; background: var(--accent) }\n.corner { position: fixed; left: 0; top: 0; width: 10vw; height: 2rem }\n.nudge { position: relative; left: 7px; top: 3px }\n.big { font-size: 2em }\n.missing { color: var(--nope, #00ff00) }\n@media (max-width: 500px) { .wide-only { display: none } }\n@media (min-width: 500px) { .narrow-only { display: none } }\nli + li { margin-top: var(--gap) }\nh3 ~ p { color: var(--accent) }\nli:nth-child(2n+1) { color: #0000ff }\ninput:checked { opacity: 0.25 }\n.list { overflow: auto; height: 50px }\n.para { width: 120px }\n" - const PAGE: string = "\n\n\n
!

moved

big

\n
\n

wide

narrow

\n
  • a
  • b
  • c
\n

h

after

fallback

\n \n

1

2

3

4

\n

a sentence long enough that it has to wrap onto several lines

\n\n\n" - entry (ui_st: mut UiState) { - ui_load_text(ui_st, LOOK, "ui/look.lss") - ui_load_text(ui_st, PAGE, "ui/css.xml") - ui_viewport(ui_st, 400.0, 300.0) - let root: UiNode = ui_nodes(ui_st, "css", view_css()) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 300.0) - let panel = root.children[0] - let badge = panel.children[0] - let ul = root.children[3] - let para = root.children[len(root.children) - 1] - print(`badge at {int(badge.x)},{int(badge.y)} bg {badge.bg}; nudged {int(panel.children[1].x)},{int(panel.children[1].y)}; big {int(panel.children[2].rsize)}; corner {int(root.children[1].x)},{int(root.children[1].y)} {int(root.children[1].cw)}x{int(root.children[1].ch)}`) - print(`media keeps {root.children[2].text}; li gaps {int(ul.children[1].mt)} {int(ul.children[0].mt)}; li colours {ul.children[0].fg} {ul.children[1].fg} {ul.children[2].fg}; after {root.children[5].fg}; fallback {root.children[6].fg}`) - print(`checked opacity {root.children[7].alpha}; list scrolls {root.children[8].kind == UI_SCROLL} {int(root.children[8].ch)} of {int(root.children[8].content_h)}; para lines {len(para.lines)}`) - } -} diff --git a/examples/library/ui_ease.ludic b/examples/library/ui_ease.ludic deleted file mode 100644 index 4da174f1..00000000 --- a/examples/library/ui_ease.ludic +++ /dev/null @@ -1,57 +0,0 @@ -# ui_ease.ludic — easing never depends on the frame rate: a transition and an animation stepped at -# 60, 120, 180 and 240 Hz on the program's clock are at the same value half way (kept in float, never -# cut to whole pixels) and land exactly on their targets when their time is up. -import "ludic.ui" -program UiEase { - numbers float - state UiEaseState { - dim: bool = false - now: float = 0.0 - } - view Ease (ui_ease_st: UiEaseState) { - dim = ui_ease_st.dim - } - const STYLE: string = "" - const BODY: string = "

a

b

c

" - function clock(ui_ease_st: UiEaseState) -> float { return ui_ease_st.now } - function nodes(ui_st: mut UiState, name: string) -> UiNode { - ui_show(ui_st, name, view_ease(), 0.0, 0.0, 200.0, 200.0) - return ui_nodes(ui_st, name, view_ease()) - } - # one rate, on its own screen: the slide from its first frame, the fade from half a second in - function at_rate(ui_ease_st: mut UiEaseState, ui_st: mut UiState, hz: int) -> string { - let name = `r{hz}` - ui_ease_st.dim = false - var mid = "" - var slid = false - var landed = false - var over = false - for k in 0 .. hz + 1 { - ui_ease_st.now = float(k) / float(hz) - if k == hz / 2 { ui_ease_st.dim = true } - let r = nodes(ui_st, name) - let go = r.children[0] - let soft = r.children[1] - let want = r.children[2].alpha - if k == hz / 10 { mid = `{int(Math.round(go.left * 100.0))} ` } - if k == hz / 2 + hz / 10 { mid = mid + `{int(Math.round(soft.alpha * 1000.0))}` } - if k == hz / 4 { slid = go.left == 37.0 } - if k == hz / 2 + hz / 4 { landed = soft.alpha == want } - if k > hz / 2 + hz / 4 and soft.alpha != want { over = true } - } - return `{hz}: {mid} slid {slid} landed {landed} left {over}` - } - entry (ui_ease_st: mut UiEaseState, ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - ui_clock(ui_st, fn clock) - var page = "" + STYLE - for i in 0 .. 4 { - let hz = 60 * (i + 1) - page = page + `` + BODY - } - ui_load_text(ui_st, page + "", "e.xml") - var out = "" - for i in 0 .. 4 { out = out + at_rate(ui_ease_st, ui_st, 60 * (i + 1)) + "; " } - print(out) - } -} diff --git a/examples/library/ui_emit_click.ludic b/examples/library/ui_emit_click.ludic deleted file mode 100644 index 8454d736..00000000 --- a/examples/library/ui_emit_click.ludic +++ /dev/null @@ -1,40 +0,0 @@ -# ui_emit_click.ludic - a component whose button says `emit click` is answered by its user's -# on-click, by the mouse or a press: on-click is kept as on-press, and the emit once looked for -# "click" and found nothing (the pack's items and the wardrobe's picks did not respond) -import "ludic.ui" -import "ui_emit_click_parts/Cell.ludic" -import "ui_emit_click_parts/Pick.ludic" -program UiEmitClick { - numbers float - function frame(ui_st: mut UiState, x: float, y: float, down: bool) -> UiNode { - let i = new UiInput - i.x = x - i.y = y - i.down = down - ui_input(ui_st, i) - ui_show(ui_st, "Pick", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "Pick", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root - } - function first_text(n: UiNode) -> string { - if n.text != "" { return n.text } - for i in 0 .. len(n.children) { let t = first_text(n.children[i]); if t != "" { return t } } - return "" - } - entry (ui_st: mut UiState) { - var root = frame(ui_st, -1.0, -1.0, false) - root = frame(ui_st, -1.0, -1.0, false) - let b = root.children[0].children[2].children[0] - let x = b.x + b.cw / 2.0 - let y = b.y + b.ch / 2.0 - root = frame(ui_st, x, y, false) - root = frame(ui_st, x, y, true) - root = frame(ui_st, x, y, false) - root = frame(ui_st, x, y, false) - let clicked = first_text(root) - ui_press(ui_st, root.children[0].children[1].children[0]) - root = frame(ui_st, x, y, false) - print(`{clicked} | pressed: {first_text(root)}`) - } -} diff --git a/examples/library/ui_emit_click_parts/Cell.lss b/examples/library/ui_emit_click_parts/Cell.lss deleted file mode 100644 index b8c03a5a..00000000 --- a/examples/library/ui_emit_click_parts/Cell.lss +++ /dev/null @@ -1 +0,0 @@ -.cell { width: 100px; height: 40px } diff --git a/examples/library/ui_emit_click_parts/Cell.ludic b/examples/library/ui_emit_click_parts/Cell.ludic deleted file mode 100644 index 0ed005d7..00000000 --- a/examples/library/ui_emit_click_parts/Cell.ludic +++ /dev/null @@ -1,3 +0,0 @@ -component Cell { - prop caption: string = "" -} diff --git a/examples/library/ui_emit_click_parts/Cell.xml b/examples/library/ui_emit_click_parts/Cell.xml deleted file mode 100644 index a88076ab..00000000 --- a/examples/library/ui_emit_click_parts/Cell.xml +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/examples/library/ui_emit_click_parts/Pick.lss b/examples/library/ui_emit_click_parts/Pick.lss deleted file mode 100644 index 1de0ef03..00000000 --- a/examples/library/ui_emit_click_parts/Pick.lss +++ /dev/null @@ -1 +0,0 @@ -div { width: 400px } diff --git a/examples/library/ui_emit_click_parts/Pick.ludic b/examples/library/ui_emit_click_parts/Pick.ludic deleted file mode 100644 index 917bb014..00000000 --- a/examples/library/ui_emit_click_parts/Pick.ludic +++ /dev/null @@ -1,4 +0,0 @@ -component Pick { - state got: int = -1 - on pick(i: int) { got = i } -} diff --git a/examples/library/ui_emit_click_parts/Pick.xml b/examples/library/ui_emit_click_parts/Pick.xml deleted file mode 100644 index aeefa2b6..00000000 --- a/examples/library/ui_emit_click_parts/Pick.xml +++ /dev/null @@ -1,5 +0,0 @@ -
-

got {got}

- - -
diff --git a/examples/library/ui_fill.ludic b/examples/library/ui_fill.ludic deleted file mode 100644 index c7214bb1..00000000 --- a/examples/library/ui_fill.ludic +++ /dev/null @@ -1,41 +0,0 @@ -# ui_fill.ludic — linear-gradient backgrounds (a direction or an angle, stops placed or spread) drawn -# as whole-pixel bands; object-fit contain and cover for a picture of its own size; aspect-ratio -# making a height from a width, and a native's width from its height. -import "ludic.ui" -program UiFill { - numbers float - state UiFillState { - rects: []string = new []string - pics: []string = new []string - clips: int = 0 - } - const STYLE: string = "" - const BODY: string = "
" - function rec_rect(ui_fill_st: mut UiFillState, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(ui_fill_st.rects, `{int(x)},{int(y)} {int(w)}x{int(h)} {ui_hex(c)}`) } - function rec_image(ui_fill_st: mut UiFillState, src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(ui_fill_st.pics, `{int(x)},{int(y)} {int(w)}x{int(h)}`) } - function rec_clip(ui_fill_st: mut UiFillState, x: float, y: float, w: float, h: float) -> void { ui_fill_st.clips += 1 } - function no_clip() -> void { } - function no_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { } - function nat_w(src: string) -> float { return 200.0 } - function nat_h(src: string) -> float { return 100.0 } - function knob_measure(n: UiNode, avail: float) -> void { } - function knob_draw(n: UiNode) -> void { } - entry (ui_fill_st: UiFillState, ui_st: mut UiState) { - let b = new UiBackend - b.rect = fn rec_rect - b.text = fn no_text - b.image = fn rec_image - b.image_w = fn nat_w - b.image_h = fn nat_h - b.clip = fn rec_clip - b.unclip = fn no_clip - ui_backend(ui_st, b) - ui_native(ui_st, "knob", fn knob_measure, fn knob_draw) - ui_load_text(ui_st, "" + STYLE + BODY + "", "f.xml") - ui_show(ui_st, "f", null, 0.0, 0.0, 400.0, 600.0) - let r: UiNode = ui_nodes(ui_st, "f", null) - ui_place(ui_st, r, 0.0, 0.0, 400.0, 600.0) - let n = len(ui_fill_st.rects) - print(`{ui_fill_st.rects[0]} .. {ui_fill_st.rects[49]} | {ui_fill_st.rects[50]} .. {ui_fill_st.rects[59]} .. {ui_fill_st.rects[69]} of {n} | {ui_fill_st.pics[0]} | {ui_fill_st.pics[1]} clipped {ui_fill_st.clips} | ratio {int(r.children[4].ch)} knob {int(r.children[5].children[0].cw)}`) - } -} diff --git a/examples/library/ui_first_show.ludic b/examples/library/ui_first_show.ludic deleted file mode 100644 index c3aeb648..00000000 --- a/examples/library/ui_first_show.ludic +++ /dev/null @@ -1,27 +0,0 @@ -# ui_first_show.ludic - a component shown for the first time once the fence is judging (a prompt's key -# hint mid-walk) makes its instance's props and model: declared, once per instance, and the failing -# fence lets it through; hidden and shown again, the instance is reused and nothing more is made: -# bad 0 shown 3 -import "ludic.ui" -import "ui_first_show_parts/Cell.ludic" -import "ui_first_show_parts/Host.ludic" -program UiFirstShow { - numbers float - function frame(ui_st: mut UiState) -> UiNode { - let root: UiNode = ui_nodes(ui_st, "Host", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root - } - entry (ui_st: mut UiState) { - var shown = 0 - for f in 0 .. 1400 { - let root = frame(ui_st) - if f == 700 or f == 900 or f == 1000 or f == 1100 or f == 1200 { - ui_press(ui_st, root.children[0].children[0]) - if f != 900 and f != 1100 { shown += 1 } - } - Mem.frame() - } - print(`bad {Mem.bad_frames()} shown {shown}`) - } -} diff --git a/examples/library/ui_first_show_parts/Cell.lss b/examples/library/ui_first_show_parts/Cell.lss deleted file mode 100644 index b8c03a5a..00000000 --- a/examples/library/ui_first_show_parts/Cell.lss +++ /dev/null @@ -1 +0,0 @@ -.cell { width: 100px; height: 40px } diff --git a/examples/library/ui_first_show_parts/Cell.ludic b/examples/library/ui_first_show_parts/Cell.ludic deleted file mode 100644 index 0ed005d7..00000000 --- a/examples/library/ui_first_show_parts/Cell.ludic +++ /dev/null @@ -1,3 +0,0 @@ -component Cell { - prop caption: string = "" -} diff --git a/examples/library/ui_first_show_parts/Cell.xml b/examples/library/ui_first_show_parts/Cell.xml deleted file mode 100644 index a88076ab..00000000 --- a/examples/library/ui_first_show_parts/Cell.xml +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/examples/library/ui_first_show_parts/Host.lss b/examples/library/ui_first_show_parts/Host.lss deleted file mode 100644 index 1bac0ccf..00000000 --- a/examples/library/ui_first_show_parts/Host.lss +++ /dev/null @@ -1 +0,0 @@ -.t { width: 60px; height: 30px } diff --git a/examples/library/ui_first_show_parts/Host.ludic b/examples/library/ui_first_show_parts/Host.ludic deleted file mode 100644 index a8e1b5ba..00000000 --- a/examples/library/ui_first_show_parts/Host.ludic +++ /dev/null @@ -1,4 +0,0 @@ -component Host { - state on: bool = false - on flip() { on = not on } -} diff --git a/examples/library/ui_first_show_parts/Host.xml b/examples/library/ui_first_show_parts/Host.xml deleted file mode 100644 index 29140558..00000000 --- a/examples/library/ui_first_show_parts/Host.xml +++ /dev/null @@ -1,4 +0,0 @@ -
- - -
diff --git a/examples/library/ui_flex.ludic b/examples/library/ui_flex.ludic deleted file mode 100644 index 50255f51..00000000 --- a/examples/library/ui_flex.ludic +++ /dev/null @@ -1,25 +0,0 @@ -# ui_flex.ludic — ludic.ui's flex layout and stylesheets without a renderer: grow shares the spare -# room, justify spreads it, a row wraps, align stretches; a stylesheet file (which @imports another) -# styles whoever imports it, a more specific rule wins, a bound class switches one on, and an attribute beats all. -import "ludic.ui" -program UiFlex { - numbers float - let picked: int = 1 - view Tags { - picked = picked - } - const BASE: string = "/* base.lss - every text small */\ntext { size: 10 }\n" - const THEME: string = "/* theme.lss - the base, and a colour for what is on */\n@import \"base.lss\";\n.on { color: #ff0000 }\n" - const PAGE: string = "\n\n\n\n \n ab\n \n lmr\n \n t{i}\n \n
x\n\n\n" - entry (ui_st: mut UiState) { - ui_load_text(ui_st, BASE, "ui/base.lss") - ui_load_text(ui_st, THEME, "ui/theme.lss") - ui_load_text(ui_st, PAGE, "ui/flex.xml") - let root: UiNode = ui_nodes(ui_st, "flex", view_tags()) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 300.0) - let bar = root.children[0] - let wrap = root.children[2] - let tall = root.children[3] - print(`grow {int(bar.children[0].cw)}:{int(bar.children[1].cw)}; between at {int(root.children[1].children[1].x)}; wrap lines at {int(wrap.children[0].y)}, {int(wrap.children[3].y)}; red {wrap.children[1].fg}; stretched {int(tall.children[0].ch)}; own size {int(tall.children[1].rsize)}`) - } -} diff --git a/examples/library/ui_focus_tip.ludic b/examples/library/ui_focus_tip.ludic deleted file mode 100644 index 126839ce..00000000 --- a/examples/library/ui_focus_tip.ludic +++ /dev/null @@ -1,41 +0,0 @@ -# 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 - state UiFocusTipState { - said: []string = new []string - now: float = 0.0 - } - const PAGE: string = "" - function rec_text(ui_focus_tip_st: mut UiFocusTipState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(ui_focus_tip_st.said, `{s} {int(x)},{int(y)}`) } - function clock(ui_focus_tip_st: UiFocusTipState) -> float { return ui_focus_tip_st.now } - function frame(ui_focus_tip_st: mut UiFocusTipState, ui_st: mut UiState, i: UiInput, secs: float) -> string { - ui_focus_tip_st.now = ui_focus_tip_st.now + secs - ui_focus_tip_st.said = new []string - ui_input(ui_st, i) - ui_show(ui_st, "f", null, 0.0, 0.0, 400.0, 400.0) - return ui_focus_tip_st.said[len(ui_focus_tip_st.said) - 1] - } - entry (ui_focus_tip_st: mut UiFocusTipState, ui_st: mut UiState) { - let b = new UiBackend - b.text = fn rec_text - ui_backend(ui_st, b) - ui_clock(ui_st, fn clock) - ui_load_text(ui_st, PAGE, "f.xml") - frame(ui_focus_tip_st, ui_st, new UiInput, 0.0) - let tab = new UiInput - tab.tab = true - frame(ui_focus_tip_st, ui_st, tab, 0.0) - let early = frame(ui_focus_tip_st, ui_st, new UiInput, 0.2) - let shown = frame(ui_focus_tip_st, ui_st, new UiInput, 0.4) - frame(ui_focus_tip_st, ui_st, tab, 0.0) - let next = frame(ui_focus_tip_st, ui_st, new UiInput, 0.6) - let moved = new UiInput - moved.x = 390.0 - moved.y = 390.0 - let gone = frame(ui_focus_tip_st, ui_st, moved, 0.6) - print(`{early} | {shown} | {next} | {gone}`) - } -} diff --git a/examples/library/ui_gamepad.ludic b/examples/library/ui_gamepad.ludic deleted file mode 100644 index b6c257a4..00000000 --- a/examples/library/ui_gamepad.ludic +++ /dev/null @@ -1,54 +0,0 @@ -# ui_gamepad.ludic — the pad and held keys, on the program's clock: a direction held presses once, -# again after 0.42 s and every 0.11 s after that, at any frame rate; A presses what has the focus, -# left and right step a range and a select, and B closes a popover. -import "ludic.ui" -import "ui_gamepad_parts/Pad.ludic" -program UiGamepad { - numbers float - state UiGamepadState { - now: float = 0.0 - } - function clock(ui_gamepad_st: UiGamepadState) -> float { return ui_gamepad_st.now } - function frame(ui_st: mut UiState, i: UiInput) -> string { - ui_input(ui_st, i) - ui_show(ui_st, "Pad", null, 0.0, 0.0, 400.0, 600.0) - let page = ui_nodes(ui_st, "Pad", null).children[0] - return page.children[len(page.children) - 1].text - } - # `what` held for `secs` at `hz`, then let go - function hold(ui_gamepad_st: mut UiGamepadState, ui_st: mut UiState, what: string, secs: float, hz: int) -> string { - let frames = int(secs * float(hz) + 0.5) - for k in 0 .. frames + 1 { - ui_gamepad_st.now = ui_gamepad_st.now + 1.0 / float(hz) - let i = new UiInput - i.held_up = what == "up" - i.held_down = what == "down" - i.held_left = what == "left" - i.held_right = what == "right" - i.pad_a = what == "a" - i.pad_b = what == "b" - frame(ui_st, i) - } - ui_gamepad_st.now = ui_gamepad_st.now + 1.0 / float(hz) - return frame(ui_st, new UiInput) - } - function tap(ui_gamepad_st: mut UiGamepadState, ui_st: mut UiState, what: string) -> string { return hold(ui_gamepad_st, ui_st, what, 0.0, 60) } - entry (ui_gamepad_st: mut UiGamepadState, ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - ui_clock(ui_st, fn clock) - frame(ui_st, new UiInput) - hold(ui_gamepad_st, ui_st, "down", 1.0, 240) - let fast = tap(ui_gamepad_st, ui_st, "a") - hold(ui_gamepad_st, ui_st, "down", 1.0, 60) - hold(ui_gamepad_st, ui_st, "up", 0.5, 60) - let slow = tap(ui_gamepad_st, ui_st, "a") - for k in 0 .. 8 { tap(ui_gamepad_st, ui_st, "down") } - let vol = hold(ui_gamepad_st, ui_st, "right", 0.5, 60) - tap(ui_gamepad_st, ui_st, "down") - let q = tap(ui_gamepad_st, ui_st, "left") - tap(ui_gamepad_st, ui_st, "down") - let open = tap(ui_gamepad_st, ui_st, "a") - let shut = tap(ui_gamepad_st, ui_st, "b") - print(`{fast} | {slow} | {vol} | {q} | {open} | {shut}`) - } -} diff --git a/examples/library/ui_gamepad_parts/Pad.ludic b/examples/library/ui_gamepad_parts/Pad.ludic deleted file mode 100644 index 6d3776cc..00000000 --- a/examples/library/ui_gamepad_parts/Pad.ludic +++ /dev/null @@ -1,7 +0,0 @@ -# Pad.ludic - eight buttons, a range, a select and a button that opens a popover, for a gamepad -component Pad { - state last: int = -1 - state vol: int = 0 - state q: int = 0 - state open: bool = false -} diff --git a/examples/library/ui_gamepad_parts/Pad.xml b/examples/library/ui_gamepad_parts/Pad.xml deleted file mode 100644 index 0596d59f..00000000 --- a/examples/library/ui_gamepad_parts/Pad.xml +++ /dev/null @@ -1,8 +0,0 @@ -
- - - - -
-

last {last} vol {vol} q {q} open {open}

-
diff --git a/examples/library/ui_hold.ludic b/examples/library/ui_hold.ludic deleted file mode 100644 index 60547577..00000000 --- a/examples/library/ui_hold.ludic +++ /dev/null @@ -1,61 +0,0 @@ -# ui_hold.ludic — on-hold fires every frame a button is held, by the pointer (still when it is dragged -# off, until let go) or by Enter or the pad's A on the focus, with event.dt and event.t on the ui clock, -# the same at 60 and 240 Hz; on-down and on-up still mark the ends. -import "ludic.ui" -program UiHold { - numbers float - state UiHoldState { - now: float = 0.0 - held: float = 0.0 - frames: int = 0 - last_t: float = 0.0 - ends: string = "" - } - view Craft (ui_hold_st: mut UiHoldState) { - on tick(dt: float, t: float) { - ui_hold_st.held = ui_hold_st.held + dt - ui_hold_st.frames = ui_hold_st.frames + 1 - ui_hold_st.last_t = t - } - on down() { ui_hold_st.ends = ui_hold_st.ends + "d" } - on up() { ui_hold_st.ends = ui_hold_st.ends + "u" } - } - const PAGE: string = "" - function clock(ui_hold_st: UiHoldState) -> float { return ui_hold_st.now } - function frame(ui_st: mut UiState, i: UiInput) -> UiNode { - ui_input(ui_st, i) - ui_show(ui_st, "h", view_craft(), 0.0, 0.0, 400.0, 400.0) - let r: UiNode = ui_nodes(ui_st, "h", view_craft()) - ui_place(ui_st, r, 0.0, 0.0, 400.0, 400.0) - return r.children[0] - } - # held for `secs` at `hz`, by the pointer (off the button after the first frame) or by A - function hold(ui_hold_st: mut UiHoldState, ui_st: mut UiState, b: UiNode, secs: float, hz: int, pad: bool) -> string { - ui_hold_st.held = 0.0 - ui_hold_st.frames = 0 - let n = int(secs * float(hz) + 0.5) - for k in 0 .. n + 1 { - let i = new UiInput - i.pad_a = pad - i.down = not pad - i.x = b.x + 5.0 - i.y = b.y + 5.0 - if k > 0 and not pad { i.x = 390.0 } - frame(ui_st, i) - ui_hold_st.now = ui_hold_st.now + 1.0 / float(hz) - } - frame(ui_st, new UiInput) - ui_hold_st.now = ui_hold_st.now + 1.0 / float(hz) - return `{ui_hold_st.frames} frames {int(Math.round(ui_hold_st.held * 1000.0))} ms t {int(Math.round(ui_hold_st.last_t * 1000.0))}` - } - entry (ui_hold_st: mut UiHoldState, ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - ui_clock(ui_st, fn clock) - ui_load_text(ui_st, PAGE, "h.xml") - let b = frame(ui_st, new UiInput) - let slow = hold(ui_hold_st, ui_st, b, 0.5, 60, false) - let fast = hold(ui_hold_st, ui_st, b, 0.5, 240, false) - let pad = hold(ui_hold_st, ui_st, b, 0.25, 60, true) - print(`{slow} | {fast} | pad {pad} | {ui_hold_st.ends}`) - } -} diff --git a/examples/library/ui_html.ludic b/examples/library/ui_html.ludic deleted file mode 100644 index f639d5bc..00000000 --- a/examples/library/ui_html.ludic +++ /dev/null @@ -1,30 +0,0 @@ -# ui_html.ludic — ludic.ui read as HTML and CSS: HTML's elements with a browser's defaults, ids, -# classes, style="...", hidden and disabled, onclick; selectors with ids, descendants, children, -# attributes and pseudo-classes, weighed by specificity; CSS's own property names and the box model. -import "ludic.ui" -program UiHtml { - numbers float - state UiHtmlState { - clicks: int = 0 - } - view Page (ui_html_st: mut UiHtmlState) { - clicks = ui_html_st.clicks - on clicked() { ui_html_st.clicks += 1 } - } - const LOOK: string = "/* look.lss */\n.card { padding: 4 8 12 16; margin: 3; border: 2px solid #0000ff; width: 50% }\n#title { font-size: 40 }\nh1.big { font-size: 20 }\n.card p { color: #00ff00 }\nul > li { height: 30 }\nli:first-child { color: #ff0000 }\nli:nth-child(even) { color: #0000ff }\nli:last-child:not(.keep) { display: none }\n[kind=warn] { background-color: #ffff00 }\nbutton:disabled { opacity: 0.5 }\n" - const PAGE: string = "\n\n\n

Title

\n

inside

careful
\n

outside

\n
  • one
  • two
  • three
  • gone
\n
\n

mid

\n \n \n
\n
\n" - entry (ui_html_st: UiHtmlState, ui_st: mut UiState) { - ui_load_text(ui_st, LOOK, "ui/look.lss") - ui_load_text(ui_st, PAGE, "ui/page.ludic.xml") - var root: UiNode = ui_nodes(ui_st, "page", view_page()) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0) - let card = root.children[1] - let items = root.children[3].children - print(`id beats class: {int(root.children[0].rsize)}; card {int(card.cw)} wide at {int(card.x)},{int(card.y)} pad {int(card.pt)} {int(card.pr)} {int(card.pb)} {int(card.pl)} border {int(card.border)}`) - print(`inside {card.children[0].fg}, outside {root.children[2].fg}; warn bg {card.children[1].bg}; items {len(items)} of 30: {int(items[0].ch)}; colours {items[0].fg} {items[1].fg} {items[2].fg}; hr {int(root.children[4].ch)}; children {len(root.children)}`) - ui_press(ui_st, root.children[6]) - root = ui_nodes(ui_st, "page", view_page()) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0) - print(`clicks {ui_html_st.clicks}; the button now disabled {not root.children[6].enabled} at opacity {root.children[6].alpha}; text-align {root.children[5].talign}`) - } -} diff --git a/examples/library/ui_inputs.ludic b/examples/library/ui_inputs.ludic deleted file mode 100644 index 4a995afc..00000000 --- a/examples/library/ui_inputs.ludic +++ /dev/null @@ -1,83 +0,0 @@ -# ui_inputs.ludic — more of ludic.ui's controls, driven by the keys: a number field takes typed digits -# (Enter or leaving it commits them, held between min and max) and steps with the arrows; a key field -# listens for the next key (Tab included), Esc stops it listening and Backspace unbinds it; a range -# shows its value as a percentage or with decimals and a unit; a label carries a note; an action may -# call an event named set; and a string prop given a number reads it as text. -import "ludic.ui" -import "ui_inputs_parts/Form.ludic" -import "ui_inputs_parts/Tag.ludic" -program UiInputs { - numbers float - function frame(ui_st: mut UiState, i: UiInput) -> UiNode { - ui_input(ui_st, i) - ui_show(ui_st, "Form", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "Form", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root.children[0] - } - function keys(tab: bool, shift: bool, enter: bool, left: bool) -> UiInput { - let i = new UiInput - i.tab = tab - i.shift = shift - i.enter = enter - i.left = left - if tab { i.key = Key.Tab } - if enter { i.key = Key.Enter } - return i - } - function typed(s: string) -> UiInput { - let i = new UiInput - i.typed = s - return i - } - function shown_state(page: UiNode) -> string { return page.children[len(page.children) - 1].text } - function last(n: UiNode) -> string { return n.children[len(n.children) - 1].text } - entry (ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - var page = frame(ui_st, new UiInput) - frame(ui_st, keys(true, false, false, false)) - frame(ui_st, typed("12")) - page = frame(ui_st, keys(false, false, true, false)) - let shown = page.children[0].children[1].children[0].text - let a = shown_state(page) - frame(ui_st, typed("99")) - page = frame(ui_st, keys(true, false, false, false)) - let b = shown_state(page) - frame(ui_st, keys(true, true, false, false)) - page = frame(ui_st, keys(false, false, false, true)) - let c = shown_state(page) - let note = page.children[0].children[0].children[1].text - frame(ui_st, keys(true, false, false, false)) - let space = page.children[1].children[1].children[0].text - frame(ui_st, keys(false, false, true, false)) - let cap = ui_capturing(ui_st) - page = frame(ui_st, keys(true, false, false, false)) - let d = shown_state(page) - frame(ui_st, keys(false, false, true, false)) - let esc = new UiInput - esc.escape = true - esc.key = Key.Escape - page = frame(ui_st, esc) - let e = shown_state(page) - let back = new UiInput - back.backspace = true - page = frame(ui_st, back) - let f = shown_state(page) - frame(ui_st, keys(true, false, false, false)) - let right = new UiInput - right.right = true - page = frame(ui_st, right) - page = frame(ui_st, new UiInput) - let button = page.children[4] - let click = new UiInput - click.x = button.x + 2.0 - click.y = button.y + 2.0 - click.down = true - frame(ui_st, click) - let up = new UiInput - up.x = click.x - up.y = click.y - page = frame(ui_st, up) - print(`count {shown} {a} | {b} | {c} | {note} | {space} listening {cap} {d} | {e} | {f} | {last(page.children[2])} {last(page.children[3])} | {shown_state(page)} | {page.children[5].text}`) - } -} diff --git a/examples/library/ui_inputs_parts/Form.lss b/examples/library/ui_inputs_parts/Form.lss deleted file mode 100644 index 0f774ffd..00000000 --- a/examples/library/ui_inputs_parts/Form.lss +++ /dev/null @@ -1,2 +0,0 @@ -.page { width: 400px; gap: 4px } -input { width: fill } diff --git a/examples/library/ui_inputs_parts/Form.ludic b/examples/library/ui_inputs_parts/Form.ludic deleted file mode 100644 index f81669f3..00000000 --- a/examples/library/ui_inputs_parts/Form.ludic +++ /dev/null @@ -1,9 +0,0 @@ -# Form.ludic - a number, a key binding, two ranges with their values formatted, a note under a label, -# and an event called set -component Form { - state count: int = 5 - state bind: int = 32 - state vol: float = 0.5 - state said: int = 0 - on set(v: int) { said = v } -} diff --git a/examples/library/ui_inputs_parts/Form.xml b/examples/library/ui_inputs_parts/Form.xml deleted file mode 100644 index 1d13d356..00000000 --- a/examples/library/ui_inputs_parts/Form.xml +++ /dev/null @@ -1,9 +0,0 @@ -
- - - - - - -

{count} {bind} {vol} {said}

-
diff --git a/examples/library/ui_inputs_parts/Tag.ludic b/examples/library/ui_inputs_parts/Tag.ludic deleted file mode 100644 index 196cadfa..00000000 --- a/examples/library/ui_inputs_parts/Tag.ludic +++ /dev/null @@ -1,4 +0,0 @@ -# Tag.ludic - a string prop, which a number given to it arrives in as text -component Tag { - prop text: string = "none" -} diff --git a/examples/library/ui_inputs_parts/Tag.xml b/examples/library/ui_inputs_parts/Tag.xml deleted file mode 100644 index 6fd5cee1..00000000 --- a/examples/library/ui_inputs_parts/Tag.xml +++ /dev/null @@ -1 +0,0 @@ -

{text}!

diff --git a/examples/library/ui_keycap.ludic b/examples/library/ui_keycap.ludic deleted file mode 100644 index 9a3ad764..00000000 --- a/examples/library/ui_keycap.ludic +++ /dev/null @@ -1,57 +0,0 @@ -# ui_keycap.ludic — a key field takes a mouse button as well as a key: listening (and :capturing, -# which a stylesheet can colour), the next press of the left, right or middle button anywhere is its -# value (UI_MOUSE_LEFT / RIGHT / MIDDLE), named for what it is, and that press presses nothing else. -import "ludic.ui" -program UiKeycap { - numbers float - state UiKeycapState { - bind: int = 32 - pressed: int = 0 - } - view Keys (ui_keycap_st: mut UiKeycapState) { - bind = ui_keycap_st.bind - pressed = ui_keycap_st.pressed - on set_bind(v: int) { ui_keycap_st.bind = v } - on press() { ui_keycap_st.pressed = ui_keycap_st.pressed + 1 } - } - const PAGE: string = "" - function frame(ui_st: mut UiState, x: float, y: float, b: int) -> UiNode { - let i = new UiInput - i.x = x - i.y = y - i.down = b == 0 - i.down_right = b == 1 - i.down_middle = b == 2 - ui_input(ui_st, i) - ui_show(ui_st, "k", view_keys(), 0.0, 0.0, 400.0, 400.0) - let r: UiNode = ui_nodes(ui_st, "k", view_keys()) - ui_place(ui_st, r, 0.0, 0.0, 400.0, 400.0) - return r - } - function listen(ui_st: mut UiState, k: UiNode) -> int { - frame(ui_st, k.x + 5.0, k.y + 5.0, 0) - frame(ui_st, k.x + 5.0, k.y + 5.0, -1) - return frame(ui_st, -1.0, -1.0, -1).children[0].bg - } - function take(ui_keycap_st: UiKeycapState, ui_st: mut UiState, at: UiNode, b: int) -> string { - frame(ui_st, at.x + 5.0, at.y + 5.0, b) - frame(ui_st, at.x + 5.0, at.y + 5.0, -1) - let r = frame(ui_st, -1.0, -1.0, -1) - let f = r.children[0] - return `{ui_keycap_st.bind} {f.children[1].children[0].text} {f.bg}` - } - entry (ui_keycap_st: UiKeycapState, ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - ui_load_text(ui_st, PAGE, "k.xml") - let r = frame(ui_st, -1.0, -1.0, -1) - let k = r.children[0] - let btn = r.children[1] - let red = listen(ui_st, k) - let right = take(ui_keycap_st, ui_st, btn, 1) - listen(ui_st, k) - let middle = take(ui_keycap_st, ui_st, btn, 2) - listen(ui_st, k) - let left = take(ui_keycap_st, ui_st, btn, 0) - print(`listening {red} | {right} | {middle} | {left} | pressed {ui_keycap_st.pressed}`) - } -} diff --git a/examples/library/ui_look.ludic b/examples/library/ui_look.ludic deleted file mode 100644 index 0e279b45..00000000 --- a/examples/library/ui_look.ludic +++ /dev/null @@ -1,67 +0,0 @@ -# ui_look.ludic — what ludic.ui draws, through a backend that writes each call down: a component -# whose root is another component, styled by every sheet above it by specificity; text-shadow under -# text; a native reading the opacity it is drawn at (ui_opacity); border-image as painted, tinted by -# a background colour, and gone under `transparent`; a picture drawn as it is and an atlas cell in -# the text's colour; a tooltip of two lines; and popovers placed beside their anchors, one flipped. -import "ludic.ui" -import "ui_look_parts/Look.ludic" -import "ui_look_parts/Middle.ludic" -import "ui_look_parts/Inner.ludic" -import "ui_look_parts/Menu.ludic" -program UiLook { - numbers float - state UiLookState { - said: []string = new []string - seen: float = 0.0 - } - function rec_rect(x: float, y: float, w: float, h: float, c: int, a: float) -> void { } - function rec_text(ui_look_st: mut UiLookState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(ui_look_st.said, `text {s} {int(x)},{int(y)} {ui_hex(c)}`) } - function rec_image(ui_look_st: mut UiLookState, src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(ui_look_st.said, `image {src} {ui_hex(c)}`) } - function rec_nine(ui_look_st: mut UiLookState, src: string, slice: float, x: float, y: float, w: float, h: float, dst: float, c: int, a: float) -> void { push(ui_look_st.said, `nine {int(w)} {ui_hex(c)}`) } - function gauge_measure(n: UiNode, avail: float) -> void { } - function gauge_draw(ui_look_st: mut UiLookState, ui_st: UiState, n: UiNode) -> void { ui_look_st.seen = ui_opacity(ui_st) } - function count(ui_look_st: UiLookState, s: string) -> int { - var n = 0 - for i in 0 .. len(ui_look_st.said) { - if Text.starts_with(ui_look_st.said[i], s) { n += 1 } - } - return n - } - function has(ui_look_st: UiLookState, s: string) -> string { - for i in 0 .. len(ui_look_st.said) { - if Text.starts_with(ui_look_st.said[i], s) { return ui_look_st.said[i] } - } - return "-" - } - function show(ui_look_st: mut UiLookState, ui_st: mut UiState, name: string, i: UiInput) -> UiNode { - ui_input(ui_st, i) - ui_look_st.said = new []string - ui_show(ui_st, name, null, 0.0, 0.0, 400.0, 300.0) - let root: UiNode = ui_nodes(ui_st, name, null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 300.0) - return root.children[0] - } - entry (ui_look_st: mut UiLookState, ui_st: mut UiState) { - let b = new UiBackend - b.rect = fn rec_rect - b.text = fn rec_text - b.image = fn rec_image - b.nine = fn rec_nine - ui_backend(ui_st, b) - ui_native(ui_st, "gauge", fn gauge_measure, fn gauge_draw) - var page = show(ui_look_st, ui_st, "Look", new UiInput) - let inner = page.children[0] - let tip = page.children[7] - let rest = new UiInput - rest.x = tip.x + 2.0 - rest.y = tip.y + 2.0 - for f in 0 .. 40 { page = show(ui_look_st, ui_st, "Look", rest) } - print(`root {inner.tag}.{inner.classes[0]}.{inner.classes[1]}.{inner.classes[2]} pad {int(inner.pt)} margin {int(inner.mt)} color {ui_hex(inner.fg)}`) - print(`{has(ui_look_st, "text Hi")} | {has(ui_look_st, "text Hi 0")} | opacity {ui_look_st.seen} | {has(ui_look_st, "nine 40 #ffffff")} {has(ui_look_st, "nine 40 #ff0000")} of {count(ui_look_st, "nine")}`) - print(`{has(ui_look_st, "image photo")} | {has(ui_look_st, "image icon")} | {has(ui_look_st, "text first")} | {has(ui_look_st, "text second")}`) - let menu = show(ui_look_st, ui_st, "Menu", new UiInput) - let a = menu.children[1] - let c = menu.children[3] - print(`by id at {int(a.x)},{int(a.y)} | flipped at {int(c.x)},{int(c.y)} {int(c.cw)}x{int(c.ch)}`) - } -} diff --git a/examples/library/ui_look_parts/Inner.lss b/examples/library/ui_look_parts/Inner.lss deleted file mode 100644 index a2a23a45..00000000 --- a/examples/library/ui_look_parts/Inner.lss +++ /dev/null @@ -1 +0,0 @@ -div.in { padding: 5px } diff --git a/examples/library/ui_look_parts/Inner.ludic b/examples/library/ui_look_parts/Inner.ludic deleted file mode 100644 index ea13f836..00000000 --- a/examples/library/ui_look_parts/Inner.ludic +++ /dev/null @@ -1,4 +0,0 @@ -# Inner.ludic - the innermost root: its own rule is more specific than Middle's, so it holds -component Inner { - state n: int = 0 -} diff --git a/examples/library/ui_look_parts/Inner.xml b/examples/library/ui_look_parts/Inner.xml deleted file mode 100644 index 6ac2251d..00000000 --- a/examples/library/ui_look_parts/Inner.xml +++ /dev/null @@ -1 +0,0 @@ -

in

diff --git a/examples/library/ui_look_parts/Look.lss b/examples/library/ui_look_parts/Look.lss deleted file mode 100644 index 10bfe048..00000000 --- a/examples/library/ui_look_parts/Look.lss +++ /dev/null @@ -1,9 +0,0 @@ -.page { width: 400px; gap: 2px } -.m { margin: 9px } -.shade { text-shadow: 2px 3px 4px #102030 } -.dim { opacity: 0.5 } -gauge { width: 10px; height: 10px } -.chip { width: 40px; height: 20px; border-image: url(chip.png) 8 } -.clear { background: transparent } -.red { background: #ff0000 } -.pics { flex-direction: row; color: #00ff00 } diff --git a/examples/library/ui_look_parts/Look.ludic b/examples/library/ui_look_parts/Look.ludic deleted file mode 100644 index 6dcb508d..00000000 --- a/examples/library/ui_look_parts/Look.ludic +++ /dev/null @@ -1,5 +0,0 @@ -# Look.ludic - what is drawn: a nested component's root styled by every sheet above it, a shadow -# under text, a native at its group's opacity, border-images, pictures, and a tooltip of two lines -component Look { - state n: int = 0 -} diff --git a/examples/library/ui_look_parts/Look.xml b/examples/library/ui_look_parts/Look.xml deleted file mode 100644 index 953c4731..00000000 --- a/examples/library/ui_look_parts/Look.xml +++ /dev/null @@ -1,10 +0,0 @@ -
- -

Hi

-
-
-
-
-
-

rest here

-
diff --git a/examples/library/ui_look_parts/Menu.lss b/examples/library/ui_look_parts/Menu.lss deleted file mode 100644 index c391c723..00000000 --- a/examples/library/ui_look_parts/Menu.lss +++ /dev/null @@ -1,4 +0,0 @@ -.page { width: 400px; height: 300px } -.cell { position: absolute; left: 20px; top: 50px; width: 60px; height: 30px } -.edge { position: absolute; left: 350px; top: 280px; width: 50px; height: 30px } -.verbs { width: 100px; margin-left: 4px; margin-right: 4px } diff --git a/examples/library/ui_look_parts/Menu.ludic b/examples/library/ui_look_parts/Menu.ludic deleted file mode 100644 index 040af0e7..00000000 --- a/examples/library/ui_look_parts/Menu.ludic +++ /dev/null @@ -1,5 +0,0 @@ -# Menu.ludic - popovers beside what they belong to: one by the anchor's id, one beside the element -# before it, which has no room on its right and opens to the left -component Menu { - state n: int = 0 -} diff --git a/examples/library/ui_look_parts/Menu.xml b/examples/library/ui_look_parts/Menu.xml deleted file mode 100644 index f1d58856..00000000 --- a/examples/library/ui_look_parts/Menu.xml +++ /dev/null @@ -1,6 +0,0 @@ -
- -
- -
-
diff --git a/examples/library/ui_look_parts/Middle.lss b/examples/library/ui_look_parts/Middle.lss deleted file mode 100644 index 25c4e0e7..00000000 --- a/examples/library/ui_look_parts/Middle.lss +++ /dev/null @@ -1 +0,0 @@ -.i { padding: 3px; color: #ff0000 } diff --git a/examples/library/ui_look_parts/Middle.ludic b/examples/library/ui_look_parts/Middle.ludic deleted file mode 100644 index f849c893..00000000 --- a/examples/library/ui_look_parts/Middle.ludic +++ /dev/null @@ -1,4 +0,0 @@ -# Middle.ludic - a component whose root is another component -component Middle { - state n: int = 0 -} diff --git a/examples/library/ui_look_parts/Middle.xml b/examples/library/ui_look_parts/Middle.xml deleted file mode 100644 index eef6d672..00000000 --- a/examples/library/ui_look_parts/Middle.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/library/ui_mounted.ludic b/examples/library/ui_mounted.ludic deleted file mode 100644 index 083468b4..00000000 --- a/examples/library/ui_mounted.ludic +++ /dev/null @@ -1,18 +0,0 @@ -# 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/examples/library/ui_nine.ludic b/examples/library/ui_nine.ludic deleted file mode 100644 index ca2efdbd..00000000 --- a/examples/library/ui_nine.ludic +++ /dev/null @@ -1,19 +0,0 @@ -# ui_nine.ludic — a nine-slice's cuts: its corners are at most half the box across and half of it -# down, each direction on its own, so a key cap shorter than two corners keeps them across; the cuts -# land on whole pixels so the pieces meet. -import "ludic.ui" -program UiNine { - numbers float - function cuts(x: float, y: float, w: float, h: float, dst: float) -> string { - let c = ui_nine_cuts(x, y, w, h, dst) - var s = "" - for i in 0 .. 8 { - if i == 4 { s = s + "/" } - s = s + ` {int(c[i])}` - } - return s - } - entry { - print(`cap{cuts(0.0, 0.0, 30.0, 16.0, 12.0)} | wide{cuts(0.0, 0.0, 100.0, 40.0, 12.0)} | odd{cuts(10.4, 3.6, 20.3, 9.0, 14.0)}`) - } -} diff --git a/examples/library/ui_override.ludic b/examples/library/ui_override.ludic deleted file mode 100644 index 867341e2..00000000 --- a/examples/library/ui_override.ludic +++ /dev/null @@ -1,36 +0,0 @@ -# ui_override.ludic - a template's text given from outside in place of its file (a studio editing a -# running game's interface): the component is shown again with the new template and its state kept, -# and letting go brings the file back, the state still kept. Prints three lines: the counters as the -# files have them, as the override has them, and as the files have them again. -import "ludic.ui" -import "ui_component_parts/Counter.ludic" -import "ui_component_parts/App.ludic" -program UiOverride { - 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(ui_st: mut UiState) -> UiNode { - let root: UiNode = ui_nodes(ui_st, "App", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root - } - function said(ui_st: mut UiState) -> string { - let out = new []string - texts(frame(ui_st), out) - var joined = "" - for i in 0 .. len(out) { joined = joined + out[i] + "|" } - return joined - } - entry (ui_st: mut UiState) { - let root = frame(ui_st) - ui_press(ui_st, root.children[0].children[1].children[1]) # counter a: + once - print(said(ui_st)) - let xml = "examples/library/ui_component_parts/Counter.xml" - ui_override(ui_st, xml, "

[{label}={count}]

") - print(`{said(ui_st)} {ui_overridden(ui_st, xml)}`) - ui_override_clear(ui_st, "") - print(`{said(ui_st)} {ui_overridden(ui_st, xml)}`) - } -} diff --git a/examples/library/ui_override_up.ludic b/examples/library/ui_override_up.ludic deleted file mode 100644 index 0a5c5824..00000000 --- a/examples/library/ui_override_up.ludic +++ /dev/null @@ -1,26 +0,0 @@ -# ui_override_up.ludic - the override through a component registered under a path with `..` in it, as a -# game's lab build registers every component (lab/../src/ui/...): the studio names the file as the game's -# root has it, and it still matches. Prints `counters|[a=0]|+|[b=0]|+|outside| 1 1` - the override shown, -# and the same file found by its plain path and by a ./ path. -import "ludic.ui" -import "../library/ui_component_parts/Counter.ludic" -import "../library/ui_component_parts/App.ludic" -program UiOverrideUp { - 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) } - } - entry (ui_st: mut UiState) { - ui_override(ui_st, "examples/library/ui_component_parts/Counter.xml", "

[{label}={count}]

") - let root: UiNode = ui_nodes(ui_st, "App", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - let out = new []string - texts(root, out) - var joined = "" - for i in 0 .. len(out) { joined = joined + out[i] + "|" } - let plain = ui_overridden(ui_st, "examples/library/ui_component_parts/Counter.xml") - let dotted = ui_overridden(ui_st, "./examples/library/../library/ui_component_parts/Counter.xml") - print(`{joined} {plain} {dotted}`) - } -} diff --git a/examples/library/ui_pointer.ludic b/examples/library/ui_pointer.ludic deleted file mode 100644 index 3a6e615d..00000000 --- a/examples/library/ui_pointer.ludic +++ /dev/null @@ -1,62 +0,0 @@ -# ui_pointer.ludic — pointer events: a native map takes presses, drags (still its own when the -# pointer leaves it, until let go), the wheel and the release, but not a press on the button over it; -# a pad takes the same in markup; a hold button says on-down and on-up, and is :active while held. -import "ludic.ui" -import "ui_pointer_parts/Chart.ludic" -program UiPointer { - numbers float - state UiPointerState { - log: string = "" - dragged: float = 0.0 - } - function map_measure(n: UiNode, avail: float) -> void { } - function map_draw(n: UiNode) -> void { } - function map_input(ui_pointer_st: mut UiPointerState, n: UiNode, e: UiPointer) -> bool { - if e.kind == "drag" { ui_pointer_st.dragged = ui_pointer_st.dragged + e.dx } else if e.kind != "pointermove" { ui_pointer_st.log = ui_pointer_st.log + `{e.kind} {int(e.x)},{int(e.y)} {e.button} {int(e.wheel)}; ` } - return true - } - function frame(ui_st: mut UiState, x: float, y: float, down: bool, wheel: float, enter: bool) -> UiNode { - let i = new UiInput - i.x = x - i.y = y - i.down = down - i.wheel = wheel - i.held_enter = enter - ui_input(ui_st, i) - ui_show(ui_st, "Chart", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "Chart", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root.children[0] - } - function said(page: UiNode) -> string { return page.children[len(page.children) - 1].text } - entry (ui_pointer_st: UiPointerState, ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - ui_native(ui_st, "chartmap", fn map_measure, fn map_draw) - ui_native_input(ui_st, "chartmap", fn map_input) - let page = frame(ui_st, -1.0, -1.0, false, 0.0, false) - let box = page.children[0] - let pad = page.children[1] - let hold = page.children[2] - frame(ui_st, box.x + 20.0, box.y + 20.0, true, 0.0, false) - frame(ui_st, box.x + 20.0, box.y + 20.0, false, 0.0, false) - frame(ui_st, box.x + 100.0, box.y + 80.0, true, 0.0, false) - frame(ui_st, box.x + 130.0, box.y + 60.0, true, 0.0, false) - frame(ui_st, box.x + 300.0, box.y + 300.0, true, 0.0, false) - frame(ui_st, box.x + 300.0, box.y + 300.0, false, 0.0, false) - frame(ui_st, box.x + 50.0, box.y + 50.0, false, 1.0, false) - frame(ui_st, pad.x + 7.0, pad.y + 9.0, true, 0.0, false) - frame(ui_st, pad.x + 17.0, pad.y + 9.0, true, 0.0, false) - frame(ui_st, pad.x + 17.0, pad.y + 9.0, false, 0.0, false) - frame(ui_st, pad.x + 17.0, pad.y + 9.0, false, 2.0, false) - frame(ui_st, hold.x + 5.0, hold.y + 5.0, true, 0.0, false) - let down = frame(ui_st, hold.x + 5.0, hold.y + 5.0, true, 0.0, false) - let active = down.children[2].bg - let mid = said(down) - frame(ui_st, hold.x + 5.0, hold.y + 90.0, false, 0.0, false) - let off = said(frame(ui_st, -1.0, -1.0, false, 0.0, false)) - frame(ui_st, -1.0, -1.0, false, 0.0, true) - let keyed = said(frame(ui_st, -1.0, -1.0, false, 0.0, true)) - let up = frame(ui_st, -1.0, -1.0, false, 0.0, false) - print(`{ui_pointer_st.log}dragged {int(ui_pointer_st.dragged)} | {mid} active {active} | {off} | {keyed} | {said(up)} {up.children[2].bg}`) - } -} diff --git a/examples/library/ui_pointer_parts/Chart.lss b/examples/library/ui_pointer_parts/Chart.lss deleted file mode 100644 index 12ead9db..00000000 --- a/examples/library/ui_pointer_parts/Chart.lss +++ /dev/null @@ -1,7 +0,0 @@ -.page { width: 400px; gap: 4px } -.box { width: 200px; height: 100px; position: relative } -.map { width: 200px; height: 100px } -.zoom { position: absolute; top: 10px; left: 10px; width: 30px; height: 30px; padding: 0 } -.pad { width: 100px; height: 50px } -.hold { width: 80px; height: 30px } -.hold:active { background: #ff0000 } diff --git a/examples/library/ui_pointer_parts/Chart.ludic b/examples/library/ui_pointer_parts/Chart.ludic deleted file mode 100644 index e2717401..00000000 --- a/examples/library/ui_pointer_parts/Chart.ludic +++ /dev/null @@ -1,12 +0,0 @@ -# Chart.ludic - a native map with a zoom button over it, a pad that takes pointer events in markup, -# and a hold button that says when it goes down and comes up -component Chart { - state zoomed: int = 0 - state px: float = -1.0 - state dragx: float = 0.0 - state ups: int = 0 - state wh: float = 0.0 - state holding: bool = false - state held: int = 0 - state clicked: int = 0 -} diff --git a/examples/library/ui_pointer_parts/Chart.xml b/examples/library/ui_pointer_parts/Chart.xml deleted file mode 100644 index ba64f3d7..00000000 --- a/examples/library/ui_pointer_parts/Chart.xml +++ /dev/null @@ -1,9 +0,0 @@ -
-
- - -
-
- -

zoomed {zoomed} pad {px} {dragx} {ups} {wh} hold {holding} {held} {clicked}

-
diff --git a/examples/library/ui_pointer_through.ludic b/examples/library/ui_pointer_through.ludic deleted file mode 100644 index a2c5e69e..00000000 --- a/examples/library/ui_pointer_through.ludic +++ /dev/null @@ -1,55 +0,0 @@ -# ui_pointer_through.ludic - a screen whose root
holds only a positioned panel (a component's -# root round a modal) has no size of its own; the pointer looks through it, so the panel's button is -# pressed and hovered - once nothing under it was. A select's < and > answer :hover as elements do. -import "ludic.ui" -program UiPointerThrough { - numbers float - state ThroughState { - got: int = 0 - v: int = 1 - } - view V (through_st: mut ThroughState) { - v = through_st.v - on hit() { through_st.got += 1 } - on set(x: int) { through_st.v = x } - } - const PAGE: string = "
" - function frame(ui_st: mut UiState, x: float, y: float, down: bool) -> UiNode { - let i = new UiInput - i.x = x - i.y = y - i.down = down - ui_input(ui_st, i) - ui_show(ui_st, "s", view_v(), 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "s", view_v()) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root - } - function find(n: UiNode, tag: string, cls: string) -> UiNode { - var has = cls == "" - for i in 0 .. len(n.classes) { if n.classes[i] == cls { has = true } } - if n.tag == tag and has { return n } - for i in 0 .. len(n.children) { - let f = find(n.children[i], tag, cls) - if f != null { return f } - } - return null - } - entry (ui_st: mut UiState, through_st: ThroughState) { - ui_backend(ui_st, new UiBackend) - ui_load_text(ui_st, PAGE, "s.xml") - var root = frame(ui_st, -1.0, -1.0, false) - let b = find(root, "button", "") - let x = b.x + b.cw / 2.0 - let y = b.y + b.ch / 2.0 - frame(ui_st, x, y, false) - root = frame(ui_st, x, y, false) - let hover = ui_hex(find(root, "button", "").bg) - frame(ui_st, x, y, true) - frame(ui_st, x, y, false) - let nx = find(root, "span", "ui-next") - frame(ui_st, nx.x + nx.cw / 2.0, nx.y + nx.ch / 2.0, false) - root = frame(ui_st, nx.x + nx.cw / 2.0, nx.y + nx.ch / 2.0, false) - print(`pressed {through_st.got}, hovered {hover}; > hovered {find(root, "span", "ui-next").hovered}`) - } -} diff --git a/examples/library/ui_popover.ludic b/examples/library/ui_popover.ludic deleted file mode 100644 index 4aafafdd..00000000 --- a/examples/library/ui_popover.ludic +++ /dev/null @@ -1,59 +0,0 @@ -# ui_popover.ludic — HTML's popover and progress in ludic.ui: a button opens a popover of verbs, the -# keyboard stays inside it, a press outside or Esc closes it, and a progress bar fills to its value. -import "ludic.ui" -import "ui_popover_parts/Verbs.ludic" -program UiPopover { - numbers float - function frame(ui_st: mut UiState, i: UiInput) -> UiNode { - ui_input(ui_st, i) - ui_show(ui_st, "Verbs", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "Verbs", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root - } - function click(ui_st: mut UiState, x: float, y: float) -> void { - let d = new UiInput - d.x = x - d.y = y - d.down = true - frame(ui_st, d) - let u = new UiInput - u.x = x - u.y = y - frame(ui_st, u) - } - function said(root: UiNode) -> string { - let page = root.children[0] - return page.children[len(page.children) - 1].text - } - entry (ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - var root = frame(ui_st, new UiInput) - let cell = root.children[0].children[0] - click(ui_st, cell.x + 2.0, cell.y + 2.0) - root = frame(ui_st, new UiInput) - let tab = new UiInput - tab.tab = true - frame(ui_st, tab) - frame(ui_st, tab) - root = frame(ui_st, tab) - let enter = new UiInput - enter.enter = true - root = frame(ui_st, enter) - root = frame(ui_st, new UiInput) - let first = said(root) - click(ui_st, cell.x + 2.0, cell.y + 2.0) - root = frame(ui_st, new UiInput) - let opened = said(root) - click(ui_st, 390.0, 390.0) - root = frame(ui_st, new UiInput) - let outside = said(root) - click(ui_st, cell.x + 2.0, cell.y + 2.0) - let esc = new UiInput - esc.escape = true - frame(ui_st, esc) - root = frame(ui_st, new UiInput) - let bar = root.children[0].children[1] - print(`{first} | {opened} | {outside} | {said(root)} | fill {int(bar.children[0].cw)} of {int(bar.cw)}`) - } -} diff --git a/examples/library/ui_popover_align.ludic b/examples/library/ui_popover_align.ludic deleted file mode 100644 index 45b8c6c2..00000000 --- a/examples/library/ui_popover_align.ludic +++ /dev/null @@ -1,20 +0,0 @@ -# ui_popover_align.ludic — anchored popovers aligned along their side (start, center, end), kept -# within a named container (within="panel") and, by default, within the scroll box they are in, -# flipping to the other side against it rather than against the screen. -import "ludic.ui" -import "ui_popover_align_parts/Menus.ludic" -program UiPopoverAlign { - numbers float - function at(n: UiNode, base: UiNode) -> string { return `{int(n.x - base.x)},{int(n.y - base.y)}` } - entry (ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - ui_show(ui_st, "Menus", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "Menus", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - let panel = root.children[0].children[0] - let list = root.children[0].children[1] - let b = list.children[1] - let p3 = list.children[2] - print(`centred, flipped in the panel {at(panel.children[1], panel)} | end below {at(panel.children[2], panel)} | above b in the list {int(p3.y + p3.ch - b.y)} inside {p3.y >= list.y}`) - } -} diff --git a/examples/library/ui_popover_align_parts/Menus.lss b/examples/library/ui_popover_align_parts/Menus.lss deleted file mode 100644 index 136750a7..00000000 --- a/examples/library/ui_popover_align_parts/Menus.lss +++ /dev/null @@ -1,8 +0,0 @@ -.page { width: 400px; height: 400px } -.panel { width: 200px; height: 150px; position: relative } -.a { position: absolute; left: 150px; top: 50px; width: 40px; height: 20px; padding: 0 } -.p1 { width: 60px; height: 40px } -.p2 { width: 80px; height: 30px } -.list { overflow: auto; width: 150px; height: 100px } -.gap { height: 60px } -.p3 { width: 50px; height: 40px } diff --git a/examples/library/ui_popover_align_parts/Menus.ludic b/examples/library/ui_popover_align_parts/Menus.ludic deleted file mode 100644 index 3269819a..00000000 --- a/examples/library/ui_popover_align_parts/Menus.ludic +++ /dev/null @@ -1,3 +0,0 @@ -# Menus.ludic - popovers anchored inside a panel and inside a scroll box, aligned along their side -component Menus { -} diff --git a/examples/library/ui_popover_align_parts/Menus.xml b/examples/library/ui_popover_align_parts/Menus.xml deleted file mode 100644 index bb735bfb..00000000 --- a/examples/library/ui_popover_align_parts/Menus.xml +++ /dev/null @@ -1,12 +0,0 @@ -
-
- -
-
-
-
-

x

- -
-
-
diff --git a/examples/library/ui_popover_mouse.ludic b/examples/library/ui_popover_mouse.ludic deleted file mode 100644 index 921a0864..00000000 --- a/examples/library/ui_popover_mouse.ludic +++ /dev/null @@ -1,52 +0,0 @@ -# ui_popover_mouse.ludic — a popover takes the mouse: its own button, at the same pixels as the grid -# cell under it, is the one a click presses; a click outside it (on that cell, or on nothing) closes -# it and presses nothing else. -import "ludic.ui" -import "ui_popover_mouse_parts/Grid.ludic" -program UiPopoverMouse { - numbers float - function frame(ui_st: mut UiState, i: UiInput) -> UiNode { - ui_input(ui_st, i) - ui_show(ui_st, "Grid", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "Grid", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root - } - function click(ui_st: mut UiState, x: float, y: float) -> string { - let d = new UiInput - d.x = x - d.y = y - d.down = true - frame(ui_st, d) - let u = new UiInput - u.x = x - u.y = y - frame(ui_st, u) - let root = frame(ui_st, new UiInput) - let page = root.children[0] - return page.children[len(page.children) - 1].text - } - function find(n: UiNode, id: string) -> UiNode { - if n.id == id { return n } - for i in 0 .. len(n.children) { - let f = find(n.children[i], id) - if f != null { return f } - } - return null - } - entry (ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - frame(ui_st, new UiInput) - let opened = click(ui_st, 20.0, 20.0) - let root = frame(ui_st, new UiInput) - let drop = find(root, "drop") - let cell = find(root, "cell") - let over = drop.x >= cell.x and drop.y >= cell.y and drop.x + drop.cw <= cell.x + cell.cw - let dropped = click(ui_st, drop.x + drop.cw / 2.0, drop.y + drop.ch / 2.0) - click(ui_st, 20.0, 20.0) - let on_cell = click(ui_st, 180.0, 90.0) - click(ui_st, 20.0, 20.0) - let on_nothing = click(ui_st, 390.0, 390.0) - print(`over the cell {over} | {opened} | {dropped} | {on_cell} | {on_nothing}`) - } -} diff --git a/examples/library/ui_popover_mouse_parts/Grid.lss b/examples/library/ui_popover_mouse_parts/Grid.lss deleted file mode 100644 index b80c6af9..00000000 --- a/examples/library/ui_popover_mouse_parts/Grid.lss +++ /dev/null @@ -1,3 +0,0 @@ -.page { width: 300px; height: 300px } -.cell { width: 200px; height: 100px } -.verbs { top: 10px; left: 10px; width: 120px } diff --git a/examples/library/ui_popover_mouse_parts/Grid.ludic b/examples/library/ui_popover_mouse_parts/Grid.ludic deleted file mode 100644 index 67823d5f..00000000 --- a/examples/library/ui_popover_mouse_parts/Grid.ludic +++ /dev/null @@ -1,8 +0,0 @@ -# Grid.ludic - a grid cell with a popover of verbs laid over it, written BEFORE the cell, so the cell -# comes later in the tree and would win any hit test that went by the order things are drawn in -component Grid { - state open: bool = false - state opened: int = 0 - state dropped: int = 0 - state closed: int = 0 -} diff --git a/examples/library/ui_popover_mouse_parts/Grid.xml b/examples/library/ui_popover_mouse_parts/Grid.xml deleted file mode 100644 index e119d580..00000000 --- a/examples/library/ui_popover_mouse_parts/Grid.xml +++ /dev/null @@ -1,9 +0,0 @@ -
- -
- -
-
- -

opened {opened} dropped {dropped} closed {closed} {open}

-
diff --git a/examples/library/ui_popover_parts/Verbs.lss b/examples/library/ui_popover_parts/Verbs.lss deleted file mode 100644 index ae965783..00000000 --- a/examples/library/ui_popover_parts/Verbs.lss +++ /dev/null @@ -1,3 +0,0 @@ -.page { width: 300px; height: 200px; gap: 4px } -.verbs { top: 40px; left: 10px; width: 120px } -progress { width: 200px } diff --git a/examples/library/ui_popover_parts/Verbs.ludic b/examples/library/ui_popover_parts/Verbs.ludic deleted file mode 100644 index 090f1929..00000000 --- a/examples/library/ui_popover_parts/Verbs.ludic +++ /dev/null @@ -1,6 +0,0 @@ -# Verbs.ludic - a grid cell that opens a popover of verbs -component Verbs { - state open: bool = false - state used: string = "none" - state health: int = 30 -} diff --git a/examples/library/ui_popover_parts/Verbs.xml b/examples/library/ui_popover_parts/Verbs.xml deleted file mode 100644 index 34ec8bb4..00000000 --- a/examples/library/ui_popover_parts/Verbs.xml +++ /dev/null @@ -1,11 +0,0 @@ -
- - -
- - -
-
- -

{used} {open}

-
diff --git a/examples/library/ui_react.ludic b/examples/library/ui_react.ludic deleted file mode 100644 index 09edaf48..00000000 --- a/examples/library/ui_react.ludic +++ /dev/null @@ -1,61 +0,0 @@ -# ui_react.ludic — ludic.ui's React side without a renderer: keyed lists keep each item's state when -# the list is reordered, names a value, context reaches through a component, named slots, -# on-mount and on-unmount, and a native element of the program's own firing on-change with a value. -import "ludic.ui" -program UiReact { - numbers float - state UiReactState { - names: []string = new []string - volume: int = 3 - mounted: int = 0 - unmounted: int = 0 - show_extra: bool = true - } - view Mixer (ui_react_st: mut UiReactState) { - names = ui_react_st.names - volume = ui_react_st.volume - extra = ui_react_st.show_extra - on set_volume(v: int) { ui_react_st.volume = v } - on mount() { ui_react_st.mounted += 1 } - on unmount() { ui_react_st.unmounted += 1 } - } - # a dial the program draws itself: it measures 40 square and, pressed, turns up by one - function dial_measure(n: UiNode, avail: float) -> void { - n.mw = 40.0 - n.mh = 40.0 - } - function dial_draw(n: UiNode) -> void { } - const PAGE: string = "\n\n

{theme}

\n
\n\n \n \n\n\n \n \n \n \n

{loud ? 'loud' : 'quiet'}

\n
\n
\n \n \n \n

extra

\n \n

checkbox {on}

\n
\n
\n" - 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(ui_st: mut UiState) -> UiNode { - ui_show(ui_st, "mix", view_mixer(), 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "mix", view_mixer()) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root - } - entry (ui_react_st: mut UiReactState, ui_st: mut UiState) { - ui_native(ui_st, "dial", fn dial_measure, fn dial_draw) - push(ui_react_st.names, "bass") - push(ui_react_st.names, "drums") - ui_load_text(ui_st, PAGE, "ui/mix.xml") - var root = frame(ui_st) - ui_press(ui_st, root.children[2]) - ui_react_st.names[0] = "drums" - ui_react_st.names[1] = "bass" - root = frame(ui_st) - let dial = root.children[3] - ui_event(ui_st, dial, "change", Value.int(ui_react_st.volume + 4)) - ui_event(ui_st, root.children[4], "change", Value.bool(1)) - ui_react_st.show_extra = false - root = frame(ui_st) - let out = new []string - texts(root, out) - var joined = "" - for i in 0 .. len(out) { joined = joined + out[i] + "|" } - print(joined) - print(`volume {ui_react_st.volume}; dial {int(dial.cw)}x{int(dial.ch)}; mounted {ui_react_st.mounted}, unmounted {ui_react_st.unmounted}`) - } -} diff --git a/examples/library/ui_reload.ludic b/examples/library/ui_reload.ludic deleted file mode 100644 index 9084d431..00000000 --- a/examples/library/ui_reload.ludic +++ /dev/null @@ -1,29 +0,0 @@ -# ui_reload.ludic — a template read from disk, changed there, and read again by ui_reload: the new -# text shows at once, and the screen's state (a counter pressed before the change) is kept. -import "ludic.ui" -program UiReload { - numbers float - view Note { - title = "note" - } - const PATH: string = "build/ui_reload_example.xml" - const V1: string = "" - const V2: string = "

{title} again {n}

" - function shown(ui_st: mut UiState) -> string { - let root: UiNode = ui_nodes(ui_st, "note", view_note()) - let first = root.children[0] - if len(first.children) > 0 { return first.children[0].text } - return first.text - } - entry (ui_st: mut UiState) { - Fs.write_text(PATH, V1) - ui_load(ui_st, PATH) - let root: UiNode = ui_nodes(ui_st, "note", view_note()) - ui_press(ui_st, root.children[0]) - let before = shown(ui_st) - let same = ui_reload(ui_st) - Fs.write_text(PATH, V2) - let changed = ui_reload(ui_st) - print(`{before} | reloaded {same} then {changed} | {shown(ui_st)}`) - } -} diff --git a/examples/library/ui_remount.ludic b/examples/library/ui_remount.ludic deleted file mode 100644 index ed607165..00000000 --- a/examples/library/ui_remount.ludic +++ /dev/null @@ -1,30 +0,0 @@ -# ui_remount.ludic - a component that comes and goes: mounted again, it starts as new (its state back -# to its defaults), and a thousand comings and goings hold the heap flat, because an unmounted -# instance is put back and mounted again rather than a record, props and model made each time -import "ludic.ui" -import "ui_remount_parts/Counter.ludic" -import "ui_remount_parts/Shown.ludic" -program UiRemount { - numbers float - function frame(ui_st: mut UiState) -> UiNode { - let root: UiNode = ui_nodes(ui_st, "Shown", null) - ui_place(ui_st, root, 0.0, 0.0, 300.0, 200.0) - return root - } - entry (ui_st: mut UiState) { - var root = frame(ui_st) - let c = root.children[0].children[1] - ui_press(ui_st, c.children[1]) - ui_press(ui_st, c.children[1]) - root = frame(ui_st) - let before = root.children[0].children[1].children[0].text - var at = 0 - for i in 0 .. 2000 { - ui_press(ui_st, root.children[0].children[0]) - root = frame(ui_st) - if i == 99 { at = Os.heap_bytes() } - } - let grew = Os.heap_bytes() - at - print(`{before} | {root.children[0].children[1].children[0].text} | heap {grew}`) - } -} diff --git a/examples/library/ui_remount_parts/Counter.lss b/examples/library/ui_remount_parts/Counter.lss deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/library/ui_remount_parts/Counter.ludic b/examples/library/ui_remount_parts/Counter.ludic deleted file mode 100644 index 57112f96..00000000 --- a/examples/library/ui_remount_parts/Counter.ludic +++ /dev/null @@ -1,9 +0,0 @@ -# Counter.ludic - a counter that starts at 0 each time it is mounted -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 } -} diff --git a/examples/library/ui_remount_parts/Counter.xml b/examples/library/ui_remount_parts/Counter.xml deleted file mode 100644 index 617de5aa..00000000 --- a/examples/library/ui_remount_parts/Counter.xml +++ /dev/null @@ -1,5 +0,0 @@ -
-

{label}: {count} ({doubled}){big() ? ' big' : ''}

- - -
diff --git a/examples/library/ui_remount_parts/Shown.ludic b/examples/library/ui_remount_parts/Shown.ludic deleted file mode 100644 index 8d808144..00000000 --- a/examples/library/ui_remount_parts/Shown.ludic +++ /dev/null @@ -1,5 +0,0 @@ -# Shown.ludic - a counter shown or not: each time it comes back it is a new counter -component Shown { - state on: bool = true - on flip() { on = not on } -} diff --git a/examples/library/ui_remount_parts/Shown.xml b/examples/library/ui_remount_parts/Shown.xml deleted file mode 100644 index cb93e733..00000000 --- a/examples/library/ui_remount_parts/Shown.xml +++ /dev/null @@ -1,4 +0,0 @@ -
- - -
diff --git a/examples/library/ui_scroll_hold.ludic b/examples/library/ui_scroll_hold.ludic deleted file mode 100644 index 70e2811a..00000000 --- a/examples/library/ui_scroll_hold.ludic +++ /dev/null @@ -1,32 +0,0 @@ -# ui_scroll_hold.ludic — scroll-top holds a box at an offset: one follows its state, which on-scroll -# keeps in step with the wheel; one stays where it is held; and ui_scroll_set moves another once. -import "ludic.ui" -import "ui_scroll_hold_parts/Held.ludic" -program UiScrollHold { - numbers float - function frame(ui_st: mut UiState, x: float, y: float, wheel: float) -> UiNode { - let i = new UiInput - i.x = x - i.y = y - i.wheel = wheel - ui_input(ui_st, i) - ui_show(ui_st, "Held", null, 0.0, 0.0, 400.0, 600.0) - let root: UiNode = ui_nodes(ui_st, "Held", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0) - return root.children[0] - } - function at(p: UiNode) -> string { return `{int(p.children[0].scroll_y)} {int(p.children[1].scroll_y)} {int(p.children[2].scroll_y)} {p.children[3].text}` } - entry (ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - let p = frame(ui_st, -1.0, -1.0, 0.0) - let first = at(frame(ui_st, -1.0, -1.0, 0.0)) - frame(ui_st, p.children[0].x + 5.0, p.children[0].y + 5.0, 1.0) - let followed = at(frame(ui_st, -1.0, -1.0, 0.0)) - frame(ui_st, p.children[1].x + 5.0, p.children[1].y + 5.0, -1.0) - let fixed = at(frame(ui_st, -1.0, -1.0, 0.0)) - ui_scroll_set(ui_st, "free", 70.0) - let set = at(frame(ui_st, -1.0, -1.0, 0.0)) - frame(ui_st, p.children[2].x + 5.0, p.children[2].y + 5.0, 1.0) - print(`{first} | {followed} | {fixed} | {set} | {at(frame(ui_st, -1.0, -1.0, 0.0))}`) - } -} diff --git a/examples/library/ui_scroll_hold_parts/Held.lss b/examples/library/ui_scroll_hold_parts/Held.lss deleted file mode 100644 index 3d39fcdf..00000000 --- a/examples/library/ui_scroll_hold_parts/Held.lss +++ /dev/null @@ -1,3 +0,0 @@ -.page { width: 400px; gap: 4px } -.list { overflow: auto; width: 200px; height: 100px } -p { height: 40px } diff --git a/examples/library/ui_scroll_hold_parts/Held.ludic b/examples/library/ui_scroll_hold_parts/Held.ludic deleted file mode 100644 index a3c2c779..00000000 --- a/examples/library/ui_scroll_hold_parts/Held.ludic +++ /dev/null @@ -1,5 +0,0 @@ -# Held.ludic - three scroll boxes: one held at its state's offset and told when it moves, one held -# at a fixed offset, and one the program moves once -component Held { - state top: float = 120.0 -} diff --git a/examples/library/ui_scroll_hold_parts/Held.xml b/examples/library/ui_scroll_hold_parts/Held.xml deleted file mode 100644 index 94d975cb..00000000 --- a/examples/library/ui_scroll_hold_parts/Held.xml +++ /dev/null @@ -1,6 +0,0 @@ -
-

{r}

-

{r}

-

{r}

-

top {top}

-
diff --git a/examples/library/ui_scrollbar.ludic b/examples/library/ui_scrollbar.ludic deleted file mode 100644 index e7756500..00000000 --- a/examples/library/ui_scrollbar.ludic +++ /dev/null @@ -1,54 +0,0 @@ -# ui_scrollbar.ludic — a scroll box's bar held and dragged: a press on the thumb holds it where it -# was taken, the pointer held moves it in proportion, letting go leaves it; a press on the track -# jumps the thumb's middle there; the wheel still scrolls; and the rows under the bar are never pressed. -import "ludic.ui" -import "ui_scrollbar_parts/Long.ludic" -program UiScrollbar { - numbers float - state UiScrollbarState { - bx: float = 0.0 - by: float = 0.0 - } - function frame(ui_st: mut UiState, i: UiInput) -> UiNode { - ui_input(ui_st, i) - ui_show(ui_st, "Long", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "Long", null) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root.children[0] - } - # the pointer at (bx + x, by + y), held or not; how far the box is scrolled then - function at(ui_scrollbar_st: UiScrollbarState, ui_st: mut UiState, x: float, y: float, down: bool) -> int { - let i = new UiInput - i.x = ui_scrollbar_st.bx + x - i.y = ui_scrollbar_st.by + y - i.down = down - return int(frame(ui_st, i).children[0].scroll_y) - } - function wheel(ui_scrollbar_st: UiScrollbarState, ui_st: mut UiState, x: float, y: float) -> int { - let i = new UiInput - i.x = ui_scrollbar_st.bx + x - i.y = ui_scrollbar_st.by + y - i.wheel = 1.0 - frame(ui_st, i) - return int(frame(ui_st, new UiInput).children[0].scroll_y) - } - entry (ui_scrollbar_st: mut UiScrollbarState, ui_st: mut UiState) { - ui_backend(ui_st, new UiBackend) - let list = frame(ui_st, new UiInput).children[0] - ui_scrollbar_st.bx = list.x - ui_scrollbar_st.by = list.y - let grab = at(ui_scrollbar_st, ui_st, 195.0, 10.0, true) - let moved = at(ui_scrollbar_st, ui_st, 195.0, 40.0, true) - let far = at(ui_scrollbar_st, ui_st, 195.0, 300.0, true) - let back = at(ui_scrollbar_st, ui_st, 195.0, 25.0, true) - let kept = at(ui_scrollbar_st, ui_st, 195.0, 25.0, false) - at(ui_scrollbar_st, ui_st, 0.0 - ui_scrollbar_st.bx - 1.0, 0.0 - ui_scrollbar_st.by - 1.0, false) - let jump = at(ui_scrollbar_st, ui_st, 195.0, 80.0, true) - at(ui_scrollbar_st, ui_st, 195.0, 80.0, false) - let wheeled = wheel(ui_scrollbar_st, ui_st, 50.0, 50.0) - at(ui_scrollbar_st, ui_st, 50.0, 20.0, true) - at(ui_scrollbar_st, ui_st, 50.0, 20.0, false) - let page = frame(ui_st, new UiInput) - print(`grab {grab} moved {moved} far {far} back {back} kept {kept} | jump {jump} | wheel {wheeled} | {page.children[1].text}`) - } -} diff --git a/examples/library/ui_scrollbar_parts/Long.lss b/examples/library/ui_scrollbar_parts/Long.lss deleted file mode 100644 index 5cee44f2..00000000 --- a/examples/library/ui_scrollbar_parts/Long.lss +++ /dev/null @@ -1,3 +0,0 @@ -.page { width: 300px; height: 300px } -.list { overflow: auto; width: 200px; height: 100px } -.row { width: fill; height: 40px; padding: 0; border-radius: 0 } diff --git a/examples/library/ui_scrollbar_parts/Long.ludic b/examples/library/ui_scrollbar_parts/Long.ludic deleted file mode 100644 index bbdf2132..00000000 --- a/examples/library/ui_scrollbar_parts/Long.ludic +++ /dev/null @@ -1,5 +0,0 @@ -# Long.ludic - a box of ten rows, four times taller than it shows; each row is a button as wide as -# the box, so the scrollbar sits over them -component Long { - state pressed: int = 0 -} diff --git a/examples/library/ui_scrollbar_parts/Long.xml b/examples/library/ui_scrollbar_parts/Long.xml deleted file mode 100644 index 08daf0ea..00000000 --- a/examples/library/ui_scrollbar_parts/Long.xml +++ /dev/null @@ -1,4 +0,0 @@ -
-
-

pressed {pressed}

-
diff --git a/examples/library/ui_select_arrows.ludic b/examples/library/ui_select_arrows.ludic deleted file mode 100644 index 20352a2a..00000000 --- a/examples/library/ui_select_arrows.ludic +++ /dev/null @@ -1,51 +0,0 @@ -# ui_select_arrows.ludic - a select is a cycler: a press let go on its < steps back and on its > -# (or anywhere else on it, or Enter) forward. The < once stepped forward too. -import "ludic.ui" -program UiSelectArrows { - numbers float - state SelState { v: int = 1 } - view V (sel_st: mut SelState) { - v = sel_st.v - on set(x: int) { sel_st.v = x } - } - const PAGE: string = "" - function frame(ui_st: mut UiState, x: float, y: float, down: bool) -> UiNode { - let i = new UiInput - i.x = x - i.y = y - i.down = down - ui_input(ui_st, i) - ui_show(ui_st, "s", view_v(), 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes(ui_st, "s", view_v()) - ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) - return root - } - function part(n: UiNode, cls: string) -> UiNode { - for i in 0 .. len(n.children) { - if List.contains(n.children[i].classes, cls) { return n.children[i] } - let f = part(n.children[i], cls) - if f != null { return f } - } - return null - } - function click(ui_st: mut UiState, cls: string) -> void { - let root = frame(ui_st, -1.0, -1.0, false) - let p = part(root, cls) - let x = p.x + p.cw / 2.0 - let y = p.y + p.ch / 2.0 - frame(ui_st, x, y, false) - frame(ui_st, x, y, true) - frame(ui_st, x, y, false) - frame(ui_st, x, y, false) - } - entry (ui_st: mut UiState, sel_st: SelState) { - ui_backend(ui_st, new UiBackend) - ui_load_text(ui_st, PAGE, "s.xml") - frame(ui_st, -1.0, -1.0, false) - click(ui_st, "ui-prev") - let a = sel_st.v - click(ui_st, "ui-next") - click(ui_st, "ui-next") - print(`prev {a} next next {sel_st.v}`) - } -} diff --git a/examples/library/ui_select_disabled.ludic b/examples/library/ui_select_disabled.ludic deleted file mode 100644 index 2adba55d..00000000 --- a/examples/library/ui_select_disabled.ludic +++ /dev/null @@ -1,55 +0,0 @@ -# ui_select_disabled.ludic -