diff --git a/examples/events/answer.ludic b/examples/events/answer.ludic
index 255ccb3b..ef5597e3 100644
--- a/examples/events/answer.ludic
+++ b/examples/events/answer.ludic
@@ -5,20 +5,22 @@
# Running it prints: 10 20 0
program Answer {
event cancellable Ask { kind: int }
- var answer: int = 0
+ state AnswerState {
+ answer: int = 0
+ }
- @On(Ask) handler One { if kind != 1 { return }; answer = 10; cancel }
- @On(Ask) handler Two { if kind != 2 { return }; answer = 20; cancel }
+ @On(Ask) handler One(answer_st: mut AnswerState) { if kind != 1 { return }; answer_st.answer = 10; cancel }
+ @On(Ask) handler Two(answer_st: mut AnswerState) { if kind != 2 { return }; answer_st.answer = 20; cancel }
- function ask(k: int) -> int {
- answer = 0
- if emit Ask(kind: k) != 0 { return answer }
+ function ask(answer_st: mut AnswerState, k: int) -> int {
+ answer_st.answer = 0
+ if emit Ask(kind: k) != 0 { return answer_st.answer }
return 0
}
- entry {
- print(ask(1))
- print(ask(2))
- print(ask(3))
+ entry (answer_st: mut AnswerState) {
+ print(ask(answer_st, 1))
+ print(ask(answer_st, 2))
+ print(ask(answer_st, 3))
}
}
diff --git a/examples/events/layer_events.ludic b/examples/events/layer_events.ludic
index ab99630c..ac6c12d0 100644
--- a/examples/events/layer_events.ludic
+++ b/examples/events/layer_events.ludic
@@ -6,7 +6,9 @@
#
# Running it prints: 50 2 1 50
program LayerEvents {
- var step: int = 0
+ state LayereventsState {
+ step: int = 0
+ }
@On(layer_Hud_show) handler Shown { print(1) } # a mod reacts when the HUD returns
@On(layer_Hud_hide) handler Hidden { print(2) } # ...and when it's hidden
@@ -16,11 +18,11 @@ program LayerEvents {
handler Draw phase Update { print(50) } # only runs while Hud is enabled
}
layer Ctrl {
- handler Drive phase LateUpdate {
- step += 1
- if step == 1 { disable layer Hud } # frame 1: hide -> Hidden 2, Draw stops
- if step == 2 { enable layer Hud } # frame 2: show -> Shown 1, Draw resumes
- if step == 3 { quit() } # frame 3: after Draw ran again
+ handler Drive(layerevents_st: mut LayereventsState) phase LateUpdate {
+ layerevents_st.step += 1
+ if layerevents_st.step == 1 { disable layer Hud } # frame 1: hide -> Hidden 2, Draw stops
+ if layerevents_st.step == 2 { enable layer Hud } # frame 2: show -> Shown 1, Draw resumes
+ if layerevents_st.step == 3 { quit() } # frame 3: after Draw ran again
}
}
}
diff --git a/examples/events/mod_events.ludic b/examples/events/mod_events.ludic
index f1b3dc23..1cf5d3da 100644
--- a/examples/events/mod_events.ludic
+++ b/examples/events/mod_events.ludic
@@ -3,14 +3,16 @@
# `Damage` has two @On listeners: one prints the amount, one accumulates a total.
# Emitting it twice runs both listeners each time. Prints 10 / 32 / 42.
program ModEvents {
- var total: int = 0
+ state ModeventsState {
+ total: int = 0
+ }
event Damage { amount: int = 0 }
@On(Damage) handler Native { print(amount) } # prints each hit
- @On(Damage) handler Accum { total += amount } # sums them
+ @On(Damage) handler Accum(modevents_st: mut ModeventsState) { modevents_st.total += amount } # sums them
- entry {
+ entry (modevents_st: ModeventsState) {
emit Damage(amount: 10) # Native prints 10
emit Damage(amount: 32) # Native prints 32
- print(total) # 42
+ print(modevents_st.total) # 42
}
}
diff --git a/examples/events/recurse.ludic b/examples/events/recurse.ludic
index e1f8be69..fdb4288a 100644
--- a/examples/events/recurse.ludic
+++ b/examples/events/recurse.ludic
@@ -6,16 +6,18 @@
#
# Running it prints: 16 (the cycle bottoms out at the depth cap, deterministically)
program Recurse {
- var n: int = 0
+ state RecurseState {
+ n: int = 0
+ }
event Ping { }
event Pong { }
- @On(Ping) handler A { n += 1; emit Pong() } # each Ping deepens by one Pong
+ @On(Ping) handler A(recurse_st: mut RecurseState) { recurse_st.n += 1; emit Pong() } # each Ping deepens by one Pong
@On(Pong) handler B { emit Ping() } # ...and each Pong by one Ping
- entry {
+ entry (recurse_st: RecurseState) {
emit Ping()
- print(n)
+ print(recurse_st.n)
}
}
diff --git a/examples/events/scoped.ludic b/examples/events/scoped.ludic
index 6d54bfe5..559a9a3a 100644
--- a/examples/events/scoped.ludic
+++ b/examples/events/scoped.ludic
@@ -6,16 +6,18 @@
program Scoped {
property Health { hp: int = 0 }
model Unit { Health }
- var fires: int = 0
+ state ScopedState {
+ fires: int = 0
+ }
event Tick { }
- @On(Tick) handler OnTick { for (Health) in query [Health, {Unit}] { fires += 1 } }
+ @On(Tick) handler OnTick(scoped_st: mut ScopedState) { for (Health) in query [Health, {Unit}] { scoped_st.fires += 1 } }
- entry {
+ entry (scoped_st: ScopedState) {
spawn Unit { Health { hp: 1 } }
emit Tick() # 1 live Unit -> fires = 1
emit Tick() # fires = 2
for (Health) in query [Health, {Unit}] { despawn self() } # kill the Unit
emit Tick() # no live Units -> fires stays 2
- print(fires) # 2
+ print(scoped_st.fires) # 2
}
}
diff --git a/examples/functions/values.ludic b/examples/functions/values.ludic
index 658bbd16..f2e87943 100644
--- a/examples/functions/values.ludic
+++ b/examples/functions/values.ludic
@@ -9,12 +9,14 @@ program FunctionValues {
function double(x: int) -> int { return x * 2 }
function square(x: int) -> int { return x * x }
function half(x: float) -> float { return x / 2.0 }
- var op: fn(int) -> int = null
+ state FunctionvaluesState {
+ op: fn(int) -> int = null
+ }
function run_with(f: fn(int) -> int, v: int) -> int { return f(v) }
function pass_on(f: fn(float) -> float) -> fn(float) -> float { return f }
- entry {
- op = fn double
- print(op(21))
+ entry (functionvalues_st: mut FunctionvaluesState) {
+ functionvalues_st.op = fn double
+ print(functionvalues_st.op(21))
let ops = new []fn(int) -> int
push(ops, fn double)
push(ops, fn square)
@@ -26,6 +28,6 @@ program FunctionValues {
print(run_with(fn square, 9))
let h = pass_on(fn half)
print(h(3.0))
- if op != null { print(1) }
+ if functionvalues_st.op != null { print(1) }
}
}
diff --git a/examples/games/chronorift/combat.ludic b/examples/games/chronorift/combat.ludic
index b5bb0e4e..381c24a5 100644
--- a/examples/games/chronorift/combat.ludic
+++ b/examples/games/chronorift/combat.ludic
@@ -8,43 +8,43 @@
enum KnightAct { Attack, Guard, Item, Flee }
enum MageAct { Attack, Heal, Guard }
-handler Battle phase Update {
- if game_mode == 1 {
+handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update {
+ if games_chronorift_st.game_mode == 1 {
let k = Input.key()
for (e) in query [Stats, {Enemy}] {
- machine turn_phase {
+ machine games_chronorift_st.turn_phase {
state KnightMenu {
for (st, pt) in query [Stats, Party] {
if pt.slot == 0 {
if st.hp <= 0 {
become MageMenu
- menu_cursor = 0
+ games_chronorift_st.menu_cursor = 0
} else {
- if k == 'w' { menu_cursor = max(0, menu_cursor - 1) }
- if k == 's' { menu_cursor = min(3, menu_cursor + 1) }
+ if k == 'w' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) }
+ if k == 's' { games_chronorift_st.menu_cursor = min(3, games_chronorift_st.menu_cursor + 1) }
if is_confirm(k) {
- match menu_cursor {
+ match games_chronorift_st.menu_cursor {
KnightAct.Attack => {
let d = max(1, st.atk - e.def + Random.range(0, 4))
e.hp -= d
- player_hit = d
+ games_chronorift_st.player_hit = d
}
- KnightAct.Guard => { st.guard = 1; player_hit = 0 }
+ KnightAct.Guard => { st.guard = 1; games_chronorift_st.player_hit = 0 }
KnightAct.Item => {
- if potions > 0 {
- potions -= 1
+ if games_chronorift_st.potions > 0 {
+ games_chronorift_st.potions -= 1
st.hp = min(st.maxhp, st.hp + 24)
- player_hit = 24
- } else { player_hit = 0 }
+ games_chronorift_st.player_hit = 24
+ } else { games_chronorift_st.player_hit = 0 }
}
KnightAct.Flee => {
- if Random.chance(50) { e.hp = 0; game_mode = 0 }
- else { player_hit = 0 }
+ if Random.chance(50) { e.hp = 0; games_chronorift_st.game_mode = 0 }
+ else { games_chronorift_st.player_hit = 0 }
}
}
become KnightResolve
- menu_cursor = 0
+ games_chronorift_st.menu_cursor = 0
}
}
}
@@ -55,13 +55,13 @@ handler Battle phase Update {
if k != 0 {
if e.hp <= 0 {
grant_xp(e.xp)
- gold += e.xp
- if enemy_type == 2 { boss_down = 1; game_mode = 3; ack = 0 }
- else { game_mode = 0 }
+ games_chronorift_st.gold += e.xp
+ if games_chronorift_st.enemy_type == 2 { games_chronorift_st.boss_down = 1; games_chronorift_st.game_mode = 3; games_chronorift_st.ack = 0 }
+ else { games_chronorift_st.game_mode = 0 }
become KnightMenu
} else {
become MageMenu
- menu_cursor = 0
+ games_chronorift_st.menu_cursor = 0
}
}
}
@@ -72,14 +72,14 @@ handler Battle phase Update {
if st.hp <= 0 {
become EnemyTurn
} else {
- if k == 'i' { menu_cursor = max(0, menu_cursor - 1) }
- if k == 'k' { menu_cursor = min(2, menu_cursor + 1) }
+ if k == 'i' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) }
+ if k == 'k' { games_chronorift_st.menu_cursor = min(2, games_chronorift_st.menu_cursor + 1) }
if k == 'j' {
- match menu_cursor {
+ match games_chronorift_st.menu_cursor {
MageAct.Attack => {
let d = max(1, st.atk + 5 - e.def + Random.range(0, 5))
e.hp -= d
- player_hit = d
+ games_chronorift_st.player_hit = d
}
MageAct.Heal => {
if st.mp >= 4 {
@@ -87,13 +87,13 @@ handler Battle phase Update {
for (t2, p2) in query [Stats, Party] {
if p2.slot == 0 { t2.hp = min(t2.maxhp, t2.hp + 16) }
}
- player_hit = 16
- } else { player_hit = 0 }
+ games_chronorift_st.player_hit = 16
+ } else { games_chronorift_st.player_hit = 0 }
}
- MageAct.Guard => { st.guard = 1; player_hit = 0 }
+ MageAct.Guard => { st.guard = 1; games_chronorift_st.player_hit = 0 }
}
become MageResolve
- menu_cursor = 0
+ games_chronorift_st.menu_cursor = 0
}
}
}
@@ -104,9 +104,9 @@ handler Battle phase Update {
if k != 0 {
if e.hp <= 0 {
grant_xp(e.xp)
- gold += e.xp
- if enemy_type == 2 { boss_down = 1; game_mode = 3; ack = 0 }
- else { game_mode = 0 }
+ games_chronorift_st.gold += e.xp
+ if games_chronorift_st.enemy_type == 2 { games_chronorift_st.boss_down = 1; games_chronorift_st.game_mode = 3; games_chronorift_st.ack = 0 }
+ else { games_chronorift_st.game_mode = 0 }
become KnightMenu
} else {
become EnemyTurn
@@ -123,7 +123,7 @@ handler Battle phase Update {
var ed = max(1, e.atk - t3.def + Random.range(0, 4))
if t3.guard == 1 { ed /= 2; t3.guard = 0 }
t3.hp -= ed
- enemy_hit = ed
+ games_chronorift_st.enemy_hit = ed
done = 1
}
}
@@ -132,10 +132,10 @@ handler Battle phase Update {
for (t4) in query [Stats, {Party}] {
if t4.hp > 0 { alive += 1 }
}
- if alive == 0 { game_mode = 2; ack = 0 }
+ if alive == 0 { games_chronorift_st.game_mode = 2; games_chronorift_st.ack = 0 }
else {
become KnightMenu
- menu_cursor = 0
+ games_chronorift_st.menu_cursor = 0
}
}
}
@@ -144,29 +144,29 @@ handler Battle phase Update {
}
}
-handler CleanBattle phase LateUpdate {
- if game_mode != 1 {
+handler CleanBattle(games_chronorift_st: GamesChronoriftState) phase LateUpdate {
+ if games_chronorift_st.game_mode != 1 {
for (e) in query [Stats, {Enemy}] {
despawn self()
}
}
}
-handler Meta phase Update {
+handler Meta(games_chronorift_st: mut GamesChronoriftState) phase Update {
let k = Input.key()
- if game_mode == 2 {
- if k == 0 { ack = 1 }
+ if games_chronorift_st.game_mode == 2 {
+ if k == 0 { games_chronorift_st.ack = 1 }
if k != 0 {
- if ack == 1 {
+ if games_chronorift_st.ack == 1 {
for (h) in query [Stats, {Party}] { h.hp = h.maxhp }
- game_mode = 0
+ games_chronorift_st.game_mode = 0
}
}
}
- if game_mode == 3 {
- if k == 0 { ack = 1 }
+ if games_chronorift_st.game_mode == 3 {
+ if k == 0 { games_chronorift_st.ack = 1 }
if k != 0 {
- if ack == 1 { game_mode = 0 }
+ if games_chronorift_st.ack == 1 { games_chronorift_st.game_mode = 0 }
}
}
}
diff --git a/examples/games/chronorift/draw.ludic b/examples/games/chronorift/draw.ludic
index c10eee0b..c65ea164 100644
--- a/examples/games/chronorift/draw.ludic
+++ b/examples/games/chronorift/draw.ludic
@@ -1,7 +1,7 @@
# draw.ludic — the Render system (overworld, battle, and overlay screens).
-handler Draw phase Render {
- let m = game_mode
+handler Draw(games_chronorift_st: GamesChronoriftState) phase Render {
+ let m = games_chronorift_st.game_mode
Screen.clear(0x101018)
if m == 0 {
@@ -31,7 +31,7 @@ handler Draw phase Render {
if c == 'D' { Screen.sprite(SPR_DOOR, sx, sy) }
if c == 'S' { Screen.sprite(SPR_DOOR, sx, sy) }
if c == 'B' {
- if boss_down == 0 { Screen.sprite(SPR_BOSS, sx, sy) }
+ if games_chronorift_st.boss_down == 0 { Screen.sprite(SPR_BOSS, sx, sy) }
}
}
}
@@ -47,9 +47,9 @@ handler Draw phase Render {
Screen.draw_text(8, 18, "HP", 0x88ff88, 1)
Screen.draw_number(24, 18, h.hp, 0xffffff, 1)
Screen.draw_text(8, 28, "G", 0xffd24a, 1)
- Screen.draw_number(18, 28, gold, 0xffffff, 1)
+ Screen.draw_number(18, 28, games_chronorift_st.gold, 0xffffff, 1)
Screen.draw_text(54, 28, "PO", 0x88ffcc, 1)
- Screen.draw_number(72, 28, potions, 0xffffff, 1)
+ Screen.draw_number(72, 28, games_chronorift_st.potions, 0xffffff, 1)
}
}
}
@@ -58,13 +58,13 @@ handler Draw phase Render {
Screen.fill_rectangle(0, 0, 320, 176, 0x241a2e)
for (e, a) in query [Stats, Actor, {Enemy}] {
var bs = 5
- if enemy_type == 2 { bs = 7 }
+ if games_chronorift_st.enemy_type == 2 { bs = 7 }
Screen.sprite_scaled(a.kind, 160 - bs * 8, 44, bs)
- Screen.draw_text(100, 20, enemy_name(enemy_type), 0xffffff, 1)
+ Screen.draw_text(100, 20, enemy_name(games_chronorift_st.enemy_type), 0xffffff, 1)
Screen.fill_rectangle(100, 30, 120, 8, 0x401018)
Screen.fill_rectangle(100, 30, e.hp * 120 / e.maxhp, 8, 0xe04040)
}
- let ph = turn_phase
+ let ph = games_chronorift_st.turn_phase
var act = 0
if ph >= 2 { act = 1 }
if ph >= 4 { act = 2 }
@@ -83,14 +83,14 @@ handler Draw phase Render {
Screen.draw_text(hx + 36, 207, "MP", 0x88aaff, 1)
Screen.draw_number(hx + 56, 207, st.mp, 0xffffff, 1)
}
- let cur = menu_cursor
+ let cur = games_chronorift_st.menu_cursor
if ph == 0 or ph == 1 {
Screen.draw_text(206, 181, "P1 KNIGHT", 0xffe08a, 1)
Screen.draw_text(214, 192, "ATTACK", 0xffffff, 1)
Screen.draw_text(214, 203, "DEFEND", 0xffffff, 1)
Screen.draw_text(214, 214, "ITEM", 0xffffff, 1)
Screen.draw_text(256, 214, "x", 0x777777, 1)
- Screen.draw_number(264, 214, potions, 0xffd24a, 1)
+ Screen.draw_number(264, 214, games_chronorift_st.potions, 0xffd24a, 1)
Screen.draw_text(214, 225, "RUN", 0xffffff, 1)
if ph == 0 { Screen.draw_text(204, 192 + cur * 11, ">", 0xffff00, 1) }
}
@@ -104,11 +104,11 @@ handler Draw phase Render {
if ph == 4 { Screen.draw_text(206, 200, "ENEMY TURN", 0xff8888, 1) }
if ph == 1 or ph == 3 {
Screen.draw_text(6, 232, "DMG", 0xffff88, 1)
- Screen.draw_number(34, 232, player_hit, 0xffffff, 1)
+ Screen.draw_number(34, 232, games_chronorift_st.player_hit, 0xffffff, 1)
}
if ph == 4 {
Screen.draw_text(6, 232, "FOE HITS", 0xff8888, 1)
- Screen.draw_number(66, 232, enemy_hit, 0xffffff, 1)
+ Screen.draw_number(66, 232, games_chronorift_st.enemy_hit, 0xffffff, 1)
}
}
@@ -128,11 +128,11 @@ handler Draw phase Render {
Screen.draw_text(80, 104, "BUY POTION", 0xffffff, 1)
Screen.draw_text(180, 104, "10 G", 0xffd24a, 1)
Screen.draw_text(80, 122, "LEAVE", 0xffffff, 1)
- Screen.draw_text(70, 104 + menu_cursor * 18, ">", 0xffff00, 1)
+ Screen.draw_text(70, 104 + games_chronorift_st.menu_cursor * 18, ">", 0xffff00, 1)
Screen.draw_text(80, 150, "GOLD", 0xffd24a, 1)
- Screen.draw_number(120, 150, gold, 0xffffff, 1)
+ Screen.draw_number(120, 150, games_chronorift_st.gold, 0xffffff, 1)
Screen.draw_text(160, 150, "POTIONS", 0x88ffcc, 1)
- Screen.draw_number(226, 150, potions, 0xffffff, 1)
+ Screen.draw_number(226, 150, games_chronorift_st.potions, 0xffffff, 1)
}
Screen.show()
}
diff --git a/examples/games/chronorift/overworld.ludic b/examples/games/chronorift/overworld.ludic
index 87cb851b..9c37e5a8 100644
--- a/examples/games/chronorift/overworld.ludic
+++ b/examples/games/chronorift/overworld.ludic
@@ -1,6 +1,6 @@
# overworld.ludic — boot, movement/encounter input, and the shop system.
-handler Boot phase Start {
+handler Boot(games_chronorift_st: mut GamesChronoriftState) phase Start {
png_load("assets/kenney/tiny-town/Tiles/tile_0000.png") # 0 grass
png_load("assets/kenney/tiny-town/Tiles/tile_0004.png") # 1 tree
png_load("assets/kenney/tiny-town/Tiles/tile_0025.png") # 2 path
@@ -25,16 +25,16 @@ handler Boot phase Start {
Party { slot: 1 }
Stats { hp: 26, maxhp: 26, mp: 16, maxmp: 16, atk: 8, def: 4, lvl: 1, xp: 0, guard: 0 }
}
- game_mode = 0
- encounter = 4
- world_map = 0
- boss_down = 0
- gold = 20
- potions = 2
+ games_chronorift_st.game_mode = 0
+ games_chronorift_st.encounter = 4
+ games_chronorift_st.world_map = 0
+ games_chronorift_st.boss_down = 0
+ games_chronorift_st.gold = 20
+ games_chronorift_st.potions = 2
}
-handler Control phase Input {
- if game_mode == 0 {
+handler Control(games_chronorift_st: mut GamesChronoriftState) phase Input {
+ if games_chronorift_st.game_mode == 0 {
for (p) in query [Pos, {Player}] {
let k = Input.key()
var nx = p.x
@@ -57,43 +57,43 @@ handler Control phase Input {
build_dungeon(0)
p.x = 3
p.y = 2
- world_map = 1
+ games_chronorift_st.world_map = 1
}
if tc == 'S' {
build_field(0)
p.x = 36
p.y = 3
- world_map = 0
+ games_chronorift_st.world_map = 0
}
if tc == 'B' {
- if boss_down == 0 {
- enemy_type = 2
+ if games_chronorift_st.boss_down == 0 {
+ games_chronorift_st.enemy_type = 2
spawn Enemy {
Actor { kind: 8 }
Stats { hp: 120, maxhp: 120, mp: 0, maxmp: 0, atk: 18, def: 10, lvl: 5, xp: 300, guard: 0 }
}
- game_mode = 1
- turn_phase = 0
- menu_cursor = 0
+ games_chronorift_st.game_mode = 1
+ games_chronorift_st.turn_phase = 0
+ games_chronorift_st.menu_cursor = 0
}
}
if tc == 'h' {
- game_mode = 4
- menu_cursor = 0
+ games_chronorift_st.game_mode = 4
+ games_chronorift_st.menu_cursor = 0
}
if tc != 'D' {
if tc != 'S' {
if tc != 'B' {
if tc != 'h' {
- encounter -= 1
- if encounter <= 0 {
- encounter = Random.range(4, 9)
+ games_chronorift_st.encounter -= 1
+ if games_chronorift_st.encounter <= 0 {
+ games_chronorift_st.encounter = Random.range(4, 9)
var et = 0
- if world_map == 1 { et = 1 }
- if world_map == 0 {
+ if games_chronorift_st.world_map == 1 { et = 1 }
+ if games_chronorift_st.world_map == 0 {
if Random.chance(40) { et = 1 }
}
- enemy_type = et
+ games_chronorift_st.enemy_type = et
if et == 0 {
spawn Enemy {
Actor { kind: 6 }
@@ -106,9 +106,9 @@ handler Control phase Input {
Stats { hp: 30, maxhp: 30, mp: 0, maxmp: 0, atk: 11, def: 5, lvl: 1, xp: 15, guard: 0 }
}
}
- game_mode = 1
- turn_phase = 0
- menu_cursor = 0
+ games_chronorift_st.game_mode = 1
+ games_chronorift_st.turn_phase = 0
+ games_chronorift_st.menu_cursor = 0
}
}
}
@@ -122,19 +122,19 @@ handler Control phase Input {
}
# shop (at the house 'h' tile): buy potions with gold
-handler Shop phase Update {
- if game_mode == 4 {
+handler Shop(games_chronorift_st: mut GamesChronoriftState) phase Update {
+ if games_chronorift_st.game_mode == 4 {
let k = Input.key()
- if k == 'w' { menu_cursor = max(0, menu_cursor - 1) }
- if k == 's' { menu_cursor = min(1, menu_cursor + 1) }
+ if k == 'w' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) }
+ if k == 's' { games_chronorift_st.menu_cursor = min(1, games_chronorift_st.menu_cursor + 1) }
if is_confirm(k) {
- if menu_cursor == 0 {
- if gold >= 10 {
- gold -= 10
- potions += 1
+ if games_chronorift_st.menu_cursor == 0 {
+ if games_chronorift_st.gold >= 10 {
+ games_chronorift_st.gold -= 10
+ games_chronorift_st.potions += 1
}
}
- if menu_cursor == 1 { game_mode = 0 }
+ if games_chronorift_st.menu_cursor == 1 { games_chronorift_st.game_mode = 0 }
}
}
}
diff --git a/examples/games/chronorift/world.ludic b/examples/games/chronorift/world.ludic
index 023c9a08..7eca4fb4 100644
--- a/examples/games/chronorift/world.ludic
+++ b/examples/games/chronorift/world.ludic
@@ -29,15 +29,17 @@ const SPR_MAGE: int = 9
const SPR_FLOOR: int = 10
const SPR_WALL: int = 11
-var game_mode: int = 0 # 0 field, 1 battle, 2 gameover, 3 victory
-var turn_phase: int = 0
-var menu_cursor: int = 0
-var encounter: int = 0
-var enemy_type: int = 0 # 0 slime, 1 skeleton, 2 boss
-var player_hit: int = 0
-var enemy_hit: int = 0
-var gold: int = 0
-var world_map: int = 0 # 0 overworld, 1 dungeon
-var boss_down: int = 0 # boss defeated flag
-var ack: int = 0 # debounce for end screens
-var potions: int = 0 # potions in inventory
+export state GamesChronoriftState {
+ game_mode: int = 0 # 0 field, 1 battle, 2 gameover, 3 victory
+ turn_phase: int = 0
+ menu_cursor: int = 0
+ encounter: int = 0
+ enemy_type: int = 0 # 0 slime, 1 skeleton, 2 boss
+ player_hit: int = 0
+ enemy_hit: int = 0
+ gold: int = 0
+ world_map: int = 0 # 0 overworld, 1 dungeon
+ boss_down: int = 0 # boss defeated flag
+ ack: int = 0 # debounce for end screens
+ potions: int = 0 # potions in inventory
+}
diff --git a/examples/games/menu.ludic b/examples/games/menu.ludic
index 8a841778..5e256c6e 100644
--- a/examples/games/menu.ludic
+++ b/examples/games/menu.ludic
@@ -9,22 +9,24 @@ program Menu {
const FONT_PATH: string = "/System/Library/Fonts/Supplemental/Arial.ttf"
const BACKDROP: int = 0x0e0e16
- var title_font: int = 0
+ state MenuState {
+ title_font: int = 0
+ }
ui MainMenu {
panel id: Root w: 288 pad: 16 gap: 6 bg: 0x1a1a2c border: 0x4a4a72 skin: "assets/ui/panel.png" inset: 10 align: center {
- label text: "CHRONO RIFT" font: title_font size: 26 fg: 0xffe060 align: center
- label text: "a Ludic retained-UI demo" font: title_font size: 14 fg: 0x9aa0c8 align: center
+ label text: "CHRONO RIFT" font: MenuState.title_font size: 26 fg: 0xffe060 align: center
+ label text: "a Ludic retained-UI demo" font: MenuState.title_font size: 14 fg: 0x9aa0c8 align: center
spacer h: 4
- button id: NewGame text: "New Game" font: title_font size: 16 w: 236
- button id: Continue text: "Continue" font: title_font size: 16 w: 236
- button id: Options text: "Options" font: title_font size: 16 w: 236
- button id: Quit text: "Quit" font: title_font size: 16 w: 236
+ button id: NewGame text: "New Game" font: MenuState.title_font size: 16 w: 236
+ button id: Continue text: "Continue" font: MenuState.title_font size: 16 w: 236
+ button id: Options text: "Options" font: MenuState.title_font size: 16 w: 236
+ button id: Quit text: "Quit" font: MenuState.title_font size: 16 w: 236
}
}
- handler Boot phase Start {
- title_font = Font.load(FONT_PATH)
+ handler Boot(menu_st: mut MenuState) phase Start {
+ menu_st.title_font = Font.load(FONT_PATH)
Ui.build()
Ui.open(UI_MainMenu)
}
diff --git a/examples/games/npcai_demo.ludic b/examples/games/npcai_demo.ludic
index 5fc48687..481347b8 100644
--- a/examples/games/npcai_demo.ludic
+++ b/examples/games/npcai_demo.ludic
@@ -33,8 +33,10 @@ program NpcAiDemo {
@OnSpawn(Ally) handler AInit { }
@OnSpawn(Dummy) handler DInit { }
- var spotted: int = 0
- @On(TargetSpotted) handler OnSpot { spotted += 1 }
+ state NpcaidemoState {
+ spotted: int = 0
+ }
+ @On(TargetSpotted) handler OnSpot(npcaidemo_st: mut NpcaidemoState) { npcaidemo_st.spotted += 1 }
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
function bi(b: bool) -> int { if b { return 1 }; return 0 }
@@ -55,7 +57,7 @@ program NpcAiDemo {
return -1
}
- entry {
+ entry (npcaidemo_st: NpcaidemoState) {
let PBr = World.prop_id("Brain")
Weapon.def("blaster", 4, 10, 10, 0, 1, 0)
@@ -72,7 +74,7 @@ program NpcAiDemo {
# ---- A. perception: the enemy spots the hostile dummy -------------------
tick_n(4)
- print(bi(spotted >= 1)) # 1 — TargetSpotted fired
+ print(bi(npcaidemo_st.spotted >= 1)) # 1 — TargetSpotted fired
print(bi(Ai.target(enemy) == dummy)) # 1 — remembers the right target
# ---- B. FSM attack: in range -> auto-aim + fire -> dummy takes damage ---
diff --git a/examples/games/platformer_demo.ludic b/examples/games/platformer_demo.ludic
index d6768a47..eb529b40 100644
--- a/examples/games/platformer_demo.ludic
+++ b/examples/games/platformer_demo.ludic
@@ -32,13 +32,15 @@ program PlatformerDemo {
@OnSpawn(Floor) handler FloorInit { }
@OnSpawn(Wall) handler WallInit { }
- var jumps: int = 0
- @On(JumpPerformed) handler CountJumps { jumps += 1 }
+ state PlatformerdemoState {
+ jumps: int = 0
+ have_power: int = 0
+ vetoed_air: int = 0
+ }
+ @On(JumpPerformed) handler CountJumps(platformerdemo_st: mut PlatformerdemoState) { platformerdemo_st.jumps += 1 }
# a game rule: forbid the air (double) jump unless a "power" flag is set — pure
# lever-4 veto, no controller edit.
- var have_power: int = 0
- var vetoed_air: int = 0
- @On(JumpRequested) handler GateAir { if (kind == 2) and (have_power == 0) { vetoed_air = 1; cancel } }
+ @On(JumpRequested) handler GateAir(platformerdemo_st: mut PlatformerdemoState) { if (kind == 2) and (platformerdemo_st.have_power == 0) { platformerdemo_st.vetoed_air = 1; cancel } }
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
function bi(b: bool) -> int { if b { return 1 }; return 0 }
@@ -47,7 +49,7 @@ program PlatformerDemo {
return World.get(e, P, World.field_id(P, "coyote_t"))
}
- entry {
+ entry (platformerdemo_st: mut PlatformerdemoState) {
let PB = World.prop_id("Body")
let PP = World.prop_id("Position")
let PL = World.prop_id("Platformer")
@@ -89,16 +91,16 @@ program PlatformerDemo {
print(bi(pf_coyote(h) == 6)) # 1
# ---- D. double jump gated by the veto hook -----------------------------
- have_power = 0
+ platformerdemo_st.have_power = 0
Platformer.jump(h); tick_n(3) # ground jump -> airborne
- let after_ground = jumps
+ let after_ground = platformerdemo_st.jumps
Platformer.jump(h); tick_n(2) # air jump attempt -> vetoed
- print(bi((jumps == after_ground) and (vetoed_air == 1))) # 1
+ print(bi((platformerdemo_st.jumps == after_ground) and (platformerdemo_st.vetoed_air == 1))) # 1
- have_power = 1
- let before_power = jumps
+ platformerdemo_st.have_power = 1
+ let before_power = platformerdemo_st.jumps
Platformer.jump(h); tick_n(2) # air jump now allowed
- print(bi(jumps == before_power + 1)) # 1
+ print(bi(platformerdemo_st.jumps == before_power + 1)) # 1
tick_n(60) # settle on the floor
# ---- E. horizontal run into the wall (collision reuse) -----------------
diff --git a/examples/games/platformer_scaffolding.ludic b/examples/games/platformer_scaffolding.ludic
index 5f1b9743..54be3989 100644
--- a/examples/games/platformer_scaffolding.ludic
+++ b/examples/games/platformer_scaffolding.ludic
@@ -33,17 +33,19 @@ program PlatformerScaffolding {
@OnSpawn(Bouncer) handler BI { }
@OnSpawn(Spike) handler SI { }
- var picked: int = 0
- @On(ItemPickedUp) handler OnPick { picked += 1 }
- var sprang: int = 0
- @On(Sprang) handler OnSpring { sprang += 1 }
- var hurt: int = 0
- @On(Hurt) handler OnHurt { hurt += 1 }
+ state PlatformerscaffoldingState {
+ picked: int = 0
+ sprang: int = 0
+ hurt: int = 0
+ }
+ @On(ItemPickedUp) handler OnPick(platformerscaffolding_st: mut PlatformerscaffoldingState) { platformerscaffolding_st.picked += 1 }
+ @On(Sprang) handler OnSpring(platformerscaffolding_st: mut PlatformerscaffoldingState) { platformerscaffolding_st.sprang += 1 }
+ @On(Hurt) handler OnHurt(platformerscaffolding_st: mut PlatformerscaffoldingState) { platformerscaffolding_st.hurt += 1 }
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
function bi(b: bool) -> int { if b { return 1 }; return 0 }
- entry {
+ entry (platformerscaffolding_st: PlatformerscaffoldingState) {
let PP = World.prop_id("Position")
let PB = World.prop_id("Body")
let PL = World.prop_id("Platformer")
@@ -69,7 +71,7 @@ program PlatformerScaffolding {
spawn Coin { Position { x: World.get(h, PP, fx), y: World.get(h, PP, fy) },
Collider { w: 10, h: 10, is_trigger: 1 }, Pickup { kind: 0, value: 5 } }
tick_n(2)
- print(bi(picked == 1)) # 1 — ItemPickedUp fired
+ print(bi(platformerscaffolding_st.picked == 1)) # 1 — ItemPickedUp fired
print(bi(Score.get(h) == 5)) # 1 — value scored
# ---- spring launches the hero upward -----------------------------------
@@ -79,13 +81,13 @@ program PlatformerScaffolding {
let vy_before = World.get(h, PB, World.field_id(PB, "vy"))
tick_n(2)
let vy_after = World.get(h, PB, World.field_id(PB, "vy"))
- print(bi((sprang >= 1) and (vy_after < 0))) # 1 — launched (vy negative = up)
+ print(bi((platformerscaffolding_st.sprang >= 1) and (vy_after < 0))) # 1 — launched (vy negative = up)
# ---- hazard damages a Life --------------------------------------------
spawn Spike { Position { x: World.get(h, PP, fx), y: World.get(h, PP, fy) },
Collider { w: 12, h: 12, is_trigger: 1 }, Hazard { dmg: 1, knockback: 6 } }
tick_n(2)
- print(bi(hurt >= 1)) # 1 — Hurt fired
+ print(bi(platformerscaffolding_st.hurt >= 1)) # 1 — Hurt fired
let PLife = World.prop_id("Life")
print(bi(World.get(h, PLife, World.field_id(PLife, "hp")) < 3)) # 1 — hp dropped
diff --git a/examples/games/rpg_demo.ludic b/examples/games/rpg_demo.ludic
index 45c9a5ab..5fc94fe4 100644
--- a/examples/games/rpg_demo.ludic
+++ b/examples/games/rpg_demo.ludic
@@ -38,14 +38,16 @@ program RpgDemo {
@OnSpawn(Cfg) handler CI { }
# a locked-door rule: veto a rightward grid step while `locked` is set (lever 4).
- var locked: int = 0
- @On(MoveRequested) handler LockDoor { if (locked == 1) and (dx == 1) { cancel } }
- var entered: int = 0
- @On(TileEntered) handler CountEnter { entered += 1 }
- var talked: int = 0
- @On(Interacted) handler OnTalk { if target >= 0 { talked += 1 } }
- var quest_done: int = 0
- @On(QuestCompleted) handler OnQuestDone { quest_done += 1 }
+ state RpgdemoState {
+ locked: int = 0
+ entered: int = 0
+ talked: int = 0
+ quest_done: int = 0
+ }
+ @On(MoveRequested) handler LockDoor(rpgdemo_st: RpgdemoState) { if (rpgdemo_st.locked == 1) and (dx == 1) { cancel } }
+ @On(TileEntered) handler CountEnter(rpgdemo_st: mut RpgdemoState) { rpgdemo_st.entered += 1 }
+ @On(Interacted) handler OnTalk(rpgdemo_st: mut RpgdemoState) { if target >= 0 { rpgdemo_st.talked += 1 } }
+ @On(QuestCompleted) handler OnQuestDone(rpgdemo_st: mut RpgdemoState) { rpgdemo_st.quest_done += 1 }
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
function bi(b: bool) -> int { if b { return 1 }; return 0 }
@@ -53,7 +55,7 @@ program RpgDemo {
function py(e: int) -> int { let P = World.prop_id("Position"); return World.get(e, P, World.field_id(P, "y")) }
function hp(e: int) -> int { let P = World.prop_id("Stats"); return World.get(e, P, World.field_id(P, "hp")) }
- entry {
+ entry (rpgdemo_st: mut RpgdemoState) {
let PM = World.prop_id("Mover")
# a 6x3 tile world; a wall glyph at cell (3,1)
@@ -71,7 +73,7 @@ program RpgDemo {
# move down into the open cell (16,32)
Mover.move(h, 0, 1); tick_n(1); Mover.move(h, 0, 0); tick_n(1)
print(bi((px(h) == 16) and (py(h) == 32))) # 1 — stepped one cell down
- print(bi(entered >= 1)) # 1 — TileEntered fired
+ print(bi(rpgdemo_st.entered >= 1)) # 1 — TileEntered fired
# try to walk into the wall at cell (3,1)=(48,16): from (32,16) moving right blocked
World.set(h, World.prop_id("Position"), World.field_id(World.prop_id("Position"), "x"), 32)
@@ -81,17 +83,17 @@ program RpgDemo {
# MoveRequested veto: a locked door forbids the rightward step (open cell now)
World.set(h, World.prop_id("Position"), World.field_id(World.prop_id("Position"), "y"), 32) # row 2 open
- locked = 1
+ rpgdemo_st.locked = 1
Mover.move(h, 1, 0); tick_n(1); Mover.move(h, 0, 0)
print(bi(px(h) == 32)) # 1 — vetoed, no move
- locked = 0
+ rpgdemo_st.locked = 0
Mover.move(h, 1, 0); tick_n(1); Mover.move(h, 0, 0)
print(bi(px(h) == 48)) # 1 — now allowed
# interact: an NPC one cell ahead (facing right after the last move)
spawn Npc { Position { x: 64, y: 32 } }
print(bi(Mover.interact(h) >= 0)) # 1 — found the NPC ahead
- print(bi(talked == 1)) # 1 — Interacted fired
+ print(bi(rpgdemo_st.talked == 1)) # 1 — Interacted fired
# ================= Module B: inventory + equipment =================
Item.def("herb", 99, 1, 2)
@@ -120,7 +122,7 @@ program RpgDemo {
Quest.start("hunt")
Quest.notify(0, 0, 1); Quest.notify(0, 0, 1); Quest.notify(0, 0, 1)
print(bi(Quest.state_of("hunt") == 3)) # 1 — auto-completed
- print(bi(quest_done == 1)) # 1 — QuestCompleted fired
+ print(bi(rpgdemo_st.quest_done == 1)) # 1 — QuestCompleted fired
# ================= Module E: dialog =================
let dn0 = Dialog.node("elder", "hello")
diff --git a/examples/games/shooter_demo.ludic b/examples/games/shooter_demo.ludic
index 297133a6..a4838c43 100644
--- a/examples/games/shooter_demo.ludic
+++ b/examples/games/shooter_demo.ludic
@@ -30,19 +30,21 @@ program ShooterDemo {
@OnSpawn(Mob) handler MInit { }
@OnSpawn(Nest) handler NInit { }
- var spawn_reqs: int = 0
- @On(SpawnRequested) handler OnSpawnReq { spawn_reqs += 1 }
- var waves_cleared: int = 0
- @On(WaveCleared) handler OnWaveClear { waves_cleared += 1 }
+ state ShooterdemoState {
+ spawn_reqs: int = 0
+ waves_cleared: int = 0
+ spawned: int = 0
+ hits: int = 0
+ deaths: int = 0
+ }
+ @On(SpawnRequested) handler OnSpawnReq(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.spawn_reqs += 1 }
+ @On(WaveCleared) handler OnWaveClear(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.waves_cleared += 1 }
- var spawned: int = 0
- @On(ProjectileSpawned) handler CountSpawn { spawned += 1 }
- var hits: int = 0
- @On(ProjectileHit) handler CountHit { hits += 1 }
+ @On(ProjectileSpawned) handler CountSpawn(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.spawned += 1 }
+ @On(ProjectileHit) handler CountHit(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.hits += 1 }
# reap the dead (the realistic policy: a Died body leaves the world) so stale
# corpses do not sit in the faction/targeting scans.
- var deaths: int = 0
- @On(Died) handler Reap { deaths += 1; despawn e }
+ @On(Died) handler Reap(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.deaths += 1; despawn e }
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
function bi(b: bool) -> int { if b { return 1 }; return 0 }
@@ -72,7 +74,7 @@ program ShooterDemo {
return -1
}
- entry {
+ entry (shooterdemo_st: ShooterdemoState) {
let PT = World.prop_id("TopDown")
let PB = World.prop_id("Body")
let PP = World.prop_id("Position")
@@ -110,7 +112,7 @@ program ShooterDemo {
TopDown.fire(g, 0)
let hp_before = hp_of(mob)
tick_n(40) # bullet flies ~120px and hits
- print(bi(spawned >= 1)) # 1 — a projectile spawned
+ print(bi(shooterdemo_st.spawned >= 1)) # 1 — a projectile spawned
print(bi(hp_of(mob) < hp_before)) # 1 — enemy took damage
# ---- C. friendly fire is ignored (same faction) ------------------------
@@ -125,29 +127,29 @@ program ShooterDemo {
print(bi(hp_of(ally) == ally_hp)) # 1 — ally unharmed (faction)
# ---- D. shotgun spread emits multiple pellets in one shot ---------------
- let s0 = spawned
+ let s0 = shooterdemo_st.spawned
Weapon.equip(g, "shotgun")
TopDown.aim(g, 0)
TopDown.fire(g, 1); tick_n(1); TopDown.fire(g, 0)
- print(bi(spawned - s0 == 5)) # 1 — 5 pellets
+ print(bi(shooterdemo_st.spawned - s0 == 5)) # 1 — 5 pellets
# ---- E. a fresh enemy is eventually killed (Combat -> Died -> reaped) --
- let d0 = deaths
+ let d0 = shooterdemo_st.deaths
Weapon.equip(g, "rifle")
TopDown.aim(g, 0)
var f = 0
- while (deaths == d0) and (f < 400) {
+ while (shooterdemo_st.deaths == d0) and (f < 400) {
TopDown.fire(g, 1); tick_fixed(); TopDown.fire(g, 0); tick_fixed()
f += 1
}
- print(bi(deaths == d0 + 1)) # 1 — enemy destroyed (Died fired)
+ print(bi(shooterdemo_st.deaths == d0 + 1)) # 1 — enemy destroyed (Died fired)
# ---- F. ring pattern (bullet-hell) emits N bullets around 360 ----------
- let r0 = spawned
+ let r0 = shooterdemo_st.spawned
let ring = Weapon.def("nova", 30, 3, 6, 0, 8, 2) # pattern 2 = ring, 8 pellets
Weapon.equip(g, "nova")
TopDown.fire(g, 1); tick_n(1); TopDown.fire(g, 0)
- print(bi(spawned - r0 == 8)) # 1 — 8-way ring
+ print(bi(shooterdemo_st.spawned - r0 == 8)) # 1 — 8-way ring
# ---- G. homing shot curves onto an off-axis enemy ----------------------
let seek = Weapon.def("seeker", 30, 40, 6, 0, 1, 0)
@@ -166,8 +168,8 @@ program ShooterDemo {
# ---- H. wave spawner: budgeted, throttled SpawnRequested + WaveCleared --
spawn Nest { Position { x: 300, y: 300 }, Spawner { rate: 3, budget: 4 } }
tick_n(20) # 4 spawns at 3-frame spacing, then cleared
- print(bi(spawn_reqs == 4)) # 1 — exactly budget requests
- print(bi(waves_cleared == 1)) # 1 — wave cleared once
+ print(bi(shooterdemo_st.spawn_reqs == 4)) # 1 — exactly budget requests
+ print(bi(shooterdemo_st.waves_cleared == 1)) # 1 — wave cleared once
quit()
}
diff --git a/examples/games/snake.ludic b/examples/games/snake.ludic
index 2cc24601..47b18fb4 100644
--- a/examples/games/snake.ludic
+++ b/examples/games/snake.ludic
@@ -24,17 +24,19 @@ program Snake {
const MODE_OVER: int = 1
# ---- the game's whole state, named ----------------------------------------
- var direction: int = DIR_RIGHT
- var length: int = 3
- var tick: int = 0
- var food_x: int = 14
- var food_y: int = 7
- var mode: int = MODE_PLAY
- var score: int = 0
- var ate: int = 0 # did the head reach the food this step?
- var tail_x: int = 0 # where the tail sat before it moved (grow here)
- var tail_y: int = 0
- var acked: int = 0 # game-over key must be released before restart
+ state SnakeState {
+ direction: int = DIR_RIGHT
+ length: int = 3
+ tick: int = 0
+ food_x: int = 14
+ food_y: int = 7
+ mode: int = MODE_PLAY
+ score: int = 0
+ ate: int = 0 # did the head reach the food this step?
+ tail_x: int = 0 # where the tail sat before it moved (grow here)
+ tail_y: int = 0
+ acked: int = 0 # game-over key must be released before restart
+ }
# ---- custom shades this game uses (values the named palette doesn't carry) -
const SHADE_BG: int = 0x0d1020 # backdrop behind the board
@@ -46,48 +48,48 @@ program Snake {
const SHADE_SCORE: int = 0xffe060
const SHADE_OVER: int = 0xff5555
- function reset(unused: int) -> int {
+ function reset(snake_st: mut SnakeState, unused: int) -> int {
for (p, s) in query [Pos, Seg] { despawn self() }
spawn S0 { Seg { order: 0 }; Pos { x: 10, y: 7 } }
spawn S1 { Seg { order: 1 }; Pos { x: 9, y: 7 } }
spawn S2 { Seg { order: 2 }; Pos { x: 8, y: 7 } }
- direction = DIR_RIGHT
- length = 3
- tick = 0
- mode = MODE_PLAY
- score = 0
- food_x = 14
- food_y = 7
+ snake_st.direction = DIR_RIGHT
+ snake_st.length = 3
+ snake_st.tick = 0
+ snake_st.mode = MODE_PLAY
+ snake_st.score = 0
+ snake_st.food_x = 14
+ snake_st.food_y = 7
return 0
}
- handler Boot phase Start { reset(0) }
+ handler Boot(snake_st: mut SnakeState) phase Start { reset(snake_st, 0) }
- handler Input phase Input {
+ handler Input(snake_st: mut SnakeState) phase Input {
let k = Input.key()
- if mode == MODE_PLAY {
- if k == 'w' { if direction != DIR_DOWN { direction = DIR_UP } }
- if k == 's' { if direction != DIR_UP { direction = DIR_DOWN } }
- if k == 'a' { if direction != DIR_RIGHT { direction = DIR_LEFT } }
- if k == 'd' { if direction != DIR_LEFT { direction = DIR_RIGHT } }
+ if snake_st.mode == MODE_PLAY {
+ if k == 'w' { if snake_st.direction != DIR_DOWN { snake_st.direction = DIR_UP } }
+ if k == 's' { if snake_st.direction != DIR_UP { snake_st.direction = DIR_DOWN } }
+ if k == 'a' { if snake_st.direction != DIR_RIGHT { snake_st.direction = DIR_LEFT } }
+ if k == 'd' { if snake_st.direction != DIR_LEFT { snake_st.direction = DIR_RIGHT } }
}
- if mode == MODE_OVER {
- if k == 0 { acked = 1 }
- if k != 0 { if acked == 1 { reset(0) } }
+ if snake_st.mode == MODE_OVER {
+ if k == 0 { snake_st.acked = 1 }
+ if k != 0 { if snake_st.acked == 1 { reset(snake_st, 0) } }
}
}
- handler Move phase Update {
- if mode == MODE_PLAY {
- tick += 1
- if tick >= 6 {
- tick = 0
+ handler Move(snake_st: mut SnakeState) phase Update {
+ if snake_st.mode == MODE_PLAY {
+ snake_st.tick += 1
+ if snake_st.tick >= 6 {
+ snake_st.tick = 0
# remember the tail cell, then drag every segment onto the one ahead of it
for (pt, stg) in query [Pos, Seg] {
- if stg.order == length - 1 { tail_x = pt.x; tail_y = pt.y }
+ if stg.order == snake_st.length - 1 { snake_st.tail_x = pt.x; snake_st.tail_y = pt.y }
}
- for step in 0 .. length {
- let i = length - 1 - step
+ for step in 0 .. snake_st.length {
+ let i = snake_st.length - 1 - step
if i >= 1 {
for (pa, sa) in query [Pos, Seg] {
if sa.order == i {
@@ -99,18 +101,18 @@ program Snake {
}
}
# move the head, check the walls, check the food
- ate = 0
+ snake_st.ate = 0
for (ph, sh) in query [Pos, Seg] {
if sh.order == 0 {
- if direction == DIR_UP { ph.y -= 1 }
- if direction == DIR_DOWN { ph.y += 1 }
- if direction == DIR_LEFT { ph.x -= 1 }
- if direction == DIR_RIGHT { ph.x += 1 }
- if ph.x < 0 { mode = MODE_OVER }
- if ph.x >= GRID_W { mode = MODE_OVER }
- if ph.y < 0 { mode = MODE_OVER }
- if ph.y >= GRID_H { mode = MODE_OVER }
- if ph.x == food_x { if ph.y == food_y { ate = 1 } }
+ if snake_st.direction == DIR_UP { ph.y -= 1 }
+ if snake_st.direction == DIR_DOWN { ph.y += 1 }
+ if snake_st.direction == DIR_LEFT { ph.x -= 1 }
+ if snake_st.direction == DIR_RIGHT { ph.x += 1 }
+ if ph.x < 0 { snake_st.mode = MODE_OVER }
+ if ph.x >= GRID_W { snake_st.mode = MODE_OVER }
+ if ph.y < 0 { snake_st.mode = MODE_OVER }
+ if ph.y >= GRID_H { snake_st.mode = MODE_OVER }
+ if ph.x == snake_st.food_x { if ph.y == snake_st.food_y { snake_st.ate = 1 } }
}
}
# self-collision: head onto any body segment
@@ -118,23 +120,23 @@ program Snake {
if sh.order == 0 {
for (pb, sb) in query [Pos, Seg] {
if sb.order > 0 {
- if pb.x == ph.x { if pb.y == ph.y { mode = MODE_OVER } }
+ if pb.x == ph.x { if pb.y == ph.y { snake_st.mode = MODE_OVER } }
}
}
}
}
- if ate == 1 {
- score += 1
- spawn Body { Seg { order: length }; Pos { x: tail_x, y: tail_y } }
- length += 1
- food_x = Random.range(low: 0, high: GRID_W - 1)
- food_y = Random.range(low: 0, high: GRID_H - 1)
+ if snake_st.ate == 1 {
+ snake_st.score += 1
+ spawn Body { Seg { order: snake_st.length }; Pos { x: snake_st.tail_x, y: snake_st.tail_y } }
+ snake_st.length += 1
+ snake_st.food_x = Random.range(low: 0, high: GRID_W - 1)
+ snake_st.food_y = Random.range(low: 0, high: GRID_H - 1)
}
}
}
}
- handler Draw phase Render {
+ handler Draw(snake_st: SnakeState) phase Render {
Screen.clear(SHADE_BG)
# checkerboard field
for gy in 0 .. GRID_H {
@@ -145,7 +147,7 @@ program Snake {
}
}
# food, inset a few pixels inside its tile
- Screen.fill_rectangle(x: food_x * TILE + 3, y: food_y * TILE + 3, width: TILE - 6, height: TILE - 6, color: SHADE_FOOD)
+ Screen.fill_rectangle(x: snake_st.food_x * TILE + 3, y: snake_st.food_y * TILE + 3, width: TILE - 6, height: TILE - 6, color: SHADE_FOOD)
# every snake segment, straight from the ECS
for (p, s) in query [Pos, Seg] {
var seg_color = SHADE_BODY
@@ -153,8 +155,8 @@ program Snake {
Screen.fill_rectangle(x: p.x * TILE + 1, y: p.y * TILE + 1, width: TILE - 2, height: TILE - 2, color: seg_color)
}
Screen.draw_text(x: 6, y: 4, text: "SCORE", color: Color.White, scale: 1)
- Screen.draw_number(x: 52, y: 4, value: score, color: SHADE_SCORE, scale: 1)
- if mode == MODE_OVER {
+ Screen.draw_number(x: 52, y: 4, value: snake_st.score, color: SHADE_SCORE, scale: 1)
+ if snake_st.mode == MODE_OVER {
Screen.draw_text(x: 112, y: 104, text: "GAME OVER", color: SHADE_OVER, scale: 2)
Screen.draw_text(x: 84, y: 140, text: "PRESS ANY KEY", color: Color.White, scale: 1)
}
diff --git a/examples/lang/floats.ludic b/examples/lang/floats.ludic
index 04a6c86c..67f6a006 100644
--- a/examples/lang/floats.ludic
+++ b/examples/lang/floats.ludic
@@ -3,13 +3,15 @@
# Running it prints: 1 2 3 4 5 6 7 8 9 10 11 12
program Floats {
const HALF: float = 0.5
- var g_gravity: float = -9.81
+ state FloatsState {
+ g_gravity: float = -9.81
+ }
property Body { x: float = 1.25, vy: float, mass: double = 70.5 }
function lerp(a: float, b: float, t: float) -> float { return a + (b - a) * t }
- entry {
+ entry (floats_st: FloatsState) {
let a: float = 0.1
if a * 3 > 0.29 and a * 3 < 0.31 { print(1) } # the int 3 promotes
if lerp(0, 10, 0.25) == 2.5 { print(2) } # ints and literals as floats
@@ -18,7 +20,7 @@ program Floats {
if string(a + 0.2) == "0.3" { print(4) } # shortest text that reads back
let b = new Body
- b.vy = b.vy + g_gravity * HALF
+ b.vy = b.vy + floats_st.g_gravity * HALF
if b.vy < -4.9 and b.vy > -4.91 and b.mass == 70.5 { print(5) }
let v = floats(3)
diff --git a/examples/lang/long_literals.ludic b/examples/lang/long_literals.ludic
index 73e106fe..cfe4285c 100644
--- a/examples/lang/long_literals.ludic
+++ b/examples/lang/long_literals.ludic
@@ -5,12 +5,14 @@
# Running it prints: 4294967295 3988292384 -4294967296 1099511627776 -3750763034362895579 -1 1
program LongLiterals {
const MASK: long = 4294967295
- var poly: long = 3988292384
+ state LongliteralsState {
+ poly: long = 3988292384
+ }
function neg() -> long { return -4294967296 }
- entry {
+ entry (longliterals_st: LongliteralsState) {
let big = 0x10000000000
let fnv = 0xcbf29ce484222325
let small = 0xFFFFFFFF
- print(`{MASK} {poly} {neg()} {big} {fnv} {small} {MASK > long(2147483647)}`)
+ print(`{MASK} {longliterals_st.poly} {neg()} {big} {fnv} {small} {MASK > long(2147483647)}`)
}
}
diff --git a/examples/lang/scenes.ludic b/examples/lang/scenes.ludic
index c23c7073..9089fc3f 100644
--- a/examples/lang/scenes.ludic
+++ b/examples/lang/scenes.ludic
@@ -15,30 +15,32 @@
# bin/ludic-dev game-build bin/ludicc examples/lang/scenes.ludic /tmp/scenes
# printf 'aaaa' | /tmp/scenes
program SceneDemo {
- var counter: int = 0
+ state ScenedemoState {
+ counter: int = 0
+ }
- handler Boot phase Start { counter = 0; print(1000) }
+ handler Boot(scenedemo_st: mut ScenedemoState) phase Start { scenedemo_st.counter = 0; print(1000) }
scene Title start {
on enter { print(1) }
on exit { print(2) }
layer Main {
- handler Tick phase Update {
- counter += 1
- print(100 + counter)
- if counter >= 2 { become Play }
+ handler Tick(scenedemo_st: mut ScenedemoState) phase Update {
+ scenedemo_st.counter += 1
+ print(100 + scenedemo_st.counter)
+ if scenedemo_st.counter >= 2 { become Play }
}
}
}
scene Play {
- on enter { print(3); counter = 0 }
+ on enter (scenedemo_st: mut ScenedemoState) { print(3); scenedemo_st.counter = 0 }
on exit { print(4) }
layer World {
- handler Step phase Update {
- counter += 1
- print(200 + counter)
- if counter >= 2 { quit() }
+ handler Step(scenedemo_st: mut ScenedemoState) phase Update {
+ scenedemo_st.counter += 1
+ print(200 + scenedemo_st.counter)
+ if scenedemo_st.counter >= 2 { quit() }
}
}
layer Hud {
diff --git a/examples/library/combat_kit.ludic b/examples/library/combat_kit.ludic
index 4afce2ce..030a4764 100644
--- a/examples/library/combat_kit.ludic
+++ b/examples/library/combat_kit.ludic
@@ -12,13 +12,15 @@ program CombatKit {
model Hero { Position, Body, Collider, TopDown, Weapon, Faction, Stats, Dash, Melee }
model Creature { Position, Body, Collider, Faction, Stats, Foe }
model WorldRules { Solids }
- var frames: int = 0
- var hero: int = -1
+ state CombatkitState {
+ frames: int = 0
+ hero: int = -1
+ }
@On(MeleeHit) handler H { print(5000 + target) }
@On(Crit) handler C { print(6000) }
- handler Tick phase Update {
- frames += 1
- if frames == 1 {
+ handler Tick(combatkit_st: mut CombatkitState) phase Update {
+ combatkit_st.frames += 1
+ if combatkit_st.frames == 1 {
Map.size(20, 15)
Dungeon.arena(Dungeon.random_style(), '#', '.')
Dungeon.set_exit(Side.North, 'D')
@@ -28,19 +30,19 @@ program CombatKit {
print(e.x * 1000 + e.y) # 153196
print(Dungeon.opposite(Side.West) * 10 + Dungeon.at_edge(IVec2.make(0, 3))) # 31
Weapon.def(name: "p", fire_rate: 10, damage: 10, speed: 8, spread: 0, pellets: 1, pattern: 0)
- hero = spawn Hero { Position { x: 100, y: 100 }, Collider { w: 14, h: 20 }, Body { policy: 1 }, TopDown { move_speed: 4, aim_mode: AimMode.Manual, aim_angle: 0 }, Faction { id: 1 },
+ combatkit_st.hero = spawn Hero { Position { x: 100, y: 100 }, Collider { w: 14, h: 20 }, Body { policy: 1 }, TopDown { move_speed: 4, aim_mode: AimMode.Manual, aim_angle: 0 }, Faction { id: 1 },
Stats { hp: 100, max_hp: 100 }, Dash { frames: 3, speed: 9, cooldown_frames: 5 }, Melee { range: 30, damage: 7, knockback: 4 } }
- Stats.add(hero, StatKind.CritPct, 100)
- Stats.add(hero, StatKind.DamagePct, 50)
- let c = Collider.center(hero); print(c.x * 1000 + c.y) # 107110
+ Stats.add(combatkit_st.hero, StatKind.CritPct, 100)
+ Stats.add(combatkit_st.hero, StatKind.DamagePct, 50)
+ let c = Collider.center(combatkit_st.hero); print(c.x * 1000 + c.y) # 107110
spawn Creature { Position { x: 120, y: 100 }, Collider { w: 14, h: 20 }, Body { policy: 1 }, Faction { id: 2 }, Stats { hp: 100, max_hp: 100, thorns: 3 }, Foe { n: 1 } }
Faction.set(1, 2, 2)
- print(Melee.swing(hero)) # 1 hit (after 6000 and 5002)
+ print(Melee.swing(combatkit_st.hero)) # 1 hit (after 6000 and 5002)
for (s, f) in query [Stats, Foe] { print(s.hp) } # 80: 7 * 150% doubled by the crit
- print(Stats.of(hero).hp) # 97: thorns
- print(Dash.start(hero, 0, 0) * 10 + Dash.active(hero)) # 11
+ print(Stats.of(combatkit_st.hero).hp) # 97: thorns
+ print(Dash.start(combatkit_st.hero, 0, 0) * 10 + Dash.active(combatkit_st.hero)) # 11
print(Prefs.max("best", 5) * 10 + Prefs.max("best", 3)) # 10
}
- if frames == 7 { print(700 + Dash.active(hero)); print(Position.of(hero).x); quit() } # 700 110
+ if combatkit_st.frames == 7 { print(700 + Dash.active(combatkit_st.hero)); print(Position.of(combatkit_st.hero).x); quit() } # 700 110
}
}
diff --git a/examples/library/component_access.ludic b/examples/library/component_access.ludic
index e07a302d..15498449 100644
--- a/examples/library/component_access.ludic
+++ b/examples/library/component_access.ludic
@@ -10,30 +10,32 @@ program ComponentAccess {
model Player { Position, Hero }
model Thing { Position, Tag }
enum HeroState { Idle, Rolling, Swinging }
- var hero_state: HeroState = HeroState.Rolling
- var player: int = -1
- var frames: int = 0
- function step() -> void {
- machine hero_state {
+ state ComponentaccessState {
+ hero_state: HeroState = HeroState.Rolling
+ player: int = -1
+ frames: int = 0
+ }
+ function step(componentaccess_st: mut ComponentaccessState) -> void {
+ machine componentaccess_st.hero_state {
state Idle { print(100); become Swinging }
state Rolling { print(101); become Idle }
state Swinging { print(102); become Rolling }
}
}
- handler Tick phase Update {
- frames += 1
- if frames == 1 {
- player = spawn Player { Position { x: 3, y: 4 }, Hero { roll_frames: 9, flash: 3 } }
+ handler Tick(componentaccess_st: mut ComponentaccessState) phase Update {
+ componentaccess_st.frames += 1
+ if componentaccess_st.frames == 1 {
+ componentaccess_st.player = spawn Player { Position { x: 3, y: 4 }, Hero { roll_frames: 9, flash: 3 } }
spawn Thing { Position { x: 30, y: 40 }, Tag { n: 7 } }
- print(Hero.of(player).roll_frames) # 9
- Hero.of(player).iframes = 20
- Hero.of(player).iframes -= 5
- print(Hero.of(player).iframes) # 15
- let hero = Hero.of(player)
+ print(Hero.of(componentaccess_st.player).roll_frames) # 9
+ Hero.of(componentaccess_st.player).iframes = 20
+ Hero.of(componentaccess_st.player).iframes -= 5
+ print(Hero.of(componentaccess_st.player).iframes) # 15
+ let hero = Hero.of(componentaccess_st.player)
hero.roll_frames += 1
- print(Hero.of(player).roll_frames) # 10
- print(Position.of(player).x + Position.of(1).x) # 33
- print(Hero.has(player)) # 1
+ print(Hero.of(componentaccess_st.player).roll_frames) # 10
+ print(Position.of(componentaccess_st.player).x + Position.of(1).x) # 33
+ print(Hero.has(componentaccess_st.player)) # 1
print(Hero.has(1)) # 0
print(Hero.has(-1)) # 0
print(Tag.has(1)) # 1
@@ -42,13 +44,13 @@ program ComponentAccess {
print(Position.count()) # 1: the player
spawn Thing { Position { x: 1 } }
}
- if frames == 2 {
- step(); step(); step() # 101 100 102
- print(hero_state) # 1
+ if componentaccess_st.frames == 2 {
+ step(componentaccess_st); step(componentaccess_st); step(componentaccess_st) # 101 100 102
+ print(componentaccess_st.hero_state) # 1
print(Position.count()) # 2: the player and the thing spawned after
}
- if frames == 3 {
- print(Hero.of(player).flash) # ticked twice: 1
+ if componentaccess_st.frames == 3 {
+ print(Hero.of(componentaccess_st.player).flash) # ticked twice: 1
Position.despawn_all()
print(Position.count()) # 0
quit()
diff --git a/examples/library/gameplay_foundation.ludic b/examples/library/gameplay_foundation.ludic
index 3d840b19..1e3b32c8 100644
--- a/examples/library/gameplay_foundation.ludic
+++ b/examples/library/gameplay_foundation.ludic
@@ -17,19 +17,21 @@ program GameplayFoundation {
# a game-supplied policy: while "enraged" is set, halve incoming damage by
# rewriting the pending amount (lever 6, the mutable hook), and hard-veto any
# hit over 100 (lever 4, cancel). Both are just listeners — no engine fork.
- var enraged: int = 0
- @On(DamageAboutToApply) handler Ward {
+ state GameplayfoundationState {
+ enraged: int = 0
+ deaths: int = 0
+ }
+ @On(DamageAboutToApply) handler Ward(gameplayfoundation_st: GameplayfoundationState) {
if amount > 100 { cancel }
- if enraged == 1 { Combat.set_amount(amount / 2) }
+ if gameplayfoundation_st.enraged == 1 { Combat.set_amount(amount / 2) }
}
- var deaths: int = 0
- @On(Died) handler Tally { deaths += 1 }
+ @On(Died) handler Tally(gameplayfoundation_st: mut GameplayfoundationState) { gameplayfoundation_st.deaths += 1 }
function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } }
function bi(b: bool) -> int { if b { return 1 }; return 0 }
- entry {
+ entry (gameplayfoundation_st: mut GameplayfoundationState) {
let PS = World.prop_id("Stats")
let fhp = World.field_id(PS, "hp")
let fatk = World.field_id(PS, "atk")
@@ -65,9 +67,9 @@ program GameplayFoundation {
print(World.get(t, PS, fhp)) # 85
# mutable hook: enrage halves the next hit ((20 - 5) -> 7)
- enraged = 1
+ gameplayfoundation_st.enraged = 1
print(Combat.damage(t, a, 20)) # 7
- enraged = 0
+ gameplayfoundation_st.enraged = 0
# veto hook: an over-100 hit is cancelled (0 applied)
print(bi(Combat.damage(t, a, 200) == 0)) # 1
@@ -75,7 +77,7 @@ program GameplayFoundation {
# finish it off with sub-100 hits so the veto never blocks the kill
var guard = 0
while (World.get(t, PS, fhp) > 0) and (guard < 100) { Combat.damage(t, a, 90); guard += 1 }
- print(deaths) # 1
+ print(gameplayfoundation_st.deaths) # 1
quit()
}
diff --git a/examples/library/input_auto.ludic b/examples/library/input_auto.ludic
index 74bcaca2..5c1ad727 100644
--- a/examples/library/input_auto.ludic
+++ b/examples/library/input_auto.ludic
@@ -12,9 +12,11 @@ program InputAuto {
property Tag { n: int = 0 }
model Player { Tag }
- var hits: int = 0
+ state InputautoState {
+ hits: int = 0
+ }
@OnStart handler Boot { Input.action("go", 'l') } # 'l' == 108
- handler Sense phase Update { if Input.active("go") { hits += 1 } }
- @OnQuit handler Report { print(hits) }
+ handler Sense(inputauto_st: mut InputautoState) phase Update { if Input.active("go") { inputauto_st.hits += 1 } }
+ @OnQuit handler Report(inputauto_st: InputautoState) { print(inputauto_st.hits) }
}
diff --git a/examples/library/managers.ludic b/examples/library/managers.ludic
index 5012d49e..7df9f023 100644
--- a/examples/library/managers.ludic
+++ b/examples/library/managers.ludic
@@ -8,10 +8,12 @@ program Managers {
property Tag { n: int = 0 }
model Thing { Position, Body, Sprite, Tag, TopDown }
model WorldRules { Solids }
- var frames: int = 0
- handler Tick phase Update {
- frames += 1
- if frames == 1 {
+ state ManagersState {
+ frames: int = 0
+ }
+ handler Tick(managers_st: mut ManagersState) phase Update {
+ managers_st.frames += 1
+ if managers_st.frames == 1 {
Map.size(10, 8)
Map.fill(glyph: '.')
Map.border(glyph: '#')
@@ -36,7 +38,7 @@ program Managers {
Fx.number(x: 20, y: 20, value: 42, color: 0xffff00)
spawn Thing { Position { x: 20, y: 20 }, Sprite { id: 5, move_id: 9, face: 1, flash: 3, blink: 6 }, TopDown { want_x: -1 } }
}
- if frames == 3 { print(Map.is_solid(x: 0, y: 0) * 10 + Map.is_solid_at(x: 40, y: 40)) } # 10
- if frames == 5 { for (s, t) in query [Sprite, Tag] { print(s.flip * 100 + s.flash * 10 + s.blink) }; quit() } # 102
+ if managers_st.frames == 3 { print(Map.is_solid(x: 0, y: 0) * 10 + Map.is_solid_at(x: 40, y: 40)) } # 10
+ if managers_st.frames == 5 { for (s, t) in query [Sprite, Tag] { print(s.flip * 100 + s.flash * 10 + s.blink) }; quit() } # 102
}
}
diff --git a/examples/library/prefabs.ludic b/examples/library/prefabs.ludic
index 3b518b06..6729a1eb 100644
--- a/examples/library/prefabs.ludic
+++ b/examples/library/prefabs.ludic
@@ -8,9 +8,11 @@ program Prefabs {
model Creature { Position, Enemy }
prefab Foe: Creature { Enemy { team: 2, hp: 1 }, Position { y: 9 } }
prefab Grunt: Foe { Enemy { kind: 1, hp: 30 } }
- var last: int = -1
- @OnSpawn(Creature) handler Remember { last = self() }
- entry {
+ state PrefabsState {
+ last: int = -1
+ }
+ @OnSpawn(Creature) handler Remember(prefabs_st: mut PrefabsState) { prefabs_st.last = self() }
+ entry (prefabs_st: PrefabsState) {
let g = spawn Grunt { Position { x: 5 } } # Foe's presets, Grunt's, then this
print(Enemy.of(g).team * 100000 + Enemy.of(g).hp * 1000 + Enemy.of(g).kind * 100 + Position.of(g).x * 10 + Position.of(g).y)
let n = Prefab.spawn(name: "Grunt")
@@ -20,6 +22,6 @@ program Prefabs {
print(Position.of(placed).x)
print(Position.of(placed).y)
print(Prefab.spawn(name: "Nope")) # no such prefab
- print(last) # self() in the hook: the last spawn
+ print(prefabs_st.last) # self() in the hook: the last spawn
}
}
diff --git a/examples/library/scene_menus.ludic b/examples/library/scene_menus.ludic
index c3978392..94c3a635 100644
--- a/examples/library/scene_menus.ludic
+++ b/examples/library/scene_menus.ludic
@@ -6,18 +6,20 @@ program SceneMenus {
import "ludic.core/components.ludic"
property Pos { x: int = 0 }
model Thing { Pos }
- var menu_font: int = 0
- var frames: int = 0
+ state ScenemenusState {
+ menu_font: int = 0
+ frames: int = 0
+ }
ui Menu {
panel id: Root w: 100 pad: 4 bg: Color.Gunmetal border: Color.SlateGray align: center {
- button id: Go text: "Go" font: menu_font size: 16 w: 80 goto: Play
+ button id: Go text: "Go" font: ScenemenusState.menu_font size: 16 w: 80 goto: Play
}
}
scene Splash start lasts 1 then Title { on enter { print(100) } }
scene Title shows Menu {
on enter { print(200) }
on exit { print(202) }
- layer L { handler Tick phase Update { frames += 1; if frames == 1 { print(201); emit UiClicked(id: UI_Go) } } }
+ layer L { handler Tick(scenemenus_st: mut ScenemenusState) phase Update { scenemenus_st.frames += 1; if scenemenus_st.frames == 1 { print(201); emit UiClicked(id: UI_Go) } } }
}
@On(UiClicked) handler Report { print(id) } # UI_Go is widget 1 (the root panel is 0)
scene Play {
diff --git a/examples/library/sprite_atlas.ludic b/examples/library/sprite_atlas.ludic
index 1af47be2..57b6b792 100644
--- a/examples/library/sprite_atlas.ludic
+++ b/examples/library/sprite_atlas.ludic
@@ -10,23 +10,25 @@
program SpriteAtlas {
import "ludic.core/components.ludic"
- var TALL: int = 0
+ state SpriteatlasState {
+ TALL: int = 0
+ }
function bi(b: bool) -> int { if b { return 1 }; return 0 }
- @OnStart handler Boot {
+ @OnStart handler Boot(spriteatlas_st: mut SpriteatlasState) {
let sheet = Sprite.sheet("assets/kenney/tiny-dungeon/Tilemap/tilemap_packed.png", 16, 16)
- TALL = Sprite.cell_span(sheet, 0, 0, 1, 2) # a 16x32 sprite: two cells tall
- spawn Tall { Position { x: 40, y: 40 }, Sprite { id: TALL, atlas: 1 } }
- spawn Tinted { Position { x: 100, y: 40 }, Sprite { id: TALL, atlas: 1, tint: 0xff0000 } }
- spawn Table { Position { x: 160, y: 40 }, Sprite { id: TALL } } # atlas: 0 -> the (empty) 16x16 table
+ spriteatlas_st.TALL = Sprite.cell_span(sheet, 0, 0, 1, 2) # a 16x32 sprite: two cells tall
+ spawn Tall { Position { x: 40, y: 40 }, Sprite { id: spriteatlas_st.TALL, atlas: 1 } }
+ spawn Tinted { Position { x: 100, y: 40 }, Sprite { id: spriteatlas_st.TALL, atlas: 1, tint: 0xff0000 } }
+ spawn Table { Position { x: 160, y: 40 }, Sprite { id: spriteatlas_st.TALL } } # atlas: 0 -> the (empty) 16x16 table
}
# An empty Render handler: the engine sprite system draws the Sprite entities.
handler Draw phase Render { }
- @OnQuit handler Report {
- print(Sprite.height(TALL)) # 32 — the span is two cells tall
+ @OnQuit handler Report(spriteatlas_st: SpriteatlasState) {
+ print(Sprite.height(spriteatlas_st.TALL)) # 32 — the span is two cells tall
var top = 0
for yy in 40 .. 56 { for xx in 40 .. 56 { if Screen.pixel(xx, yy) != 0 { top = 1 } } }
diff --git a/examples/library/sprite_render.ludic b/examples/library/sprite_render.ludic
index 48139610..bac6a39e 100644
--- a/examples/library/sprite_render.ludic
+++ b/examples/library/sprite_render.ludic
@@ -9,12 +9,14 @@
program SpriteRender {
import "ludic.core/components.ludic"
- var HERO: int = 0
+ state SpriterenderState {
+ HERO: int = 0
+ }
- @OnStart handler Boot {
- HERO = png_load("assets/kenney/tiny-dungeon/Tiles/tile_0096.png")
- spawn Shown { Position { x: 40, y: 40 }, Sprite { id: HERO } }
- spawn Hidden { Position { x: 100, y: 40 }, Sprite { id: HERO, hidden: 1 } }
+ @OnStart handler Boot(spriterender_st: mut SpriterenderState) {
+ spriterender_st.HERO = png_load("assets/kenney/tiny-dungeon/Tiles/tile_0096.png")
+ spawn Shown { Position { x: 40, y: 40 }, Sprite { id: spriterender_st.HERO } }
+ spawn Hidden { Position { x: 100, y: 40 }, Sprite { id: spriterender_st.HERO, hidden: 1 } }
}
# An empty Render handler: the engine sprite system draws the Sprite entities.
diff --git a/examples/library/threads.ludic b/examples/library/threads.ludic
index 91ad7175..24327230 100644
--- a/examples/library/threads.ludic
+++ b/examples/library/threads.ludic
@@ -6,25 +6,27 @@
program Threads {
const N: int = 20000
- var calls: int = 0 # an atomic handle, made on the main thread before any work
- var lock: int = 0 # a mutex handle
- var total: long = 0 # guarded by `lock`
+ state ThreadsState {
+ calls: int = 0 # an atomic handle, made on the main thread before any work
+ lock: int = 0 # a mutex handle
+ total: long = 0 # guarded by `lock`
+ }
# out[i] = i * i: every index written exactly once, by whichever thread claimed it
function square(i: int, out: words) -> void { out[i] = i * i }
# count the call atomically, and add i to a shared total under the mutex
- function tally(i: int, ctx: pointer) -> void {
- Sync.add(calls, 1)
- Sync.lock(lock)
- total = total + i
- Sync.unlock(lock)
+ function tally(threads_st: mut ThreadsState, i: int, ctx: pointer) -> void {
+ Sync.add(threads_st.calls, 1)
+ Sync.lock(threads_st.lock)
+ threads_st.total = threads_st.total + i
+ Sync.unlock(threads_st.lock)
}
# remember which calls ran on a pool thread
function placed(i: int, out: words) -> void { if Job.is_worker() { out[i] = 1 } else { out[i] = 0 } }
- entry {
+ entry (threads_st: mut ThreadsState) {
if Sync.cpu_count() >= 1 { print(1) }
if not Job.is_worker() { print(2) }
@@ -34,12 +36,12 @@ program Threads {
for i in 0 .. N { if sq[i] != i * i { right = false } }
if right { print(3) }
- calls = Sync.atomic()
- lock = Sync.mutex()
+ threads_st.calls = Sync.atomic()
+ threads_st.lock = Sync.mutex()
Job.parallel_for(N, fn tally, null)
- if Sync.get(calls) == N { print(4) }
+ if Sync.get(threads_st.calls) == N { print(4) }
let want: long = N * (N - 1) / 2
- if total == want { print(5) }
+ if threads_st.total == want { print(5) }
let ran = words(N)
Job.parallel_for(N, fn placed, ran)
diff --git a/examples/library/ui_anim.ludic b/examples/library/ui_anim.ludic
index 64207c23..3a8d0bed 100644
--- a/examples/library/ui_anim.ludic
+++ b/examples/library/ui_anim.ludic
@@ -4,24 +4,26 @@
import "ludic.ui"
program UiAnim {
numbers float
- var dim: bool = false
- view Anim {
- dim = dim
+ state UianimState {
+ dim: bool = false
+ }
+ view Anim (uianim_st: UianimState) {
+ dim = uianim_st.dim
}
const PAGE: string = "a
b
c
"
- function at() -> string {
- ui_show("a", view_anim(), 0.0, 0.0, 200.0, 200.0)
- let r: UiNode = ui_nodes("a", view_anim())
+ 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_load_text(PAGE, "a.xml")
- var out = at()
- for i in 0 .. 29 { at() }
- out = out + " | " + at()
- dim = true
- for i in 0 .. 29 { at() }
- out = out + " | " + at()
+ entry (ui_st: mut UiState, uianim_st: mut UianimState) {
+ 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)
+ uianim_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
index 08a97263..2f8f29ff 100644
--- a/examples/library/ui_box.ludic
+++ b/examples/library/ui_box.ludic
@@ -6,14 +6,14 @@ program UiBox {
numbers float
const PAGE: string = "title
"
function scale() -> float { return 1.5 }
- entry {
+ entry (ui_st: mut UiState) {
let b = new UiBackend
b.scale = fn scale
- ui_backend(b)
- ui_load_text(PAGE, "b.xml")
- ui_show("b", null, 20.0, 30.0, 800.0, 600.0)
- let p = ui_box("panel")
- let none = ui_box("nothing")
- print(`scale {ui_scale()} | panel {int(p.x)},{int(p.y)} {int(p.w)}x{int(p.h)} | missing {none == null}`)
+ 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
index 62d9f399..7242d520 100644
--- a/examples/library/ui_boxes.ludic
+++ b/examples/library/ui_boxes.ludic
@@ -6,11 +6,11 @@ import "ludic.ui"
import "ui_boxes_parts/Boxes.ludic"
program UiBoxes {
numbers float
- entry {
- ui_backend(new UiBackend)
- ui_show("Boxes", null, 0.0, 0.0, 400.0, 600.0)
- let root: UiNode = ui_nodes("Boxes", null)
- ui_place(root, 0.0, 0.0, 400.0, 600.0)
+ 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]
diff --git a/examples/library/ui_calc.ludic b/examples/library/ui_calc.ludic
index 545ca437..7a4eea33 100644
--- a/examples/library/ui_calc.ludic
+++ b/examples/library/ui_calc.ludic
@@ -5,12 +5,12 @@ program UiCalc {
numbers float
const STYLE: string = ""
const BODY: string = ""
- entry {
- ui_backend(new UiBackend)
- ui_load_text("" + STYLE + BODY + "", "c.xml")
- ui_show("c", null, 0.0, 0.0, 400.0, 600.0)
- let r: UiNode = ui_nodes("c", null)
- ui_place(r, 0.0, 0.0, 400.0, 600.0)
+ 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]
diff --git a/examples/library/ui_component.ludic b/examples/library/ui_component.ludic
index b79a3074..cac2a70b 100644
--- a/examples/library/ui_component.ludic
+++ b/examples/library/ui_component.ludic
@@ -11,18 +11,18 @@ program UiComponent {
if n.text != "" { push(out, n.text) }
for i in 0 .. len(n.children) { texts(n.children[i], out) }
}
- function frame() -> UiNode {
- let root: UiNode = ui_nodes("App", null)
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ 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 {
- var root = frame()
+ entry (ui_st: mut UiState) {
+ var root = frame(ui_st)
let b = root.children[0].children[2]
- ui_press(b.children[1])
- ui_press(b.children[1])
- ui_press(root.children[0].children[1].children[1])
- root = frame()
+ 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 = ""
@@ -30,8 +30,8 @@ program UiComponent {
print(joined)
let b2 = root.children[0].children[2]
print(`b margin {int(b2.mt)}; b text {b2.children[0].fg}; outside {root.children[0].children[3].fg}; button {int(b2.children[1].ch)}`)
- ui_press(b2.children[2])
- root = frame()
+ 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_controls.ludic b/examples/library/ui_controls.ludic
index 1cde14b9..7e4508a7 100644
--- a/examples/library/ui_controls.ludic
+++ b/examples/library/ui_controls.ludic
@@ -6,11 +6,11 @@ import "ludic.ui"
import "ui_controls_parts/Settings.ludic"
program UiControls {
numbers float
- function frame(i: UiInput) -> UiNode {
- ui_input(i)
- ui_show("Settings", null, 0.0, 0.0, 400.0, 400.0)
- let root: UiNode = ui_nodes("Settings", null)
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ 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 {
@@ -26,30 +26,30 @@ program UiControls {
let last = page.children[len(page.children) - 1]
return last.text
}
- entry {
- ui_backend(new UiBackend)
- var root = frame(new UiInput)
- root = frame(press(true, false, false, false))
- root = frame(press(false, false, false, true))
- root = frame(press(false, true, false, false))
- root = frame(press(false, false, false, true))
- root = frame(press(false, true, false, false))
- root = frame(press(false, false, true, false))
- root = frame(press(false, false, true, false))
- root = frame(press(false, true, false, false))
- root = frame(press(false, false, true, false))
- root = frame(press(false, true, false, false))
+ 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(typed)
+ root = frame(ui_st, typed)
let back = new UiInput
back.backspace = true
- root = frame(back)
- root = frame(press(false, true, false, false))
- root = frame(press(false, false, false, 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(k)
+ root = frame(ui_st, k)
print(state(root))
let range = root.children[0].children[2]
let track = range.children[1]
@@ -57,15 +57,15 @@ program UiControls {
at.x = track.x + track.cw * 0.8
at.y = track.y + 2.0
at.down = true
- root = frame(at)
- root = frame(new UiInput)
+ 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(w)
- root = frame(new UiInput)
+ 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_css.ludic b/examples/library/ui_css.ludic
index b1500bdb..90a3feac 100644
--- a/examples/library/ui_css.ludic
+++ b/examples/library/ui_css.ludic
@@ -9,12 +9,12 @@ program UiCss {
}
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 \n \n wide
narrow
\n \n h
after
fallback
\n \n \n a sentence long enough that it has to wrap onto several lines
\n\n\n"
- entry {
- ui_load_text(LOOK, "ui/look.lss")
- ui_load_text(PAGE, "ui/css.xml")
- ui_viewport(400.0, 300.0)
- let root: UiNode = ui_nodes("css", view_css())
- ui_place(root, 0.0, 0.0, 400.0, 300.0)
+ 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]
diff --git a/examples/library/ui_ease.ludic b/examples/library/ui_ease.ludic
index 1504f61b..027352bb 100644
--- a/examples/library/ui_ease.ludic
+++ b/examples/library/ui_ease.ludic
@@ -4,30 +4,32 @@
import "ludic.ui"
program UiEase {
numbers float
- var dim: bool = false
- var now: float = 0.0
- view Ease {
- dim = dim
+ state UieaseState {
+ dim: bool = false
+ now: float = 0.0
+ }
+ view Ease (uiease_st: UieaseState) {
+ dim = uiease_st.dim
}
const STYLE: string = ""
const BODY: string = "
a
b
c
"
- function clock() -> float { return now }
- function nodes(name: string) -> UiNode {
- ui_show(name, view_ease(), 0.0, 0.0, 200.0, 200.0)
- return ui_nodes(name, view_ease())
+ function clock(uiease_st: UieaseState) -> float { return uiease_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(hz: int) -> string {
+ function at_rate(ui_st: mut UiState, uiease_st: mut UieaseState, hz: int) -> string {
let name = `r{hz}`
- dim = false
+ uiease_st.dim = false
var mid = ""
var slid = false
var landed = false
var over = false
for k in 0 .. hz + 1 {
- now = float(k) / float(hz)
- if k == hz / 2 { dim = true }
- let r = nodes(name)
+ uiease_st.now = float(k) / float(hz)
+ if k == hz / 2 { uiease_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
@@ -39,17 +41,17 @@ program UiEase {
}
return `{hz}: {mid} slid {slid} landed {landed} left {over}`
}
- entry {
- ui_backend(new UiBackend)
- ui_clock(fn clock)
+ entry (ui_st: mut UiState, uiease_st: mut UieaseState) {
+ 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(page + "", "e.xml")
+ ui_load_text(ui_st, page + "", "e.xml")
var out = ""
- for i in 0 .. 4 { out = out + at_rate(60 * (i + 1)) + "; " }
+ for i in 0 .. 4 { out = out + at_rate(ui_st, uiease_st, 60 * (i + 1)) + "; " }
print(out)
}
}
diff --git a/examples/library/ui_fill.ludic b/examples/library/ui_fill.ludic
index dff937b3..dba16273 100644
--- a/examples/library/ui_fill.ludic
+++ b/examples/library/ui_fill.ludic
@@ -4,21 +4,23 @@
import "ludic.ui"
program UiFill {
numbers float
- var rects: []string = new []string
- var pics: []string = new []string
- var clips: int = 0
+ state UifillState {
+ rects: []string = new []string
+ pics: []string = new []string
+ clips: int = 0
+ }
const STYLE: string = ""
const BODY: string = "

"
- function rec_rect(x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(rects, `{int(x)},{int(y)} {int(w)}x{int(h)} {ui_hex(c)}`) }
- function rec_image(src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(pics, `{int(x)},{int(y)} {int(w)}x{int(h)}`) }
- function rec_clip(x: float, y: float, w: float, h: float) -> void { clips += 1 }
+ function rec_rect(uifill_st: mut UifillState, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(uifill_st.rects, `{int(x)},{int(y)} {int(w)}x{int(h)} {ui_hex(c)}`) }
+ function rec_image(uifill_st: mut UifillState, src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(uifill_st.pics, `{int(x)},{int(y)} {int(w)}x{int(h)}`) }
+ function rec_clip(uifill_st: mut UifillState, x: float, y: float, w: float, h: float) -> void { uifill_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 {
+ entry (ui_st: mut UiState, uifill_st: UifillState) {
let b = new UiBackend
b.rect = fn rec_rect
b.text = fn no_text
@@ -27,13 +29,13 @@ program UiFill {
b.image_h = fn nat_h
b.clip = fn rec_clip
b.unclip = fn no_clip
- ui_backend(b)
- ui_native("knob", fn knob_measure, fn knob_draw)
- ui_load_text("" + STYLE + BODY + "", "f.xml")
- ui_show("f", null, 0.0, 0.0, 400.0, 600.0)
- let r: UiNode = ui_nodes("f", null)
- ui_place(r, 0.0, 0.0, 400.0, 600.0)
- let n = len(rects)
- print(`{rects[0]} .. {rects[49]} | {rects[50]} .. {rects[59]} .. {rects[69]} of {n} | {pics[0]} | {pics[1]} clipped {clips} | ratio {int(r.children[4].ch)} knob {int(r.children[5].children[0].cw)}`)
+ 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(uifill_st.rects)
+ print(`{uifill_st.rects[0]} .. {uifill_st.rects[49]} | {uifill_st.rects[50]} .. {uifill_st.rects[59]} .. {uifill_st.rects[69]} of {n} | {uifill_st.pics[0]} | {uifill_st.pics[1]} clipped {uifill_st.clips} | ratio {int(r.children[4].ch)} knob {int(r.children[5].children[0].cw)}`)
}
}
diff --git a/examples/library/ui_flex.ludic b/examples/library/ui_flex.ludic
index 21cde37a..477a4091 100644
--- a/examples/library/ui_flex.ludic
+++ b/examples/library/ui_flex.ludic
@@ -4,19 +4,21 @@
import "ludic.ui"
program UiFlex {
numbers float
- var picked: int = 1
- view Tags {
- picked = picked
+ state UiflexState {
+ picked: int = 1
+ }
+ view Tags (uiflex_st: UiflexState) {
+ picked = uiflex_st.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_load_text(BASE, "ui/base.lss")
- ui_load_text(THEME, "ui/theme.lss")
- ui_load_text(PAGE, "ui/flex.xml")
- let root: UiNode = ui_nodes("flex", view_tags())
- ui_place(root, 0.0, 0.0, 400.0, 300.0)
+ 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]
diff --git a/examples/library/ui_focus_tip.ludic b/examples/library/ui_focus_tip.ludic
index 356de6a9..b27495ca 100644
--- a/examples/library/ui_focus_tip.ludic
+++ b/examples/library/ui_focus_tip.ludic
@@ -4,36 +4,38 @@
import "ludic.ui"
program UiFocusTip {
numbers float
- var said: []string = new []string
- var now: float = 0.0
- const PAGE: string = ""
- function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, `{s} {int(x)},{int(y)}`) }
- function clock() -> float { return now }
- function frame(i: UiInput, secs: float) -> string {
- now = now + secs
- said = new []string
- ui_input(i)
- ui_show("f", null, 0.0, 0.0, 400.0, 400.0)
- return said[len(said) - 1]
+ state UifocustipState {
+ said: []string = new []string
+ now: float = 0.0
}
- entry {
+ const PAGE: string = ""
+ function rec_text(uifocustip_st: mut UifocustipState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(uifocustip_st.said, `{s} {int(x)},{int(y)}`) }
+ function clock(uifocustip_st: UifocustipState) -> float { return uifocustip_st.now }
+ function frame(ui_st: mut UiState, uifocustip_st: mut UifocustipState, i: UiInput, secs: float) -> string {
+ uifocustip_st.now = uifocustip_st.now + secs
+ uifocustip_st.said = new []string
+ ui_input(ui_st, i)
+ ui_show(ui_st, "f", null, 0.0, 0.0, 400.0, 400.0)
+ return uifocustip_st.said[len(uifocustip_st.said) - 1]
+ }
+ entry (ui_st: mut UiState, uifocustip_st: mut UifocustipState) {
let b = new UiBackend
b.text = fn rec_text
- ui_backend(b)
- ui_clock(fn clock)
- ui_load_text(PAGE, "f.xml")
- frame(new UiInput, 0.0)
+ ui_backend(ui_st, b)
+ ui_clock(ui_st, fn clock)
+ ui_load_text(ui_st, PAGE, "f.xml")
+ frame(ui_st, uifocustip_st, new UiInput, 0.0)
let tab = new UiInput
tab.tab = true
- frame(tab, 0.0)
- let early = frame(new UiInput, 0.2)
- let shown = frame(new UiInput, 0.4)
- frame(tab, 0.0)
- let next = frame(new UiInput, 0.6)
+ frame(ui_st, uifocustip_st, tab, 0.0)
+ let early = frame(ui_st, uifocustip_st, new UiInput, 0.2)
+ let shown = frame(ui_st, uifocustip_st, new UiInput, 0.4)
+ frame(ui_st, uifocustip_st, tab, 0.0)
+ let next = frame(ui_st, uifocustip_st, new UiInput, 0.6)
let moved = new UiInput
moved.x = 390.0
moved.y = 390.0
- let gone = frame(moved, 0.6)
+ let gone = frame(ui_st, uifocustip_st, moved, 0.6)
print(`{early} | {shown} | {next} | {gone}`)
}
}
diff --git a/examples/library/ui_gamepad.ludic b/examples/library/ui_gamepad.ludic
index dc4a27bb..26d0fe14 100644
--- a/examples/library/ui_gamepad.ludic
+++ b/examples/library/ui_gamepad.ludic
@@ -5,19 +5,21 @@ import "ludic.ui"
import "ui_gamepad_parts/Pad.ludic"
program UiGamepad {
numbers float
- var now: float = 0.0
- function clock() -> float { return now }
- function frame(i: UiInput) -> string {
- ui_input(i)
- ui_show("Pad", null, 0.0, 0.0, 400.0, 600.0)
- let page = ui_nodes("Pad", null).children[0]
+ state UigamepadState {
+ now: float = 0.0
+ }
+ function clock(uigamepad_st: UigamepadState) -> float { return uigamepad_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(what: string, secs: float, hz: int) -> string {
+ function hold(ui_st: mut UiState, uigamepad_st: mut UigamepadState, what: string, secs: float, hz: int) -> string {
let frames = int(secs * float(hz) + 0.5)
for k in 0 .. frames + 1 {
- now = now + 1.0 / float(hz)
+ uigamepad_st.now = uigamepad_st.now + 1.0 / float(hz)
let i = new UiInput
i.held_up = what == "up"
i.held_down = what == "down"
@@ -25,28 +27,28 @@ program UiGamepad {
i.held_right = what == "right"
i.pad_a = what == "a"
i.pad_b = what == "b"
- frame(i)
+ frame(ui_st, i)
}
- now = now + 1.0 / float(hz)
- return frame(new UiInput)
+ uigamepad_st.now = uigamepad_st.now + 1.0 / float(hz)
+ return frame(ui_st, new UiInput)
}
- function tap(what: string) -> string { return hold(what, 0.0, 60) }
- entry {
- ui_backend(new UiBackend)
- ui_clock(fn clock)
- frame(new UiInput)
- hold("down", 1.0, 240)
- let fast = tap("a")
- hold("down", 1.0, 60)
- hold("up", 0.5, 60)
- let slow = tap("a")
- for k in 0 .. 8 { tap("down") }
- let vol = hold("right", 0.5, 60)
- tap("down")
- let q = tap("left")
- tap("down")
- let open = tap("a")
- let shut = tap("b")
+ function tap(ui_st: mut UiState, uigamepad_st: mut UigamepadState, what: string) -> string { return hold(ui_st, uigamepad_st, what, 0.0, 60) }
+ entry (ui_st: mut UiState, uigamepad_st: mut UigamepadState) {
+ ui_backend(ui_st, new UiBackend)
+ ui_clock(ui_st, fn clock)
+ frame(ui_st, new UiInput)
+ hold(ui_st, uigamepad_st, "down", 1.0, 240)
+ let fast = tap(ui_st, uigamepad_st, "a")
+ hold(ui_st, uigamepad_st, "down", 1.0, 60)
+ hold(ui_st, uigamepad_st, "up", 0.5, 60)
+ let slow = tap(ui_st, uigamepad_st, "a")
+ for k in 0 .. 8 { tap(ui_st, uigamepad_st, "down") }
+ let vol = hold(ui_st, uigamepad_st, "right", 0.5, 60)
+ tap(ui_st, uigamepad_st, "down")
+ let q = tap(ui_st, uigamepad_st, "left")
+ tap(ui_st, uigamepad_st, "down")
+ let open = tap(ui_st, uigamepad_st, "a")
+ let shut = tap(ui_st, uigamepad_st, "b")
print(`{fast} | {slow} | {vol} | {q} | {open} | {shut}`)
}
}
diff --git a/examples/library/ui_hold.ludic b/examples/library/ui_hold.ludic
index 2a5abf16..42c19af7 100644
--- a/examples/library/ui_hold.ludic
+++ b/examples/library/ui_hold.ludic
@@ -4,33 +4,35 @@
import "ludic.ui"
program UiHold {
numbers float
- var now: float = 0.0
- var held: float = 0.0
- var frames: int = 0
- var last_t: float = 0.0
- var ends: string = ""
- view Craft {
+ state UiholdState {
+ now: float = 0.0
+ held: float = 0.0
+ frames: int = 0
+ last_t: float = 0.0
+ ends: string = ""
+ }
+ view Craft (uihold_st: mut UiholdState) {
on tick(dt: float, t: float) {
- held = held + dt
- frames = frames + 1
- last_t = t
+ uihold_st.held = uihold_st.held + dt
+ uihold_st.frames = uihold_st.frames + 1
+ uihold_st.last_t = t
}
- on down() { ends = ends + "d" }
- on up() { ends = ends + "u" }
+ on down() { uihold_st.ends = uihold_st.ends + "d" }
+ on up() { uihold_st.ends = uihold_st.ends + "u" }
}
const PAGE: string = ""
- function clock() -> float { return now }
- function frame(i: UiInput) -> UiNode {
- ui_input(i)
- ui_show("h", view_craft(), 0.0, 0.0, 400.0, 400.0)
- let r: UiNode = ui_nodes("h", view_craft())
- ui_place(r, 0.0, 0.0, 400.0, 400.0)
+ function clock(uihold_st: UiholdState) -> float { return uihold_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(b: UiNode, secs: float, hz: int, pad: bool) -> string {
- held = 0.0
- frames = 0
+ function hold(ui_st: mut UiState, uihold_st: mut UiholdState, b: UiNode, secs: float, hz: int, pad: bool) -> string {
+ uihold_st.held = 0.0
+ uihold_st.frames = 0
let n = int(secs * float(hz) + 0.5)
for k in 0 .. n + 1 {
let i = new UiInput
@@ -39,21 +41,21 @@ program UiHold {
i.x = b.x + 5.0
i.y = b.y + 5.0
if k > 0 and not pad { i.x = 390.0 }
- frame(i)
- now = now + 1.0 / float(hz)
+ frame(ui_st, i)
+ uihold_st.now = uihold_st.now + 1.0 / float(hz)
}
- frame(new UiInput)
- now = now + 1.0 / float(hz)
- return `{frames} frames {int(Math.round(held * 1000.0))} ms t {int(Math.round(last_t * 1000.0))}`
+ frame(ui_st, new UiInput)
+ uihold_st.now = uihold_st.now + 1.0 / float(hz)
+ return `{uihold_st.frames} frames {int(Math.round(uihold_st.held * 1000.0))} ms t {int(Math.round(uihold_st.last_t * 1000.0))}`
}
- entry {
- ui_backend(new UiBackend)
- ui_clock(fn clock)
- ui_load_text(PAGE, "h.xml")
- let b = frame(new UiInput)
- let slow = hold(b, 0.5, 60, false)
- let fast = hold(b, 0.5, 240, false)
- let pad = hold(b, 0.25, 60, true)
- print(`{slow} | {fast} | pad {pad} | {ends}`)
+ entry (ui_st: mut UiState, uihold_st: mut UiholdState) {
+ 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_st, uihold_st, b, 0.5, 60, false)
+ let fast = hold(ui_st, uihold_st, b, 0.5, 240, false)
+ let pad = hold(ui_st, uihold_st, b, 0.25, 60, true)
+ print(`{slow} | {fast} | pad {pad} | {uihold_st.ends}`)
}
}
diff --git a/examples/library/ui_html.ludic b/examples/library/ui_html.ludic
index b4ceafcf..6b22607d 100644
--- a/examples/library/ui_html.ludic
+++ b/examples/library/ui_html.ludic
@@ -4,25 +4,27 @@
import "ludic.ui"
program UiHtml {
numbers float
- var clicks: int = 0
- view Page {
- clicks = clicks
- on clicked() { clicks += 1 }
+ state UihtmlState {
+ clicks: int = 0
+ }
+ view Page (uihtml_st: mut UihtmlState) {
+ clicks = uihtml_st.clicks
+ on clicked() { uihtml_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 \n outside
\n \n
\n mid
\n \n never
\n\n\n"
- entry {
- ui_load_text(LOOK, "ui/look.lss")
- ui_load_text(PAGE, "ui/page.ludic.xml")
- var root: UiNode = ui_nodes("page", view_page())
- ui_place(root, 0.0, 0.0, 400.0, 600.0)
+ entry (ui_st: mut UiState, uihtml_st: UihtmlState) {
+ 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(root.children[6])
- root = ui_nodes("page", view_page())
- ui_place(root, 0.0, 0.0, 400.0, 600.0)
- print(`clicks {clicks}; the button now disabled {not root.children[6].enabled} at opacity {root.children[6].alpha}; text-align {root.children[5].talign}`)
+ 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 {uihtml_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
index 3ec19ee8..4a995afc 100644
--- a/examples/library/ui_inputs.ludic
+++ b/examples/library/ui_inputs.ludic
@@ -8,11 +8,11 @@ import "ui_inputs_parts/Form.ludic"
import "ui_inputs_parts/Tag.ludic"
program UiInputs {
numbers float
- function frame(i: UiInput) -> UiNode {
- ui_input(i)
- ui_show("Form", null, 0.0, 0.0, 400.0, 400.0)
- let root: UiNode = ui_nodes("Form", null)
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ 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 {
@@ -32,52 +32,52 @@ program UiInputs {
}
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_backend(new UiBackend)
- var page = frame(new UiInput)
- frame(keys(true, false, false, false))
- frame(typed("12"))
- page = frame(keys(false, false, true, false))
+ 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(typed("99"))
- page = frame(keys(true, false, false, false))
+ frame(ui_st, typed("99"))
+ page = frame(ui_st, keys(true, false, false, false))
let b = shown_state(page)
- frame(keys(true, true, false, false))
- page = frame(keys(false, false, false, true))
+ 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(keys(true, false, false, false))
+ frame(ui_st, keys(true, false, false, false))
let space = page.children[1].children[1].children[0].text
- frame(keys(false, false, true, false))
- let cap = ui_capturing()
- page = frame(keys(true, false, false, false))
+ 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(keys(false, false, true, false))
+ frame(ui_st, keys(false, false, true, false))
let esc = new UiInput
esc.escape = true
esc.key = Key.Escape
- page = frame(esc)
+ page = frame(ui_st, esc)
let e = shown_state(page)
let back = new UiInput
back.backspace = true
- page = frame(back)
+ page = frame(ui_st, back)
let f = shown_state(page)
- frame(keys(true, false, false, false))
+ frame(ui_st, keys(true, false, false, false))
let right = new UiInput
right.right = true
- page = frame(right)
- page = frame(new UiInput)
+ 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(click)
+ frame(ui_st, click)
let up = new UiInput
up.x = click.x
up.y = click.y
- page = frame(up)
+ 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_keycap.ludic b/examples/library/ui_keycap.ludic
index 39e9898f..6c9fbc3d 100644
--- a/examples/library/ui_keycap.ludic
+++ b/examples/library/ui_keycap.ludic
@@ -4,52 +4,54 @@
import "ludic.ui"
program UiKeycap {
numbers float
- var bind: int = 32
- var pressed: int = 0
- view Keys {
- bind = bind
- pressed = pressed
- on set_bind(v: int) { bind = v }
- on press() { pressed = pressed + 1 }
+ state UikeycapState {
+ bind: int = 32
+ pressed: int = 0
+ }
+ view Keys (uikeycap_st: mut UikeycapState) {
+ bind = uikeycap_st.bind
+ pressed = uikeycap_st.pressed
+ on set_bind(v: int) { uikeycap_st.bind = v }
+ on press() { uikeycap_st.pressed = uikeycap_st.pressed + 1 }
}
const PAGE: string = ""
- function frame(x: float, y: float, b: int) -> UiNode {
+ 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(i)
- ui_show("k", view_keys(), 0.0, 0.0, 400.0, 400.0)
- let r: UiNode = ui_nodes("k", view_keys())
- ui_place(r, 0.0, 0.0, 400.0, 400.0)
+ 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(k: UiNode) -> int {
- frame(k.x + 5.0, k.y + 5.0, 0)
- frame(k.x + 5.0, k.y + 5.0, -1)
- return frame(-1.0, -1.0, -1).children[0].bg
+ 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(at: UiNode, b: int) -> string {
- frame(at.x + 5.0, at.y + 5.0, b)
- frame(at.x + 5.0, at.y + 5.0, -1)
- let r = frame(-1.0, -1.0, -1)
+ function take(ui_st: mut UiState, uikeycap_st: UikeycapState, 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 `{bind} {f.children[1].children[0].text} {f.bg}`
+ return `{uikeycap_st.bind} {f.children[1].children[0].text} {f.bg}`
}
- entry {
- ui_backend(new UiBackend)
- ui_load_text(PAGE, "k.xml")
- let r = frame(-1.0, -1.0, -1)
+ entry (ui_st: mut UiState, uikeycap_st: UikeycapState) {
+ 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(k)
- let right = take(btn, 1)
- listen(k)
- let middle = take(btn, 2)
- listen(k)
- let left = take(btn, 0)
- print(`listening {red} | {right} | {middle} | {left} | pressed {pressed}`)
+ let red = listen(ui_st, k)
+ let right = take(ui_st, uikeycap_st, btn, 1)
+ listen(ui_st, k)
+ let middle = take(ui_st, uikeycap_st, btn, 2)
+ listen(ui_st, k)
+ let left = take(ui_st, uikeycap_st, btn, 0)
+ print(`listening {red} | {right} | {middle} | {left} | pressed {uikeycap_st.pressed}`)
}
}
diff --git a/examples/library/ui_look.ludic b/examples/library/ui_look.ludic
index 9d5beab7..fbecd017 100644
--- a/examples/library/ui_look.ludic
+++ b/examples/library/ui_look.ludic
@@ -10,54 +10,56 @@ import "ui_look_parts/Inner.ludic"
import "ui_look_parts/Menu.ludic"
program UiLook {
numbers float
- var said: []string = new []string
- var seen: float = 0.0
+ 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(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, `text {s} {int(x)},{int(y)} {ui_hex(c)}`) }
- function rec_image(src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(said, `image {src} {ui_hex(c)}`) }
- function rec_nine(src: string, slice: float, x: float, y: float, w: float, h: float, dst: float, c: int, a: float) -> void { push(said, `nine {int(w)} {ui_hex(c)}`) }
+ function rec_text(uilook_st: mut UilookState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(uilook_st.said, `text {s} {int(x)},{int(y)} {ui_hex(c)}`) }
+ function rec_image(uilook_st: mut UilookState, src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(uilook_st.said, `image {src} {ui_hex(c)}`) }
+ function rec_nine(uilook_st: mut UilookState, src: string, slice: float, x: float, y: float, w: float, h: float, dst: float, c: int, a: float) -> void { push(uilook_st.said, `nine {int(w)} {ui_hex(c)}`) }
function gauge_measure(n: UiNode, avail: float) -> void { }
- function gauge_draw(n: UiNode) -> void { seen = ui_opacity() }
- function count(s: string) -> int {
+ function gauge_draw(ui_st: UiState, uilook_st: mut UilookState, n: UiNode) -> void { uilook_st.seen = ui_opacity(ui_st) }
+ function count(uilook_st: UilookState, s: string) -> int {
var n = 0
- for i in 0 .. len(said) {
- if Text.starts_with(said[i], s) { n += 1 }
+ for i in 0 .. len(uilook_st.said) {
+ if Text.starts_with(uilook_st.said[i], s) { n += 1 }
}
return n
}
- function has(s: string) -> string {
- for i in 0 .. len(said) {
- if Text.starts_with(said[i], s) { return said[i] }
+ function has(uilook_st: UilookState, s: string) -> string {
+ for i in 0 .. len(uilook_st.said) {
+ if Text.starts_with(uilook_st.said[i], s) { return uilook_st.said[i] }
}
return "-"
}
- function show(name: string, i: UiInput) -> UiNode {
- ui_input(i)
- said = new []string
- ui_show(name, null, 0.0, 0.0, 400.0, 300.0)
- let root: UiNode = ui_nodes(name, null)
- ui_place(root, 0.0, 0.0, 400.0, 300.0)
+ function show(ui_st: mut UiState, uilook_st: mut UilookState, name: string, i: UiInput) -> UiNode {
+ ui_input(ui_st, i)
+ uilook_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 {
+ entry (ui_st: mut UiState, uilook_st: mut UilookState) {
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(b)
- ui_native("gauge", fn gauge_measure, fn gauge_draw)
- var page = show("Look", new UiInput)
+ ui_backend(ui_st, b)
+ ui_native(ui_st, "gauge", fn gauge_measure, fn gauge_draw)
+ var page = show(ui_st, uilook_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("Look", rest) }
+ for f in 0 .. 40 { page = show(ui_st, uilook_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("text Hi")} | {has("text Hi 0")} | opacity {seen} | {has("nine 40 #ffffff")} {has("nine 40 #ff0000")} of {count("nine")}`)
- print(`{has("image photo")} | {has("image icon")} | {has("text first")} | {has("text second")}`)
- let menu = show("Menu", new UiInput)
+ print(`{has(uilook_st, "text Hi")} | {has(uilook_st, "text Hi 0")} | opacity {uilook_st.seen} | {has(uilook_st, "nine 40 #ffffff")} {has(uilook_st, "nine 40 #ff0000")} of {count(uilook_st, "nine")}`)
+ print(`{has(uilook_st, "image photo")} | {has(uilook_st, "image icon")} | {has(uilook_st, "text first")} | {has(uilook_st, "text second")}`)
+ let menu = show(ui_st, uilook_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_pointer.ludic b/examples/library/ui_pointer.ludic
index 1b46386d..dab7cc95 100644
--- a/examples/library/ui_pointer.ludic
+++ b/examples/library/ui_pointer.ludic
@@ -5,56 +5,58 @@ import "ludic.ui"
import "ui_pointer_parts/Chart.ludic"
program UiPointer {
numbers float
- var log: string = ""
- var dragged: float = 0.0
+ 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(n: UiNode, e: UiPointer) -> bool {
- if e.kind == "drag" { dragged = dragged + e.dx } else if e.kind != "pointermove" { log = log + `{e.kind} {int(e.x)},{int(e.y)} {e.button} {int(e.wheel)}; ` }
+ function map_input(uipointer_st: mut UipointerState, n: UiNode, e: UiPointer) -> bool {
+ if e.kind == "drag" { uipointer_st.dragged = uipointer_st.dragged + e.dx } else if e.kind != "pointermove" { uipointer_st.log = uipointer_st.log + `{e.kind} {int(e.x)},{int(e.y)} {e.button} {int(e.wheel)}; ` }
return true
}
- function frame(x: float, y: float, down: bool, wheel: float, enter: bool) -> UiNode {
+ 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(i)
- ui_show("Chart", null, 0.0, 0.0, 400.0, 400.0)
- let root: UiNode = ui_nodes("Chart", null)
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ 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_backend(new UiBackend)
- ui_native("chartmap", fn map_measure, fn map_draw)
- ui_native_input("chartmap", fn map_input)
- let page = frame(-1.0, -1.0, false, 0.0, false)
+ entry (ui_st: mut UiState, uipointer_st: UipointerState) {
+ 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(box.x + 20.0, box.y + 20.0, true, 0.0, false)
- frame(box.x + 20.0, box.y + 20.0, false, 0.0, false)
- frame(box.x + 100.0, box.y + 80.0, true, 0.0, false)
- frame(box.x + 130.0, box.y + 60.0, true, 0.0, false)
- frame(box.x + 300.0, box.y + 300.0, true, 0.0, false)
- frame(box.x + 300.0, box.y + 300.0, false, 0.0, false)
- frame(box.x + 50.0, box.y + 50.0, false, 1.0, false)
- frame(pad.x + 7.0, pad.y + 9.0, true, 0.0, false)
- frame(pad.x + 17.0, pad.y + 9.0, true, 0.0, false)
- frame(pad.x + 17.0, pad.y + 9.0, false, 0.0, false)
- frame(pad.x + 17.0, pad.y + 9.0, false, 2.0, false)
- frame(hold.x + 5.0, hold.y + 5.0, true, 0.0, false)
- let down = frame(hold.x + 5.0, hold.y + 5.0, true, 0.0, false)
+ 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(hold.x + 5.0, hold.y + 90.0, false, 0.0, false)
- let off = frame(-1.0, -1.0, false, 0.0, false)
- frame(-1.0, -1.0, false, 0.0, true)
- let keyed = said(frame(-1.0, -1.0, false, 0.0, true))
- let up = frame(-1.0, -1.0, false, 0.0, false)
- print(`{log}dragged {int(dragged)} | {mid} active {active} | {said(off)} | {keyed} | {said(up)} {up.children[2].bg}`)
+ frame(ui_st, hold.x + 5.0, hold.y + 90.0, false, 0.0, false)
+ let off = 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(`{uipointer_st.log}dragged {int(uipointer_st.dragged)} | {mid} active {active} | {said(off)} | {keyed} | {said(up)} {up.children[2].bg}`)
}
}
diff --git a/examples/library/ui_popover.ludic b/examples/library/ui_popover.ludic
index ef1af5af..4aafafdd 100644
--- a/examples/library/ui_popover.ludic
+++ b/examples/library/ui_popover.ludic
@@ -4,55 +4,55 @@ import "ludic.ui"
import "ui_popover_parts/Verbs.ludic"
program UiPopover {
numbers float
- function frame(i: UiInput) -> UiNode {
- ui_input(i)
- ui_show("Verbs", null, 0.0, 0.0, 400.0, 400.0)
- let root: UiNode = ui_nodes("Verbs", null)
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ 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(x: float, y: float) -> void {
+ 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(d)
+ frame(ui_st, d)
let u = new UiInput
u.x = x
u.y = y
- frame(u)
+ frame(ui_st, u)
}
function said(root: UiNode) -> string {
let page = root.children[0]
return page.children[len(page.children) - 1].text
}
- entry {
- ui_backend(new UiBackend)
- var root = frame(new UiInput)
+ 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(cell.x + 2.0, cell.y + 2.0)
- root = frame(new UiInput)
+ 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(tab)
- frame(tab)
- root = frame(tab)
+ frame(ui_st, tab)
+ frame(ui_st, tab)
+ root = frame(ui_st, tab)
let enter = new UiInput
enter.enter = true
- root = frame(enter)
- root = frame(new UiInput)
+ root = frame(ui_st, enter)
+ root = frame(ui_st, new UiInput)
let first = said(root)
- click(cell.x + 2.0, cell.y + 2.0)
- root = frame(new UiInput)
+ click(ui_st, cell.x + 2.0, cell.y + 2.0)
+ root = frame(ui_st, new UiInput)
let opened = said(root)
- click(390.0, 390.0)
- root = frame(new UiInput)
+ click(ui_st, 390.0, 390.0)
+ root = frame(ui_st, new UiInput)
let outside = said(root)
- click(cell.x + 2.0, cell.y + 2.0)
+ click(ui_st, cell.x + 2.0, cell.y + 2.0)
let esc = new UiInput
esc.escape = true
- frame(esc)
- root = frame(new UiInput)
+ 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
index 127dd987..45b8c6c2 100644
--- a/examples/library/ui_popover_align.ludic
+++ b/examples/library/ui_popover_align.ludic
@@ -6,11 +6,11 @@ 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_backend(new UiBackend)
- ui_show("Menus", null, 0.0, 0.0, 400.0, 400.0)
- let root: UiNode = ui_nodes("Menus", null)
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ 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]
diff --git a/examples/library/ui_popover_mouse.ludic b/examples/library/ui_popover_mouse.ludic
index a8a4c9f8..921a0864 100644
--- a/examples/library/ui_popover_mouse.ludic
+++ b/examples/library/ui_popover_mouse.ludic
@@ -5,24 +5,24 @@ import "ludic.ui"
import "ui_popover_mouse_parts/Grid.ludic"
program UiPopoverMouse {
numbers float
- function frame(i: UiInput) -> UiNode {
- ui_input(i)
- ui_show("Grid", null, 0.0, 0.0, 400.0, 400.0)
- let root: UiNode = ui_nodes("Grid", null)
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ 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(x: float, y: float) -> string {
+ 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(d)
+ frame(ui_st, d)
let u = new UiInput
u.x = x
u.y = y
- frame(u)
- let root = frame(new UiInput)
+ frame(ui_st, u)
+ let root = frame(ui_st, new UiInput)
let page = root.children[0]
return page.children[len(page.children) - 1].text
}
@@ -34,19 +34,19 @@ program UiPopoverMouse {
}
return null
}
- entry {
- ui_backend(new UiBackend)
- frame(new UiInput)
- let opened = click(20.0, 20.0)
- let root = frame(new UiInput)
+ 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(drop.x + drop.cw / 2.0, drop.y + drop.ch / 2.0)
- click(20.0, 20.0)
- let on_cell = click(180.0, 90.0)
- click(20.0, 20.0)
- let on_nothing = click(390.0, 390.0)
+ 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_react.ludic b/examples/library/ui_react.ludic
index 576e71da..f33159d6 100644
--- a/examples/library/ui_react.ludic
+++ b/examples/library/ui_react.ludic
@@ -4,18 +4,20 @@
import "ludic.ui"
program UiReact {
numbers float
- var names: []string = new []string
- var volume: int = 3
- var mounted: int = 0
- var unmounted: int = 0
- var show_extra: bool = true
- view Mixer {
- names = names
- volume = volume
- extra = show_extra
- on set_volume(v: int) { volume = v }
- on mount() { mounted += 1 }
- on unmount() { unmounted += 1 }
+ state UireactState {
+ names: []string = new []string
+ volume: int = 3
+ mounted: int = 0
+ unmounted: int = 0
+ show_extra: bool = true
+ }
+ view Mixer (uireact_st: mut UireactState) {
+ names = uireact_st.names
+ volume = uireact_st.volume
+ extra = uireact_st.show_extra
+ on set_volume(v: int) { uireact_st.volume = v }
+ on mount() { uireact_st.mounted += 1 }
+ on unmount() { uireact_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 {
@@ -28,32 +30,32 @@ program UiReact {
if n.text != "" { push(out, n.text) }
for i in 0 .. len(n.children) { texts(n.children[i], out) }
}
- function frame() -> UiNode {
- ui_show("mix", view_mixer(), 0.0, 0.0, 400.0, 400.0)
- let root: UiNode = ui_nodes("mix", view_mixer())
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ 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_native("dial", fn dial_measure, fn dial_draw)
- push(names, "bass")
- push(names, "drums")
- ui_load_text(PAGE, "ui/mix.xml")
- var root = frame()
- ui_press(root.children[2])
- names[0] = "drums"
- names[1] = "bass"
- root = frame()
+ entry (ui_st: mut UiState, uireact_st: mut UireactState) {
+ ui_native(ui_st, "dial", fn dial_measure, fn dial_draw)
+ push(uireact_st.names, "bass")
+ push(uireact_st.names, "drums")
+ ui_load_text(ui_st, PAGE, "ui/mix.xml")
+ var root = frame(ui_st)
+ ui_press(ui_st, root.children[2])
+ uireact_st.names[0] = "drums"
+ uireact_st.names[1] = "bass"
+ root = frame(ui_st)
let dial = root.children[3]
- ui_event(dial, "change", Value.int(volume + 4))
- ui_event(root.children[4], "change", Value.bool(1))
- show_extra = false
- root = frame()
+ ui_event(ui_st, dial, "change", Value.int(uireact_st.volume + 4))
+ ui_event(ui_st, root.children[4], "change", Value.bool(1))
+ uireact_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 {volume}; dial {int(dial.cw)}x{int(dial.ch)}; mounted {mounted}, unmounted {unmounted}`)
+ print(`volume {uireact_st.volume}; dial {int(dial.cw)}x{int(dial.ch)}; mounted {uireact_st.mounted}, unmounted {uireact_st.unmounted}`)
}
}
diff --git a/examples/library/ui_reload.ludic b/examples/library/ui_reload.ludic
index aad448a9..9084d431 100644
--- a/examples/library/ui_reload.ludic
+++ b/examples/library/ui_reload.ludic
@@ -9,21 +9,21 @@ program UiReload {
const PATH: string = "build/ui_reload_example.xml"
const V1: string = ""
const V2: string = "{title} again {n}
"
- function shown() -> string {
- let root: UiNode = ui_nodes("note", view_note())
+ 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 {
+ entry (ui_st: mut UiState) {
Fs.write_text(PATH, V1)
- ui_load(PATH)
- let root: UiNode = ui_nodes("note", view_note())
- ui_press(root.children[0])
- let before = shown()
- let same = ui_reload()
+ 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()
- print(`{before} | reloaded {same} then {changed} | {shown()}`)
+ let changed = ui_reload(ui_st)
+ print(`{before} | reloaded {same} then {changed} | {shown(ui_st)}`)
}
}
diff --git a/examples/library/ui_scroll_hold.ludic b/examples/library/ui_scroll_hold.ludic
index 6808ae19..70e2811a 100644
--- a/examples/library/ui_scroll_hold.ludic
+++ b/examples/library/ui_scroll_hold.ludic
@@ -4,29 +4,29 @@ import "ludic.ui"
import "ui_scroll_hold_parts/Held.ludic"
program UiScrollHold {
numbers float
- function frame(x: float, y: float, wheel: float) -> UiNode {
+ 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(i)
- ui_show("Held", null, 0.0, 0.0, 400.0, 600.0)
- let root: UiNode = ui_nodes("Held", null)
- ui_place(root, 0.0, 0.0, 400.0, 600.0)
+ 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_backend(new UiBackend)
- let p = frame(-1.0, -1.0, 0.0)
- let first = at(frame(-1.0, -1.0, 0.0))
- frame(p.children[0].x + 5.0, p.children[0].y + 5.0, 1.0)
- let followed = at(frame(-1.0, -1.0, 0.0))
- frame(p.children[1].x + 5.0, p.children[1].y + 5.0, -1.0)
- let fixed = at(frame(-1.0, -1.0, 0.0))
- ui_scroll_set("free", 70.0)
- let set = at(frame(-1.0, -1.0, 0.0))
- frame(p.children[2].x + 5.0, p.children[2].y + 5.0, 1.0)
- print(`{first} | {followed} | {fixed} | {set} | {at(frame(-1.0, -1.0, 0.0))}`)
+ 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_scrollbar.ludic b/examples/library/ui_scrollbar.ludic
index b411078e..cbf644e8 100644
--- a/examples/library/ui_scrollbar.ludic
+++ b/examples/library/ui_scrollbar.ludic
@@ -5,48 +5,50 @@ import "ludic.ui"
import "ui_scrollbar_parts/Long.ludic"
program UiScrollbar {
numbers float
- var bx: float = 0.0
- var by: float = 0.0
- function frame(i: UiInput) -> UiNode {
- ui_input(i)
- ui_show("Long", null, 0.0, 0.0, 400.0, 400.0)
- let root: UiNode = ui_nodes("Long", null)
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ 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(x: float, y: float, down: bool) -> int {
+ function at(ui_st: mut UiState, uiscrollbar_st: UiscrollbarState, x: float, y: float, down: bool) -> int {
let i = new UiInput
- i.x = bx + x
- i.y = by + y
+ i.x = uiscrollbar_st.bx + x
+ i.y = uiscrollbar_st.by + y
i.down = down
- return int(frame(i).children[0].scroll_y)
+ return int(frame(ui_st, i).children[0].scroll_y)
}
- function wheel(x: float, y: float) -> int {
+ function wheel(ui_st: mut UiState, uiscrollbar_st: UiscrollbarState, x: float, y: float) -> int {
let i = new UiInput
- i.x = bx + x
- i.y = by + y
+ i.x = uiscrollbar_st.bx + x
+ i.y = uiscrollbar_st.by + y
i.wheel = 1.0
- frame(i)
- return int(frame(new UiInput).children[0].scroll_y)
+ frame(ui_st, i)
+ return int(frame(ui_st, new UiInput).children[0].scroll_y)
}
- entry {
- ui_backend(new UiBackend)
- let list = frame(new UiInput).children[0]
- bx = list.x
- by = list.y
- let grab = at(195.0, 10.0, true)
- let moved = at(195.0, 40.0, true)
- let far = at(195.0, 300.0, true)
- let back = at(195.0, 25.0, true)
- let kept = at(195.0, 25.0, false)
- at(0.0 - bx - 1.0, 0.0 - by - 1.0, false)
- let jump = at(195.0, 80.0, true)
- at(195.0, 80.0, false)
- let wheeled = wheel(50.0, 50.0)
- at(50.0, 20.0, true)
- at(50.0, 20.0, false)
- let page = frame(new UiInput)
+ entry (ui_st: mut UiState, uiscrollbar_st: mut UiscrollbarState) {
+ ui_backend(ui_st, new UiBackend)
+ let list = frame(ui_st, new UiInput).children[0]
+ uiscrollbar_st.bx = list.x
+ uiscrollbar_st.by = list.y
+ let grab = at(ui_st, uiscrollbar_st, 195.0, 10.0, true)
+ let moved = at(ui_st, uiscrollbar_st, 195.0, 40.0, true)
+ let far = at(ui_st, uiscrollbar_st, 195.0, 300.0, true)
+ let back = at(ui_st, uiscrollbar_st, 195.0, 25.0, true)
+ let kept = at(ui_st, uiscrollbar_st, 195.0, 25.0, false)
+ at(ui_st, uiscrollbar_st, 0.0 - uiscrollbar_st.bx - 1.0, 0.0 - uiscrollbar_st.by - 1.0, false)
+ let jump = at(ui_st, uiscrollbar_st, 195.0, 80.0, true)
+ at(ui_st, uiscrollbar_st, 195.0, 80.0, false)
+ let wheeled = wheel(ui_st, uiscrollbar_st, 50.0, 50.0)
+ at(ui_st, uiscrollbar_st, 50.0, 20.0, true)
+ at(ui_st, uiscrollbar_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_submit.ludic b/examples/library/ui_submit.ludic
index 491f7de8..debe24e6 100644
--- a/examples/library/ui_submit.ludic
+++ b/examples/library/ui_submit.ludic
@@ -3,43 +3,45 @@
import "ludic.ui"
program UiSubmit {
numbers float
- var said: string = ""
- var chat: string = ""
- var name: string = ""
- view Chat {
- chat = chat
- name = name
- on set_chat(v: string) { chat = v }
- on set_name(v: string) { name = v }
- on send(v: string) { said = said + "[" + v + "]" }
+ state UisubmitState {
+ said: string = ""
+ chat: string = ""
+ name: string = ""
+ }
+ view Chat (uisubmit_st: mut UisubmitState) {
+ chat = uisubmit_st.chat
+ name = uisubmit_st.name
+ on set_chat(v: string) { uisubmit_st.chat = v }
+ on set_name(v: string) { uisubmit_st.name = v }
+ on send(v: string) { uisubmit_st.said = uisubmit_st.said + "[" + v + "]" }
}
const PAGE: string = ""
- function frame(i: UiInput) -> void {
- ui_input(i)
- ui_show("c", view_chat(), 0.0, 0.0, 400.0, 400.0)
+ function frame(ui_st: mut UiState, i: UiInput) -> void {
+ ui_input(ui_st, i)
+ ui_show(ui_st, "c", view_chat(), 0.0, 0.0, 400.0, 400.0)
}
- function press(tab: bool, enter: bool, pad: bool, typed: string) -> void {
+ function press(ui_st: mut UiState, tab: bool, enter: bool, pad: bool, typed: string) -> void {
let i = new UiInput
i.tab = tab
i.enter = enter
i.pad_a = pad
i.typed = typed
- frame(i)
- frame(new UiInput)
+ frame(ui_st, i)
+ frame(ui_st, new UiInput)
}
- entry {
- ui_backend(new UiBackend)
- ui_load_text(PAGE, "c.xml")
- frame(new UiInput)
- press(true, false, false, "")
- press(false, false, false, "hi")
- press(false, true, false, "")
- let after = `{said} chat '{chat}'`
- press(false, false, false, "yo")
- press(false, false, true, "")
- press(true, false, false, "")
- press(false, false, false, "Ada")
- press(false, true, false, "")
- print(`{after} | {said} chat '{chat}' name '{name}'`)
+ entry (ui_st: mut UiState, uisubmit_st: UisubmitState) {
+ ui_backend(ui_st, new UiBackend)
+ ui_load_text(ui_st, PAGE, "c.xml")
+ frame(ui_st, new UiInput)
+ press(ui_st, true, false, false, "")
+ press(ui_st, false, false, false, "hi")
+ press(ui_st, false, true, false, "")
+ let after = `{uisubmit_st.said} chat '{uisubmit_st.chat}'`
+ press(ui_st, false, false, false, "yo")
+ press(ui_st, false, false, true, "")
+ press(ui_st, true, false, false, "")
+ press(ui_st, false, false, false, "Ada")
+ press(ui_st, false, true, false, "")
+ print(`{after} | {uisubmit_st.said} chat '{uisubmit_st.chat}' name '{uisubmit_st.name}'`)
}
}
diff --git a/examples/library/ui_templates.ludic b/examples/library/ui_templates.ludic
index 1a380ad6..63ee09cd 100644
--- a/examples/library/ui_templates.ludic
+++ b/examples/library/ui_templates.ludic
@@ -9,40 +9,42 @@ program UiTemplates {
name: string = ""
cost: int = 0
}
- var stock: []Item = new []Item
- var purse: int = 100
- var bought: []int = new []int
- function add(name: string, cost: int) -> void {
+ state UitemplatesState {
+ stock: []Item = new []Item
+ purse: int = 100
+ bought: []int = new []int
+ }
+ function add(uitemplates_st: mut UitemplatesState, name: string, cost: int) -> void {
let it = new Item
it.name = name
it.cost = cost
- push(stock, it)
+ push(uitemplates_st.stock, it)
}
- view Shop {
- purse = purse
- stock = stock
- owned: int = len(bought)
- function afford(i: int) -> bool { return purse >= stock[i].cost }
+ view Shop (uitemplates_st: mut UitemplatesState) {
+ purse = uitemplates_st.purse
+ stock = uitemplates_st.stock
+ owned: int = len(uitemplates_st.bought)
+ function afford(i: int) -> bool { return uitemplates_st.purse >= uitemplates_st.stock[i].cost }
on buy(i: int) {
- purse -= stock[i].cost
- push(bought, i)
+ uitemplates_st.purse -= uitemplates_st.stock[i].cost
+ push(uitemplates_st.bought, i)
}
}
const KIT: string = "\n${cost}\n\n \n \n {item.name} -\n \n \n {picks}\n \n
\n\n\n"
const PAGE: string = "\n\n\n \n The purse: ${purse}\n \n \n \n Nothing bought yet\n {owned} bought, the last {pick + 1}\n\n\n"
const PRIVATE: string = ""
- entry {
- add("Rope", 30)
- add("Lantern", 80)
- ui_load_text(KIT, "ui/kit.xml")
- ui_load_text(PAGE, "ui/shop.xml")
- ui_load_text(PRIVATE, "ui/peek.xml")
- var root: UiNode = ui_nodes("shop", view_shop())
- ui_place(root, 0.0, 0.0, 600.0, 400.0)
+ entry (ui_st: mut UiState, uitemplates_st: mut UitemplatesState) {
+ add(uitemplates_st, "Rope", 30)
+ add(uitemplates_st, "Lantern", 80)
+ ui_load_text(ui_st, KIT, "ui/kit.xml")
+ ui_load_text(ui_st, PAGE, "ui/shop.xml")
+ ui_load_text(ui_st, PRIVATE, "ui/peek.xml")
+ var root: UiNode = ui_nodes(ui_st, "shop", view_shop())
+ ui_place(ui_st, root, 0.0, 0.0, 600.0, 400.0)
let press = root.children[1].children[4]
- ui_press(ui_hit(root, press.x + 1.0, press.y + 1.0))
- root = ui_nodes("shop", view_shop())
- ui_place(root, 0.0, 0.0, 600.0, 400.0)
- print(`purse {purse}, bought {len(bought)}; the lantern is enabled: {root.children[2].children[4].enabled}`)
+ ui_press(ui_st, ui_hit(root, press.x + 1.0, press.y + 1.0))
+ root = ui_nodes(ui_st, "shop", view_shop())
+ ui_place(ui_st, root, 0.0, 0.0, 600.0, 400.0)
+ print(`purse {uitemplates_st.purse}, bought {len(uitemplates_st.bought)}; the lantern is enabled: {root.children[2].children[4].enabled}`)
}
}
diff --git a/examples/library/ui_text.ludic b/examples/library/ui_text.ludic
index 73b74967..ec96f882 100644
--- a/examples/library/ui_text.ludic
+++ b/examples/library/ui_text.ludic
@@ -5,19 +5,21 @@ import "ludic.ui"
import "ui_text_parts/Fit.ludic"
program UiText {
numbers float
- var said: []string = new []string
- function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, `{s}@{int(size * 10.0)} y{int(y)}`) }
+ state UitextState {
+ said: []string = new []string
+ }
+ function rec_text(uitext_st: mut UitextState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(uitext_st.said, `{s}@{int(size * 10.0)} y{int(y)}`) }
function w(s: string, size: float) -> float { return float(len(s)) * size * 0.5 }
- entry {
+ entry (ui_st: mut UiState, uitext_st: UitextState) {
let b = new UiBackend
b.text = fn rec_text
b.measure = fn w
- ui_backend(b)
- ui_show("Fit", null, 0.0, 0.0, 400.0, 600.0)
- let root: UiNode = ui_nodes("Fit", null)
- ui_place(root, 0.0, 0.0, 400.0, 600.0)
+ ui_backend(ui_st, b)
+ ui_show(ui_st, "Fit", null, 0.0, 0.0, 400.0, 600.0)
+ let root: UiNode = ui_nodes(ui_st, "Fit", null)
+ ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0)
let page = root.children[0]
let lines = page.children[3]
- print(`{said[0]} | {said[1]} | {said[2]} | lines {len(lines.lines)} {int(lines.mh)} {said[3]} {said[4]} | tall {int(page.children[4].mh)} | em {int(page.children[5].pt)} {int(page.children[6].pt)} | tie {ui_hex(page.children[7].fg)}`)
+ print(`{uitext_st.said[0]} | {uitext_st.said[1]} | {uitext_st.said[2]} | lines {len(lines.lines)} {int(lines.mh)} {uitext_st.said[3]} {uitext_st.said[4]} | tall {int(page.children[4].mh)} | em {int(page.children[5].pt)} {int(page.children[6].pt)} | tie {ui_hex(page.children[7].fg)}`)
}
}
diff --git a/examples/library/ui_theme.ludic b/examples/library/ui_theme.ludic
index 11600312..dc86c079 100644
--- a/examples/library/ui_theme.ludic
+++ b/examples/library/ui_theme.ludic
@@ -5,10 +5,10 @@ import "ui_theme_parts/Bare.ludic"
import "ui_theme_parts/Shell.ludic"
program UiTheme {
numbers float
- entry {
- ui_backend(new UiBackend)
- let root: UiNode = ui_nodes("Shell", null)
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ entry (ui_st: mut UiState) {
+ ui_backend(ui_st, new UiBackend)
+ let root: UiNode = ui_nodes(ui_st, "Shell", null)
+ ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
let bare = root.children[0].children[0]
print(`accent {ui_hex(bare.children[0].fg)} gap {int(bare.gap)}`)
}
diff --git a/examples/library/ui_translate.ludic b/examples/library/ui_translate.ludic
index 2370a584..7fac808c 100644
--- a/examples/library/ui_translate.ludic
+++ b/examples/library/ui_translate.ludic
@@ -4,8 +4,10 @@
import "ludic.ui"
program UiTranslate {
numbers float
- var said: []string = new []string
- var now: float = 0.0
+ state UitranslateState {
+ said: []string = new []string
+ now: float = 0.0
+ }
const PAGE: string = "Hello
Hello
a
b
"
function tr(s: string) -> string {
if s == "Hello" { return "Merhaba" }
@@ -14,31 +16,31 @@ program UiTranslate {
if s == "Line two" { return "Satir iki" }
return s
}
- function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, s) }
- function clock() -> float { return now }
- function tip(root: UiNode, k: int) -> string {
+ function rec_text(uitranslate_st: mut UitranslateState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(uitranslate_st.said, s) }
+ function clock(uitranslate_st: UitranslateState) -> float { return uitranslate_st.now }
+ function tip(ui_st: mut UiState, uitranslate_st: mut UitranslateState, root: UiNode, k: int) -> string {
let n = root.children[k]
let i = new UiInput
i.x = n.x + 1.0
i.y = n.y + 1.0
- ui_input(i)
- ui_show("t", null, 0.0, 0.0, 400.0, 400.0)
- now = now + 1.0
- said = new []string
- ui_input(i)
- ui_show("t", null, 0.0, 0.0, 400.0, 400.0)
- return `{said[len(said) - 2]}/{said[len(said) - 1]}`
+ ui_input(ui_st, i)
+ ui_show(ui_st, "t", null, 0.0, 0.0, 400.0, 400.0)
+ uitranslate_st.now = uitranslate_st.now + 1.0
+ uitranslate_st.said = new []string
+ ui_input(ui_st, i)
+ ui_show(ui_st, "t", null, 0.0, 0.0, 400.0, 400.0)
+ return `{uitranslate_st.said[len(uitranslate_st.said) - 2]}/{uitranslate_st.said[len(uitranslate_st.said) - 1]}`
}
- entry {
+ entry (ui_st: mut UiState, uitranslate_st: mut UitranslateState) {
let b = new UiBackend
b.text = fn rec_text
- ui_backend(b)
- ui_translator(fn tr)
- ui_clock(fn clock)
- ui_load_text(PAGE, "t.xml")
- let root: UiNode = ui_nodes("t", null)
- ui_place(root, 0.0, 0.0, 400.0, 400.0)
+ ui_backend(ui_st, b)
+ ui_translator(ui_st, fn tr)
+ ui_clock(ui_st, fn clock)
+ ui_load_text(ui_st, PAGE, "t.xml")
+ let root: UiNode = ui_nodes(ui_st, "t", null)
+ ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0)
let texts = `{root.children[0].text} {root.children[1].text} {root.children[2].children[0].text} {root.children[2].children[1].text}`
- print(`{texts} | {tip(root, 3)} | {tip(root, 4)} | {tip(root, 5)}`)
+ print(`{texts} | {tip(ui_st, uitranslate_st, root, 3)} | {tip(ui_st, uitranslate_st, root, 4)} | {tip(ui_st, uitranslate_st, root, 5)}`)
}
}
diff --git a/examples/library/ui_zoom.ludic b/examples/library/ui_zoom.ludic
index 923a8a1a..0cdd46f6 100644
--- a/examples/library/ui_zoom.ludic
+++ b/examples/library/ui_zoom.ludic
@@ -6,12 +6,12 @@ program UiZoom {
numbers float
const STYLE: string = ""
const BODY: string = ""
- entry {
- ui_backend(new UiBackend)
- ui_load_text("" + STYLE + BODY + "", "z.xml")
- ui_show("z", null, 0.0, 0.0, 400.0, 600.0)
- let r: UiNode = ui_nodes("z", null)
- ui_place(r, 0.0, 0.0, 400.0, 600.0)
+ entry (ui_st: mut UiState) {
+ ui_backend(ui_st, new UiBackend)
+ ui_load_text(ui_st, "" + STYLE + BODY + "", "z.xml")
+ ui_show(ui_st, "z", null, 0.0, 0.0, 400.0, 600.0)
+ let r: UiNode = ui_nodes(ui_st, "z", null)
+ ui_place(ui_st, r, 0.0, 0.0, 400.0, 600.0)
let plain = r.children[0]
let half = r.children[1]
let inner = half.children[1].children[0]
diff --git a/examples/modules/bank/ledger.ludic b/examples/modules/bank/ledger.ludic
index 24cb4d30..4c86821a 100644
--- a/examples/modules/bank/ledger.ludic
+++ b/examples/modules/bank/ledger.ludic
@@ -1,13 +1,15 @@
# bank/ledger.ludic - inherits `module bank` from the barrel that imports it
-var balance: int = 0
-export event Deposited { amount: int }
-function add(n: int) -> void {
- balance += n
+export state BankState {
+ balance: int = 0
}
-export function deposit(n: int) -> void {
- add(n)
+export event Deposited { amount: int }
+function add(bank_st: mut BankState, n: int) -> void {
+ bank_st.balance += n
+}
+export function deposit(bank_st: mut BankState, n: int) -> void {
+ add(bank_st, n)
emit Deposited(amount: n)
}
-export function total() -> int {
- return balance
+export function total(bank_st: BankState) -> int {
+ return bank_st.balance
}
diff --git a/examples/modules/friend_of.ludic b/examples/modules/friend_of.ludic
index a5de3846..2fa5942c 100644
--- a/examples/modules/friend_of.ludic
+++ b/examples/modules/friend_of.ludic
@@ -5,8 +5,8 @@
import "bank"
program FriendOf {
friend module lab of bank
- entry {
- add(7)
- print(`{balance} {total()}`)
+ entry (bank_st: mut BankState) {
+ add(bank_st, 7)
+ print(`{bank_st.balance} {total(bank_st)}`)
}
}
diff --git a/examples/modules/kinds/fishing/index.ludic b/examples/modules/kinds/fishing/index.ludic
index e8b3ef2c..518ad6ab 100644
--- a/examples/modules/kinds/fishing/index.ludic
+++ b/examples/modules/kinds/fishing/index.ludic
@@ -4,17 +4,19 @@ property Catch {
fish: int = 0
}
event Landed { fish: int }
-var landed: int = 0
-var last: Catch = null
-@On(Landed) handler Count { landed += fish }
+export state FishingState {
+ landed: int = 0
+ last: Catch = null
+}
+@On(Landed) handler Count(fishing_st: mut FishingState) { fishing_st.landed += fish }
function catch_new(n: int) -> Catch {
let c = new Catch
c.fish = n
return c
}
-export function fishing_day() -> int {
- last = catch_new(3)
- emit Landed(fish: last.fish)
+export function fishing_day(fishing_st: mut FishingState) -> int {
+ fishing_st.last = catch_new(3)
+ emit Landed(fish: fishing_st.last.fish)
emit Landed(fish: 4)
- return landed
+ return fishing_st.landed
}
diff --git a/examples/modules/kinds/hunting/index.ludic b/examples/modules/kinds/hunting/index.ludic
index a7a73d5b..ba9a9b35 100644
--- a/examples/modules/kinds/hunting/index.ludic
+++ b/examples/modules/kinds/hunting/index.ludic
@@ -5,11 +5,13 @@ property Catch {
weight: float = 0.0
}
event Landed { name: string }
-var heard: string = ""
-@On(Landed) handler Hear { heard = heard + name }
-export function hunting_day() -> string {
+export state HuntingState {
+ heard: string = ""
+}
+@On(Landed) handler Hear(hunting_st: mut HuntingState) { hunting_st.heard = hunting_st.heard + name }
+export function hunting_day(hunting_st: HuntingState) -> string {
let c = new Catch
c.name = "hare"
emit Landed(name: c.name)
- return heard
+ return hunting_st.heard
}
diff --git a/examples/modules/port_vars.ludic b/examples/modules/port_vars.ludic
index ed0f1605..fd5cd1ee 100644
--- a/examples/modules/port_vars.ludic
+++ b/examples/modules/port_vars.ludic
@@ -8,15 +8,17 @@ program PortVars {
money: fn() -> int
price: fn(int) -> int
}
- var g_money: int = 40
- var g_rate: int = 3
- function priced(n: int) -> int {
- return n * g_rate
+ state PortvarsState {
+ g_money: int = 40
+ g_rate: int = 3
}
- bind Purse { money: g_money, price: fn priced }
- entry {
+ function priced(portvars_st: PortvarsState, n: int) -> int {
+ return n * portvars_st.g_rate
+ }
+ bind Purse { money: PortvarsState.g_money, price: fn priced }
+ entry (portvars_st: mut PortvarsState) {
let a = Purse.money()
- g_money += 5
+ portvars_st.g_money += 5
print(`{a} {Purse.money()} {Purse.price(4)}`)
}
}
diff --git a/examples/modules/ports.ludic b/examples/modules/ports.ludic
index f8d640a5..5520ef17 100644
--- a/examples/modules/ports.ludic
+++ b/examples/modules/ports.ludic
@@ -7,9 +7,9 @@ import "station/units"
import "station/clock"
import "station/app"
program Ports {
- entry {
+ entry (app_st: mut AppState) {
let a = Clock.now()
- advance(-25)
+ advance(app_st, -25)
print(`{a} {hour_of_day()}`)
}
}
diff --git a/examples/modules/private_kinds.ludic b/examples/modules/private_kinds.ludic
index 91280678..f3247887 100644
--- a/examples/modules/private_kinds.ludic
+++ b/examples/modules/private_kinds.ludic
@@ -6,7 +6,7 @@
import "kinds/fishing"
import "kinds/hunting"
program PrivateKinds {
- entry {
- print(`{fishing_day()} {hunting_day()}`)
+ entry (fishing_st: mut FishingState, hunting_st: HuntingState) {
+ print(`{fishing_day(fishing_st)} {hunting_day(hunting_st)}`)
}
}
diff --git a/examples/modules/private_twins.ludic b/examples/modules/private_twins.ludic
index c87ce4fe..01f1a219 100644
--- a/examples/modules/private_twins.ludic
+++ b/examples/modules/private_twins.ludic
@@ -9,7 +9,7 @@ program PrivateTwins {
function seed() -> int {
return 7
}
- entry {
- print(`{shop_seed()} {weather_seed()} {seed()}`)
+ entry (shop_st: ShopState, weather_st: WeatherState) {
+ print(`{shop_seed(shop_st)} {weather_seed(weather_st)} {seed()}`)
}
}
diff --git a/examples/modules/station/app/index.ludic b/examples/modules/station/app/index.ludic
index 568e24f9..982eec93 100644
--- a/examples/modules/station/app/index.ludic
+++ b/examples/modules/station/app/index.ludic
@@ -1,10 +1,12 @@
# app/index.ludic - the app puts the program together: it sees the port and fills it
module app uses clock
bind Clock { now: fn game_hours }
-var g_hours: int = 30
-function game_hours() -> int {
- return g_hours
+export state AppState {
+ g_hours: int = 30
}
-export function advance(h: int) -> void {
- g_hours += h
+function game_hours(app_st: AppState) -> int {
+ return app_st.g_hours
+}
+export function advance(app_st: mut AppState, h: int) -> void {
+ app_st.g_hours += h
}
diff --git a/examples/modules/tangle.ludic b/examples/modules/tangle.ludic
index c21888ea..c019dbfd 100644
--- a/examples/modules/tangle.ludic
+++ b/examples/modules/tangle.ludic
@@ -7,8 +7,8 @@ import "tangle/c"
import "tangle/a"
import "tangle/b"
program Tangle {
- entry {
- a_bump()
- print(a_run() + b_rec.n - 1)
+ entry (b_st: mut BState) {
+ a_bump(b_st)
+ print(a_run(b_st) + b_st.b_rec.n - 1)
}
}
diff --git a/examples/modules/tangle/a/index.ludic b/examples/modules/tangle/a/index.ludic
index f74836e1..7107561d 100644
--- a/examples/modules/tangle/a/index.ludic
+++ b/examples/modules/tangle/a/index.ludic
@@ -1,13 +1,13 @@
# a/index.ludic - uses b, and writes b's global from outside it
module a
-export function a_run() -> int {
- b_count = b_count + 1
+export function a_run(b_st: mut BState) -> int {
+ b_st.b_count = b_st.b_count + 1
return b_run() + c_one()
}
export function a_two() -> int {
return 2
}
-export function a_bump() -> void {
- let r = b_rec
+export function a_bump(b_st: mut BState) -> void {
+ let r = b_st.b_rec
r.n = r.n + 1
}
diff --git a/examples/modules/tangle/b/index.ludic b/examples/modules/tangle/b/index.ludic
index 67a925f5..9d4501f8 100644
--- a/examples/modules/tangle/b/index.ludic
+++ b/examples/modules/tangle/b/index.ludic
@@ -1,10 +1,12 @@
# b/index.ludic - uses a back: a and b are a cycle
module b
-export var b_count: int = 0
+export state BState {
+ b_count: int = 0
+ b_rec: Tally = new Tally
+}
export property Tally {
n: int = 0
}
-export var b_rec: Tally = new Tally
export function b_run() -> int {
return a_two() + c_one()
}
diff --git a/examples/modules/twins/shop/index.ludic b/examples/modules/twins/shop/index.ludic
index adc8e0ed..dac15388 100644
--- a/examples/modules/twins/shop/index.ludic
+++ b/examples/modules/twins/shop/index.ludic
@@ -1,10 +1,12 @@
# shop/index.ludic - private names a neighbour also uses: seed, dice, LIMIT
module shop
-var dice: int = 100
+export state ShopState {
+ dice: int = 100
+}
const LIMIT: int = 5
-function seed() -> int {
- return dice + LIMIT
+function seed(shop_st: ShopState) -> int {
+ return shop_st.dice + LIMIT
}
-export function shop_seed() -> int {
- return seed()
+export function shop_seed(shop_st: ShopState) -> int {
+ return seed(shop_st)
}
diff --git a/examples/modules/twins/weather/index.ludic b/examples/modules/twins/weather/index.ludic
index 560fe306..f0da9a29 100644
--- a/examples/modules/twins/weather/index.ludic
+++ b/examples/modules/twins/weather/index.ludic
@@ -1,13 +1,15 @@
# weather/index.ludic - the same private spellings, its own; a local named dice is its own too
module weather
-var dice: int = 20
+export state WeatherState {
+ dice: int = 20
+}
const LIMIT: int = 2
-function seed() -> int {
- return dice * LIMIT
+function seed(weather_st: WeatherState) -> int {
+ return weather_st.dice * LIMIT
}
function roll(dice: int) -> int {
return dice + 1
}
-export function weather_seed() -> int {
- return seed() + roll(0)
+export function weather_seed(weather_st: WeatherState) -> int {
+ return seed(weather_st) + roll(0)
}
diff --git a/examples/modules/visible.ludic b/examples/modules/visible.ludic
index 915a5b3b..73d27c61 100644
--- a/examples/modules/visible.ludic
+++ b/examples/modules/visible.ludic
@@ -4,11 +4,13 @@
# Running it prints: 42 42
import "bank"
program Visible {
- var heard: int = 0
- @On(Deposited) handler Heard { heard += amount }
- entry {
- deposit(30)
- deposit(12)
- print(`{total()} {heard}`)
+ state VisibleState {
+ heard: int = 0
+ }
+ @On(Deposited) handler Heard(visible_st: mut VisibleState) { visible_st.heard += amount }
+ entry (bank_st: mut BankState, visible_st: VisibleState) {
+ deposit(bank_st, 30)
+ deposit(bank_st, 12)
+ print(`{total(bank_st)} {visible_st.heard}`)
}
}
diff --git a/examples/rendering/gl_triangle.ludic b/examples/rendering/gl_triangle.ludic
index ca88eaf2..b20ecab6 100644
--- a/examples/rendering/gl_triangle.ludic
+++ b/examples/rendering/gl_triangle.ludic
@@ -7,19 +7,21 @@ program GlTriangle {
property Marker { on: int = 1 }
model Anchor { Marker }
- var prog: int = 0
- var vao: int = 0
- var frame: int = 0
+ state GltriangleState {
+ prog: int = 0
+ vao: int = 0
+ frame: int = 0
+ }
const VS: string = "#version 410 core\nlayout(location=0) in vec2 p; layout(location=1) in vec3 c; out vec3 vc;\nvoid main(){ vc = c; gl_Position = vec4(p, 0.0, 1.0); }\n"
const FS: string = "#version 410 core\nin vec3 vc; out vec4 o;\nvoid main(){ o = vec4(vc, 1.0); }\n"
- handler Boot phase Start {
+ handler Boot(gltriangle_st: mut GltriangleState) phase Start {
spawn Anchor {}
if not Gl.open(width: 640, height: 360, title: "Ludic GL") { print("no GL context"); quit() }
print(Gl.get_string(name: GL_VERSION))
- prog = Gl.program(vs: VS, fs: FS)
- vao = Gl.vao()
+ gltriangle_st.prog = Gl.program(vs: VS, fs: FS)
+ gltriangle_st.vao = Gl.vao()
let vbo = Gl.buffer()
Gl.bind_buffer(target: GL_ARRAY_BUFFER, buffer: vbo)
let v = Gl.floats(15)
@@ -34,16 +36,16 @@ program GlTriangle {
Gl.check(tag: "boot")
}
- handler Draw phase Render {
+ handler Draw(gltriangle_st: mut GltriangleState) phase Render {
Gl.bind_framebuffer(target: GL_FRAMEBUFFER, framebuffer: Gl.screen_fbo())
Gl.viewport(x: 0, y: 0, width: Gl.width(), height: Gl.height())
Gl.clear_color(red: 0.1, green: 0.12, blue: 0.18, alpha: 1.0)
Gl.clear(mask: GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
- Gl.use_program(prog: prog)
- Gl.bind_vertex_array(array: vao)
+ Gl.use_program(prog: gltriangle_st.prog)
+ Gl.bind_vertex_array(array: gltriangle_st.vao)
Gl.draw_arrays(mode: GL_TRIANGLES, first: 0, count: 3)
- frame += 1
- if frame == 1 { Gl.screenshot(path: "build/gl_triangle.ppm") }
+ gltriangle_st.frame += 1
+ if gltriangle_st.frame == 1 { Gl.screenshot(path: "build/gl_triangle.ppm") }
Gl.swap()
}
}
diff --git a/examples/rendering/reload.ludic b/examples/rendering/reload.ludic
index 46232ee1..f60f18b9 100644
--- a/examples/rendering/reload.ludic
+++ b/examples/rendering/reload.ludic
@@ -14,8 +14,8 @@ program Reload {
# A program only links the GL runtime render3d stands on when it names Gl.* itself
# (the parser's g_uses_gl), so even a check that draws nothing presents its frame.
# render3d calls back into the program for what stands on the terrain
- function scene_draw() -> void { scatter_draw() }
- function scene_draw_casters(light_vp: floats) -> void { scatter_draw_casters(light_vp) }
+ function scene_draw(render3d_st: mut Render3dState) -> void { scatter_draw(render3d_st) }
+ function scene_draw_casters(render3d_st: mut Render3dState, light_vp: floats) -> void { scatter_draw_casters(render3d_st, light_vp) }
# no streamed cover here, but stream.ludic names the generator, so every program supplies one
function stream_fill(s: Stream, cx: int, cz: int, band: int) -> void { }
@@ -27,60 +27,60 @@ program Reload {
if not cond { print(`reload: FAILED - {what}`) }
return cond
}
- function populate() -> void {
- let l = layer_cards(model_cross_card(), 1000, F_ZERO, fi(400))
+ function populate(render3d_st: mut Render3dState) -> void {
+ let l = layer_cards(render3d_st, model_cross_card(render3d_st), 1000, F_ZERO, fi(400))
for i in 0 .. 400 {
let x = fi(i * 2 - 400)
- layer_add(l, x, terrain_height(x, fi(30)), fi(30), F_ONE, F_ZERO, F_ZERO, F_ZERO)
- col_add(x, fi(30), F_ONE)
+ layer_add(l, x, terrain_height(render3d_st, x, fi(30)), fi(30), F_ONE, F_ZERO, F_ZERO, F_ZERO)
+ col_add(render3d_st, x, fi(30), F_ONE)
}
- col_build()
+ col_build(render3d_st)
}
- handler Boot phase Start {
+ handler Boot(render3d_st: mut Render3dState) phase Start {
spawn Anchor {}
- r3d_on_draw(fn scene_draw)
- r3d_on_casters(fn scene_draw_casters)
- r3d_on_stream_fill(fn stream_fill)
+ r3d_on_draw(render3d_st, fn scene_draw)
+ r3d_on_casters(render3d_st, fn scene_draw_casters)
+ r3d_on_stream_fill(render3d_st, fn stream_fill)
TERRAIN_HALF = 1000
ter_smooth = true
- if not r3d_init(640, 360, "Reload") {
+ if not r3d_init(render3d_st, 640, 360, "Reload") {
quit()
return
}
- cam_set(F_ZERO, fi(80), fi(300), fi(180), f_neg(fi(10)))
- water_init(fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200))
- populate()
+ cam_set(render3d_st, F_ZERO, fi(80), fi(300), fi(180), f_neg(fi(10)))
+ water_init(render3d_st, fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200))
+ populate(render3d_st)
var ok = true
ok = check("the first map has layers and colliders", len(sc_layers) == 1 and col_n == 400) and ok
# --- swap to a 3 km map with a sea and a lake above it
- scatter_clear_all()
- actor_clear_all()
- col_reset()
- water_bodies_clear()
- terrain_lake(F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO)
- terrain_reload("", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1500)
+ scatter_clear_all(render3d_st)
+ actor_clear_all(render3d_st)
+ col_reset(render3d_st)
+ water_bodies_clear(render3d_st)
+ terrain_lake(render3d_st, F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO)
+ terrain_reload(render3d_st, "", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1500)
ok = check("everything of the first map released", len(sc_layers) == 0 and col_n == 0 and wb_n == 0 and not water_on) and ok
ok = check("the new half", TERRAIN_HALF == 1500 and ter_heights != null and ter_height_tex != 0) and ok
ok = check("patch bounds rebuilt", cd_min != null and len(cd_min) == CD_LEVELS) and ok
- ok = check("patches scale with the map", cd_size(0) == f_mul(fi(32), fr(3000, 8192))) and ok
- terrain_height(F_ZERO, F_ZERO)
+ ok = check("patches scale with the map", cd_size(render3d_st, 0) == f_mul(fi(32), fr(3000, 8192))) and ok
+ terrain_height(render3d_st, F_ZERO, F_ZERO)
ok = check("the cached height scale is the new map's", ter_h_scale == fr(TERRAIN_RES, 3000)) and ok
- let sea = water_body_add(F_ZERO, F_ZERO, F_ZERO, fi(20000), fi(20000), true)
- let lake = water_body_add(fl(12.0), fi(600), fi(-400), fi(150), fi(100), false)
+ let sea = water_body_add(render3d_st, F_ZERO, F_ZERO, F_ZERO, fi(20000), fi(20000), true)
+ let lake = water_body_add(render3d_st, fl(12.0), fi(600), fi(-400), fi(150), fi(100), false)
ok = check("two water bodies, the sea mirrored", sea == 0 and lake == 1 and wb_n == 2 and wb_primary == 0 and water_level == F_ZERO and water_on) and ok
- populate()
+ populate(render3d_st)
ok = check("the new map takes layers and colliders", len(sc_layers) == 1 and col_n == 400 and col_side == 3000 / COL_CELL) and ok
# a sea below the carved lake: set once, and not undone by a reload
- terrain_sea(fl(-2.0))
- terrain_lake(fl(12.0), fi(600), fi(-400), fi(150), fi(100))
- ok = check("the sea is its own level", ter_sea_set and ter_sea_gen() == fl(-2.0) and ter_lake_level == fl(12.0)) and ok
+ terrain_sea(render3d_st, fl(-2.0))
+ terrain_lake(render3d_st, fl(12.0), fi(600), fi(-400), fi(150), fi(100))
+ ok = check("the sea is its own level", ter_sea_set and ter_sea_gen(render3d_st) == fl(-2.0) and ter_lake_level == fl(12.0)) and ok
# --- and back: the swap is repeatable
- scatter_clear_all()
- col_reset()
- water_init(fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200))
- terrain_reload("", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1000)
- populate()
+ scatter_clear_all(render3d_st)
+ col_reset(render3d_st)
+ water_init(render3d_st, fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200))
+ terrain_reload(render3d_st, "", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1000)
+ populate(render3d_st)
ok = check("back to 2 km", TERRAIN_HALF == 1000 and len(cd_min) == CD_LEVELS and wb_n == 1 and col_side == 2000 / COL_CELL) and ok
if ok { print("RELOAD OK") } else { print("RELOAD FAILED") }
quit()
diff --git a/examples/rendering/smooth.ludic b/examples/rendering/smooth.ludic
index 5830790a..9a795e80 100644
--- a/examples/rendering/smooth.ludic
+++ b/examples/rendering/smooth.ludic
@@ -13,17 +13,19 @@ program Smooth {
property Marker { on: int = 1 }
model Anchor { Marker }
- var frame: int = 0
- var shot_at: int = 40
- var walk: bool = false
- var spin: bool = false
- var l_blades: Layer = null
- var l_grass_a: Layer = null
- var l_grass_b: Layer = null
- var s_blades: Stream = null
- var s_cards_a: Stream = null
- var s_cards_b: Stream = null
- var l_trees: Layer = null
+ state SmoothState {
+ frame: int = 0
+ shot_at: int = 40
+ walk: bool = false
+ spin: bool = false
+ l_blades: Layer = null
+ l_grass_a: Layer = null
+ l_grass_b: Layer = null
+ s_blades: Stream = null
+ s_cards_a: Stream = null
+ s_cards_b: Stream = null
+ l_trees: Layer = null
+ }
function rnd() -> float { return float(rng_range(0, 9999)) / 10000.0 }
function rnd_range(a: float, b: float) -> float { return Math.lerp(a, b, rnd()) }
@@ -31,10 +33,10 @@ program Smooth {
let t = Math.clamp((x - a) / (b - a), 0.0, 1.0)
return t * t * (3.0 - 2.0 * t)
}
- function slope_at(x: float, z: float) -> float {
+ function slope_at(render3d_st: mut Render3dState, x: float, z: float) -> float {
let e = 2.0
- let dx = terrain_height(x + e, z) - terrain_height(x - e, z)
- let dz = terrain_height(x, z + e) - terrain_height(x, z - e)
+ let dx = terrain_height(render3d_st, x + e, z) - terrain_height(render3d_st, x - e, z)
+ let dz = terrain_height(render3d_st, x, z + e) - terrain_height(render3d_st, x, z - e)
let ny = 4.0 / Math.sqrt(dx * dx + dz * dz + 16.0)
return 1.0 - ny
}
@@ -44,7 +46,7 @@ program Smooth {
}
# grass only where the ground is grassy: gentle, above the water, below the mountain
- function stream_fill(s: Stream, cx: int, cz: int, band: int) -> void {
+ function stream_fill(render3d_st: mut Render3dState, s: Stream, cx: int, cz: int, band: int) -> void {
seed((cx * 73856093) ^ (cz * 19349663) ^ (band * 83492791) ^ (s.kind * 2654435761))
let size = s.size
let x0 = float(cx) * size; let z0 = float(cz) * size
@@ -61,19 +63,19 @@ program Smooth {
while x < x0 + size {
let px = x + rnd() * step
let pz = z + rnd() * step
- let h = terrain_height(px, pz)
+ let h = terrain_height(render3d_st, px, pz)
# Grassy ground only: above the water, off the steep parts, below the rim. Every
# test is a smooth ramp — a hard height cut carves the cover into contour rings,
# because the cut lands on a line of constant elevation.
var keep = smooth(0.2, 1.6, h) # out of the water
keep = keep * smooth(90.0, 45.0, h) # below the mountain
- keep = keep * smooth(0.42, 0.16, slope_at(px, pz))
+ keep = keep * smooth(0.42, 0.16, slope_at(render3d_st, px, pz))
keep = keep * (0.25 + 0.9 * noise01(px, pz, 0.02))
keep = keep * 0.75
var sc = rnd_range(0.16, 0.4)
if s.kind != 0 { sc = rnd_range(1.5, 2.5) * (1.0 + 0.5 * float(band)) }
if rnd() < keep {
- stream_emit(s, px, h - 0.03, pz, sc, rnd() * (2.0 * PI), rnd(), rnd_range(0.6, 1.0))
+ stream_emit(render3d_st, s, px, h - 0.03, pz, sc, rnd() * (2.0 * PI), rnd(), rnd_range(0.6, 1.0))
}
x = x + step
}
@@ -81,24 +83,24 @@ program Smooth {
}
}
- function scene_draw() -> void { scatter_draw() }
- function scene_draw_casters(light_vp: floats) -> void { scatter_draw_casters(light_vp) }
+ function scene_draw(render3d_st: mut Render3dState) -> void { scatter_draw(render3d_st) }
+ function scene_draw_casters(render3d_st: mut Render3dState, light_vp: floats) -> void { scatter_draw_casters(render3d_st, light_vp) }
- handler Boot phase Start {
+ handler Boot(render3d_st: mut Render3dState, smooth_st: mut SmoothState) phase Start {
spawn Anchor {}
- r3d_on_draw(fn scene_draw)
- r3d_on_casters(fn scene_draw_casters)
- r3d_on_stream_fill(fn stream_fill)
+ r3d_on_draw(render3d_st, fn scene_draw)
+ r3d_on_casters(render3d_st, fn scene_draw_casters)
+ r3d_on_stream_fill(render3d_st, fn stream_fill)
# 2 km square, generated analytically
TERRAIN_HALF = 1000
ter_smooth = true
- if not r3d_init(1920, 1080, "Smooth") {
+ if not r3d_init(render3d_st, 1920, 1080, "Smooth") {
quit()
return
}
- walk = Os.has_env("R3D_WALK")
- spin = Os.has_env("R3D_SPIN")
- if Os.has_env("R3D_SHOT") { shot_at = Text.to_int(Os.env("R3D_SHOT")) }
+ smooth_st.walk = Os.has_env("R3D_WALK")
+ smooth_st.spin = Os.has_env("R3D_SPIN")
+ if Os.has_env("R3D_SHOT") { smooth_st.shot_at = Text.to_int(Os.env("R3D_SHOT")) }
var cx = 0.0; var cz = 260.0
if Os.has_env("R3D_CAM_X") { cx = float(Text.to_int(Os.env("R3D_CAM_X"))) }
@@ -107,54 +109,54 @@ program Smooth {
if Os.has_env("R3D_CAM_H") { ch = float(Text.to_int(Os.env("R3D_CAM_H"))) }
if Os.has_env("R3D_CAM_PITCH") { cp = float(Text.to_int(Os.env("R3D_CAM_PITCH"))) }
if Os.has_env("R3D_CAM_YAW") { cy = float(Text.to_int(Os.env("R3D_CAM_YAW"))) }
- cam_set(cx, terrain_height(cx, cz) + ch, cz, cy, cp)
+ cam_set(render3d_st, cx, terrain_height(render3d_st, cx, cz) + ch, cz, cy, cp)
# a small pond in the middle of the meadow — the plane self-clips to the basin, so
# its extent only has to cover the hollow, not the map
- water_init(4.0, 0.0, 0.0, 200.0, 200.0)
- sky_set_yaw(Math.deg_to_rad(120.0))
+ water_init(render3d_st, 4.0, 0.0, 0.0, 200.0, 200.0)
+ sky_set_yaw(render3d_st, Math.deg_to_rad(120.0))
# grass: blades underfoot, cards beyond
let dir = r3d_assets + "/models/grass_medium_01"
- let ga = gltf_load(dir, "grass_medium_01_1k.gltf", "grass_medium_01_tall_a_LOD0")
- let gb = gltf_load(dir, "grass_medium_01_1k.gltf", "grass_medium_01_mid_a_LOD0")
+ let ga = gltf_load(render3d_st, dir, "grass_medium_01_1k.gltf", "grass_medium_01_tall_a_LOD0")
+ let gb = gltf_load(render3d_st, dir, "grass_medium_01_1k.gltf", "grass_medium_01_mid_a_LOD0")
if ga == null or gb == null { print("smooth: no grass models"); return }
# blades are the expensive layer: keep them near, and cap them low
- l_blades = layer_new(model_blade(), 400000, true, 3.0, 0.0, 70.0)
- l_blades.blade = true
- l_grass_a = layer_cards(ga, 200000, 0.3, 260.0)
- l_grass_b = layer_cards(gb, 200000, 0.3, 260.0)
- v3_set(l_grass_a.tint, 0.95, 1.0, 0.85)
- v3_set(l_grass_b.tint, 0.9, 0.98, 0.8)
- l_grass_a.rough = 1.6; l_grass_b.rough = 1.6
- s_blades = stream_new(l_blades, 8.0, 60.0, 12.0, 28.0, 60.0, 60.0)
- s_cards_a = stream_new(l_grass_a, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0)
- s_cards_b = stream_new(l_grass_b, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0)
- s_blades.kind = 0; s_cards_a.kind = 1; s_cards_b.kind = 2
- place_trees()
+ smooth_st.l_blades = layer_new(render3d_st, model_blade(render3d_st), 400000, true, 3.0, 0.0, 70.0)
+ smooth_st.l_blades.blade = true
+ smooth_st.l_grass_a = layer_cards(render3d_st, ga, 200000, 0.3, 260.0)
+ smooth_st.l_grass_b = layer_cards(render3d_st, gb, 200000, 0.3, 260.0)
+ v3_set(smooth_st.l_grass_a.tint, 0.95, 1.0, 0.85)
+ v3_set(smooth_st.l_grass_b.tint, 0.9, 0.98, 0.8)
+ smooth_st.l_grass_a.rough = 1.6; smooth_st.l_grass_b.rough = 1.6
+ smooth_st.s_blades = stream_new(render3d_st, smooth_st.l_blades, 8.0, 60.0, 12.0, 28.0, 60.0, 60.0)
+ smooth_st.s_cards_a = stream_new(render3d_st, smooth_st.l_grass_a, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0)
+ smooth_st.s_cards_b = stream_new(render3d_st, smooth_st.l_grass_b, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0)
+ smooth_st.s_blades.kind = 0; smooth_st.s_cards_a.kind = 1; smooth_st.s_cards_b.kind = 2
+ place_trees(render3d_st, smooth_st)
}
# scattered firs on the gentle ground, thinning toward the pond and the rim
- function place_trees() -> void {
- let model = gltf_load(r3d_assets + "/models/fir_tree_01", "fir_tree_01_1k.gltf", "fir_tree_01_c_LOD0")
+ function place_trees(render3d_st: mut Render3dState, smooth_st: mut SmoothState) -> void {
+ let model = gltf_load(render3d_st, r3d_assets + "/models/fir_tree_01", "fir_tree_01_1k.gltf", "fir_tree_01_c_LOD0")
if model == null { print("smooth: no fir model"); return }
- l_trees = layer_new(model, 20000, false, 0.0, 90.0, 0.0)
- layer_set_impostor(l_trees, impostor_bake(model, 12, 512, 1024))
- v3_set(l_trees.tint, 0.9, 1.0, 0.85)
+ smooth_st.l_trees = layer_new(render3d_st, model, 20000, false, 0.0, 90.0, 0.0)
+ layer_set_impostor(render3d_st, smooth_st.l_trees, impostor_bake(render3d_st, model, 12, 512, 1024))
+ v3_set(smooth_st.l_trees.tint, 0.9, 1.0, 0.85)
seed(4242)
var z = -900.0
while z < 900.0 {
var x = -900.0
while x < 900.0 {
let px = x + rnd() * 14.0; let pz = z + rnd() * 14.0
- let h = terrain_height(px, pz)
+ let h = terrain_height(render3d_st, px, pz)
var keep = smooth(7.0, 14.0, h) # above the pond shore
keep = keep * smooth(120.0, 60.0, h) # below the rim
- keep = keep * smooth(0.45, 0.2, slope_at(px, pz))
+ keep = keep * smooth(0.45, 0.2, slope_at(render3d_st, px, pz))
keep = keep * smooth(0.45, 0.75, noise01(px, pz, 0.004))
if rnd() < keep * 0.5 {
let sc = rnd_range(0.7, 1.35)
- layer_add(l_trees, px, h - 0.2, pz, sc, rnd() * (2.0 * PI), rnd(), 0.0)
+ layer_add(smooth_st.l_trees, px, h - 0.2, pz, sc, rnd() * (2.0 * PI), rnd(), 0.0)
}
x = x + 14.0
}
@@ -162,9 +164,9 @@ program Smooth {
}
}
- handler Fly phase Input {
- if walk { cam_move(1.0, 0.0, 0.0, 0.003, 0.0); return }
- if spin { cam_move(0.0, 0.0, 0.0, 0.02, 0.0); return }
+ handler Fly(render3d_st: mut Render3dState, smooth_st: SmoothState) phase Input {
+ if smooth_st.walk { cam_move(render3d_st, 1.0, 0.0, 0.0, 0.003, 0.0); return }
+ if smooth_st.spin { cam_move(render3d_st, 0.0, 0.0, 0.0, 0.02, 0.0); return }
if not is_windowed() { return }
Input.poll()
var fwd = 0.0; var side = 0.0; var up = 0.0
@@ -180,17 +182,17 @@ program Smooth {
dyaw = float(Input.mouse_dx()) * -0.004
dpitch = float(Input.mouse_dy()) * -0.004
}
- if fwd != 0 or side != 0 or up != 0 or dyaw != 0 or dpitch != 0 { cam_move(fwd, side, up, dyaw, dpitch) }
+ if fwd != 0 or side != 0 or up != 0 or dyaw != 0 or dpitch != 0 { cam_move(render3d_st, fwd, side, up, dyaw, dpitch) }
if Input.key_pressed(key: 'p') {
- let gy = terrain_height(cam_pos[0], cam_pos[2])
+ let gy = terrain_height(render3d_st, cam_pos[0], cam_pos[2])
print(`R3D_CAM_X={string(int(cam_pos[0]))} R3D_CAM_Z={string(int(cam_pos[2]))} R3D_CAM_H={string(int(cam_pos[1] - gy))} R3D_CAM_YAW={string(int(cam_yaw * (180.0 / PI)))} R3D_CAM_PITCH={string(int(cam_pitch * (180.0 / PI)))}`)
}
}
- handler Draw phase Render {
- r3d_frame(float(Time.elapsed()))
- frame += 1
- if frame == shot_at { Gl.screenshot(path: "build/smooth.ppm") }
+ handler Draw(render3d_st: mut Render3dState, smooth_st: mut SmoothState) phase Render {
+ r3d_frame(render3d_st, float(Time.elapsed()))
+ smooth_st.frame += 1
+ if smooth_st.frame == smooth_st.shot_at { Gl.screenshot(path: "build/smooth.ppm") }
Gl.swap()
}
}
diff --git a/examples/rendering/vk_compute.ludic b/examples/rendering/vk_compute.ludic
index afec3c62..6197a720 100644
--- a/examples/rendering/vk_compute.ludic
+++ b/examples/rendering/vk_compute.ludic
@@ -20,7 +20,10 @@ program VkCompute {
const W: int = 640
const H: int = 360
- var dev: pointer = null
+ state VkcomputeState {
+ dev: pointer = null
+ spv_len: int = 0
+ }
unsafe function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
unsafe function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
@@ -30,7 +33,7 @@ program VkCompute {
}
return false
}
- unsafe function read_all(path: string) -> bytes {
+ unsafe function read_all(vkcompute_st: mut VkcomputeState, path: string) -> bytes {
let f = file_open(path, "rb")
if f == null { return null }
file_seek(f, 0, 2)
@@ -39,16 +42,15 @@ program VkCompute {
let b = bytes(n + 4)
file_read(f, b, n)
file_close(f)
- spv_len = n
+ vkcompute_st.spv_len = n
return b
}
- var spv_len: int = 0
- handler Boot phase Start {
+ handler Boot(vkcompute_st: mut VkcomputeState) phase Start {
spawn Anchor {}
var spv_path = "build/vk_compute.spv"
if Os.has_env("VKC_SPV") { spv_path = Os.env("VKC_SPV") }
- let spv = read_all(spv_path)
+ let spv = read_all(vkcompute_st, spv_path)
if spv == null { print(`vulkan: no SPIR-V at {spv_path} (compile vk_compute.slang with slangc)`); quit() }
if Vk.open() == 0 { print("vulkan: no loader"); quit() }
@@ -126,8 +128,8 @@ program VkCompute {
}
r = vk_create_device(pd, dci, null, out)
if r != VK_SUCCESS { fail("vkCreateDevice", r) }
- dev = vk_get_ptr(out, 0)
- vk_get_device_queue(dev, family, 0, out)
+ vkcompute_st.dev = vk_get_ptr(out, 0)
+ vk_get_device_queue(vkcompute_st.dev, family, 0, out)
let queue = vk_get_ptr(out, 0)
# ---- a host-visible storage buffer for the picture
@@ -138,11 +140,11 @@ program VkCompute {
vk_put_i64(bci, VkBufferCreateInfo_size, nbytes)
vk_put_i32(bci, VkBufferCreateInfo_usage, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT)
vk_put_i32(bci, VkBufferCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE)
- r = vk_create_buffer(dev, bci, null, out)
+ r = vk_create_buffer(vkcompute_st.dev, bci, null, out)
if r != VK_SUCCESS { fail("vkCreateBuffer", r) }
let buf = handle(out)
let req = bytes(VkMemoryRequirements_sizeof)
- vk_get_buffer_memory_requirements(dev, buf, req)
+ vk_get_buffer_memory_requirements(vkcompute_st.dev, buf, req)
let mp = bytes(VkPhysicalDeviceMemoryProperties_sizeof)
vk_get_physical_device_memory_properties(pd, mp)
let want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
@@ -158,21 +160,21 @@ program VkCompute {
vk_put_i32(mai, VkMemoryAllocateInfo_sType, VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO)
vk_put_i64(mai, VkMemoryAllocateInfo_allocationSize, vk_get_i64(req, VkMemoryRequirements_size))
vk_put_i32(mai, VkMemoryAllocateInfo_memoryTypeIndex, mtype)
- r = vk_allocate_memory(dev, mai, null, out)
+ r = vk_allocate_memory(vkcompute_st.dev, mai, null, out)
if r != VK_SUCCESS { fail("vkAllocateMemory", r) }
let mem = handle(out)
let zero: long = 0
- r = vk_bind_buffer_memory(dev, buf, mem, zero)
+ r = vk_bind_buffer_memory(vkcompute_st.dev, buf, mem, zero)
if r != VK_SUCCESS { fail("vkBindBufferMemory", r) }
# ---- the shader, its layout, the pipeline
let smci = bytes(VkShaderModuleCreateInfo_sizeof)
vk_zero(smci, VkShaderModuleCreateInfo_sizeof)
vk_put_i32(smci, VkShaderModuleCreateInfo_sType, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO)
- let code_size: long = spv_len
+ let code_size: long = vkcompute_st.spv_len
vk_put_i64(smci, VkShaderModuleCreateInfo_codeSize, code_size)
vk_put_ptr(smci, VkShaderModuleCreateInfo_pCode, spv)
- r = vk_create_shader_module(dev, smci, null, out)
+ r = vk_create_shader_module(vkcompute_st.dev, smci, null, out)
if r != VK_SUCCESS { fail("vkCreateShaderModule", r) }
let module = handle(out)
@@ -188,7 +190,7 @@ program VkCompute {
vk_put_i32(dslci, VkDescriptorSetLayoutCreateInfo_bindingCount, 1)
vk_put_ptr(dslci, VkDescriptorSetLayoutCreateInfo_pBindings, binding)
let set_layouts = bytes(8)
- r = vk_create_descriptor_set_layout(dev, dslci, null, set_layouts)
+ r = vk_create_descriptor_set_layout(vkcompute_st.dev, dslci, null, set_layouts)
if r != VK_SUCCESS { fail("vkCreateDescriptorSetLayout", r) }
let pcr = bytes(VkPushConstantRange_sizeof)
@@ -202,7 +204,7 @@ program VkCompute {
vk_put_ptr(plci, VkPipelineLayoutCreateInfo_pSetLayouts, set_layouts)
vk_put_i32(plci, VkPipelineLayoutCreateInfo_pushConstantRangeCount, 1)
vk_put_ptr(plci, VkPipelineLayoutCreateInfo_pPushConstantRanges, pcr)
- r = vk_create_pipeline_layout(dev, plci, null, out)
+ r = vk_create_pipeline_layout(vkcompute_st.dev, plci, null, out)
if r != VK_SUCCESS { fail("vkCreatePipelineLayout", r) }
let layout = handle(out)
@@ -215,7 +217,7 @@ program VkCompute {
vk_put_i64(cpci, st + VkPipelineShaderStageCreateInfo_module, module)
vk_put_ptr(cpci, st + VkPipelineShaderStageCreateInfo_pName, "main")
vk_put_i64(cpci, VkComputePipelineCreateInfo_layout, layout)
- r = vk_create_compute_pipelines(dev, zero, 1, cpci, null, out)
+ r = vk_create_compute_pipelines(vkcompute_st.dev, zero, 1, cpci, null, out)
if r != VK_SUCCESS { fail("vkCreateComputePipelines", r) }
let pipeline = handle(out)
@@ -229,7 +231,7 @@ program VkCompute {
vk_put_i32(dpci, VkDescriptorPoolCreateInfo_maxSets, 1)
vk_put_i32(dpci, VkDescriptorPoolCreateInfo_poolSizeCount, 1)
vk_put_ptr(dpci, VkDescriptorPoolCreateInfo_pPoolSizes, psz)
- r = vk_create_descriptor_pool(dev, dpci, null, out)
+ r = vk_create_descriptor_pool(vkcompute_st.dev, dpci, null, out)
if r != VK_SUCCESS { fail("vkCreateDescriptorPool", r) }
let pool = handle(out)
let dsai = bytes(VkDescriptorSetAllocateInfo_sizeof)
@@ -239,7 +241,7 @@ program VkCompute {
vk_put_i32(dsai, VkDescriptorSetAllocateInfo_descriptorSetCount, 1)
vk_put_ptr(dsai, VkDescriptorSetAllocateInfo_pSetLayouts, set_layouts)
let sets = bytes(8)
- r = vk_allocate_descriptor_sets(dev, dsai, sets)
+ r = vk_allocate_descriptor_sets(vkcompute_st.dev, dsai, sets)
if r != VK_SUCCESS { fail("vkAllocateDescriptorSets", r) }
let dbi = bytes(VkDescriptorBufferInfo_sizeof)
vk_put_i64(dbi, VkDescriptorBufferInfo_buffer, buf)
@@ -253,14 +255,14 @@ program VkCompute {
vk_put_i32(wds, VkWriteDescriptorSet_descriptorCount, 1)
vk_put_i32(wds, VkWriteDescriptorSet_descriptorType, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
vk_put_ptr(wds, VkWriteDescriptorSet_pBufferInfo, dbi)
- vk_update_descriptor_sets(dev, 1, wds, 0, null)
+ vk_update_descriptor_sets(vkcompute_st.dev, 1, wds, 0, null)
# ---- record, submit, wait
let cpi = bytes(VkCommandPoolCreateInfo_sizeof)
vk_zero(cpi, VkCommandPoolCreateInfo_sizeof)
vk_put_i32(cpi, VkCommandPoolCreateInfo_sType, VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO)
vk_put_i32(cpi, VkCommandPoolCreateInfo_queueFamilyIndex, family)
- r = vk_create_command_pool(dev, cpi, null, out)
+ r = vk_create_command_pool(vkcompute_st.dev, cpi, null, out)
if r != VK_SUCCESS { fail("vkCreateCommandPool", r) }
let cmd_pool = handle(out)
let cbai = bytes(VkCommandBufferAllocateInfo_sizeof)
@@ -270,7 +272,7 @@ program VkCompute {
vk_put_i32(cbai, VkCommandBufferAllocateInfo_level, VK_COMMAND_BUFFER_LEVEL_PRIMARY)
vk_put_i32(cbai, VkCommandBufferAllocateInfo_commandBufferCount, 1)
let cbs = bytes(8)
- r = vk_allocate_command_buffers(dev, cbai, cbs)
+ r = vk_allocate_command_buffers(vkcompute_st.dev, cbai, cbs)
if r != VK_SUCCESS { fail("vkAllocateCommandBuffers", r) }
let cb = vk_get_ptr(cbs, 0)
let cbbi = bytes(VkCommandBufferBeginInfo_sizeof)
@@ -294,7 +296,7 @@ program VkCompute {
vk_zero(fci, VkFenceCreateInfo_sizeof)
vk_put_i32(fci, VkFenceCreateInfo_sType, VK_STRUCTURE_TYPE_FENCE_CREATE_INFO)
let fences = bytes(8)
- r = vk_create_fence(dev, fci, null, fences)
+ r = vk_create_fence(vkcompute_st.dev, fci, null, fences)
if r != VK_SUCCESS { fail("vkCreateFence", r) }
let si = bytes(VkSubmitInfo_sizeof)
vk_zero(si, VkSubmitInfo_sizeof)
@@ -304,13 +306,13 @@ program VkCompute {
r = vk_queue_submit(queue, 1, si, vk_get_i64(fences, 0))
if r != VK_SUCCESS { fail("vkQueueSubmit", r) }
let forever: long = -1
- r = vk_wait_for_fences(dev, 1, fences, 1, forever)
+ r = vk_wait_for_fences(vkcompute_st.dev, 1, fences, 1, forever)
if r != VK_SUCCESS { fail("vkWaitForFences", r) }
print(`vulkan: dispatched {(W + 15) / 16}x{(H + 15) / 16} groups and waited for the fence`)
# ---- read the picture back
let pp = bytes(8)
- r = vk_map_memory(dev, mem, zero, nbytes, 0, pp)
+ r = vk_map_memory(vkcompute_st.dev, mem, zero, nbytes, 0, pp)
if r != VK_SUCCESS { fail("vkMapMemory", r) }
let px = vk_get_ptr(pp, 0)
var out_path = "build/vk_compute.ppm"
@@ -329,19 +331,19 @@ program VkCompute {
file_write(f, row, W * 3)
}
file_close(f)
- vk_unmap_memory(dev, mem)
+ vk_unmap_memory(vkcompute_st.dev, mem)
print(`vulkan: {lit} of {W * H} pixels written by the shader -> {out_path}`)
- vk_destroy_fence(dev, vk_get_i64(fences, 0), null)
- vk_destroy_command_pool(dev, cmd_pool, null)
- vk_destroy_descriptor_pool(dev, pool, null)
- vk_destroy_pipeline(dev, pipeline, null)
- vk_destroy_pipeline_layout(dev, layout, null)
- vk_destroy_descriptor_set_layout(dev, vk_get_i64(set_layouts, 0), null)
- vk_destroy_shader_module(dev, module, null)
- vk_destroy_buffer(dev, buf, null)
- vk_free_memory(dev, mem, null)
- vk_destroy_device(dev, null)
+ vk_destroy_fence(vkcompute_st.dev, vk_get_i64(fences, 0), null)
+ vk_destroy_command_pool(vkcompute_st.dev, cmd_pool, null)
+ vk_destroy_descriptor_pool(vkcompute_st.dev, pool, null)
+ vk_destroy_pipeline(vkcompute_st.dev, pipeline, null)
+ vk_destroy_pipeline_layout(vkcompute_st.dev, layout, null)
+ vk_destroy_descriptor_set_layout(vkcompute_st.dev, vk_get_i64(set_layouts, 0), null)
+ vk_destroy_shader_module(vkcompute_st.dev, module, null)
+ vk_destroy_buffer(vkcompute_st.dev, buf, null)
+ vk_free_memory(vkcompute_st.dev, mem, null)
+ vk_destroy_device(vkcompute_st.dev, null)
vk_destroy_instance(inst, null)
if lit == W * H { print("VKCOMPUTE OK") } else { print("VKCOMPUTE INCOMPLETE") }
quit()
diff --git a/examples/rendering/vk_resources.ludic b/examples/rendering/vk_resources.ludic
index 6efadf9c..78333547 100644
--- a/examples/rendering/vk_resources.ludic
+++ b/examples/rendering/vk_resources.ludic
@@ -13,12 +13,14 @@ program VkResources {
property Marker { on: int = 1 }
model Anchor { Marker }
- var failures: int = 0
- unsafe function check(what: string, ok: bool) -> void {
- if ok { print(`vulkan: {what} ok`) } else { print(`vulkan: {what} FAILED`); failures += 1 }
+ state VkresourcesState {
+ failures: int = 0
+ }
+ unsafe function check(vkresources_st: mut VkresourcesState, what: string, ok: bool) -> void {
+ if ok { print(`vulkan: {what} ok`) } else { print(`vulkan: {what} FAILED`); vkresources_st.failures += 1 }
}
- handler Boot phase Start {
+ handler Boot(vkresources_st: mut VkresourcesState) phase Start {
spawn Anchor {}
print(`vulkan: OpenGL names spliced (pixel scale {Gl.pixel_scale()})`)
if not gvk_init() { print(`VKRES NONE: {gvk_why}`); quit() }
@@ -29,56 +31,56 @@ program VkResources {
let px = bytes(w * h * 4)
for i in 0 .. w * h { px[i * 4] = i & 255; px[i * 4 + 1] = (i >> 8) & 255; px[i * 4 + 2] = (i * 7) & 255; px[i * 4 + 3] = 200 }
let t1 = gvk_tex_new()
- check("RGBA8 storage with mips", gvk_tex_storage(t1, false, GL_RGBA8, w, h, 1, true))
- check("RGBA8 upload", gvk_tex_upload(t1, GL_RGBA8, w, h, 1, GL_RGBA, GL_UNSIGNED_BYTE, px))
- check("RGBA8 mip chain (7 levels)", gvk_tex_levels[t1] == 7 and gvk_tex_mips(t1, w, h))
+ check(vkresources_st, "RGBA8 storage with mips", gvk_tex_storage(t1, false, GL_RGBA8, w, h, 1, true))
+ check(vkresources_st, "RGBA8 upload", gvk_tex_upload(t1, GL_RGBA8, w, h, 1, GL_RGBA, GL_UNSIGNED_BYTE, px))
+ check(vkresources_st, "RGBA8 mip chain (7 levels)", gvk_tex_levels[t1] == 7 and gvk_tex_mips(t1, w, h))
let back = bytes(w * h * 4)
- check("RGBA8 read-back call", gvk_tex_read(t1, GL_RGBA8, w, h, GL_RGBA, GL_UNSIGNED_BYTE, back))
+ check(vkresources_st, "RGBA8 read-back call", gvk_tex_read(t1, GL_RGBA8, w, h, GL_RGBA, GL_UNSIGNED_BYTE, back))
var same = true
for i in 0 .. w * h * 4 { if back[i] != px[i] { same = false } }
- check("RGBA8 read-back matches", same)
+ check(vkresources_st, "RGBA8 read-back matches", same)
# R32F, the terrain's height field path
let hf = bytes(w * h * 4)
for i in 0 .. w * h { Vk.put_i32(hf, i * 4, 0x40000000 + i) }
let t2 = gvk_tex_new()
- check("R32F target", gvk_tex_storage(t2, false, GL_R32F, w, h, 1, false))
- check("R32F upload", gvk_tex_upload(t2, GL_R32F, w, h, 1, GL_RED, GL_FLOAT, hf))
+ check(vkresources_st, "R32F target", gvk_tex_storage(t2, false, GL_R32F, w, h, 1, false))
+ check(vkresources_st, "R32F upload", gvk_tex_upload(t2, GL_R32F, w, h, 1, GL_RED, GL_FLOAT, hf))
let hb = bytes(w * h * 4)
- check("R32F read-back call", gvk_tex_read(t2, GL_R32F, w, h, GL_RED, GL_FLOAT, hb))
+ check(vkresources_st, "R32F read-back call", gvk_tex_read(t2, GL_R32F, w, h, GL_RED, GL_FLOAT, hb))
var hsame = true
for i in 0 .. w * h * 4 { if hb[i] != hf[i] { hsame = false } }
- check("R32F read-back matches", hsame)
+ check(vkresources_st, "R32F read-back matches", hsame)
# RGB8 stored as RGBA, and an HDR image's float RGB into half floats
let rgb = bytes(w * h * 3)
for i in 0 .. w * h * 3 { rgb[i] = (i * 3) & 255 }
let t3 = gvk_tex_new()
- check("SRGB8 expanded to RGBA", gvk_tex_storage(t3, false, GL_SRGB8, w, h, 1, true) and gvk_tex_upload(t3, GL_SRGB8, w, h, 1, GL_RGB, GL_UNSIGNED_BYTE, rgb))
+ check(vkresources_st, "SRGB8 expanded to RGBA", gvk_tex_storage(t3, false, GL_SRGB8, w, h, 1, true) and gvk_tex_upload(t3, GL_SRGB8, w, h, 1, GL_RGB, GL_UNSIGNED_BYTE, rgb))
let fl = bytes(w * h * 12)
for i in 0 .. w * h * 3 { Vk.put_i32(fl, i * 4, 0x3FC00000) }
let t4 = gvk_tex_new()
- check("RGB16F from floats (halved)", gvk_tex_storage(t4, false, GL_RGB16F, w, h, 1, true) and gvk_tex_upload(t4, GL_RGB16F, w, h, 1, GL_RGB, GL_FLOAT, fl))
- check("half of 1.5 is 0x3E00", gvk_half(0x3FC00000) == 0x3E00)
+ check(vkresources_st, "RGB16F from floats (halved)", gvk_tex_storage(t4, false, GL_RGB16F, w, h, 1, true) and gvk_tex_upload(t4, GL_RGB16F, w, h, 1, GL_RGB, GL_FLOAT, fl))
+ check(vkresources_st, "half of 1.5 is 0x3E00", gvk_half(0x3FC00000) == 0x3E00)
# a 16-bit RGB PNG (a normal map): big-endian samples, swapped on upload, alpha filled opaque
let n16 = bytes(w * h * 6)
for i in 0 .. w * h * 3 { n16[i * 2] = (i >> 8) & 255; n16[i * 2 + 1] = i & 255 }
let t7 = gvk_tex_new()
gvk_unpack_swap = true
- check("RGB16 swapped upload", gvk_tex_storage(t7, false, GL_RGB16, w, h, 1, true) and gvk_tex_upload(t7, GL_RGB16, w, h, 1, GL_RGB, GL_UNSIGNED_SHORT, n16))
+ check(vkresources_st, "RGB16 swapped upload", gvk_tex_storage(t7, false, GL_RGB16, w, h, 1, true) and gvk_tex_upload(t7, GL_RGB16, w, h, 1, GL_RGB, GL_UNSIGNED_SHORT, n16))
gvk_unpack_swap = false
let b16 = bytes(w * h * 6)
- check("RGB16 read-back call", gvk_tex_read(t7, GL_RGB16, w, h, GL_RGB, GL_UNSIGNED_SHORT, b16))
+ check(vkresources_st, "RGB16 read-back call", gvk_tex_read(t7, GL_RGB16, w, h, GL_RGB, GL_UNSIGNED_SHORT, b16))
var s16 = true
for i in 0 .. w * h * 3 { if b16[i * 2] != (i & 255) or b16[i * 2 + 1] != ((i >> 8) & 255) { s16 = false } }
- check("RGB16 samples arrive little-endian", s16)
+ check(vkresources_st, "RGB16 samples arrive little-endian", s16)
# the shadow cascades: a depth array, and a render-sized half-float target with no pixels
let t5 = gvk_tex_new()
- check("D32 array of 4", gvk_tex_storage(t5, true, GL_DEPTH_COMPONENT32F, 256, 256, 4, false))
+ check(vkresources_st, "D32 array of 4", gvk_tex_storage(t5, true, GL_DEPTH_COMPONENT32F, 256, 256, 4, false))
let t6 = gvk_tex_new()
- check("RGBA16F target", gvk_tex_storage(t6, false, GL_RGBA16F, 320, 180, 1, false))
+ check(vkresources_st, "RGBA16F target", gvk_tex_storage(t6, false, GL_RGBA16F, 320, 180, 1, false))
# the samplers the renderer asks for, and that asking twice returns the same one
let s1 = gvk_sampler(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT, 0, 0x41800000)
@@ -86,25 +88,25 @@ program VkResources {
let s3 = gvk_sampler(GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER, GL_LEQUAL, 0)
let s4 = gvk_sampler(GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0, 0)
let again = gvk_sampler(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT, 0, 0x41800000)
- check("four samplers made", s1 != 0 and s2 != 0 and s3 != 0 and s4 != 0 and len(gvk_smp) == 4)
- check("a sampler asked for twice is one sampler", again == s1)
+ check(vkresources_st, "four samplers made", s1 != 0 and s2 != 0 and s3 != 0 and s4 != 0 and len(gvk_smp) == 4)
+ check(vkresources_st, "a sampler asked for twice is one sampler", again == s1)
# a buffer filled, filled again smaller (kept), then larger (grown)
let b = gvk_buf_new()
let data = bytes(4096)
for i in 0 .. 4096 { data[i] = i & 255 }
- check("buffer upload", gvk_buf_upload(b, 1024, data))
+ check(vkresources_st, "buffer upload", gvk_buf_upload(b, 1024, data))
let first = gvk_buf[b]
- check("a smaller upload keeps the buffer", gvk_buf_upload(b, 512, data) and gvk_buf[b] == first)
- check("a larger upload grows it", gvk_buf_upload(b, 4096, data) and gvk_buf_size[b] >= 4096)
+ check(vkresources_st, "a smaller upload keeps the buffer", gvk_buf_upload(b, 512, data) and gvk_buf[b] == first)
+ check(vkresources_st, "a larger upload grows it", gvk_buf_upload(b, 4096, data) and gvk_buf_size[b] >= 4096)
for t in 1 .. 8 { gvk_tex_release(t) }
gvk_buf_release(b)
for i in 0 .. len(gvk_smp) { Vk.destroy_sampler(gvk_dev, gvk_smp[i], null) }
print(`vulkan: {gvk_n_allocs} allocations left after freeing`)
- check("every allocation freed", gvk_n_allocs == 0)
+ check(vkresources_st, "every allocation freed", gvk_n_allocs == 0)
gvk_shutdown()
- if failures == 0 { print("VKRES OK") } else { print(`VKRES FAILED ({failures})`) }
+ if vkresources_st.failures == 0 { print("VKRES OK") } else { print(`VKRES FAILED ({vkresources_st.failures})`) }
quit()
}
}
diff --git a/examples/rendering/vk_triangle.ludic b/examples/rendering/vk_triangle.ludic
index b642c6a0..2ddee991 100644
--- a/examples/rendering/vk_triangle.ludic
+++ b/examples/rendering/vk_triangle.ludic
@@ -29,8 +29,10 @@ program VkTriangle {
}
return false
}
- var spv_len: int = 0
- unsafe function read_all(path: string) -> bytes {
+ state VktriangleState {
+ spv_len: int = 0
+ }
+ unsafe function read_all(vktriangle_st: mut VktriangleState, path: string) -> bytes {
let f = file_open(path, "rb")
if f == null { return null }
file_seek(f, 0, 2)
@@ -39,7 +41,7 @@ program VkTriangle {
let b = bytes(n + 4)
file_read(f, b, n)
file_close(f)
- spv_len = n
+ vktriangle_st.spv_len = n
return b
}
# the first memory type the resource allows that has every property wanted
@@ -72,13 +74,13 @@ program VkTriangle {
color_range(b, VkImageMemoryBarrier_subresourceRange)
vk_cmd_pipeline_barrier(cb, src_stage, dst_stage, 0, 0, null, 0, null, 1, b)
}
- unsafe function shader_module(dev: pointer, path: string) -> long {
- let spv = read_all(path)
+ unsafe function shader_module(vktriangle_st: mut VktriangleState, dev: pointer, path: string) -> long {
+ let spv = read_all(vktriangle_st, path)
if spv == null { print(`vulkan: no SPIR-V at {path} (compile vk_triangle.slang with slangc)`); quit() }
let smci = bytes(VkShaderModuleCreateInfo_sizeof)
vk_zero(smci, VkShaderModuleCreateInfo_sizeof)
vk_put_i32(smci, VkShaderModuleCreateInfo_sType, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO)
- let code_size: long = spv_len
+ let code_size: long = vktriangle_st.spv_len
vk_put_i64(smci, VkShaderModuleCreateInfo_codeSize, code_size)
vk_put_ptr(smci, VkShaderModuleCreateInfo_pCode, spv)
let out = bytes(8)
@@ -87,7 +89,7 @@ program VkTriangle {
return handle(out)
}
- handler Boot phase Start {
+ handler Boot(vktriangle_st: mut VktriangleState) phase Start {
spawn Anchor {}
var vs_path = "build/vk_triangle_vs.spv"
var fs_path = "build/vk_triangle_fs.spv"
@@ -253,8 +255,8 @@ program VkTriangle {
if r != VK_SUCCESS { fail("vkBindBufferMemory", r) }
# ---- the pipeline: two stages, no vertex input, dynamic viewport and scissor, one colour format
- let vs = shader_module(dev, vs_path)
- let fs = shader_module(dev, fs_path)
+ let vs = shader_module(vktriangle_st, dev, vs_path)
+ let fs = shader_module(vktriangle_st, dev, fs_path)
let plci = bytes(VkPipelineLayoutCreateInfo_sizeof)
vk_zero(plci, VkPipelineLayoutCreateInfo_sizeof)
vk_put_i32(plci, VkPipelineLayoutCreateInfo_sType, VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO)
diff --git a/packages/ludic.dungeon/dungeon.ludic b/packages/ludic.dungeon/dungeon.ludic
index a7400098..30c1164e 100644
--- a/packages/ludic.dungeon/dungeon.ludic
+++ b/packages/ludic.dungeon/dungeon.ludic
@@ -20,8 +20,10 @@ enum RoomStyle { Pillars, Columns, CenterSlab, Bars, Scatter }
const DUNGEON_MARGIN: int = 2
-var dungeon_wall: int = '#'
-var dungeon_floor: int = '.'
+state DungeonState {
+ dungeon_wall: int = '#'
+ dungeon_floor: int = '.'
+}
function dungeon_columns() -> int { return Map.width() }
function dungeon_rows() -> int { return Map.height() }
@@ -29,62 +31,62 @@ function dungeon_door_column() -> int { return Map.width() / 2 - 1 } # the l
function dungeon_door_row() -> int { return Map.height() / 2 } # the top of the two door cells
# a wall, but never in the margin so rooms stay traversable
-function dungeon_place(x: int, y: int) -> void {
+function dungeon_place(dungeon_st: DungeonState, x: int, y: int) -> void {
if (x < DUNGEON_MARGIN) or (x > dungeon_columns() - 1 - DUNGEON_MARGIN) { return }
if (y < DUNGEON_MARGIN) or (y > dungeon_rows() - 1 - DUNGEON_MARGIN) { return }
- Map.set(x: x, y: y, glyph: dungeon_wall)
+ Map.set(x: x, y: y, glyph: dungeon_st.dungeon_wall)
}
-function dungeon_mirrored(x: int, y: int) -> void {
- dungeon_place(x, y)
- dungeon_place(dungeon_columns() - 1 - x, y)
- dungeon_place(x, dungeon_rows() - 1 - y)
- dungeon_place(dungeon_columns() - 1 - x, dungeon_rows() - 1 - y)
+function dungeon_mirrored(dungeon_st: DungeonState, x: int, y: int) -> void {
+ dungeon_place(dungeon_st, x, y)
+ dungeon_place(dungeon_st, dungeon_columns() - 1 - x, y)
+ dungeon_place(dungeon_st, x, dungeon_rows() - 1 - y)
+ dungeon_place(dungeon_st, dungeon_columns() - 1 - x, dungeon_rows() - 1 - y)
}
-function dungeon_pillar(x: int, y: int) -> void { # 2x2, mirrored four ways
- dungeon_mirrored(x, y)
- dungeon_mirrored(x + 1, y)
- dungeon_mirrored(x, y + 1)
- dungeon_mirrored(x + 1, y + 1)
+function dungeon_pillar(dungeon_st: DungeonState, x: int, y: int) -> void { # 2x2, mirrored four ways
+ dungeon_mirrored(dungeon_st, x, y)
+ dungeon_mirrored(dungeon_st, x + 1, y)
+ dungeon_mirrored(dungeon_st, x, y + 1)
+ dungeon_mirrored(dungeon_st, x + 1, y + 1)
}
-function dungeon_style(style: int) -> void {
+function dungeon_style(dungeon_st: DungeonState, style: int) -> void {
let cols = dungeon_columns()
let rows = dungeon_rows()
match style {
RoomStyle.Pillars => {
let x = Random.range(low: 3, high: 5)
let y = Random.range(low: 3, high: 4)
- dungeon_pillar(x, y)
- if Random.chance(percent: 60) { dungeon_pillar(x + 3, y + 2) }
+ dungeon_pillar(dungeon_st, x, y)
+ if Random.chance(percent: 60) { dungeon_pillar(dungeon_st, x + 3, y + 2) }
}
RoomStyle.Columns => {
let x = Random.range(low: 4, high: 6)
- for y in 3 .. rows - 3 { dungeon_mirrored(x, y) }
+ for y in 3 .. rows - 3 { dungeon_mirrored(dungeon_st, x, y) }
}
- RoomStyle.CenterSlab => { Map.rect(x: 6, y: 5, width: cols - 12, height: rows - 10, glyph: dungeon_wall) }
+ RoomStyle.CenterSlab => { Map.rect(x: 6, y: 5, width: cols - 12, height: rows - 10, glyph: dungeon_st.dungeon_wall) }
RoomStyle.Bars => {
let y = Random.range(low: 3, high: 4)
- for x in 4 .. cols - 4 { dungeon_mirrored(x, y) }
+ for x in 4 .. cols - 4 { dungeon_mirrored(dungeon_st, x, y) }
}
_ => {
for i in 0 .. Random.range(low: 3, high: 5) {
let x = Random.range(low: 3, high: cols / 2 - 1)
- dungeon_place(x, Random.range(low: 3, high: rows - 4))
- dungeon_place(cols - 1 - x, Random.range(low: 3, high: rows - 4))
+ dungeon_place(dungeon_st, x, Random.range(low: 3, high: rows - 4))
+ dungeon_place(dungeon_st, cols - 1 - x, Random.range(low: 3, high: rows - 4))
}
}
}
}
-@Namespace(Dungeon) function dungeon_open_arena(wall: int, floor: int) -> void {
- dungeon_wall = wall
- dungeon_floor = floor
+@Namespace(Dungeon) function dungeon_open_arena(dungeon_st: mut DungeonState, wall: int, floor: int) -> void {
+ dungeon_st.dungeon_wall = wall
+ dungeon_st.dungeon_floor = floor
Map.fill(glyph: floor)
Map.border(glyph: wall)
}
-@Namespace(Dungeon) function dungeon_arena(style: int, wall: int, floor: int) -> void {
- dungeon_open_arena(wall, floor)
- dungeon_style(style)
+@Namespace(Dungeon) function dungeon_arena(dungeon_st: mut DungeonState, style: int, wall: int, floor: int) -> void {
+ dungeon_open_arena(dungeon_st, wall, floor)
+ dungeon_style(dungeon_st, style)
Map.rect(x: dungeon_door_column(), y: 1, width: 2, height: dungeon_rows() - 2, glyph: floor) # the door lanes
Map.rect(x: 1, y: dungeon_door_row(), width: dungeon_columns() - 2, height: 2, glyph: floor)
}
diff --git a/packages/ludic.gameplay/combat.ludic b/packages/ludic.gameplay/combat.ludic
index bb27d7ea..f2e1c283 100644
--- a/packages/ludic.gameplay/combat.ludic
+++ b/packages/ludic.gameplay/combat.ludic
@@ -23,13 +23,16 @@ event Died { e: int = 0, src: int = 0 }
event Healed { e: int = 0, amount: int = 0 }
event Crit { target: int = 0, src: int = 0 } # the attacker's crit_pct fired
-var combat_reflecting: bool = false # thorns never reflect thorns
+state GameplayState {
+ combat_reflecting: bool = false # thorns never reflect thorns
+ combat_pending: int = 0
+ fac_tbl: words = null
+}
# the mutable channel for lever 6: listeners rewrite the pending damage here.
-var combat_pending: int = 0
-@Namespace(Combat) function combat_amount() -> int { return combat_pending }
-@Namespace(Combat) function combat_set_amount(n: int) -> void { combat_pending = n }
+@Namespace(Combat) function combat_amount(gameplay_st: GameplayState) -> int { return gameplay_st.combat_pending }
+@Namespace(Combat) function combat_set_amount(gameplay_st: mut GameplayState, n: int) -> void { gameplay_st.combat_pending = n }
# Combat.mitigate(target, raw) — raw minus effective defence, floored at 1 so any
# landed hit chips at least a point (a common, tunable default). A game that wants
@@ -43,7 +46,7 @@ var combat_pending: int = 0
# Combat.damage(target, src, amount) — the full pipeline. Returns the damage
# actually applied (0 when vetoed or the target has no Stats/hp).
-@Namespace(Combat) function combat_damage(target: int, src: int, amount: int) -> int {
+@Namespace(Combat) function combat_damage(gameplay_st: mut GameplayState, target: int, src: int, amount: int) -> int {
let P = World.prop_id("Stats")
if P < 0 { return 0 }
if World.has(target, P) == 0 { return 0 }
@@ -54,9 +57,9 @@ var combat_pending: int = 0
var raw = amount * stats_field(src, "damage_pct", 100) / 100
let crit = stats_field(src, "crit_pct", 0)
if (crit > 0) and Random.chance(percent: crit) { raw *= 2; emit Crit(target: target, src: src) }
- combat_pending = combat_mitigate(target, raw)
- if emit DamageAboutToApply(target: target, src: src, amount: combat_pending) != 0 { return 0 }
- var dmg = combat_pending
+ gameplay_st.combat_pending = combat_mitigate(target, raw)
+ if emit DamageAboutToApply(target: target, src: src, amount: gameplay_st.combat_pending) != 0 { return 0 }
+ var dmg = gameplay_st.combat_pending
if dmg < 0 { dmg = 0 }
var hp = World.get(target, P, fhp) - dmg
@@ -69,10 +72,10 @@ var combat_pending: int = 0
let leech = stats_field(src, "leech_pct", 0)
if (leech > 0) and Random.chance(percent: leech) { combat_heal(src, stats_field(src, "leech_hp", 1)) }
let thorns = stats_field(target, "thorns", 0)
- if (thorns > 0) and (src != target) and (not combat_reflecting) and (World.has(src, P) != 0) {
- combat_reflecting = true
- combat_damage(src, target, thorns)
- combat_reflecting = false
+ if (thorns > 0) and (src != target) and (not gameplay_st.combat_reflecting) and (World.has(src, P) != 0) {
+ gameplay_st.combat_reflecting = true
+ combat_damage(gameplay_st, src, target, thorns)
+ gameplay_st.combat_reflecting = false
}
}
return dmg
diff --git a/packages/ludic.gameplay/faction.ludic b/packages/ludic.gameplay/faction.ludic
index 7078ef92..95d0301f 100644
--- a/packages/ludic.gameplay/faction.ludic
+++ b/packages/ludic.gameplay/faction.ludic
@@ -16,26 +16,25 @@ const REL_HOSTILE: int = 2
# relationships stored as (rel + 1); a zero slot means "unset -> use the default".
# A global var initialiser must be a literal, so it is allocated lazily.
-var fac_tbl: words = null
-function fac_ensure() -> void {
- if fac_tbl == null { fac_tbl = words(FAC_MAX * FAC_MAX) }
+function fac_ensure(gameplay_st: mut GameplayState) -> void {
+ if gameplay_st.fac_tbl == null { gameplay_st.fac_tbl = words(FAC_MAX * FAC_MAX) }
}
# Faction.set(a, b, rel) — record a symmetric relationship between two factions.
-@Namespace(Faction) function faction_set(a: int, b: int, rel: int) -> void {
- fac_ensure()
+@Namespace(Faction) function faction_set(gameplay_st: mut GameplayState, a: int, b: int, rel: int) -> void {
+ fac_ensure(gameplay_st)
if (a < 0) or (a >= FAC_MAX) { return }
if (b < 0) or (b >= FAC_MAX) { return }
- fac_tbl[a * FAC_MAX + b] = rel + 1
- fac_tbl[b * FAC_MAX + a] = rel + 1
+ gameplay_st.fac_tbl[a * FAC_MAX + b] = rel + 1
+ gameplay_st.fac_tbl[b * FAC_MAX + a] = rel + 1
}
# Faction.rel(a, b) — the relationship code (0 friend, 1 neutral, 2 hostile).
-@Namespace(Faction) function faction_rel(a: int, b: int) -> int {
- fac_ensure()
+@Namespace(Faction) function faction_rel(gameplay_st: mut GameplayState, a: int, b: int) -> int {
+ fac_ensure(gameplay_st)
if (a >= 0) and (a < FAC_MAX) and (b >= 0) and (b < FAC_MAX) {
- let v = fac_tbl[a * FAC_MAX + b]
+ let v = gameplay_st.fac_tbl[a * FAC_MAX + b]
if v != 0 { return v - 1 }
}
if a == b { return REL_FRIEND }
@@ -43,12 +42,12 @@ function fac_ensure() -> void {
}
# Faction.hostile(a, b) / Faction.friendly(a, b) — the common yes/no queries.
-@Namespace(Faction) function faction_hostile(a: int, b: int) -> int {
- if faction_rel(a, b) == REL_HOSTILE { return 1 }
+@Namespace(Faction) function faction_hostile(gameplay_st: mut GameplayState, a: int, b: int) -> int {
+ if faction_rel(gameplay_st, a, b) == REL_HOSTILE { return 1 }
return 0
}
-@Namespace(Faction) function faction_friendly(a: int, b: int) -> int {
- if faction_rel(a, b) == REL_FRIEND { return 1 }
+@Namespace(Faction) function faction_friendly(gameplay_st: mut GameplayState, a: int, b: int) -> int {
+ if faction_rel(gameplay_st, a, b) == REL_FRIEND { return 1 }
return 0
}
@@ -65,6 +64,6 @@ function fac_ensure() -> void {
# Faction.enemies(a, b) — true when the two entities' factions are hostile; the
# one-call test the shooter and AI reach for. Missing factions read as hostile
# (an unaligned hazard hits anyone) unless the ids happen to match.
-@Namespace(Faction) function faction_enemies(ea: int, eb: int) -> int {
- return faction_hostile(faction_id_of(ea), faction_id_of(eb))
+@Namespace(Faction) function faction_enemies(gameplay_st: mut GameplayState, ea: int, eb: int) -> int {
+ return faction_hostile(gameplay_st, faction_id_of(ea), faction_id_of(eb))
}
diff --git a/packages/ludic.lab/example/plate.ludic b/packages/ludic.lab/example/plate.ludic
index 7c406e59..da90f718 100644
--- a/packages/ludic.lab/example/plate.ludic
+++ b/packages/ludic.lab/example/plate.ludic
@@ -6,15 +6,15 @@
import "ludic.lab"
program LabPlate {
numbers float
- function probe_setup() -> void {
- lab_model(lab_plate_dir(), "probe.gltf", "probe", 0.0, 0.0, 0.5)
- lab_note("a box and a ball, in the plate's light")
- lab_shot_at("front", 0.0, 0.7, 0.0, 4.0, 0.0, 12.0)
- lab_shot_at("side", 0.0, 0.7, 0.0, 4.0, 90.0, 12.0)
- lab_shot_at("above", 0.0, 0.5, 0.0, 7.0, 30.0, 55.0)
+ function probe_setup(lab_st: mut LabState, render3d_st: mut Render3dState) -> void {
+ lab_model(render3d_st, lab_plate_dir(lab_st), "probe.gltf", "probe", 0.0, 0.0, 0.5)
+ lab_note(lab_st, "a box and a ball, in the plate's light")
+ lab_shot_at(lab_st, "front", 0.0, 0.7, 0.0, 4.0, 0.0, 12.0)
+ lab_shot_at(lab_st, "side", 0.0, 0.7, 0.0, 4.0, 90.0, 12.0)
+ lab_shot_at(lab_st, "above", 0.0, 0.5, 0.0, 7.0, 30.0, 55.0)
}
def LabScenes probe { setup: fn probe_setup }
- handler Boot phase Start { lab_boot() }
- handler Look phase Input { lab_input() }
- handler Draw phase Render { lab_render() }
+ handler Boot(lab_st: mut LabState, render3d_st: mut Render3dState) phase Start { lab_boot(lab_st, render3d_st) }
+ handler Look(lab_st: mut LabState, render3d_st: mut Render3dState) phase Input { lab_input(lab_st, render3d_st) }
+ handler Draw(lab_st: mut LabState, render3d_st: mut Render3dState) phase Render { lab_render(lab_st, render3d_st) }
}
diff --git a/packages/ludic.net/peers.ludic b/packages/ludic.net/peers.ludic
index 86ccc9c7..96d61833 100644
--- a/packages/ludic.net/peers.ludic
+++ b/packages/ludic.net/peers.ludic
@@ -17,7 +17,7 @@ export function np_of_pid(net_st: NetState, pid: int) -> int {
return -1
}
-function net__open(net_st: NetState, slot: int, ip: int, port: int, pid: int) -> void {
+function net__open(net_st: mut NetState, slot: int, ip: int, port: int, pid: int) -> void {
let p = net_st.net__peers[slot]
p.on = true
p.ip = ip
@@ -32,7 +32,7 @@ function net__open(net_st: NetState, slot: int, ip: int, port: int, pid: int) ->
p.closing = false
}
-export function np_close(net_st: NetState, slot: int) -> void {
+export function np_close(net_st: mut NetState, slot: int) -> void {
if net_st.net__peers == null or slot < 0 or slot >= NET_PEERS { return }
let p = net_st.net__peers[slot]
p.on = false
@@ -41,7 +41,7 @@ export function np_close(net_st: NetState, slot: int) -> void {
}
# the written message, for one peer (a reliable one is kept until it is taken)
-export function np_queue(net_st: NetState, slot: int, kind: int, rel: bool) -> void {
+export function np_queue(net_st: mut NetState, slot: int, kind: int, rel: bool) -> void {
if net_st.net__peers == null or slot < 0 or slot >= NET_PEERS { return }
let p = net_st.net__peers[slot]
if not p.on { return }
@@ -63,7 +63,7 @@ export function np_queue(net_st: NetState, slot: int, kind: int, rel: bool) -> v
}
# to everyone this machine talks to (a host: every guest but `except`; a guest: the host)
-export function np_queue_all(net_st: NetState, kind: int, rel: bool, except: int) -> void {
+export function np_queue_all(net_st: mut NetState, kind: int, rel: bool, except: int) -> void {
if net_st.net__peers == null { return }
for i in 0 .. NET_PEERS { if net_st.net__peers[i].on and i != except { np_queue(net_st, i, kind, rel) } }
}
@@ -94,7 +94,7 @@ export function net_peer_heard(net_st: NetState, slot: int) -> float {
}
# let the slot go once it has taken everything queued for it so far, or after `secs`
-export function net_close_when_sent(net_st: NetState, slot: int, secs: float) -> void {
+export function net_close_when_sent(net_st: mut NetState, slot: int, secs: float) -> void {
if not net_peer_on(net_st, slot) { return }
let p = net_st.net__peers[slot]
p.closing = true
diff --git a/packages/ludic.net/receive.ludic b/packages/ludic.net/receive.ludic
index 035c33a8..d1e5b104 100644
--- a/packages/ludic.net/receive.ludic
+++ b/packages/ludic.net/receive.ludic
@@ -29,7 +29,7 @@ function net__packet(base_st: mut BaseState, net_st: mut NetState, n: int, ip: i
}
# the known peer at that address, a stranger's new slot (a host, and only with a hello), or -1
-function net__slot_for(net_st: NetState, n: int, ip: int, port: int) -> int {
+function net__slot_for(net_st: mut NetState, n: int, ip: int, port: int) -> int {
let token = nb_get32(net_st.net__in, 4)
var slot = net__find(net_st, ip, port)
if slot >= 0 {
diff --git a/packages/ludic.prefs/prefs.ludic b/packages/ludic.prefs/prefs.ludic
index 099e0a3a..6b1ddcf9 100644
--- a/packages/ludic.prefs/prefs.ludic
+++ b/packages/ludic.prefs/prefs.ludic
@@ -9,49 +9,51 @@
const PREFS_MAX: int = 64
-var prefs_path: pointer = null
-var prefs_names: pointers = null
-var prefs_vals: words = null
-var prefs_n: int = 0
+state PrefsState {
+ prefs_path: pointer = null
+ prefs_names: pointers = null
+ prefs_vals: words = null
+ prefs_n: int = 0
+}
namespace Prefs {
- internal function init() -> void {
- if prefs_names != null { return }
- prefs_names = pointers(PREFS_MAX)
- prefs_vals = words(PREFS_MAX)
- prefs_n = 0
+ internal function init(prefs_st: mut PrefsState) -> void {
+ if prefs_st.prefs_names != null { return }
+ prefs_st.prefs_names = pointers(PREFS_MAX)
+ prefs_st.prefs_vals = words(PREFS_MAX)
+ prefs_st.prefs_n = 0
}
- internal function find(key: pointer) -> int {
+ internal function find(prefs_st: PrefsState, key: pointer) -> int {
var i = 0
- while i < prefs_n { if prefs_names[i] == key { return i }; i += 1 }
+ while i < prefs_st.prefs_n { if prefs_st.prefs_names[i] == key { return i }; i += 1 }
return -1
}
- export function get(key: pointer, fallback: int) -> int {
- init()
- let i = find(key)
+ export function get(prefs_st: mut PrefsState, key: pointer, fallback: int) -> int {
+ init(prefs_st)
+ let i = find(prefs_st, key)
if i < 0 { return fallback }
- return prefs_vals[i]
+ return prefs_st.prefs_vals[i]
}
# keep the larger of the stored value and `value`; true when `value` is a new record
- export function max(key: pointer, value: int) -> bool {
- if value <= get(key, 0) { return false }
- set(key, value)
+ export function max(prefs_st: mut PrefsState, key: pointer, value: int) -> bool {
+ if value <= get(prefs_st, key, 0) { return false }
+ set(prefs_st, key, value)
return true
}
- export function set(key: pointer, value: int) -> void {
- init()
- var i = find(key)
+ export function set(prefs_st: mut PrefsState, key: pointer, value: int) -> void {
+ init(prefs_st)
+ var i = find(prefs_st, key)
if i < 0 {
- if prefs_n >= PREFS_MAX { return }
- i = prefs_n
- prefs_names[i] = key
- prefs_n += 1
+ if prefs_st.prefs_n >= PREFS_MAX { return }
+ i = prefs_st.prefs_n
+ prefs_st.prefs_names[i] = key
+ prefs_st.prefs_n += 1
}
- prefs_vals[i] = value
+ prefs_st.prefs_vals[i] = value
}
# the text of a whole file, or null when it does not exist
@@ -69,7 +71,7 @@ namespace Prefs {
}
# digits after `=` on one line -> an int (a leading '-' negates)
- internal function parse_line(buf: pointer, from: int, to: int) -> void {
+ internal function parse_line(prefs_st: mut PrefsState, buf: pointer, from: int, to: int) -> void {
var eq = from
while (eq < to) and (buf[eq] != '=') { eq += 1 } # '='
if eq >= to { return }
@@ -83,21 +85,21 @@ namespace Prefs {
if (p < to) and (buf[p] == '-') { neg = true; p += 1 } # '-'
while (p < to) and (buf[p] >= '0') and (buf[p] <= '9') { v = v * 10 + (buf[p] - 48); p += 1 }
if neg { v = -v }
- set(key, v)
+ set(prefs_st, key, v)
}
- export function load(path: pointer) -> void {
- init()
- prefs_path = path
+ export function load(prefs_st: mut PrefsState, path: pointer) -> void {
+ init(prefs_st)
+ prefs_st.prefs_path = path
let buf = read_all(path)
if buf == null { return }
var start = 0
var i = 0
while buf[i] != 0 {
- if buf[i] == '\n' { parse_line(buf, start, i); start = i + 1 } # newline
+ if buf[i] == '\n' { parse_line(prefs_st, buf, start, i); start = i + 1 } # newline
i += 1
}
- if i > start { parse_line(buf, start, i) }
+ if i > start { parse_line(prefs_st, buf, start, i) }
}
internal function write_text(f: pointer, s: pointer) -> void {
@@ -106,14 +108,14 @@ namespace Prefs {
file_write(f, s, n)
}
- export function save() -> void {
- init()
- if prefs_path == null { return }
- let f = file_open(prefs_path, "wb")
+ export function save(prefs_st: mut PrefsState) -> void {
+ init(prefs_st)
+ if prefs_st.prefs_path == null { return }
+ let f = file_open(prefs_st.prefs_path, "wb")
if f == null { return }
var i = 0
- while i < prefs_n {
- write_text(f, `{prefs_names[i]}={string(prefs_vals[i])}\n`)
+ while i < prefs_st.prefs_n {
+ write_text(f, `{prefs_st.prefs_names[i]}={string(prefs_st.prefs_vals[i])}\n`)
i += 1
}
file_close(f)
diff --git a/packages/ludic.rpg/crafting.ludic b/packages/ludic.rpg/crafting.ludic
index 7f058a87..d54c2861 100644
--- a/packages/ludic.rpg/crafting.ludic
+++ b/packages/ludic.rpg/crafting.ludic
@@ -9,20 +9,12 @@ import "ludic.rpg/inventory.ludic"
const CRAFT_MAX: int = 128
const CRAFT_ING_MAX: int = 512
-var recipe_reg: Dict = null # recipe name -> id
-var recipe_n: int = 0
-var recipe_out: pointers = null # output item name
-var recipe_outqty: words = null
-var ing_n: int = 0
-var ing_rid: words = null # owning recipe id
-var ing_name: pointers = null # ingredient item name
-var ing_qty: words = null
-function craft_ensure() -> void {
- if recipe_reg == null {
- recipe_reg = Dict.new()
- recipe_out = pointers(CRAFT_MAX); recipe_outqty = words(CRAFT_MAX)
- ing_rid = words(CRAFT_ING_MAX); ing_name = pointers(CRAFT_ING_MAX); ing_qty = words(CRAFT_ING_MAX)
+function craft_ensure(rpg_st: mut RpgState) -> void {
+ if rpg_st.recipe_reg == null {
+ rpg_st.recipe_reg = Dict.new()
+ rpg_st.recipe_out = pointers(CRAFT_MAX); rpg_st.recipe_outqty = words(CRAFT_MAX)
+ rpg_st.ing_rid = words(CRAFT_ING_MAX); rpg_st.ing_name = pointers(CRAFT_ING_MAX); rpg_st.ing_qty = words(CRAFT_ING_MAX)
}
}
@@ -30,35 +22,35 @@ event cancellable CraftRequested { e: int = 0, recipe: int = 0 }
event CraftCompleted { e: int = 0, recipe: int = 0 }
# Craft.recipe(name, out_item, out_qty) — declare a recipe; returns its id.
-@Namespace(Craft) function craft_recipe(name: pointer, out_item: pointer, out_qty: int) -> int {
- craft_ensure()
- if recipe_n >= CRAFT_MAX { return -1 }
- let id = recipe_n
- recipe_out[id] = out_item; recipe_outqty[id] = out_qty
- recipe_n += 1
- Dict.set(recipe_reg, name, id)
+@Namespace(Craft) function craft_recipe(rpg_st: mut RpgState, name: pointer, out_item: pointer, out_qty: int) -> int {
+ craft_ensure(rpg_st)
+ if rpg_st.recipe_n >= CRAFT_MAX { return -1 }
+ let id = rpg_st.recipe_n
+ rpg_st.recipe_out[id] = out_item; rpg_st.recipe_outqty[id] = out_qty
+ rpg_st.recipe_n += 1
+ Dict.set(rpg_st.recipe_reg, name, id)
return id
}
-@Namespace(Craft) function craft_id(name: pointer) -> int { craft_ensure(); return Dict.get_or(recipe_reg, name, -1) }
+@Namespace(Craft) function craft_id(rpg_st: mut RpgState, name: pointer) -> int { craft_ensure(rpg_st); return Dict.get_or(rpg_st.recipe_reg, name, -1) }
# Craft.ingredient(recipe_name, item, qty) — add an ingredient requirement.
-@Namespace(Craft) function craft_ingredient(recipe_name: pointer, item: pointer, qty: int) -> void {
- craft_ensure()
- let rid = craft_id(recipe_name)
+@Namespace(Craft) function craft_ingredient(rpg_st: mut RpgState, recipe_name: pointer, item: pointer, qty: int) -> void {
+ craft_ensure(rpg_st)
+ let rid = craft_id(rpg_st, recipe_name)
if rid < 0 { return }
- if ing_n >= CRAFT_ING_MAX { return }
- ing_rid[ing_n] = rid; ing_name[ing_n] = item; ing_qty[ing_n] = qty
- ing_n += 1
+ if rpg_st.ing_n >= CRAFT_ING_MAX { return }
+ rpg_st.ing_rid[rpg_st.ing_n] = rid; rpg_st.ing_name[rpg_st.ing_n] = item; rpg_st.ing_qty[rpg_st.ing_n] = qty
+ rpg_st.ing_n += 1
}
# Craft.can(e, recipe_name) — does the owner hold every ingredient?
-@Namespace(Craft) function craft_can(e: int, recipe_name: pointer) -> int {
- craft_ensure()
- let rid = craft_id(recipe_name)
+@Namespace(Craft) function craft_can(rpg_st: mut RpgState, e: int, recipe_name: pointer) -> int {
+ craft_ensure(rpg_st)
+ let rid = craft_id(rpg_st, recipe_name)
if rid < 0 { return 0 }
var i = 0
- while i < ing_n {
- if ing_rid[i] == rid {
- if Inventory.count(e, ing_name[i]) < ing_qty[i] { return 0 }
+ while i < rpg_st.ing_n {
+ if rpg_st.ing_rid[i] == rid {
+ if Inventory.count(e, rpg_st.ing_name[i]) < rpg_st.ing_qty[i] { return 0 }
}
i += 1
}
@@ -66,18 +58,18 @@ event CraftCompleted { e: int = 0, recipe: int = 0 }
}
# Craft.make(e, recipe_name) — consume ingredients + produce output; 1 on success.
-@Namespace(Craft) function craft_make(e: int, recipe_name: pointer) -> int {
- craft_ensure()
- let rid = craft_id(recipe_name)
+@Namespace(Craft) function craft_make(rpg_st: mut RpgState, e: int, recipe_name: pointer) -> int {
+ craft_ensure(rpg_st)
+ let rid = craft_id(rpg_st, recipe_name)
if rid < 0 { return 0 }
- if craft_can(e, recipe_name) == 0 { return 0 }
+ if craft_can(rpg_st, e, recipe_name) == 0 { return 0 }
if emit CraftRequested(e: e, recipe: rid) != 0 { return 0 }
var i = 0
- while i < ing_n {
- if ing_rid[i] == rid { Inventory.remove(e, ing_name[i], ing_qty[i]) }
+ while i < rpg_st.ing_n {
+ if rpg_st.ing_rid[i] == rid { Inventory.remove(e, rpg_st.ing_name[i], rpg_st.ing_qty[i]) }
i += 1
}
- Inventory.add(e, recipe_out[rid], recipe_outqty[rid])
+ Inventory.add(e, rpg_st.recipe_out[rid], rpg_st.recipe_outqty[rid])
emit CraftCompleted(e: e, recipe: rid)
return 1
}
diff --git a/packages/ludic.rpg/dialog.ludic b/packages/ludic.rpg/dialog.ludic
index 41103b8d..91bd8e41 100644
--- a/packages/ludic.rpg/dialog.ludic
+++ b/packages/ludic.rpg/dialog.ludic
@@ -8,23 +8,13 @@ const DLG_TREE_MAX: int = 64
const DLG_NODE_MAX: int = 512
const DLG_CHOICE_MAX: int = 1024
-var dlg_reg: Dict = null # tree name -> tree id
-var dlg_tree_n: int = 0
-var dlg_tree_start: words = null # first node id per tree
-var dlg_node_n: int = 0
-var dlg_node_tree: words = null
-var dlg_node_text: pointers = null
-var dlg_choice_n: int = 0
-var dlg_choice_node: words = null # owning node
-var dlg_choice_label: pointers = null
-var dlg_choice_next: words = null # next node id, or -1 to end
-function dlg_ensure() -> void {
- if dlg_reg == null {
- dlg_reg = Dict.new()
- dlg_tree_start = words(DLG_TREE_MAX)
- dlg_node_tree = words(DLG_NODE_MAX); dlg_node_text = pointers(DLG_NODE_MAX)
- dlg_choice_node = words(DLG_CHOICE_MAX); dlg_choice_label = pointers(DLG_CHOICE_MAX); dlg_choice_next = words(DLG_CHOICE_MAX)
+function dlg_ensure(rpg_st: mut RpgState) -> void {
+ if rpg_st.dlg_reg == null {
+ rpg_st.dlg_reg = Dict.new()
+ rpg_st.dlg_tree_start = words(DLG_TREE_MAX)
+ rpg_st.dlg_node_tree = words(DLG_NODE_MAX); rpg_st.dlg_node_text = pointers(DLG_NODE_MAX)
+ rpg_st.dlg_choice_node = words(DLG_CHOICE_MAX); rpg_st.dlg_choice_label = pointers(DLG_CHOICE_MAX); rpg_st.dlg_choice_next = words(DLG_CHOICE_MAX)
}
}
@@ -36,37 +26,37 @@ event cancellable DialogChoice { e: int = 0, node: int = 0, choice: int = 0 }
event DialogEnded { e: int = 0 }
# Dialog.tree(name) — register (or fetch) a tree id.
-@Namespace(Dialog) function dialog_tree(name: pointer) -> int {
- dlg_ensure()
- let ex = Dict.get_or(dlg_reg, name, -1)
+@Namespace(Dialog) function dialog_tree(rpg_st: mut RpgState, name: pointer) -> int {
+ dlg_ensure(rpg_st)
+ let ex = Dict.get_or(rpg_st.dlg_reg, name, -1)
if ex >= 0 { return ex }
- if dlg_tree_n >= DLG_TREE_MAX { return -1 }
- let id = dlg_tree_n
- dlg_tree_start[id] = -1
- dlg_tree_n += 1
- Dict.set(dlg_reg, name, id)
+ if rpg_st.dlg_tree_n >= DLG_TREE_MAX { return -1 }
+ let id = rpg_st.dlg_tree_n
+ rpg_st.dlg_tree_start[id] = -1
+ rpg_st.dlg_tree_n += 1
+ Dict.set(rpg_st.dlg_reg, name, id)
return id
}
# Dialog.node(tree_name, text) — add a line node; the first node added to a tree is
# its entry point. Returns the node id.
-@Namespace(Dialog) function dialog_node(tree_name: pointer, text: pointer) -> int {
- dlg_ensure()
- let tid = dialog_tree(tree_name)
+@Namespace(Dialog) function dialog_node(rpg_st: mut RpgState, tree_name: pointer, text: pointer) -> int {
+ dlg_ensure(rpg_st)
+ let tid = dialog_tree(rpg_st, tree_name)
if tid < 0 { return -1 }
- if dlg_node_n >= DLG_NODE_MAX { return -1 }
- let id = dlg_node_n
- dlg_node_tree[id] = tid; dlg_node_text[id] = text
- dlg_node_n += 1
- if dlg_tree_start[tid] < 0 { dlg_tree_start[tid] = id }
+ if rpg_st.dlg_node_n >= DLG_NODE_MAX { return -1 }
+ let id = rpg_st.dlg_node_n
+ rpg_st.dlg_node_tree[id] = tid; rpg_st.dlg_node_text[id] = text
+ rpg_st.dlg_node_n += 1
+ if rpg_st.dlg_tree_start[tid] < 0 { rpg_st.dlg_tree_start[tid] = id }
return id
}
# Dialog.choice(node, label, next_node) — add a branch (next_node -1 ends).
-@Namespace(Dialog) function dialog_choice(node: int, label: pointer, next_node: int) -> int {
- dlg_ensure()
- if dlg_choice_n >= DLG_CHOICE_MAX { return -1 }
- let id = dlg_choice_n
- dlg_choice_node[id] = node; dlg_choice_label[id] = label; dlg_choice_next[id] = next_node
- dlg_choice_n += 1
+@Namespace(Dialog) function dialog_choice(rpg_st: mut RpgState, node: int, label: pointer, next_node: int) -> int {
+ dlg_ensure(rpg_st)
+ if rpg_st.dlg_choice_n >= DLG_CHOICE_MAX { return -1 }
+ let id = rpg_st.dlg_choice_n
+ rpg_st.dlg_choice_node[id] = node; rpg_st.dlg_choice_label[id] = label; rpg_st.dlg_choice_next[id] = next_node
+ rpg_st.dlg_choice_n += 1
return id
}
@@ -80,45 +70,45 @@ function dlg_set(e: int, name: pointer, v: int) -> void {
}
# Dialog.start(e, tree_name) — open the tree on a speaker (DialogStarted + Line).
-@Namespace(Dialog) function dialog_start(e: int, tree_name: pointer) -> void {
- dlg_ensure()
- let tid = dialog_tree(tree_name)
+@Namespace(Dialog) function dialog_start(rpg_st: mut RpgState, e: int, tree_name: pointer) -> void {
+ dlg_ensure(rpg_st)
+ let tid = dialog_tree(rpg_st, tree_name)
if tid < 0 { return }
dlg_set(e, "tree", tid)
- dlg_set(e, "node", dlg_tree_start[tid])
+ dlg_set(e, "node", rpg_st.dlg_tree_start[tid])
dlg_set(e, "active", 1)
emit DialogStarted(e: e, tree: tid)
- emit DialogLine(e: e, node: dlg_tree_start[tid])
+ emit DialogLine(e: e, node: rpg_st.dlg_tree_start[tid])
}
# Dialog.line(e) — text of the current node (empty when inactive).
-@Namespace(Dialog) function dialog_line(e: int) -> pointer {
+@Namespace(Dialog) function dialog_line(rpg_st: RpgState, e: int) -> pointer {
if dlg_get(e, "active") == 0 { return "" }
- return dlg_node_text[dlg_get(e, "node")]
+ return rpg_st.dlg_node_text[dlg_get(e, "node")]
}
# Dialog.count(e) — number of choices at the current node.
-@Namespace(Dialog) function dialog_count(e: int) -> int {
- dlg_ensure()
+@Namespace(Dialog) function dialog_count(rpg_st: mut RpgState, e: int) -> int {
+ dlg_ensure(rpg_st)
if dlg_get(e, "active") == 0 { return 0 }
let node = dlg_get(e, "node")
var n = 0; var i = 0
- while i < dlg_choice_n { if dlg_choice_node[i] == node { n += 1 }; i += 1 }
+ while i < rpg_st.dlg_choice_n { if rpg_st.dlg_choice_node[i] == node { n += 1 }; i += 1 }
return n
}
# Dialog.choose(e, i) — take the i-th choice (veto-able); advance or end.
-@Namespace(Dialog) function dialog_choose(e: int, idx: int) -> int {
- dlg_ensure()
+@Namespace(Dialog) function dialog_choose(rpg_st: mut RpgState, e: int, idx: int) -> int {
+ dlg_ensure(rpg_st)
if dlg_get(e, "active") == 0 { return 0 }
let node = dlg_get(e, "node")
var seen = 0; var i = 0; var pick = -1
- while i < dlg_choice_n {
- if dlg_choice_node[i] == node {
- if seen == idx { pick = i; i = dlg_choice_n } else { seen += 1 }
+ while i < rpg_st.dlg_choice_n {
+ if rpg_st.dlg_choice_node[i] == node {
+ if seen == idx { pick = i; i = rpg_st.dlg_choice_n } else { seen += 1 }
}
i += 1
}
if pick < 0 { return 0 }
if emit DialogChoice(e: e, node: node, choice: idx) != 0 { return 0 }
- let nxt = dlg_choice_next[pick]
+ let nxt = rpg_st.dlg_choice_next[pick]
if nxt < 0 {
dlg_set(e, "active", 0)
emit DialogEnded(e: e)
diff --git a/packages/ludic.rpg/inventory.ludic b/packages/ludic.rpg/inventory.ludic
index fbb193f2..cf188a28 100644
--- a/packages/ludic.rpg/inventory.ludic
+++ b/packages/ludic.rpg/inventory.ludic
@@ -10,40 +10,70 @@ import "ludic.gameplay/stats.ludic"
const INV_MAX: int = 256
# ---- item registry --------------------------------------------------------
-var item_reg: Dict = null
-var item_n: int = 0
-var item_stackmax: words = null
-var item_weight: words = null
-var item_value: words = null
+state RpgState {
+ recipe_reg: Dict = null # recipe name -> id
+ recipe_n: int = 0
+ recipe_out: pointers = null # output item name
+ recipe_outqty: words = null
+ ing_n: int = 0
+ ing_rid: words = null # owning recipe id
+ ing_name: pointers = null # ingredient item name
+ ing_qty: words = null
+ dlg_reg: Dict = null # tree name -> tree id
+ dlg_tree_n: int = 0
+ dlg_tree_start: words = null # first node id per tree
+ dlg_node_n: int = 0
+ dlg_node_tree: words = null
+ dlg_node_text: pointers = null
+ dlg_choice_n: int = 0
+ dlg_choice_node: words = null # owning node
+ dlg_choice_label: pointers = null
+ dlg_choice_next: words = null # next node id, or -1 to end
+ item_reg: Dict = null
+ item_n: int = 0
+ item_stackmax: words = null
+ item_weight: words = null
+ item_value: words = null
+ inv_counts: Dict = null
+ quest_reg: Dict = null # quest name -> id
+ quest_n: int = 0
+ quest_state: words = null
+ obj_n: int = 0
+ obj_qid: words = null # owning quest id
+ obj_kind: words = null
+ obj_target: words = null # a target id/tag (kind-specific; 0 = any)
+ obj_need: words = null
+ obj_have: words = null
+ quest_flags: Dict = null # global flag store
+}
-function item_ensure() -> void {
- if item_reg == null {
- item_reg = Dict.new()
- item_stackmax = words(INV_MAX); item_weight = words(INV_MAX); item_value = words(INV_MAX)
+function item_ensure(rpg_st: mut RpgState) -> void {
+ if rpg_st.item_reg == null {
+ rpg_st.item_reg = Dict.new()
+ rpg_st.item_stackmax = words(INV_MAX); rpg_st.item_weight = words(INV_MAX); rpg_st.item_value = words(INV_MAX)
}
}
# Item.def(name, stack_max, weight, value) — register an item template. Returns id.
-@Namespace(Item) function item_def(name: pointer, stack_max: int, weight: int, value: int) -> int {
- item_ensure()
- if item_n >= INV_MAX { return -1 }
- let id = item_n
- item_stackmax[id] = stack_max; item_weight[id] = weight; item_value[id] = value
- item_n += 1
- Dict.set(item_reg, name, id)
+@Namespace(Item) function item_def(rpg_st: mut RpgState, name: pointer, stack_max: int, weight: int, value: int) -> int {
+ item_ensure(rpg_st)
+ if rpg_st.item_n >= INV_MAX { return -1 }
+ let id = rpg_st.item_n
+ rpg_st.item_stackmax[id] = stack_max; rpg_st.item_weight[id] = weight; rpg_st.item_value[id] = value
+ rpg_st.item_n += 1
+ Dict.set(rpg_st.item_reg, name, id)
return id
}
-@Namespace(Item) function item_id(name: pointer) -> int { item_ensure(); return Dict.get_or(item_reg, name, -1) }
-@Namespace(Item) function item_value_of(name: pointer) -> int {
- let id = item_id(name); if id < 0 { return 0 }
- return item_value[id]
+@Namespace(Item) function item_id(rpg_st: mut RpgState, name: pointer) -> int { item_ensure(rpg_st); return Dict.get_or(rpg_st.item_reg, name, -1) }
+@Namespace(Item) function item_value_of(rpg_st: mut RpgState, name: pointer) -> int {
+ let id = item_id(rpg_st, name); if id < 0 { return 0 }
+ return rpg_st.item_value[id]
}
# ---- per-owner counts -----------------------------------------------------
property Inventory { gold: int = 0, weight_max: int = 0, slots: int = 0 }
-var inv_counts: Dict = null
-function inv_ensure() -> void { if inv_counts == null { inv_counts = Dict.new() } }
+function inv_ensure(rpg_st: mut RpgState) -> void { if rpg_st.inv_counts == null { rpg_st.inv_counts = Dict.new() } }
function inv_key(e: int, name: pointer) -> pointer { return `{e}/{name}` }
event ItemAdded { e: int = 0, item: int = 0, qty: int = 0 } # item = registry id
@@ -51,36 +81,36 @@ event ItemRemoved { e: int = 0, item: int = 0, qty: int = 0 }
event cancellable ItemUse { e: int = 0, item: int = 0 } # veto a use (e.g. cursed / requirement)
# Inventory.add(e, name, n) — add n of an item; emits ItemAdded.
-@Namespace(Inventory) function inventory_add(e: int, name: pointer, n: int) -> void {
- inv_ensure()
+@Namespace(Inventory) function inventory_add(rpg_st: mut RpgState, e: int, name: pointer, n: int) -> void {
+ inv_ensure(rpg_st)
let k = inv_key(e, name)
- Dict.set(inv_counts, k, Dict.get_or(inv_counts, k, 0) + n)
- emit ItemAdded(e: e, item: item_id(name), qty: n)
+ Dict.set(rpg_st.inv_counts, k, Dict.get_or(rpg_st.inv_counts, k, 0) + n)
+ emit ItemAdded(e: e, item: item_id(rpg_st, name), qty: n)
}
# Inventory.count(e, name) — how many the owner holds.
-@Namespace(Inventory) function inventory_count(e: int, name: pointer) -> int {
- inv_ensure()
- return Dict.get_or(inv_counts, inv_key(e, name), 0)
+@Namespace(Inventory) function inventory_count(rpg_st: mut RpgState, e: int, name: pointer) -> int {
+ inv_ensure(rpg_st)
+ return Dict.get_or(rpg_st.inv_counts, inv_key(e, name), 0)
}
-@Namespace(Inventory) function inventory_has(e: int, name: pointer) -> int {
- if inventory_count(e, name) > 0 { return 1 }
+@Namespace(Inventory) function inventory_has(rpg_st: mut RpgState, e: int, name: pointer) -> int {
+ if inventory_count(rpg_st, e, name) > 0 { return 1 }
return 0
}
# Inventory.remove(e, name, n) — remove up to n; returns 1 if it had enough.
-@Namespace(Inventory) function inventory_remove(e: int, name: pointer, n: int) -> int {
- inv_ensure()
+@Namespace(Inventory) function inventory_remove(rpg_st: mut RpgState, e: int, name: pointer, n: int) -> int {
+ inv_ensure(rpg_st)
let k = inv_key(e, name)
- let have = Dict.get_or(inv_counts, k, 0)
+ let have = Dict.get_or(rpg_st.inv_counts, k, 0)
if have < n { return 0 }
- Dict.set(inv_counts, k, have - n)
- emit ItemRemoved(e: e, item: item_id(name), qty: n)
+ Dict.set(rpg_st.inv_counts, k, have - n)
+ emit ItemRemoved(e: e, item: item_id(rpg_st, name), qty: n)
return 1
}
# Inventory.use(e, name) — consume one item unless a listener vetoes ItemUse.
-@Namespace(Inventory) function inventory_use(e: int, name: pointer) -> int {
- if inventory_count(e, name) <= 0 { return 0 }
- if emit ItemUse(e: e, item: item_id(name)) != 0 { return 0 }
- return inventory_remove(e, name, 1)
+@Namespace(Inventory) function inventory_use(rpg_st: mut RpgState, e: int, name: pointer) -> int {
+ if inventory_count(rpg_st, e, name) <= 0 { return 0 }
+ if emit ItemUse(e: e, item: item_id(rpg_st, name)) != 0 { return 0 }
+ return inventory_remove(rpg_st, e, name, 1)
}
# Inventory.gold / add_gold / spend_gold
@Namespace(Inventory) function inventory_gold(e: int) -> int {
diff --git a/packages/ludic.rpg/quests.ludic b/packages/ludic.rpg/quests.ludic
index 3e6105e7..277ec1cb 100644
--- a/packages/ludic.rpg/quests.ludic
+++ b/packages/ludic.rpg/quests.ludic
@@ -14,23 +14,13 @@
const QUEST_MAX: int = 128
const OBJ_MAX: int = 512
-var quest_reg: Dict = null # quest name -> id
-var quest_n: int = 0
-var quest_state: words = null
-var obj_n: int = 0
-var obj_qid: words = null # owning quest id
-var obj_kind: words = null
-var obj_target: words = null # a target id/tag (kind-specific; 0 = any)
-var obj_need: words = null
-var obj_have: words = null
-var quest_flags: Dict = null # global flag store
-function quest_ensure() -> void {
- if quest_reg == null {
- quest_reg = Dict.new(); quest_flags = Dict.new()
- quest_state = words(QUEST_MAX)
- obj_qid = words(OBJ_MAX); obj_kind = words(OBJ_MAX); obj_target = words(OBJ_MAX)
- obj_need = words(OBJ_MAX); obj_have = words(OBJ_MAX)
+function quest_ensure(rpg_st: mut RpgState) -> void {
+ if rpg_st.quest_reg == null {
+ rpg_st.quest_reg = Dict.new(); rpg_st.quest_flags = Dict.new()
+ rpg_st.quest_state = words(QUEST_MAX)
+ rpg_st.obj_qid = words(OBJ_MAX); rpg_st.obj_kind = words(OBJ_MAX); rpg_st.obj_target = words(OBJ_MAX)
+ rpg_st.obj_need = words(OBJ_MAX); rpg_st.obj_have = words(OBJ_MAX)
}
}
@@ -39,48 +29,48 @@ event QuestCompleted { quest: int = 0 }
event ObjectiveProgressed { quest: int = 0, objective: int = 0, have: int = 0, need: int = 0 }
# Quest.define(name) — register a quest (state hidden). Returns id.
-@Namespace(Quest) function quest_define(name: pointer) -> int {
- quest_ensure()
- if quest_n >= QUEST_MAX { return -1 }
- let id = quest_n
- quest_state[id] = 0
- quest_n += 1
- Dict.set(quest_reg, name, id)
+@Namespace(Quest) function quest_define(rpg_st: mut RpgState, name: pointer) -> int {
+ quest_ensure(rpg_st)
+ if rpg_st.quest_n >= QUEST_MAX { return -1 }
+ let id = rpg_st.quest_n
+ rpg_st.quest_state[id] = 0
+ rpg_st.quest_n += 1
+ Dict.set(rpg_st.quest_reg, name, id)
return id
}
-@Namespace(Quest) function quest_id(name: pointer) -> int { quest_ensure(); return Dict.get_or(quest_reg, name, -1) }
+@Namespace(Quest) function quest_id(rpg_st: mut RpgState, name: pointer) -> int { quest_ensure(rpg_st); return Dict.get_or(rpg_st.quest_reg, name, -1) }
# Quest.objective(quest_name, kind, target, need) — add an objective.
-@Namespace(Quest) function quest_objective(quest_name: pointer, kind: int, target: int, need: int) -> int {
- quest_ensure()
- let qid = quest_id(quest_name)
+@Namespace(Quest) function quest_objective(rpg_st: mut RpgState, quest_name: pointer, kind: int, target: int, need: int) -> int {
+ quest_ensure(rpg_st)
+ let qid = quest_id(rpg_st, quest_name)
if qid < 0 { return -1 }
- if obj_n >= OBJ_MAX { return -1 }
- let id = obj_n
- obj_qid[id] = qid; obj_kind[id] = kind; obj_target[id] = target
- obj_need[id] = need; obj_have[id] = 0
- obj_n += 1
+ if rpg_st.obj_n >= OBJ_MAX { return -1 }
+ let id = rpg_st.obj_n
+ rpg_st.obj_qid[id] = qid; rpg_st.obj_kind[id] = kind; rpg_st.obj_target[id] = target
+ rpg_st.obj_need[id] = need; rpg_st.obj_have[id] = 0
+ rpg_st.obj_n += 1
return id
}
# Quest.start(name) — activate a quest (emits QuestStarted).
-@Namespace(Quest) function quest_start(name: pointer) -> void {
- quest_ensure()
- let qid = quest_id(name)
+@Namespace(Quest) function quest_start(rpg_st: mut RpgState, name: pointer) -> void {
+ quest_ensure(rpg_st)
+ let qid = quest_id(rpg_st, name)
if qid < 0 { return }
- quest_state[qid] = 2
+ rpg_st.quest_state[qid] = 2
emit QuestStarted(quest: qid)
}
-@Namespace(Quest) function quest_state_of(name: pointer) -> int {
- quest_ensure()
- let qid = quest_id(name)
+@Namespace(Quest) function quest_state_of(rpg_st: mut RpgState, name: pointer) -> int {
+ quest_ensure(rpg_st)
+ let qid = quest_id(rpg_st, name)
if qid < 0 { return -1 }
- return quest_state[qid]
+ return rpg_st.quest_state[qid]
}
# is every objective of a quest satisfied?
-function quest_all_done(qid: int) -> int {
+function quest_all_done(rpg_st: RpgState, qid: int) -> int {
var i = 0
- while i < obj_n {
- if obj_qid[i] == qid { if obj_have[i] < obj_need[i] { return 0 } }
+ while i < rpg_st.obj_n {
+ if rpg_st.obj_qid[i] == qid { if rpg_st.obj_have[i] < rpg_st.obj_need[i] { return 0 } }
i += 1
}
return 1
@@ -89,20 +79,20 @@ function quest_all_done(qid: int) -> int {
# Quest.notify(kind, target, amount) — report a gameplay signal. Every ACTIVE
# quest's matching objectives tick up (target 0 on the objective = "any"); a quest
# whose objectives are all met auto-completes.
-@Namespace(Quest) function quest_notify(kind: int, target: int, amount: int) -> void {
- quest_ensure()
+@Namespace(Quest) function quest_notify(rpg_st: mut RpgState, kind: int, target: int, amount: int) -> void {
+ quest_ensure(rpg_st)
var i = 0
- while i < obj_n {
- let qid = obj_qid[i]
- if quest_state[qid] == 2 {
- if obj_kind[i] == kind {
- if (obj_target[i] == 0) or (obj_target[i] == target) {
- if obj_have[i] < obj_need[i] {
- obj_have[i] += amount
- if obj_have[i] > obj_need[i] { obj_have[i] = obj_need[i] }
- emit ObjectiveProgressed(quest: qid, objective: i, have: obj_have[i], need: obj_need[i])
- if quest_all_done(qid) == 1 {
- quest_state[qid] = 3
+ while i < rpg_st.obj_n {
+ let qid = rpg_st.obj_qid[i]
+ if rpg_st.quest_state[qid] == 2 {
+ if rpg_st.obj_kind[i] == kind {
+ if (rpg_st.obj_target[i] == 0) or (rpg_st.obj_target[i] == target) {
+ if rpg_st.obj_have[i] < rpg_st.obj_need[i] {
+ rpg_st.obj_have[i] += amount
+ if rpg_st.obj_have[i] > rpg_st.obj_need[i] { rpg_st.obj_have[i] = rpg_st.obj_need[i] }
+ emit ObjectiveProgressed(quest: qid, objective: i, have: rpg_st.obj_have[i], need: rpg_st.obj_need[i])
+ if quest_all_done(rpg_st, qid) == 1 {
+ rpg_st.quest_state[qid] = 3
emit QuestCompleted(quest: qid)
}
}
@@ -114,5 +104,5 @@ function quest_all_done(qid: int) -> int {
}
# ---- global flag store (branching / world state) --------------------------
-@Namespace(Quest) function quest_flag(name: pointer, value: int) -> void { quest_ensure(); Dict.set(quest_flags, name, value) }
-@Namespace(Quest) function quest_flag_get(name: pointer) -> int { quest_ensure(); return Dict.get_or(quest_flags, name, 0) }
+@Namespace(Quest) function quest_flag(rpg_st: mut RpgState, name: pointer, value: int) -> void { quest_ensure(rpg_st); Dict.set(rpg_st.quest_flags, name, value) }
+@Namespace(Quest) function quest_flag_get(rpg_st: mut RpgState, name: pointer) -> int { quest_ensure(rpg_st); return Dict.get_or(rpg_st.quest_flags, name, 0) }
diff --git a/packages/ludic.session/roster.ludic b/packages/ludic.session/roster.ludic
index 9d56a989..3bf26e21 100644
--- a/packages/ludic.session/roster.ludic
+++ b/packages/ludic.session/roster.ludic
@@ -54,7 +54,7 @@ export function ply_leave(base_st: mut BaseState, session_st: mut SessionState,
export function ply_leave_all(base_st: mut BaseState, session_st: mut SessionState) -> void { for p in 1 .. PLY_MAX { ply_leave(base_st, session_st, p) } }
# what a remote player's machine reported this tick
-export function ply_report(session_st: SessionState, p: int, x: float, y: float, z: float, yaw: float, speed: float) -> void {
+export function ply_report(session_st: mut SessionState, p: int, x: float, y: float, z: float, yaw: float, speed: float) -> void {
if p <= 0 or not ply_on(session_st, p) { return }
let r = session_st.session__slot[p]
r.x = x
diff --git a/packages/ludic.shooter/shooter.ludic b/packages/ludic.shooter/shooter.ludic
index 7170f36d..ca38bbba 100644
--- a/packages/ludic.shooter/shooter.ludic
+++ b/packages/ludic.shooter/shooter.ludic
@@ -69,76 +69,79 @@ event WeaponFired { owner: int = 0, def_id: int = 0 }
# ---------------------------------------------------------------------------
# Weapon registry (name-keyed, mod-friendly — add a gun with no code)
# ---------------------------------------------------------------------------
-var wpn_reg: Dict = null
-var wpn_n: int = 0
-var wpn_rate: words = null
-var wpn_dmg: words = null
-var wpn_speed: words = null
-var wpn_spread: words = null
-var wpn_pellets: words = null
-var wpn_pattern: words = null
-var wpn_ttl: words = null
-var wpn_pierce: words = null
-var wpn_homing: words = null
-var wpn_color: words = null
+state ShooterState {
+ wpn_reg: Dict = null
+ wpn_n: int = 0
+ wpn_rate: words = null
+ wpn_dmg: words = null
+ wpn_speed: words = null
+ wpn_spread: words = null
+ wpn_pellets: words = null
+ wpn_pattern: words = null
+ wpn_ttl: words = null
+ wpn_pierce: words = null
+ wpn_homing: words = null
+ wpn_color: words = null
+ wpn_rate_base: words = null
+ wpn_fire_color: int = 0xffffff # the colour of the weapon being fired (sh_fire)
+}
-function wpn_ensure() -> void {
- if wpn_reg == null {
- wpn_reg = Dict.new()
- wpn_rate = words(WPN_MAX); wpn_dmg = words(WPN_MAX); wpn_speed = words(WPN_MAX)
- wpn_spread = words(WPN_MAX); wpn_pellets = words(WPN_MAX); wpn_pattern = words(WPN_MAX)
- wpn_ttl = words(WPN_MAX); wpn_pierce = words(WPN_MAX); wpn_homing = words(WPN_MAX)
- wpn_color = words(WPN_MAX); wpn_rate_base = words(WPN_MAX)
+function wpn_ensure(shooter_st: mut ShooterState) -> void {
+ if shooter_st.wpn_reg == null {
+ shooter_st.wpn_reg = Dict.new()
+ shooter_st.wpn_rate = words(WPN_MAX); shooter_st.wpn_dmg = words(WPN_MAX); shooter_st.wpn_speed = words(WPN_MAX)
+ shooter_st.wpn_spread = words(WPN_MAX); shooter_st.wpn_pellets = words(WPN_MAX); shooter_st.wpn_pattern = words(WPN_MAX)
+ shooter_st.wpn_ttl = words(WPN_MAX); shooter_st.wpn_pierce = words(WPN_MAX); shooter_st.wpn_homing = words(WPN_MAX)
+ shooter_st.wpn_color = words(WPN_MAX); shooter_st.wpn_rate_base = words(WPN_MAX)
}
}
# Weapon.def(name, fire_rate, damage, speed, spread, pellets, pattern) — register a
# gun. pattern: 0 single/aimed, 1 spread cone, 2 ring, 3 spiral. Returns the def id.
-@Namespace(Weapon) function weapon_def(name: pointer, fire_rate: int, damage: int, speed: int, spread: int, pellets: int, pattern: int) -> int {
- wpn_ensure()
- if wpn_n >= WPN_MAX { return -1 }
- let id = wpn_n
- wpn_rate[id] = fire_rate; wpn_dmg[id] = damage; wpn_speed[id] = speed
- wpn_spread[id] = spread; wpn_pellets[id] = pellets; wpn_pattern[id] = pattern
- wpn_ttl[id] = 120
- wpn_color[id] = 0xffffff
- wpn_rate_base[id] = fire_rate
- wpn_n += 1
- Dict.set(wpn_reg, name, id)
+@Namespace(Weapon) function weapon_def(shooter_st: mut ShooterState, name: pointer, fire_rate: int, damage: int, speed: int, spread: int, pellets: int, pattern: int) -> int {
+ wpn_ensure(shooter_st)
+ if shooter_st.wpn_n >= WPN_MAX { return -1 }
+ let id = shooter_st.wpn_n
+ shooter_st.wpn_rate[id] = fire_rate; shooter_st.wpn_dmg[id] = damage; shooter_st.wpn_speed[id] = speed
+ shooter_st.wpn_spread[id] = spread; shooter_st.wpn_pellets[id] = pellets; shooter_st.wpn_pattern[id] = pattern
+ shooter_st.wpn_ttl[id] = 120
+ shooter_st.wpn_color[id] = 0xffffff
+ shooter_st.wpn_rate_base[id] = fire_rate
+ shooter_st.wpn_n += 1
+ Dict.set(shooter_st.wpn_reg, name, id)
return id
}
-@Namespace(Weapon) function weapon_id(name: pointer) -> int {
- wpn_ensure()
- return Dict.get_or(wpn_reg, name, -1)
+@Namespace(Weapon) function weapon_id(shooter_st: mut ShooterState, name: pointer) -> int {
+ wpn_ensure(shooter_st)
+ return Dict.get_or(shooter_st.wpn_reg, name, -1)
}
# Weapon.set_rate(id, frames) — frames between shots (a fire-rate upgrade changes it in place).
-@Namespace(Weapon) function weapon_set_rate(id: int, frames: int) -> void {
- wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { wpn_rate[id] = frames }
+@Namespace(Weapon) function weapon_set_rate(shooter_st: mut ShooterState, id: int, frames: int) -> void {
+ wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_rate[id] = frames }
}
-@Namespace(Weapon) function weapon_rate(id: int) -> int {
- wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { return wpn_rate[id] }; return 0
+@Namespace(Weapon) function weapon_rate(shooter_st: mut ShooterState, id: int) -> int {
+ wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { return shooter_st.wpn_rate[id] }; return 0
}
# Weapon.reset(id) — back to the definition: no pierce, no homing, the defined fire rate.
-var wpn_rate_base: words = null
-@Namespace(Weapon) function weapon_reset(id: int) -> void {
- wpn_ensure(); if (id < 0) or (id >= WPN_MAX) { return }
- wpn_pierce[id] = 0; wpn_homing[id] = 0; wpn_rate[id] = wpn_rate_base[id]
+@Namespace(Weapon) function weapon_reset(shooter_st: mut ShooterState, id: int) -> void {
+ wpn_ensure(shooter_st); if (id < 0) or (id >= WPN_MAX) { return }
+ shooter_st.wpn_pierce[id] = 0; shooter_st.wpn_homing[id] = 0; shooter_st.wpn_rate[id] = shooter_st.wpn_rate_base[id]
}
# Weapon.set_color(id, color) — the colour the engine draws this weapon's shots in.
-@Namespace(Weapon) function weapon_set_color(id: int, color: int) -> void {
- wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { wpn_color[id] = color }
+@Namespace(Weapon) function weapon_set_color(shooter_st: mut ShooterState, id: int, color: int) -> void {
+ wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_color[id] = color }
}
# Weapon.set_ttl(id, frames) — projectile lifetime for a weapon (range control).
-@Namespace(Weapon) function weapon_set_ttl(id: int, frames: int) -> void {
- wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { wpn_ttl[id] = frames }
+@Namespace(Weapon) function weapon_set_ttl(shooter_st: mut ShooterState, id: int, frames: int) -> void {
+ wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_ttl[id] = frames }
}
# Weapon.set_pierce(id, n) — a shot passes through n targets before it dies.
-@Namespace(Weapon) function weapon_set_pierce(id: int, n: int) -> void {
- wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { wpn_pierce[id] = n }
+@Namespace(Weapon) function weapon_set_pierce(shooter_st: mut ShooterState, id: int, n: int) -> void {
+ wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_pierce[id] = n }
}
# Weapon.set_homing(id, deg) — max degrees/frame a shot turns toward an enemy.
-@Namespace(Weapon) function weapon_set_homing(id: int, deg: int) -> void {
- wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { wpn_homing[id] = deg }
+@Namespace(Weapon) function weapon_set_homing(shooter_st: mut ShooterState, id: int, deg: int) -> void {
+ wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_homing[id] = deg }
}
# ---------------------------------------------------------------------------
@@ -493,7 +496,7 @@ function sh_angle_diff(from: int, to: int) -> int {
}
# spawn one projectile travelling at `deg` degrees from (px,py).
-function sh_spawn_bullet(owner: int, fac: int, px: int, py: int, deg: int, speed: int, dmg: int, ttl: int, pierce: int, homing: int) -> int {
+function sh_spawn_bullet(shooter_st: ShooterState, owner: int, fac: int, px: int, py: int, deg: int, speed: int, dmg: int, ttl: int, pierce: int, homing: int) -> int {
let m = World.model_id("Bullet")
if m < 0 { return -1 }
let b = World.spawn(m)
@@ -518,21 +521,20 @@ function sh_spawn_bullet(owner: int, fac: int, px: int, py: int, deg: int, speed
World.set(b, PC, World.field_id(PC, "h"), 4)
}
# announce it: a listener may veto (cancel -> despawn) or tweak the Projectile.
- pr_set(b, "color", wpn_fire_color)
+ pr_set(b, "color", shooter_st.wpn_fire_color)
if emit ProjectileSpawned(proj: b, owner: owner, damage: dmg) != 0 { despawn b; return -1 }
return b
}
-var wpn_fire_color: int = 0xffffff # the colour of the weapon being fired (sh_fire)
# ===========================================================================
# esys_weapon (Update) — fire-rate gating + pattern emission. Reads the owner's
# TopDown.aim_angle + Weapon.want-fire-equivalent (TopDown.want_fire); one impl
# serves player and NPC.
# ===========================================================================
-@EngineSystem(Weapon, Update) function esys_weapon() -> void {
+@EngineSystem(Weapon, Update) function esys_weapon(shooter_st: mut ShooterState) -> void {
let P = World.prop_id("Weapon")
if P < 0 { return }
- wpn_ensure()
+ wpn_ensure(shooter_st)
var e = World.query_next(P, 0)
while e >= 0 {
let cd = World.get(e, P, World.field_id(P, "cooldown"))
@@ -540,34 +542,34 @@ var wpn_fire_color: int = 0xffffff # the colour of the weapon being
let fire = td_get(e, "want_fire")
if (fire == 1) and (cd <= 0) {
let id = World.get(e, P, World.field_id(P, "def_id"))
- if (id >= 0) and (id < wpn_n) {
- sh_fire(e, id)
- World.set(e, P, World.field_id(P, "cooldown"), wpn_rate[id] * stats_field(e, "fire_rate_pct", 100) / 100)
+ if (id >= 0) and (id < shooter_st.wpn_n) {
+ sh_fire(shooter_st, e, id)
+ World.set(e, P, World.field_id(P, "cooldown"), shooter_st.wpn_rate[id] * stats_field(e, "fire_rate_pct", 100) / 100)
}
}
e = World.query_next(P, e + 1)
}
}
-function sh_fire(owner: int, id: int) -> void {
+function sh_fire(shooter_st: mut ShooterState, owner: int, id: int) -> void {
let px = sh_cx(owner)
let py = sh_cy(owner)
let base = td_get(owner, "aim_angle")
let fac = Faction.id_of(owner)
- let speed = wpn_speed[id]
- let dmg = wpn_dmg[id]
- let spread = wpn_spread[id]
- let pellets0 = wpn_pellets[id]
- let pattern = wpn_pattern[id]
- let ttl = wpn_ttl[id]
- let pierce = wpn_pierce[id]
- let homing = wpn_homing[id]
- wpn_fire_color = wpn_color[id]
+ let speed = shooter_st.wpn_speed[id]
+ let dmg = shooter_st.wpn_dmg[id]
+ let spread = shooter_st.wpn_spread[id]
+ let pellets0 = shooter_st.wpn_pellets[id]
+ let pattern = shooter_st.wpn_pattern[id]
+ let ttl = shooter_st.wpn_ttl[id]
+ let pierce = shooter_st.wpn_pierce[id]
+ let homing = shooter_st.wpn_homing[id]
+ shooter_st.wpn_fire_color = shooter_st.wpn_color[id]
var pellets = pellets0; if pellets < 1 { pellets = 1 }
emit WeaponFired(owner: owner, def_id: id)
if pattern == WeaponPattern.Ring { # pellets around 360
var i = 0
- while i < pellets { sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i += 1 }
+ while i < pellets { sh_spawn_bullet(shooter_st, owner, fac, px, py, sh_wrap_deg(base + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i += 1 }
return
}
if pattern == WeaponPattern.Spiral { # ring + a rotating phase
@@ -575,18 +577,18 @@ function sh_fire(owner: int, id: int) -> void {
let fph = World.field_id(PW, "phase")
var ph = 0; if fph >= 0 { ph = World.get(owner, PW, fph) }
var i = 0
- while i < pellets { sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + ph + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i += 1 }
+ while i < pellets { sh_spawn_bullet(shooter_st, owner, fac, px, py, sh_wrap_deg(base + ph + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i += 1 }
if fph >= 0 { World.set(owner, PW, fph, sh_wrap_deg(ph + 15)) }
return
}
# single / spread cone centred on the aim
if pellets == 1 {
- sh_spawn_bullet(owner, fac, px, py, base, speed, dmg, ttl, pierce, homing)
+ sh_spawn_bullet(shooter_st, owner, fac, px, py, base, speed, dmg, ttl, pierce, homing)
} else {
var i = 0
while i < pellets {
let off = -(spread / 2) + i * spread / (pellets - 1)
- sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + off), speed, dmg, ttl, pierce, homing)
+ sh_spawn_bullet(shooter_st, owner, fac, px, py, sh_wrap_deg(base + off), speed, dmg, ttl, pierce, homing)
i += 1
}
}
@@ -727,8 +729,8 @@ event WaveCleared { spawner: int = 0, wave: int = 0 }
@Namespace(TopDown) function topdown_aim(e: int, deg: int) -> void { td_set(e, "aim_angle", deg) }
@Namespace(TopDown) function topdown_fire(e: int, on: int) -> void { td_set(e, "want_fire", on) }
# equip a weapon by registry name.
-@Namespace(Weapon) function weapon_equip(e: int, name: pointer) -> void {
+@Namespace(Weapon) function weapon_equip(shooter_st: mut ShooterState, e: int, name: pointer) -> void {
let P = World.prop_id("Weapon")
if P < 0 { return }
- World.set(e, P, World.field_id(P, "def_id"), weapon_id(name))
+ World.set(e, P, World.field_id(P, "def_id"), weapon_id(shooter_st, name))
}
diff --git a/packages/ludic.ui/act.ludic b/packages/ludic.ui/act.ludic
index 65aae0b3..38276e58 100644
--- a/packages/ludic.ui/act.ludic
+++ b/packages/ludic.ui/act.ludic
@@ -51,20 +51,20 @@ function act_keyword(r: UiRd, word: string) -> bool {
}
return true
}
-function act_run(a: UiAct, e: UiEnv) -> void {
+function act_run(ui_st: mut UiState, a: UiAct, e: UiEnv) -> void {
for i in 0 .. len(a.kinds) {
- if a.kinds[i] == A_SET { env_set(e, a.names[i], ui_eval(a.exprs[i], e)) }
- if a.kinds[i] == A_CALL { ui_eval(a.exprs[i], e) }
- if a.kinds[i] == A_EMIT { act_emit(a.names[i], e) }
+ if a.kinds[i] == A_SET { env_set(ui_st, e, a.names[i], ui_eval(ui_st, a.exprs[i], e)) }
+ if a.kinds[i] == A_CALL { ui_eval(ui_st, a.exprs[i], e) }
+ if a.kinds[i] == A_EMIT { act_emit(ui_st, a.names[i], e) }
}
}
-function act_emit(name: string, e: UiEnv) -> void {
+function act_emit(ui_st: mut UiState, name: string, e: UiEnv) -> void {
var at = e
while at != null {
if at.on_names != null {
for i in 0 .. len(at.on_names) {
if at.on_names[i] == name {
- act_run(at.on_acts[i], at.on_envs[i])
+ act_run(ui_st, at.on_acts[i], at.on_envs[i])
return
}
}
diff --git a/packages/ludic.ui/anim.ludic b/packages/ludic.ui/anim.ludic
index 99a448b0..22932f41 100644
--- a/packages/ludic.ui/anim.ludic
+++ b/packages/ludic.ui/anim.ludic
@@ -6,17 +6,13 @@ export property UiKeyframes {
stops: []float = null
rules: []UiRule = null
}
-var an_frames: []UiKeyframes = new []UiKeyframes
-var an_keys: []string = new []string
-var an_start: []float = new []float
-var an_frame: int = 0
# the renderer's clock in seconds, or the frames shown at sixty a second (a test, a still picture)
-function an_now() -> float {
- if ui_be != null and ui_be.now != null { return ui_be.now() }
- return float(an_frame) / 60.0
+function an_now(ui_st: UiState) -> float {
+ if ui_st.ui_be != null and ui_st.ui_be.now != null { return ui_st.ui_be.now() }
+ return float(ui_st.an_frame) / 60.0
}
# @keyframes NAME { from {...} 50% {...} to {...} }
-function an_read(name: string, body: string, err: []string) -> void {
+function an_read(ui_st: mut UiState, name: string, body: string, err: []string) -> void {
let k = new UiKeyframes
k.name = tpl_words(name)
k.stops = new []float
@@ -34,40 +30,40 @@ function an_read(name: string, body: string, err: []string) -> void {
push(k.stops, at)
push(k.rules, decls)
}
- push(an_frames, k)
+ push(ui_st.an_frames, k)
}
-function an_find(name: string) -> UiKeyframes {
- for i in 0 .. len(an_frames) {
- if an_frames[i].name == name { return an_frames[i] }
+function an_find(ui_st: UiState, name: string) -> UiKeyframes {
+ for i in 0 .. len(ui_st.an_frames) {
+ if ui_st.an_frames[i].name == name { return ui_st.an_frames[i] }
}
return null
}
# when this element's animation began: the first frame it was seen with it
-function an_began(key: string) -> float {
- for i in 0 .. len(an_keys) {
- if an_keys[i] == key { return an_start[i] }
+function an_began(ui_st: mut UiState, key: string) -> float {
+ for i in 0 .. len(ui_st.an_keys) {
+ if ui_st.an_keys[i] == key { return ui_st.an_start[i] }
}
- push(an_keys, key)
- push(an_start, an_now())
- return an_now()
+ push(ui_st.an_keys, key)
+ push(ui_st.an_start, an_now(ui_st))
+ return an_now(ui_st)
}
# the element's animation at this moment: each property between the stops around it
-function an_apply(n: UiNode) -> void {
+function an_apply(ui_st: mut UiState, n: UiNode) -> void {
if n.anim == "" or n.anim_dur <= 0.0 { return }
- let k = an_find(n.anim)
+ let k = an_find(ui_st, n.anim)
if k == null {
- ui_err(`animation {n.anim}: there is no @keyframes of that name`)
+ ui_err(ui_st, `animation {n.anim}: there is no @keyframes of that name`)
return
}
- var t = (an_now() - an_began(`{n.key}@{n.anim}`)) / n.anim_dur
+ var t = (an_now(ui_st) - an_began(ui_st, `{n.key}@{n.anim}`)) / n.anim_dur
if n.anim_loop {
let lap = int(t)
t = t - float(lap)
if n.anim_alt and lap % 2 == 1 { t = 1.0 - t }
} else { t = Math.clamp(t, 0.0, 1.0) }
- an_at(n, k, t)
+ an_at(ui_st, n, k, t)
}
-function an_at(n: UiNode, k: UiKeyframes, t: float) -> void {
+function an_at(ui_st: mut UiState, n: UiNode, k: UiKeyframes, t: float) -> void {
var lo = 0
var hi = len(k.stops) - 1
for i in 0 .. len(k.stops) {
@@ -79,11 +75,11 @@ function an_at(n: UiNode, k: UiKeyframes, t: float) -> void {
let a = k.rules[lo]
let b = k.rules[hi]
for i in 0 .. len(a.keys) {
- let va = el_num(vl_text(ui_eval(a.vals[i], n.env)))
+ let va = el_num(vl_text(ui_eval(ui_st, a.vals[i], n.env)))
var vb = va
for j in 0 .. len(b.keys) {
- if b.keys[j] == a.keys[i] { vb = el_num(vl_text(ui_eval(b.vals[j], n.env))) }
+ if b.keys[j] == a.keys[i] { vb = el_num(vl_text(ui_eval(ui_st, b.vals[j], n.env))) }
}
- el_apply(n, a.keys[i], Value.float(va + (vb - va) * f))
+ el_apply(ui_st, n, a.keys[i], Value.float(va + (vb - va) * f))
}
}
diff --git a/packages/ludic.ui/attr.ludic b/packages/ludic.ui/attr.ludic
index 62510a58..c7ca60cd 100644
--- a/packages/ludic.ui/attr.ludic
+++ b/packages/ludic.ui/attr.ludic
@@ -3,21 +3,21 @@
const AT_ALIAS: string = " width=w height=h min-width=min-w max-width=max-w min-height=min-h max-height=max-h padding=pad background=bg background-color=bg font-size=size opacity=alpha flex-grow=grow flex-shrink=shrink flex-direction=dir flex-wrap=wrap justify-content=justify align-items=align align-self=self row-gap=gap column-gap=gap text-align=talign "
const AT_UNDRAWN: string = " font-weight font-family font-style box-shadow cursor transition animation letter-spacing text-decoration "
# a stylesheet's property: one that means nothing is said
-function el_attr(n: UiNode, key: string, v: Val) -> void {
- if el_apply(n, key, v) { return }
- if lk_in(AT_UNDRAWN, at_alias(key)) { ui_err(`{key}: this renderer does not draw it`) } else { ui_err(`{key}: there is no such property`) }
+function el_attr(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> void {
+ if el_apply(ui_st, n, key, v) { return }
+ if lk_in(AT_UNDRAWN, at_alias(key)) { ui_err(ui_st, `{key}: this renderer does not draw it`) } else { ui_err(ui_st, `{key}: there is no such property`) }
}
# an element's attribute: a property when it names one, otherwise only there for [selectors]
-function el_apply(n: UiNode, key: string, v0: Val) -> bool {
+function el_apply(ui_st: mut UiState, n: UiNode, key: string, v0: Val) -> bool {
let k = at_alias(key)
- el_cur = n
- el_key = k
+ ui_st.el_cur = n
+ ui_st.el_key = k
if len(k) > 2 and k[0..2] == "--" {
cs_var_set(n, k, vl_text(v0))
return true
}
- let v = cs_vars_in(n, v0)
- return el_size(n, k, v) or el_box(n, k, v) or el_flex(n, k, v) or el_look(n, k, v) or el_place_prop(n, k, v) or el_edge(n, k, v) or el_more(n, k, v)
+ let v = cs_vars_in(ui_st, n, v0)
+ return el_size(ui_st, n, k, v) or el_box(ui_st, n, k, v) or el_flex(n, k, v) or el_look(ui_st, n, k, v) or el_place_prop(ui_st, n, k, v) or el_edge(ui_st, n, k, v) or el_more(ui_st, n, k, v)
}
function at_alias(key: string) -> string {
let pat = ` {key}=`
@@ -31,8 +31,8 @@ function at_alias(key: string) -> string {
}
return key
}
-function el_size(n: UiNode, key: string, v: Val) -> bool {
- if key == "w" or key == "h" { el_dim(n, key == "w", v) } else if key == "min-w" { n.min_w = el_len(v) } else if key == "max-w" { n.max_w = el_len(v) } else if key == "min-h" { n.min_h = el_len(v) } else if key == "max-h" { n.max_h = el_len(v) } else if key == "gap" { n.gap = el_len(v) } else { return false }
+function el_size(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
+ if key == "w" or key == "h" { el_dim(ui_st, n, key == "w", v) } else if key == "min-w" { n.min_w = el_len(ui_st, v) } else if key == "max-w" { n.max_w = el_len(ui_st, v) } else if key == "min-h" { n.min_h = el_len(ui_st, v) } else if key == "max-h" { n.max_h = el_len(ui_st, v) } else if key == "gap" { n.gap = el_len(ui_st, v) } else { return false }
return true
}
function el_flex(n: UiNode, key: string, v: Val) -> bool {
@@ -43,33 +43,33 @@ function el_flex(n: UiNode, key: string, v: Val) -> bool {
} else if key == "grow" { n.grow = el_num(s) } else if key == "flex" { el_flex_short(n, s) } else if key == "wrap" { n.wrap = s == "wrap" or (vl_truthy(v) and s != "false" and s != "nowrap") } else if key == "align" { n.align = el_place(v) } else if key == "self" { n.self = el_place(v) } else if key == "justify" { n.justify = el_place(v) } else if key == "talign" { n.talign = el_place(v) } else if key == "display" { n.hidden = s == "none" } else { return false }
return true
}
-function el_look(n: UiNode, key: string, v: Val) -> bool {
+function el_look(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
let s = vl_text(v)
- if key == "size" { n.size = el_len(v) } else if key == "color" {
- n.fg = ui_color(s)
- n.fg_a = cl_alpha
+ if key == "size" { n.size = el_len(ui_st, v) } else if key == "color" {
+ n.fg = ui_color(ui_st, s)
+ n.fg_a = ui_st.cl_alpha
} else if key == "bg" {
- if gr_is(tpl_words(s)) { nx(n).grad = gr_parse(s) } else if len(s) > 4 and s[0..4] == "url(" { n.bg_img = el_url(s) } else if tpl_words(s) == "transparent" {
+ if gr_is(tpl_words(s)) { nx(n).grad = gr_parse(ui_st, s) } else if len(s) > 4 and s[0..4] == "url(" { n.bg_img = el_url(s) } else if tpl_words(s) == "transparent" {
n.bg = 0
n.bg_a = 0.0
} else {
- n.bg = ui_color(s)
- n.bg_a = cl_alpha
+ n.bg = ui_color(ui_st, s)
+ n.bg_a = ui_st.cl_alpha
}
} else if key == "alpha" { n.alpha = el_num(s) } else if key == "enabled" { n.enabled = vl_truthy(v) and s != "false" } else if key == "border-color" {
- n.border_c = ui_color(s)
- n.border_a = cl_alpha
- } else if key == "border-width" { el_sides_border(n, el_len(v)) } else if key == "border" { el_border(n, s) } else if key == "border-top" or key == "border-right" or key == "border-bottom" or key == "border-left" { el_border_side(n, key, s) } else { return false }
+ n.border_c = ui_color(ui_st, s)
+ n.border_a = ui_st.cl_alpha
+ } else if key == "border-width" { el_sides_border(n, el_len(ui_st, v)) } else if key == "border" { el_border(ui_st, n, s) } else if key == "border-top" or key == "border-right" or key == "border-bottom" or key == "border-left" { el_border_side(ui_st, n, key, s) } else { return false }
return true
}
# border: 1 #ffcc00, or 1px solid accent
-function el_border(n: UiNode, s: string) -> void {
+function el_border(ui_st: mut UiState, n: UiNode, s: string) -> void {
let parts = lss_split(s, 32)
for i in 0 .. len(parts) {
let p = parts[i]
- if len(p) > 0 and ((p[0] >= 48 and p[0] <= 57) or p[0] == 46) { el_sides_border(n, el_unit(p)) } else if p == "none" { el_sides_border(n, 0.0) } else if len(p) > 0 and p != "solid" {
- n.border_c = ui_color(p)
- n.border_a = cl_alpha
+ if len(p) > 0 and ((p[0] >= 48 and p[0] <= 57) or p[0] == 46) { el_sides_border(n, el_unit(ui_st, p)) } else if p == "none" { el_sides_border(n, 0.0) } else if len(p) > 0 and p != "solid" {
+ n.border_c = ui_color(ui_st, p)
+ n.border_a = ui_st.cl_alpha
}
}
if n.border_c == UI_NONE { n.border_c = n.rfg }
diff --git a/packages/ludic.ui/attr_border.ludic b/packages/ludic.ui/attr_border.ludic
index dec8b96c..462bde71 100644
--- a/packages/ludic.ui/attr_border.ludic
+++ b/packages/ludic.ui/attr_border.ludic
@@ -8,12 +8,12 @@ function el_sides_border(n: UiNode, w: float) -> void {
n.bl = w
}
# border-bottom: 3px solid var(--accent) - one side's width, and the colour for the border
-function el_border_side(n: UiNode, key: string, s: string) -> void {
+function el_border_side(ui_st: mut UiState, n: UiNode, key: string, s: string) -> void {
let keep_t = n.bt
let keep_r = n.br
let keep_b = n.bb
let keep_l = n.bl
- el_border(n, s)
+ el_border(ui_st, n, s)
let w = n.bt
n.bt = keep_t
n.br = keep_r
diff --git a/packages/ludic.ui/attr_box.ludic b/packages/ludic.ui/attr_box.ludic
index 06dca9a3..6b072ff5 100644
--- a/packages/ludic.ui/attr_box.ludic
+++ b/packages/ludic.ui/attr_box.ludic
@@ -1,50 +1,46 @@
# attr_box.ludic - padding and margin (one to four lengths, as CSS writes them, or one side by
# name), lengths with px or %, and the words that place things
-function el_box(n: UiNode, key: string, v: Val) -> bool {
+function el_box(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
if key == "pad" {
- el_sides(v)
- n.pt = el_s0
- n.pr = el_s1
- n.pb = el_s2
- n.pl = el_s3
+ el_sides(ui_st, v)
+ n.pt = ui_st.el_s0
+ n.pr = ui_st.el_s1
+ n.pb = ui_st.el_s2
+ n.pl = ui_st.el_s3
} else if key == "margin" {
- el_sides(v)
- n.mt = el_s0
- n.mr = el_s1
- n.mb = el_s2
- n.ml = el_s3
- } else if key == "padding-top" { n.pt = el_len(v) } else if key == "padding-right" { n.pr = el_len(v) } else if key == "padding-bottom" { n.pb = el_len(v) } else if key == "padding-left" { n.pl = el_len(v) } else if key == "margin-top" { n.mt = el_len(v) } else if key == "margin-right" { n.mr = el_len(v) } else if key == "margin-bottom" { n.mb = el_len(v) } else if key == "margin-left" { n.ml = el_len(v) } else { return false }
+ el_sides(ui_st, v)
+ n.mt = ui_st.el_s0
+ n.mr = ui_st.el_s1
+ n.mb = ui_st.el_s2
+ n.ml = ui_st.el_s3
+ } else if key == "padding-top" { n.pt = el_len(ui_st, v) } else if key == "padding-right" { n.pr = el_len(ui_st, v) } else if key == "padding-bottom" { n.pb = el_len(ui_st, v) } else if key == "padding-left" { n.pl = el_len(ui_st, v) } else if key == "margin-top" { n.mt = el_len(ui_st, v) } else if key == "margin-right" { n.mr = el_len(ui_st, v) } else if key == "margin-bottom" { n.mb = el_len(ui_st, v) } else if key == "margin-left" { n.ml = el_len(ui_st, v) } else { return false }
return true
}
-var el_s0: float = 0.0
-var el_s1: float = 0.0
-var el_s2: float = 0.0
-var el_s3: float = 0.0
# top right bottom left out of 1 to 4 lengths
-function el_sides(v: Val) -> void {
+function el_sides(ui_st: mut UiState, v: Val) -> void {
let parts = new []float
- if vl_num(v) { push(parts, el_px(Value.as_float(v))) } else {
+ if vl_num(v) { push(parts, el_px(ui_st, Value.as_float(v))) } else {
let words = el_words(vl_text(v))
for i in 0 .. len(words) {
- if len(words[i]) > 0 { push(parts, el_word_len(words[i])) }
+ if len(words[i]) > 0 { push(parts, el_word_len(ui_st, words[i])) }
}
}
while len(parts) < 4 {
if len(parts) == 0 { push(parts, 0.0) } else if len(parts) == 1 { push(parts, parts[0]) } else if len(parts) == 2 { push(parts, parts[0]) } else { push(parts, parts[1]) }
}
- el_s0 = parts[0]
- el_s1 = parts[1]
- el_s2 = parts[2]
- el_s3 = parts[3]
+ ui_st.el_s0 = parts[0]
+ ui_st.el_s1 = parts[1]
+ ui_st.el_s2 = parts[2]
+ ui_st.el_s3 = parts[3]
}
# a length: a number, 10px, 1.5em, 2rem, 50vw, calc(...), fill, fit or auto (a percentage is el_pct's)
-function el_len(v: Val) -> float {
- if vl_num(v) { return el_px(Value.as_float(v)) }
+function el_len(ui_st: mut UiState, v: Val) -> float {
+ if vl_num(v) { return el_px(ui_st, Value.as_float(v)) }
let s = vl_text(v)
if s == "fill" { return UI_FILL }
- if ca_is(s) { return el_calc_len(s) }
+ if ca_is(s) { return el_calc_len(ui_st, s) }
if s == "fit" or s == "auto" or (len(s) > 0 and s[len(s) - 1] == 37) { return 0.0 }
- return el_unit(s)
+ return el_unit(ui_st, s)
}
function el_pct(v: Val) -> float {
let s = vl_text(v)
diff --git a/packages/ludic.ui/attr_edge.ludic b/packages/ludic.ui/attr_edge.ludic
index d4a324fb..97adbd08 100644
--- a/packages/ludic.ui/attr_edge.ludic
+++ b/packages/ludic.ui/attr_edge.ludic
@@ -1,14 +1,14 @@
# attr_edge.ludic - border-radius, outline (and its width, colour and offset), scrollbar-color
-function el_edge(n: UiNode, key: string, v: Val) -> bool {
+function el_edge(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
let s = vl_text(v)
- if key == "border-radius" { n.radius = el_len(v) } else if key == "outline" { el_outline(n, s) } else if key == "outline-width" { n.outline_w = el_len(v) } else if key == "outline-offset" { n.outline_off = el_len(v) } else if key == "outline-color" {
- n.outline_c = ui_color(s)
- n.outline_a = cl_alpha
+ if key == "border-radius" { n.radius = el_len(ui_st, v) } else if key == "outline" { el_outline(ui_st, n, s) } else if key == "outline-width" { n.outline_w = el_len(ui_st, v) } else if key == "outline-offset" { n.outline_off = el_len(ui_st, v) } else if key == "outline-color" {
+ n.outline_c = ui_color(ui_st, s)
+ n.outline_a = ui_st.cl_alpha
} else if key == "scrollbar-color" {
let parts = lss_split(s, 32)
- if len(parts) > 0 { n.bar_c = ui_color(parts[0]) }
- if len(parts) > 1 { n.bar_track = ui_color(parts[1]) }
- } else if key == "background-image" and gr_is(tpl_words(s)) { nx(n).grad = gr_parse(s) } else if key == "background-image" { n.bg_img = el_url(s) } else if key == "border-image" { el_border_image(n, s) } else if key == "box-shadow" { el_shadow(n, s) } else if key == "animation" { el_animation(n, s) } else if key == "transition" { n.tr_dur = el_secs(s) } else { return false }
+ if len(parts) > 0 { n.bar_c = ui_color(ui_st, parts[0]) }
+ if len(parts) > 1 { n.bar_track = ui_color(ui_st, parts[1]) }
+ } else if key == "background-image" and gr_is(tpl_words(s)) { nx(n).grad = gr_parse(ui_st, s) } else if key == "background-image" { n.bg_img = el_url(s) } else if key == "border-image" { el_border_image(ui_st, n, s) } else if key == "box-shadow" { el_shadow(ui_st, n, s) } else if key == "animation" { el_animation(n, s) } else if key == "transition" { n.tr_dur = el_secs(s) } else { return false }
return true
}
# url(path) or url("path") -> path
@@ -20,17 +20,17 @@ function el_url(s: string) -> string {
return t
}
# border-image: url(chip.png) 14 / 12px - the source's corner size, then how wide they are drawn
-function el_border_image(n: UiNode, s: string) -> void {
+function el_border_image(ui_st: UiState, n: UiNode, s: string) -> void {
var close = 0
while close < len(s) and s[close] != 41 { close += 1 }
n.bi_src = el_url(s[0..Math.min(close + 1, len(s))])
let rest = lss_split(s[Math.min(close + 1, len(s))..len(s)], 47)
n.bi_slice = el_num(tpl_words(rest[0]))
- n.bi_width = el_px(n.bi_slice)
- if len(rest) > 1 { n.bi_width = el_unit(tpl_words(rest[1])) }
+ n.bi_width = el_px(ui_st, n.bi_slice)
+ if len(rest) > 1 { n.bi_width = el_unit(ui_st, tpl_words(rest[1])) }
}
# box-shadow: 6px 10px 0 rgba(0, 0, 0, 0.45) - x, y, a blur this renderer draws sharp, the colour
-function el_shadow(n: UiNode, s: string) -> void {
+function el_shadow(ui_st: mut UiState, n: UiNode, s: string) -> void {
if tpl_words(s) == "none" {
n.sh_c = -2
return
@@ -42,24 +42,24 @@ function el_shadow(n: UiNode, s: string) -> void {
let p = parts[i]
if len(p) == 0 { continue }
if col == "" and ((p[0] >= 48 and p[0] <= 57) or p[0] == 45 or p[0] == 46) {
- if nums == 0 { n.sh_x = el_unit(p) }
- if nums == 1 { n.sh_y = el_unit(p) }
+ if nums == 0 { n.sh_x = el_unit(ui_st, p) }
+ if nums == 1 { n.sh_y = el_unit(ui_st, p) }
nums += 1
} else if col == "" { col = p } else { col = col + " " + p }
}
if col == "" { col = "rgba(0,0,0,0.5)" }
- n.sh_c = ui_color(col)
- n.sh_a = cl_alpha
+ n.sh_c = ui_color(ui_st, col)
+ n.sh_a = ui_st.cl_alpha
}
# outline: 2px solid #ffcc00, or none
-function el_outline(n: UiNode, s: string) -> void {
+function el_outline(ui_st: mut UiState, n: UiNode, s: string) -> void {
let parts = lss_split(s, 32)
n.outline_w = 0.0
for i in 0 .. len(parts) {
let p = parts[i]
- if len(p) > 0 and ((p[0] >= 48 and p[0] <= 57) or p[0] == 46) { n.outline_w = el_unit(p) } else if len(p) > 0 and p != "solid" and p != "none" {
- n.outline_c = ui_color(p)
- n.outline_a = cl_alpha
+ if len(p) > 0 and ((p[0] >= 48 and p[0] <= 57) or p[0] == 46) { n.outline_w = el_unit(ui_st, p) } else if len(p) > 0 and p != "solid" and p != "none" {
+ n.outline_c = ui_color(ui_st, p)
+ n.outline_a = ui_st.cl_alpha
}
}
}
diff --git a/packages/ludic.ui/attr_fit.ludic b/packages/ludic.ui/attr_fit.ludic
index cde3db3d..fa083941 100644
--- a/packages/ludic.ui/attr_fit.ludic
+++ b/packages/ludic.ui/attr_fit.ludic
@@ -1,6 +1,6 @@
# attr_fit.ludic - aspect-ratio (16 / 9, or 1.5), object-fit (fill, contain, cover, none, scale-down)
# for pictures and natives, zoom (zoom.ludic), and a list split at its top-level commas
-function el_fit_props(n: UiNode, key: string, s: string) -> bool {
+function el_fit_props(ui_st: mut UiState, n: UiNode, key: string, s: string) -> bool {
if key == "aspect-ratio" {
let parts = lss_split(s, 47)
var r = el_num(tpl_words(parts[0]))
@@ -12,7 +12,7 @@ function el_fit_props(n: UiNode, key: string, s: string) -> bool {
var k = 0
if w == "contain" { k = 1 } else if w == "cover" { k = 2 } else if w == "none" { k = 3 } else if w == "scale-down" { k = 4 }
nx(n).fit_obj = k
- } else if key == "zoom" { el_zoom(n, s) } else { return false }
+ } else if key == "zoom" { el_zoom(ui_st, n, s) } else { return false }
return true
}
# "a, rgba(1, 2, 3, 0.5) 40%, b" -> its three parts
diff --git a/packages/ludic.ui/attr_inset.ludic b/packages/ludic.ui/attr_inset.ludic
index 7ae1430b..b33af5a1 100644
--- a/packages/ludic.ui/attr_inset.ludic
+++ b/packages/ludic.ui/attr_inset.ludic
@@ -1,21 +1,21 @@
# attr_inset.ludic - top, right, bottom and left as lengths, percentages or calc() of both: the
# percentage is kept apart and taken of the containing block's width (left, right) or height (top,
# bottom) when the element is placed
-function el_inset(n: UiNode, side: int, v: Val) -> float {
+function el_inset(ui_st: mut UiState, n: UiNode, side: int, v: Val) -> float {
let s = vl_text(v)
var pct = 0.0
var px = UI_AUTO
if s != "auto" {
if ca_is(s) {
px = 0.0
- if ca_eval(s) {
- pct = ca_pct
- px = ca_px
+ if ca_eval(ui_st, s) {
+ pct = ui_st.ca_pct
+ px = ui_st.ca_px
}
} else if len(s) > 1 and s[len(s) - 1] == 37 {
pct = el_num(s)
px = 0.0
- } else { px = el_len(v) }
+ } else { px = el_len(ui_st, v) }
}
if pct != 0.0 or n.ex != null {
let x = nx(n)
diff --git a/packages/ludic.ui/attr_more.ludic b/packages/ludic.ui/attr_more.ludic
index dbd4d887..426b78b0 100644
--- a/packages/ludic.ui/attr_more.ludic
+++ b/packages/ludic.ui/attr_more.ludic
@@ -1,26 +1,26 @@
# attr_more.ludic - width and height (where 0 is 0 and calc() may hold a percentage), the flex
# shorthand, flex-shrink, order, text-shadow, pointer-events, text-fit and line-height (text_fit.ludic)
-function el_more(n: UiNode, key: string, v: Val) -> bool {
+function el_more(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
let s = vl_text(v)
- if key == "shrink" { nx(n).shrink = Math.max(el_num(s), 0.0) } else if key == "order" { nx(n).order = int(el_num(s)) } else if key == "text-shadow" { el_text_shadow(n, s) } else if key == "pointer-events" { nx(n).no_pointer = tpl_words(s) == "none" } else if key == "text-fit" { el_text_fit(n, s) } else if key == "line-height" { el_line_h(n, s) } else { return el_fit_props(n, key, s) }
+ if key == "shrink" { nx(n).shrink = Math.max(el_num(s), 0.0) } else if key == "order" { nx(n).order = int(el_num(s)) } else if key == "text-shadow" { el_text_shadow(ui_st, n, s) } else if key == "pointer-events" { nx(n).no_pointer = tpl_words(s) == "none" } else if key == "text-fit" { el_text_fit(ui_st, n, s) } else if key == "line-height" { el_line_h(ui_st, n, s) } else { return el_fit_props(ui_st, n, key, s) }
return true
}
# width or height: a length (0 included), fill, a percentage, calc(), or auto/fit (none)
-function el_dim(n: UiNode, wide: bool, v: Val) -> void {
+function el_dim(ui_st: mut UiState, n: UiNode, wide: bool, v: Val) -> void {
let s = vl_text(v)
var fixed = UI_AUTO
var pct = 0.0
var off = 0.0
if ca_is(s) {
- if ca_eval(s) {
- pct = ca_pct
- off = ca_px
- if pct <= 0.0 { fixed = Math.max(ca_px, 0.0) }
+ if ca_eval(ui_st, s) {
+ pct = ui_st.ca_pct
+ off = ui_st.ca_px
+ if pct <= 0.0 { fixed = Math.max(ui_st.ca_px, 0.0) }
}
} else if s == "fill" { fixed = UI_FILL } else if len(s) > 1 and s[len(s) - 1] == 37 {
pct = el_num(s)
if pct <= 0.0 { fixed = 0.0 }
- } else if s != "auto" and s != "fit" and s != "" { fixed = el_len(v) }
+ } else if s != "auto" and s != "fit" and s != "" { fixed = el_len(ui_st, v) }
if pct <= 0.0 { off = 0.0 }
if n.ex != null or off != 0.0 {
if wide { nx(n).w_off = off } else { nx(n).h_off = off }
@@ -34,10 +34,10 @@ function el_dim(n: UiNode, wide: bool, v: Val) -> void {
}
}
# a length out of calc(); a percentage has nothing to be a share of here
-function el_calc_len(s: string) -> float {
- if not ca_eval(s) { return 0.0 }
- if ca_pct != 0.0 { ui_err(`{el_key}: {s} - a percentage in calc() is for a width or a height`) }
- return ca_px
+function el_calc_len(ui_st: mut UiState, s: string) -> float {
+ if not ca_eval(ui_st, s) { return 0.0 }
+ if ui_st.ca_pct != 0.0 { ui_err(ui_st, `{ui_st.el_key}: {s} - a percentage in calc() is for a width or a height`) }
+ return ui_st.ca_px
}
# flex: 1 | flex: 1 0 | flex: 2 1 auto | flex: none - the grow, then the shrink
function el_flex_short(n: UiNode, s: string) -> void {
@@ -67,7 +67,7 @@ function el_words(s: string) -> []string {
return out
}
# text-shadow: 1px 2px 3px rgba(0, 0, 0, 0.6) - x, y, a blur drawn sharp, the colour; or none
-function el_text_shadow(n: UiNode, s: string) -> void {
+function el_text_shadow(ui_st: mut UiState, n: UiNode, s: string) -> void {
let x = nx(n)
x.tsh_set = true
x.tsh_c = -2
@@ -78,17 +78,17 @@ function el_text_shadow(n: UiNode, s: string) -> void {
for i in 0 .. len(parts) {
let p = parts[i]
if col == "" and ((p[0] >= 48 and p[0] <= 57) or p[0] == 45 or p[0] == 46) {
- if nums == 0 { x.tsh_x = el_unit(p) }
- if nums == 1 { x.tsh_y = el_unit(p) }
+ if nums == 0 { x.tsh_x = el_unit(ui_st, p) }
+ if nums == 1 { x.tsh_y = el_unit(ui_st, p) }
nums += 1
} else if col == "" { col = p } else { col = col + " " + p }
}
if col == "" { col = "rgba(0,0,0,0.6)" }
- x.tsh_c = ui_color(col)
- x.tsh_a = cl_alpha
+ x.tsh_c = ui_color(ui_st, col)
+ x.tsh_a = ui_st.cl_alpha
}
# one word of a list of lengths (padding: calc(1em / 4) 2px)
-function el_word_len(w: string) -> float {
- if ca_is(w) { return el_calc_len(w) }
- return el_unit(w)
+function el_word_len(ui_st: mut UiState, w: string) -> float {
+ if ca_is(w) { return el_calc_len(ui_st, w) }
+ return el_unit(ui_st, w)
}
diff --git a/packages/ludic.ui/attr_place.ludic b/packages/ludic.ui/attr_place.ludic
index 16ee6d40..31263c8e 100644
--- a/packages/ludic.ui/attr_place.ludic
+++ b/packages/ludic.ui/attr_place.ludic
@@ -1,17 +1,17 @@
# attr_place.ludic - position and its insets, z-index, overflow, white-space and text-overflow
-function el_place_prop(n: UiNode, key: string, v: Val) -> bool {
+function el_place_prop(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
let s = vl_text(v)
if key == "position" {
n.pos = 0
if s == "relative" { n.pos = 1 }
if s == "absolute" { n.pos = 2 }
if s == "fixed" { n.pos = 3 }
- } else if key == "top" { n.top = el_inset(n, 0, v) } else if key == "right" { n.right = el_inset(n, 1, v) } else if key == "bottom" { n.bottom = el_inset(n, 2, v) } else if key == "left" { n.left = el_inset(n, 3, v) } else if key == "inset" {
- el_sides(v)
- n.top = el_s0
- n.right = el_s1
- n.bottom = el_s2
- n.left = el_s3
+ } else if key == "top" { n.top = el_inset(ui_st, n, 0, v) } else if key == "right" { n.right = el_inset(ui_st, n, 1, v) } else if key == "bottom" { n.bottom = el_inset(ui_st, n, 2, v) } else if key == "left" { n.left = el_inset(ui_st, n, 3, v) } else if key == "inset" {
+ el_sides(ui_st, v)
+ n.top = ui_st.el_s0
+ n.right = ui_st.el_s1
+ n.bottom = ui_st.el_s2
+ n.left = ui_st.el_s3
} else if key == "z-index" { n.z = int(el_num(s)) } else if key == "overflow" or key == "overflow-y" {
if (s == "auto" or s == "scroll" or s == "hidden") and n.kind == UI_BOX { n.kind = UI_SCROLL }
} else if key == "white-space" { n.nowrap = s == "nowrap" or s == "pre" } else if key == "text-overflow" { n.ellipsis = s == "ellipsis" } else { return false }
diff --git a/packages/ludic.ui/backend.ludic b/packages/ludic.ui/backend.ludic
index 447df199..21b2f3e2 100644
--- a/packages/ludic.ui/backend.ludic
+++ b/packages/ludic.ui/backend.ludic
@@ -30,30 +30,155 @@ function ui_default_backend() -> UiBackend {
if len(UiRenderers) == 0 { return null }
return UiRenderers[0].make()
}
-var ui_be: UiBackend = ui_default_backend()
-export function ui_backend(b: UiBackend) -> void { ui_be = b }
-function ui_px(v: float) -> float {
- if ui_be != null and ui_be.scale != null { return v * ui_be.scale() }
+export state UiState {
+ an_frames: []UiKeyframes = new []UiKeyframes
+ an_keys: []string = new []string
+ an_start: []float = new []float
+ an_frame: int = 0
+ el_s0: float = 0.0
+ el_s1: float = 0.0
+ el_s2: float = 0.0
+ el_s3: float = 0.0
+ ui_be: UiBackend = ui_default_backend()
+ tr_no: bool = false # inside translate="no" while a frame is built
+ ca_s: string = ""
+ ca_i: int = 0
+ ca_bad: string = ""
+ ca_px: float = 0.0 # the result: its lengths, its percentage, and whether it had units
+ ca_pct: float = 0.0
+ ca_num: float = 0.0 # a plain number's own value, and whether it was one
+ ca_bare: bool = false
+ cs_pass: int = 0
+ ua_sh: UiSheet = null
+ uc_list: []UiClass = null # filled by the compiler's registrations, before the program's globals
+ cl_alpha: float = 1.0
+ ct_capture: string = ""
+ ct_capture_at: int = -1 # the frame listening began, whose own key started it
+ ct_capture_seen: int = -1 # the last frame that began listening, for ui_capturing
+ ct_num_key: string = ""
+ ct_num_text: string = ""
+ dv_on: bool = false
+ dv_frames: int = 0
+ dv_dumped: bool = false
+ fr_ga: float = 1.0 # the opacity of everything around this element, which it multiplies
+ gr_a: float = 1.0
+ fr_clips: []float = new []float
+ ui_view: UiView = null
+ ui_errs: []string = new []string
+ ui_at: string = ""
+ fc_key: string = ""
+ fc_visible: bool = false # focus came from the keyboard: :focus-visible, and the ring
+ fc_list: []UiNode = new []UiNode
+ fb_roots: []UiNode = new []UiNode
+ hd_key: string = ""
+ hd_t0: float = 0.0
+ hd_last: float = 0.0
+ in_now: UiInput = new UiInput
+ in_was_down: bool = false
+ in_last: UiInput = new UiInput
+ in_given: bool = false
+ rp_next: []float = [-1.0, -1.0, -1.0, -1.0]
+ rp_a: bool = false
+ rp_b: bool = false
+ rp_act: bool = false
+ in_act_down: bool = false # A or Enter went down this frame, or came up
+ in_act_up: bool = false
+ in_list: []UiInst = new []UiInst
+ in_build: int = 0
+ ly_fg_a: float = 1.0
+ ly_lead: float = 0.0
+ ly_extra: float = 0.0
+ lib_files: []UiFile = new []UiFile
+ lib_screen_names: []string = new []string
+ lib_screens: []UiTpl = new []UiTpl
+ lc_keys: []string = new []string
+ lc_mounts: []UiAct = new []UiAct
+ lc_unmounts: []UiAct = new []UiAct
+ lc_envs: []UiEnv = new []UiEnv
+ lc_prev_keys: []string = new []string
+ lc_prev_unmounts: []UiAct = new []UiAct
+ lc_prev_envs: []UiEnv = new []UiEnv
+ nt_list: []UiNative = new []UiNative
+ nt_q_nodes: []UiNode = new []UiNode
+ nt_q_names: []string = new []string
+ nt_q_vals: []Val = new []Val
+ fr_hover: []string = new []string
+ fr_press_key: string = ""
+ fr_down_now: bool = false
+ fr_key_press: string = "" # the control Enter or the pad's A is holding down
+ pe_key: string = "" # the element the pointer is captured by
+ pe_btn: int = 0
+ pop_list: []UiNode = new []UiNode
+ pp_bx: float = 0.0
+ pp_by: float = 0.0
+ pp_bw: float = 0.0
+ pp_bh: float = 0.0
+ r3u_user: float = 1.0
+ r3u_tex_paths: []string = new []string
+ r3u_tex_ids: []int = new []int
+ r3u_tex_sizes: []int = new []int
+ r3u_tex_seen: []float = new []float
+ r3u_atlas: []string = new []string
+ r3u_atlas_tex: []int = new []int
+ r3u_atlas_cols: []int = new []int
+ r3u_atlas_rows: []int = new []int
+ r3u_atlas_names: [][]string = new [][]string
+ sc_keys: []string = new []string
+ sc_offs: []float = new []float
+ sc_drag: string = ""
+ sc_scope: UiNode = null # the popover that has the pointer this frame, if one is up
+ sb_x: float = 0.0
+ sb_w: float = 0.0
+ sb_ty: float = 0.0
+ sb_th: float = 0.0
+ sb_bar: UiNode = null
+ sc_grab: float = 0.0 # how far below the thumb's top the pointer holds it
+ sc_want_keys: []string = new []string
+ sc_want_px: []float = new []float
+ st_keys: []string = new []string
+ st_vals: []Val = new []Val
+ tf_size: float = 0.0 # the size tf_fit drew at
+ tt_key: string = ""
+ tt_since: float = 0.0
+ tt_text: string = ""
+ tt_x: float = 0.0
+ tt_y: float = 0.0
+ tt_by_keys: bool = false # the focus moved by the keyboard since the pointer last moved
+ tt_focus_seen: string = ""
+ tp_nl: []int = new []int
+ tr_keys: []string = new []string
+ tr_from: []float = new []float
+ tr_to: []float = new []float
+ tr_at: []float = new []float
+ el_cur: UiNode = null
+ el_key: string = ""
+ ly_vp_x: float = 0.0
+ ly_vp_y: float = 0.0
+ ly_vp_w: float = 0.0
+ ly_vp_h: float = 0.0
+}
+export function ui_backend(ui_st: mut UiState, b: UiBackend) -> void { ui_st.ui_be = b }
+function ui_px(ui_st: UiState, v: float) -> float {
+ if ui_st.ui_be != null and ui_st.ui_be.scale != null { return v * ui_st.ui_be.scale() }
return v
}
-var tr_no: bool = false # inside translate="no" while a frame is built
-function ui_tr(s: string) -> string {
- if tr_no { return s }
- if ui_be != null and ui_be.translate != null { return ui_be.translate(s) }
+function ui_tr(ui_st: UiState, s: string) -> string {
+ if ui_st.tr_no { return s }
+ if ui_st.ui_be != null and ui_st.ui_be.translate != null { return ui_st.ui_be.translate(s) }
return s
}
-function ui_sound(name: string) -> void {
- if ui_be != null and ui_be.sound != null { ui_be.sound(name) }
+function ui_sound(ui_st: UiState, name: string) -> void {
+ if ui_st.ui_be != null and ui_st.ui_be.sound != null { ui_st.ui_be.sound(name) }
}
# the program's language: every text a template shows goes through `f` before it is measured
-export function ui_translator(f: fn(string) -> string) -> void {
- if ui_be != null { ui_be.translate = f }
+export function ui_translator(ui_st: mut UiState, f: fn(string) -> string) -> void {
+ if ui_st.ui_be != null { ui_st.ui_be.translate = f }
}
# the program's sounds: "click" when a control is used, or the control's own sound="..."
-export function ui_sounds(f: fn(string) -> void) -> void {
- if ui_be != null { ui_be.sound = f }
+export function ui_sounds(ui_st: mut UiState, f: fn(string) -> void) -> void {
+ if ui_st.ui_be != null { ui_st.ui_be.sound = f }
}
# the program's clock, in seconds, for animation (a game's own time, which a headless run counts)
-export function ui_clock(f: fn() -> float) -> void {
- if ui_be != null { ui_be.now = f }
+export function ui_clock(ui_st: mut UiState, f: fn() -> float) -> void {
+ if ui_st.ui_be != null { ui_st.ui_be.now = f }
}
diff --git a/packages/ludic.ui/build.ludic b/packages/ludic.ui/build.ludic
index 425de853..74b12d07 100644
--- a/packages/ludic.ui/build.ludic
+++ b/packages/ludic.ui/build.ludic
@@ -2,47 +2,47 @@
# are the template's own; a tag its file linked to a component is that one,
# given its attributes as props (over the defaults its own tag gives: ), its on-* as what `emit` runs, and its content as .
-function bd_kids(kids: []UiTpl, e0: UiEnv, key: string, out: []UiNode) -> void {
+function bd_kids(ui_st: mut UiState, kids: []UiTpl, e0: UiEnv, key: string, out: []UiNode) -> void {
var last_if = true
var e = e0
for i in 0 .. len(kids) {
let k = kids[i]
- ui_at = k.where
+ ui_st.ui_at = k.where
let ck = `{key}/{i}`
if k.tag == "state" or k.tag == "style" { continue }
if k.tag == "let" {
- e = bd_let(k, e)
+ e = bd_let(ui_st, k, e)
continue
}
- if k.tag == "fragment" or k.tag == "#text" or (k.tag == "template" and tpl_attr(k, "slot") == null) { bd_group(k, e, ck, out) } else if k.tag == "provide" { bd_provide(k, e, ck, out) } else if k.tag == "template" { continue } else if k.tag == "if" {
- last_if = bd_test(k, e)
- if last_if { bd_kids(k.kids, e, ck, out) }
+ if k.tag == "fragment" or k.tag == "#text" or (k.tag == "template" and tpl_attr(k, "slot") == null) { bd_group(ui_st, k, e, ck, out) } else if k.tag == "provide" { bd_provide(ui_st, k, e, ck, out) } else if k.tag == "template" { continue } else if k.tag == "if" {
+ last_if = bd_test(ui_st, k, e)
+ if last_if { bd_kids(ui_st, k.kids, e, ck, out) }
} else if k.tag == "else" {
- if not last_if { bd_kids(k.kids, e, ck, out) }
+ if not last_if { bd_kids(ui_st, k.kids, e, ck, out) }
last_if = true
- } else if k.tag == "each" { bd_each(k, e, ck, out) } else if k.tag == "slot" { bd_slot(k, e, ck, out) } else if not bd_comp(k, e, ck, out) { push(out, bd_elem(k, e, ck)) }
+ } else if k.tag == "each" { bd_each(ui_st, k, e, ck, out) } else if k.tag == "slot" { bd_slot(ui_st, k, e, ck, out) } else if not bd_comp(ui_st, k, e, ck, out) { push(out, bd_elem(ui_st, k, e, ck)) }
}
}
-function bd_test(k: UiTpl, e: UiEnv) -> bool {
+function bd_test(ui_st: mut UiState, k: UiTpl, e: UiEnv) -> bool {
let t = tpl_attr(k, "test")
if t == null {
- ui_err(" needs a test=\"{...}\"")
+ ui_err(ui_st, " needs a test=\"{...}\"")
return false
}
- return ui_eval_bool(t, e)
+ return ui_eval_bool(ui_st, t, e)
}
function bd_name(k: UiTpl, attr: string, dflt: string) -> string {
let x = tpl_attr(k, attr)
if x == null or x.op != X_LIT { return dflt }
return Value.as_str(x.v)
}
-function bd_each(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
+function bd_each(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
let src = tpl_attr(k, "in")
if src == null {
- ui_err(" needs an in=\"{...}\"")
+ ui_err(ui_st, " needs an in=\"{...}\"")
return
}
- let list = ui_eval(src, e)
+ let list = ui_eval(ui_st, src, e)
let as_name = bd_name(k, "as", "item")
let at_name = bd_name(k, "index", "index")
let by = tpl_attr(k, "key")
@@ -51,28 +51,28 @@ function bd_each(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
env_bind(le, as_name, Value.at(list, j))
env_bind(le, at_name, Value.int(j))
var id = string(j)
- if by != null { id = ui_eval_text(by, le) }
- bd_kids(k.kids, le, `{key}#{id}`, out)
+ if by != null { id = ui_eval_text(ui_st, by, le) }
+ bd_kids(ui_st, k.kids, le, `{key}#{id}`, out)
}
}
# is what the component's user put inside it; their
-function bd_slot(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
+function bd_slot(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
var at = e
while at != null and at.slot == null { at = at.parent }
if at == null { return }
let name = bd_name(k, "name", "")
if name == "" {
- bd_kids(at.slot.kids, at.slot_env, key, out)
+ bd_kids(ui_st, at.slot.kids, at.slot_env, key, out)
return
}
for i in 0 .. len(at.slot.kids) {
let t = at.slot.kids[i]
- if t.tag == "template" and bd_name(t, "slot", "") == name { bd_group(t, at.slot_env, key, out) }
+ if t.tag == "template" and bd_name(t, "slot", "") == name { bd_group(ui_st, t, at.slot_env, key, out) }
}
}
-function bd_comp(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> bool {
+function bd_comp(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> bool {
if k.cls != null {
- bd_class(k, e, key, out)
+ bd_class(ui_st, k, e, key, out)
return true
}
if k.comp == null { return false }
@@ -81,9 +81,9 @@ function bd_comp(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> bool {
ce.model = e.model
ce.ctx = e.ctx
for i in 0 .. len(c.keys) {
- if c.keys[i] != "name" and c.keys[i] != "export" { env_bind(ce, c.keys[i], ui_eval(c.vals[i], ce)) }
+ if c.keys[i] != "name" and c.keys[i] != "export" { env_bind(ce, c.keys[i], ui_eval(ui_st, c.vals[i], ce)) }
}
- for i in 0 .. len(k.keys) { env_bind(ce, k.keys[i], ui_eval(k.vals[i], e)) }
+ for i in 0 .. len(k.keys) { env_bind(ce, k.keys[i], ui_eval(ui_st, k.vals[i], e)) }
ce.on_names = k.on_keys
ce.on_acts = k.on_acts
ce.on_envs = new []UiEnv
@@ -91,8 +91,8 @@ function bd_comp(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> bool {
ce.slot = k
ce.slot_env = e
ce.ctx = e.ctx
- ce.state = st_for(key, c, ce)
- lc_note(key, k.on_keys, k.on_acts, e)
- bd_kids(c.kids, ce, key, out)
+ ce.state = st_for(ui_st, key, c, ce)
+ lc_note(ui_st, key, k.on_keys, k.on_acts, e)
+ bd_kids(ui_st, c.kids, ce, key, out)
return true
}
diff --git a/packages/ludic.ui/build_class.ludic b/packages/ludic.ui/build_class.ludic
index c9ddec9f..62e11c71 100644
--- a/packages/ludic.ui/build_class.ludic
+++ b/packages/ludic.ui/build_class.ludic
@@ -1,14 +1,14 @@
# build_class.ludic - a Ludic component's instance built: the parent's attributes are its props (id,
# class and style go on its root element instead, styled by the parent's sheet as well as its own),
# its model is its props, state and fields, and its template reads nothing else
-function bd_class(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
+function bd_class(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
let cls = k.cls
- uc_load(cls)
- let it = in_get(key, cls)
+ uc_load(ui_st, cls)
+ let it = in_get(ui_st, key, cls)
let props = Value.object()
for i in 0 .. len(k.keys) {
let a = k.keys[i]
- if a != "class" and a != "style" and a != "id" { Value.put(props, a, ui_eval(k.vals[i], e)) }
+ if a != "class" and a != "style" and a != "id" { Value.put(props, a, ui_eval(ui_st, k.vals[i], e)) }
}
cls.props(it.ptr, props)
let ce = env_new(null)
@@ -21,25 +21,25 @@ function bd_class(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
ce.on_acts = k.on_acts
ce.on_envs = new []UiEnv
for i in 0 .. len(k.on_keys) { push(ce.on_envs, e) }
- lc_note(key, k.on_keys, k.on_acts, e)
+ lc_note(ui_st, key, k.on_keys, k.on_acts, e)
let at = len(out)
let root = new []UiTpl
push(root, cls.root)
- bd_kids(root, ce, key, out)
- if len(out) > at { bd_pass_down(out[at], k, e) }
+ bd_kids(ui_st, root, ce, key, out)
+ if len(out) > at { bd_pass_down(ui_st, out[at], k, e) }
}
# puts those on the card's own root element; when that
# root is itself a component, each user in turn adds theirs (the outermost's id wins)
-function bd_pass_down(n: UiNode, k: UiTpl, e: UiEnv) -> void {
+function bd_pass_down(ui_st: mut UiState, n: UiNode, k: UiTpl, e: UiEnv) -> void {
let c = tpl_attr(k, "class")
if c != null {
- let parts = lss_split(ui_eval_text(c, e), 32)
+ let parts = lss_split(ui_eval_text(ui_st, c, e), 32)
for i in 0 .. len(parts) {
if len(parts[i]) > 0 { push(n.classes, parts[i]) }
}
}
let id = tpl_attr(k, "id")
- if id != null { n.id = ui_eval_text(id, e) }
+ if id != null { n.id = ui_eval_text(ui_st, id, e) }
let up = new UiPass
up.sheet = k.sheet
up.style = k.style
diff --git a/packages/ludic.ui/build_more.ludic b/packages/ludic.ui/build_more.ludic
index 99141e88..0e58a44c 100644
--- a/packages/ludic.ui/build_more.ludic
+++ b/packages/ludic.ui/build_more.ludic
@@ -1,27 +1,27 @@
# build_more.ludic - names a value for the siblings after it, and
# gives it to everything inside, components included (React's context)
-function bd_let(k: UiTpl, e: UiEnv) -> UiEnv {
+function bd_let(ui_st: mut UiState, k: UiTpl, e: UiEnv) -> UiEnv {
let le = env_new(e)
- for i in 0 .. len(k.keys) { env_bind(le, k.keys[i], ui_eval(k.vals[i], e)) }
+ for i in 0 .. len(k.keys) { env_bind(le, k.keys[i], ui_eval(ui_st, k.vals[i], e)) }
return le
}
-function bd_provide(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
+function bd_provide(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
let cx = env_new(e.ctx)
- for i in 0 .. len(k.keys) { env_bind(cx, k.keys[i], ui_eval(k.vals[i], e)) }
+ for i in 0 .. len(k.keys) { env_bind(cx, k.keys[i], ui_eval(ui_st, k.vals[i], e)) }
let pe = env_new(e)
pe.ctx = cx
- bd_kids(k.kids, pe, key, out)
+ bd_kids(ui_st, k.kids, pe, key, out)
}
# a fragment's (or a template's) content: its elements, or its words as a text of their own
-function bd_group(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
+function bd_group(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
if k.text == null {
- bd_kids(k.kids, e, key, out)
+ bd_kids(ui_st, k.kids, e, key, out)
return
}
let t = new UiNode
t.kind = UI_TEXT
t.key = key
- t.text = ui_tr(ui_eval_text(k.text, e))
+ t.text = ui_tr(ui_st, ui_eval_text(ui_st, k.text, e))
t.env = e
t.children = new []UiNode
push(out, t)
diff --git a/packages/ludic.ui/calc.ludic b/packages/ludic.ui/calc.ludic
index 10849836..4f92f448 100644
--- a/packages/ludic.ui/calc.ludic
+++ b/packages/ludic.ui/calc.ludic
@@ -1,31 +1,24 @@
# calc.ludic - CSS's calc(), min(), max() and clamp(): + - * / and brackets over lengths (px, em, rem,
# vw, vh, a bare number is px), percentages and plain numbers. A width, height or inset keeps its
# percentage apart, to be taken of the room it is given; elsewhere a percentage has nothing to share.
-var ca_s: string = ""
-var ca_i: int = 0
-var ca_bad: string = ""
-var ca_px: float = 0.0 # the result: its lengths, its percentage, and whether it had units
-var ca_pct: float = 0.0
-var ca_num: float = 0.0 # a plain number's own value, and whether it was one
-var ca_bare: bool = false
function ca_is(s: string) -> bool { return ca_fn_at(s, 0) != "" }
# calc(...) worked out into ca_px and ca_pct; false (with the reason said) when it cannot be
-function ca_eval(s: string) -> bool {
- ca_s = s
- ca_i = 0
- ca_bad = ""
- let v = ca_factor()
- ca_ws()
- if ca_bad == "" and ca_i < len(ca_s) { ca_bad = "nothing after its closing )" }
- if ca_bad != "" {
- ui_err(`{s}: calc() expected {ca_bad}`)
+function ca_eval(ui_st: mut UiState, s: string) -> bool {
+ ui_st.ca_s = s
+ ui_st.ca_i = 0
+ ui_st.ca_bad = ""
+ let v = ca_factor(ui_st)
+ ca_ws(ui_st)
+ if ui_st.ca_bad == "" and ui_st.ca_i < len(ui_st.ca_s) { ui_st.ca_bad = "nothing after its closing )" }
+ if ui_st.ca_bad != "" {
+ ui_err(ui_st, `{s}: calc() expected {ui_st.ca_bad}`)
return false
}
- ca_px = v.px
- ca_pct = v.pct
- ca_num = v.n
- ca_bare = v.bare
- if v.bare { ca_px = el_px(v.n) }
+ ui_st.ca_px = v.px
+ ui_st.ca_pct = v.pct
+ ui_st.ca_num = v.n
+ ui_st.ca_bare = v.bare
+ if v.bare { ui_st.ca_px = el_px(ui_st, v.n) }
return true
}
property CaVal {
@@ -34,22 +27,22 @@ property CaVal {
n: float = 0.0
bare: bool = true # a plain number, which may scale a length
}
-function ca_ws() -> void {
- while ca_i < len(ca_s) and (ca_s[ca_i] == 32 or ca_s[ca_i] == 9 or ca_s[ca_i] == 10) { ca_i += 1 }
+function ca_ws(ui_st: mut UiState) -> void {
+ while ui_st.ca_i < len(ui_st.ca_s) and (ui_st.ca_s[ui_st.ca_i] == 32 or ui_st.ca_s[ui_st.ca_i] == 9 or ui_st.ca_s[ui_st.ca_i] == 10) { ui_st.ca_i += 1 }
}
-function ca_sum() -> CaVal {
- var a = ca_product()
- while ca_bad == "" {
- ca_ws()
- if ca_i >= len(ca_s) or (ca_s[ca_i] != 43 and ca_s[ca_i] != 45) { return a }
- let minus = ca_s[ca_i] == 45
- ca_i += 1
- let b = ca_product()
- a = ca_add(a, b, minus)
+function ca_sum(ui_st: mut UiState) -> CaVal {
+ var a = ca_product(ui_st)
+ while ui_st.ca_bad == "" {
+ ca_ws(ui_st)
+ if ui_st.ca_i >= len(ui_st.ca_s) or (ui_st.ca_s[ui_st.ca_i] != 43 and ui_st.ca_s[ui_st.ca_i] != 45) { return a }
+ let minus = ui_st.ca_s[ui_st.ca_i] == 45
+ ui_st.ca_i += 1
+ let b = ca_product(ui_st)
+ a = ca_add(ui_st, a, b, minus)
}
return a
}
-function ca_add(a: CaVal, b: CaVal, minus: bool) -> CaVal {
+function ca_add(ui_st: UiState, a: CaVal, b: CaVal, minus: bool) -> CaVal {
var sign = 1.0
if minus { sign = -1.0 }
let r = new CaVal
@@ -58,15 +51,15 @@ function ca_add(a: CaVal, b: CaVal, minus: bool) -> CaVal {
return r
}
r.bare = false
- r.px = ca_len(a).px + sign * ca_len(b).px
+ r.px = ca_len(ui_st, a).px + sign * ca_len(ui_st, b).px
r.pct = a.pct + sign * b.pct
return r
}
# a bare number beside a length is a length in px
-function ca_len(a: CaVal) -> CaVal {
+function ca_len(ui_st: UiState, a: CaVal) -> CaVal {
if not a.bare { return a }
let r = new CaVal
r.bare = false
- r.px = el_px(a.n)
+ r.px = el_px(ui_st, a.n)
return r
}
diff --git a/packages/ludic.ui/calc_fns.ludic b/packages/ludic.ui/calc_fns.ludic
index b9eb7b4a..d3f17233 100644
--- a/packages/ludic.ui/calc_fns.ludic
+++ b/packages/ludic.ui/calc_fns.ludic
@@ -9,40 +9,40 @@ function ca_fn_at(s: string, at: int) -> string {
return ""
}
function ca_word(s: string, at: int, w: string) -> bool { return at + len(w) <= len(s) and s[at..at + len(w)] == w }
-function ca_fn(f: string) -> CaVal {
- ca_i = ca_i + len(f) + 1
+function ca_fn(ui_st: mut UiState, f: string) -> CaVal {
+ ui_st.ca_i = ui_st.ca_i + len(f) + 1
let args = new []CaVal
- while ca_bad == "" {
- push(args, ca_sum())
- ca_ws()
- if ca_i < len(ca_s) and ca_s[ca_i] == 44 { ca_i += 1 } else { break }
+ while ui_st.ca_bad == "" {
+ push(args, ca_sum(ui_st))
+ ca_ws(ui_st)
+ if ui_st.ca_i < len(ui_st.ca_s) and ui_st.ca_s[ui_st.ca_i] == 44 { ui_st.ca_i += 1 } else { break }
}
- if ca_bad == "" and (ca_i >= len(ca_s) or ca_s[ca_i] != 41) { ca_bad = "a closing )" }
- ca_i += 1
- if ca_bad != "" { return new CaVal }
+ if ui_st.ca_bad == "" and (ui_st.ca_i >= len(ui_st.ca_s) or ui_st.ca_s[ui_st.ca_i] != 41) { ui_st.ca_bad = "a closing )" }
+ ui_st.ca_i += 1
+ if ui_st.ca_bad != "" { return new CaVal }
if f == "clamp" and len(args) != 3 {
- ca_bad = "clamp(least, want, most)"
+ ui_st.ca_bad = "clamp(least, want, most)"
return new CaVal
}
for i in 0 .. len(args) {
if args[i].pct != 0.0 {
- ca_bad = `lengths in {f}(), not a percentage`
+ ui_st.ca_bad = `lengths in {f}(), not a percentage`
return new CaVal
}
}
- if f == "clamp" { return ca_pick(ca_pick(args[1], args[0], false), args[2], true) }
+ if f == "clamp" { return ca_pick(ui_st, ca_pick(ui_st, args[1], args[0], false), args[2], true) }
var r = args[0]
- for i in 1 .. len(args) { r = ca_pick(r, args[i], f == "min") }
+ for i in 1 .. len(args) { r = ca_pick(ui_st, r, args[i], f == "min") }
return r
}
# the smaller (or the larger) of two, a bare number beside a length being px
-function ca_pick(a: CaVal, b: CaVal, smaller: bool) -> CaVal {
+function ca_pick(ui_st: UiState, a: CaVal, b: CaVal, smaller: bool) -> CaVal {
if a.bare and b.bare {
if (b.n < a.n) == smaller and b.n != a.n { return b }
return a
}
- let la = ca_len(a)
- let lb = ca_len(b)
+ let la = ca_len(ui_st, a)
+ let lb = ca_len(ui_st, b)
if (lb.px < la.px) == smaller and lb.px != la.px { return lb }
return la
}
diff --git a/packages/ludic.ui/calc_terms.ludic b/packages/ludic.ui/calc_terms.ludic
index d5c7cd5f..dd3b01b0 100644
--- a/packages/ludic.ui/calc_terms.ludic
+++ b/packages/ludic.ui/calc_terms.ludic
@@ -1,28 +1,28 @@
# calc_terms.ludic - calc()'s products and its terms: a number with its unit, a bracket, a nested
# calc(), a minus in front; a product needs a plain number on one side, and a quotient a plain number
# or a length on its right (a length over a length is a plain number, as CSS's typed division makes)
-function ca_product() -> CaVal {
- var a = ca_factor()
- while ca_bad == "" {
- ca_ws()
- if ca_i >= len(ca_s) or (ca_s[ca_i] != 42 and ca_s[ca_i] != 47) { return a }
- let div = ca_s[ca_i] == 47
- ca_i += 1
- let b = ca_factor()
+function ca_product(ui_st: mut UiState) -> CaVal {
+ var a = ca_factor(ui_st)
+ while ui_st.ca_bad == "" {
+ ca_ws(ui_st)
+ if ui_st.ca_i >= len(ui_st.ca_s) or (ui_st.ca_s[ui_st.ca_i] != 42 and ui_st.ca_s[ui_st.ca_i] != 47) { return a }
+ let div = ui_st.ca_s[ui_st.ca_i] == 47
+ ui_st.ca_i += 1
+ let b = ca_factor(ui_st)
if div and not b.bare and not a.bare and a.pct == 0.0 and b.pct == 0.0 and b.px != 0.0 {
a = ca_ratio(a.px / b.px)
continue
}
if div and not b.bare {
- ca_bad = "a plain number, or a length under a length, after /"
+ ui_st.ca_bad = "a plain number, or a length under a length, after /"
return a
}
if div and b.n == 0.0 {
- ca_bad = "a number that is not 0 after /"
+ ui_st.ca_bad = "a number that is not 0 after /"
return a
}
if div { a = ca_scale(a, 1.0 / b.n) } else if a.bare { a = ca_scale(b, a.n) } else if b.bare { a = ca_scale(a, b.n) } else {
- ca_bad = "a plain number on one side of *"
+ ui_st.ca_bad = "a plain number on one side of *"
return a
}
}
@@ -42,47 +42,47 @@ function ca_scale(a: CaVal, k: float) -> CaVal {
r.pct = a.pct * k
return r
}
-function ca_factor() -> CaVal {
- ca_ws()
- if ca_i >= len(ca_s) {
- ca_bad = "a value"
+function ca_factor(ui_st: mut UiState) -> CaVal {
+ ca_ws(ui_st)
+ if ui_st.ca_i >= len(ui_st.ca_s) {
+ ui_st.ca_bad = "a value"
return new CaVal
}
- let c = ca_s[ca_i]
+ let c = ui_st.ca_s[ui_st.ca_i]
if c == 45 {
- ca_i += 1
- return ca_scale(ca_factor(), -1.0)
+ ui_st.ca_i += 1
+ return ca_scale(ca_factor(ui_st), -1.0)
}
- let f = ca_fn_at(ca_s, ca_i)
- if f == "min" or f == "max" or f == "clamp" { return ca_fn(f) }
+ let f = ca_fn_at(ui_st.ca_s, ui_st.ca_i)
+ if f == "min" or f == "max" or f == "clamp" { return ca_fn(ui_st, f) }
if c == 40 or f == "calc" {
- while ca_s[ca_i] != 40 { ca_i += 1 }
- ca_i += 1
- let v = ca_sum()
- ca_ws()
- if ca_i < len(ca_s) and ca_s[ca_i] == 41 { ca_i += 1 } else if ca_bad == "" { ca_bad = "a closing )" }
+ while ui_st.ca_s[ui_st.ca_i] != 40 { ui_st.ca_i += 1 }
+ ui_st.ca_i += 1
+ let v = ca_sum(ui_st)
+ ca_ws(ui_st)
+ if ui_st.ca_i < len(ui_st.ca_s) and ui_st.ca_s[ui_st.ca_i] == 41 { ui_st.ca_i += 1 } else if ui_st.ca_bad == "" { ui_st.ca_bad = "a closing )" }
return v
}
- return ca_number()
+ return ca_number(ui_st)
}
# 12, 1.5em, 50%, 20vw
-function ca_number() -> CaVal {
- let at = ca_i
- while ca_i < len(ca_s) and ((ca_s[ca_i] >= 48 and ca_s[ca_i] <= 57) or ca_s[ca_i] == 46) { ca_i += 1 }
- let num_end = ca_i
- while ca_i < len(ca_s) and ((ca_s[ca_i] >= 97 and ca_s[ca_i] <= 122) or ca_s[ca_i] == 37) { ca_i += 1 }
+function ca_number(ui_st: mut UiState) -> CaVal {
+ let at = ui_st.ca_i
+ while ui_st.ca_i < len(ui_st.ca_s) and ((ui_st.ca_s[ui_st.ca_i] >= 48 and ui_st.ca_s[ui_st.ca_i] <= 57) or ui_st.ca_s[ui_st.ca_i] == 46) { ui_st.ca_i += 1 }
+ let num_end = ui_st.ca_i
+ while ui_st.ca_i < len(ui_st.ca_s) and ((ui_st.ca_s[ui_st.ca_i] >= 97 and ui_st.ca_s[ui_st.ca_i] <= 122) or ui_st.ca_s[ui_st.ca_i] == 37) { ui_st.ca_i += 1 }
let r = new CaVal
if num_end == at {
- ca_bad = "a number"
+ ui_st.ca_bad = "a number"
return r
}
- let word: string = ca_s[at..ca_i]
- let unit: string = ca_s[num_end..ca_i]
+ let word: string = ui_st.ca_s[at..ui_st.ca_i]
+ let unit: string = ui_st.ca_s[num_end..ui_st.ca_i]
if unit == "" {
r.n = el_num(word)
return r
}
r.bare = false
- if unit == "%" { r.pct = el_num(word) } else if unit == "px" or unit == "em" or unit == "rem" or unit == "vw" or unit == "vh" { r.px = el_unit(word) } else { ca_bad = `a unit calc() knows, not {unit}` }
+ if unit == "%" { r.pct = el_num(word) } else if unit == "px" or unit == "em" or unit == "rem" or unit == "vw" or unit == "vh" { r.px = el_unit(ui_st, word) } else { ui_st.ca_bad = `a unit calc() knows, not {unit}` }
return r
}
diff --git a/packages/ludic.ui/cascade.ludic b/packages/ludic.ui/cascade.ludic
index b91a631d..57670225 100644
--- a/packages/ludic.ui/cascade.ludic
+++ b/packages/ludic.ui/cascade.ludic
@@ -1,14 +1,14 @@
# cascade.ludic - once the frame's tree is built, every element is styled as a browser would:
# the default sheet, then the file's rules from least to most specific, then its style="...", then
# its own attributes. Then what is display: none leaves the tree.
-function cs_tree(n: UiNode) -> void {
+function cs_tree(ui_st: mut UiState, n: UiNode) -> void {
for i in 0 .. len(n.children) {
let c = n.children[i]
c.parent = n
c.index = i
}
- if n.tag != "" { cs_node(n) }
- for i in 0 .. len(n.children) { cs_tree(n.children[i]) }
+ if n.tag != "" { cs_node(ui_st, n) }
+ for i in 0 .. len(n.children) { cs_tree(ui_st, n.children[i]) }
var shown = new []UiNode
for i in 0 .. len(n.children) {
if not n.children[i].hidden { push(shown, n.children[i]) }
@@ -18,54 +18,52 @@ function cs_tree(n: UiNode) -> void {
}
# twice: the font size (and zoom) first, from every rule, so an em anywhere reads the size the
# element ends with (again when it zooms, so its own lengths are zoomed)
-function cs_node(n: UiNode) -> void {
- cs_pass = 1
- cs_styles(n)
- if zm_own(n) != 1.0 { cs_styles(n) }
- cs_pass = 0
- cs_styles(n)
- an_apply(n)
- tr_apply(n)
+function cs_node(ui_st: mut UiState, n: UiNode) -> void {
+ ui_st.cs_pass = 1
+ cs_styles(ui_st, n)
+ if zm_own(n) != 1.0 { cs_styles(ui_st, n) }
+ ui_st.cs_pass = 0
+ cs_styles(ui_st, n)
+ an_apply(ui_st, n)
+ tr_apply(ui_st, n)
}
-function cs_styles(n: UiNode) -> void {
- cs_sheet(n, ua_sheet())
- if n.ups != null { cs_merged(n) } else if n.sheet != null { cs_sheet(n, n.sheet) }
- if n.style != null { cs_rule(n, n.style) }
+function cs_styles(ui_st: mut UiState, n: UiNode) -> void {
+ cs_sheet(ui_st, n, ua_sheet(ui_st))
+ if n.ups != null { cs_merged(ui_st, n) } else if n.sheet != null { cs_sheet(ui_st, n, n.sheet) }
+ if n.style != null { cs_rule(ui_st, n, n.style) }
if n.ups != null {
for i in 0 .. len(n.ups) {
- if n.ups[i].style != null { cs_rule_in(n, n.ups[i].style, n.ups[i].env) }
+ if n.ups[i].style != null { cs_rule_in(ui_st, n, n.ups[i].style, n.ups[i].env) }
}
}
for i in 0 .. len(n.akeys) {
- if cs_pass_ok(n.akeys[i]) { el_apply(n, n.akeys[i], n.vals[i]) }
+ if cs_pass_ok(ui_st, n.akeys[i]) { el_apply(ui_st, n, n.akeys[i], n.vals[i]) }
}
}
-var cs_pass: int = 0
-function cs_pass_ok(key: string) -> bool { return cs_pass == 0 or at_alias(key) == "size" or key == "zoom" }
-function cs_sheet(n: UiNode, sh: UiSheet) -> void {
+function cs_pass_ok(ui_st: UiState, key: string) -> bool { return ui_st.cs_pass == 0 or at_alias(key) == "size" or key == "zoom" }
+function cs_sheet(ui_st: mut UiState, n: UiNode, sh: UiSheet) -> void {
for i in 0 .. len(sh.rules) {
let r = sh.rules[i]
- if md_ok(r) and sel_match(r.sel, n) { cs_rule(n, r) }
+ if md_ok(ui_st, r) and sel_match(ui_st, r.sel, n) { cs_rule(ui_st, n, r) }
}
}
-function cs_rule(n: UiNode, r: UiRule) -> void { cs_rule_in(n, r, n.env) }
+function cs_rule(ui_st: mut UiState, n: UiNode, r: UiRule) -> void { cs_rule_in(ui_st, n, r, n.env) }
# a rule's custom properties first, so its other declarations can read them; values read in `e`
-function cs_rule_in(n: UiNode, r: UiRule, e: UiEnv) -> void {
+function cs_rule_in(ui_st: mut UiState, n: UiNode, r: UiRule, e: UiEnv) -> void {
for j in 0 .. len(r.keys) {
- if len(r.keys[j]) > 2 and r.keys[j][0..2] == "--" { el_attr(n, r.keys[j], ui_eval(r.vals[j], e)) }
+ if len(r.keys[j]) > 2 and r.keys[j][0..2] == "--" { el_attr(ui_st, n, r.keys[j], ui_eval(ui_st, r.vals[j], e)) }
}
for j in 0 .. len(r.keys) {
- if not (len(r.keys[j]) > 2 and r.keys[j][0..2] == "--") and cs_pass_ok(r.keys[j]) { el_attr(n, r.keys[j], ui_eval(r.vals[j], e)) }
+ if not (len(r.keys[j]) > 2 and r.keys[j][0..2] == "--") and cs_pass_ok(ui_st, r.keys[j]) { el_attr(ui_st, n, r.keys[j], ui_eval(ui_st, r.vals[j], e)) }
}
}
-var ua_sh: UiSheet = null
-function ua_sheet() -> UiSheet {
- if ua_sh != null { return ua_sh }
+function ua_sheet(ui_st: mut UiState) -> UiSheet {
+ if ui_st.ua_sh != null { return ui_st.ua_sh }
let f = new UiFile
f.path = "the default sheet"
f.imp = new []UiFile
f.own = new []UiRule
- lss_text(f, UA_LSS)
- ua_sh = sheet_of(f)
- return ua_sh
+ lss_text(ui_st, f, UA_LSS)
+ ui_st.ua_sh = sheet_of(f)
+ return ui_st.ua_sh
}
diff --git a/packages/ludic.ui/cascade_merge.ludic b/packages/ludic.ui/cascade_merge.ludic
index 0f5f6bea..1210b945 100644
--- a/packages/ludic.ui/cascade_merge.ludic
+++ b/packages/ludic.ui/cascade_merge.ludic
@@ -2,25 +2,25 @@
# that passed a class down to it, all at once: the rules that match, from all of them, ordered by
# specificity as one sheet's are. On a tie the user's rule wins (the outermost user's last), as a
# later rule does. Then `order`, which rearranges a flex box's children without touching the tree.
-function cs_merged(n: UiNode) -> void {
+function cs_merged(ui_st: mut UiState, n: UiNode) -> void {
let rules = new []UiRule
let envs = new []UiEnv
let seen = new []UiSheet
- if n.sheet != null { cs_gather(n, n.sheet, n.env, rules, envs, seen) }
+ if n.sheet != null { cs_gather(ui_st, n, n.sheet, n.env, rules, envs, seen) }
for i in 0 .. len(n.ups) {
- if n.ups[i].sheet != null { cs_gather(n, n.ups[i].sheet, n.ups[i].env, rules, envs, seen) }
+ if n.ups[i].sheet != null { cs_gather(ui_st, n, n.ups[i].sheet, n.ups[i].env, rules, envs, seen) }
}
- for i in 0 .. len(rules) { cs_rule_in(n, rules[i], envs[i]) }
+ for i in 0 .. len(rules) { cs_rule_in(ui_st, n, rules[i], envs[i]) }
}
# a sheet's matching rules into the list, each after every rule at most as specific (stable)
-function cs_gather(n: UiNode, sh: UiSheet, e: UiEnv, rules: []UiRule, envs: []UiEnv, seen: []UiSheet) -> void {
+function cs_gather(ui_st: mut UiState, n: UiNode, sh: UiSheet, e: UiEnv, rules: []UiRule, envs: []UiEnv, seen: []UiSheet) -> void {
for i in 0 .. len(seen) {
if seen[i] == sh { return }
}
push(seen, sh)
for i in 0 .. len(sh.rules) {
let r = sh.rules[i]
- if md_ok(r) and sel_match(r.sel, n) {
+ if md_ok(ui_st, r) and sel_match(ui_st, r.sel, n) {
push(rules, r)
push(envs, e)
var j = len(rules) - 1
diff --git a/packages/ludic.ui/class.ludic b/packages/ludic.ui/class.ludic
index 39ddd39b..c95080ba 100644
--- a/packages/ludic.ui/class.ludic
+++ b/packages/ludic.ui/class.ludic
@@ -16,43 +16,42 @@ export property UiClass {
seen_xml: string = "" # the files as a developer's build last found them on disk
seen_lss: string = ""
}
-var uc_list: []UiClass = null # filled by the compiler's registrations, before the program's globals
-export function ui_register(k: UiClass) -> int {
- if uc_list == null { uc_list = new []UiClass }
- push(uc_list, k)
- return len(uc_list)
+export function ui_register(ui_st: mut UiState, k: UiClass) -> int {
+ if ui_st.uc_list == null { ui_st.uc_list = new []UiClass }
+ push(ui_st.uc_list, k)
+ return len(ui_st.uc_list)
}
-function uc_find(name: string) -> UiClass {
- if uc_list == null { return null }
- for i in 0 .. len(uc_list) {
- if uc_list[i].name == name { return uc_list[i] }
+function uc_find(ui_st: UiState, name: string) -> UiClass {
+ if ui_st.uc_list == null { return null }
+ for i in 0 .. len(ui_st.uc_list) {
+ if ui_st.uc_list[i].name == name { return ui_st.uc_list[i] }
}
return null
}
# the class's template and styles, read the first time it is shown
-function uc_load(k: UiClass) -> void {
+function uc_load(ui_st: mut UiState, k: UiClass) -> void {
if k.root != null { return }
let f = lib_blank(k.xml_path)
f.src = k.xml
- if k.lss != "" { lss_text(f, k.lss) }
+ if k.lss != "" { lss_text(ui_st, f, k.lss) }
f.sheet = sheet_of(f)
- tp_lines(k.xml)
- let root = tpl_read(Xml.parse(k.xml), k.xml_path)
+ tp_lines(ui_st, k.xml)
+ let root = tpl_read(ui_st, Xml.parse(k.xml), k.xml_path)
k.sheet = f.sheet
- lib_link(f, root)
+ lib_link(ui_st, f, root)
k.root = root
}
# a developer's build reads a class's files again when they change on disk: the first look is what
# they are, and a later one that differs is taken (an @import in the styles is read from disk too)
-function uc_reload() -> bool {
- if uc_list == null { return false }
+function uc_reload(ui_st: mut UiState) -> bool {
+ if ui_st.uc_list == null { return false }
var changed = false
- for i in 0 .. len(uc_list) {
- let k = uc_list[i]
+ for i in 0 .. len(ui_st.uc_list) {
+ let k = ui_st.uc_list[i]
if k.xml_path != "" and Fs.exists(k.xml_path) {
- let x = lib_text(k.xml_path)
+ let x = lib_text(ui_st, k.xml_path)
var l = ""
- if k.lss_path != "" and Fs.exists(k.lss_path) { l = lib_text(k.lss_path) }
+ if k.lss_path != "" and Fs.exists(k.lss_path) { l = lib_text(ui_st, k.lss_path) }
if k.seen_xml != "" and (x != k.seen_xml or l != k.seen_lss) {
k.xml = x
k.lss = l
@@ -66,7 +65,7 @@ function uc_reload() -> bool {
return changed
}
# every class reads its template and styles again, as when a stylesheet they import has changed
-function uc_invalidate() -> void {
- if uc_list == null { return }
- for i in 0 .. len(uc_list) { uc_list[i].root = null }
+function uc_invalidate(ui_st: mut UiState) -> void {
+ if ui_st.uc_list == null { return }
+ for i in 0 .. len(ui_st.uc_list) { ui_st.uc_list[i].root = null }
}
diff --git a/packages/ludic.ui/color.ludic b/packages/ludic.ui/color.ludic
index 1a238c5c..88ae3493 100644
--- a/packages/ludic.ui/color.ludic
+++ b/packages/ludic.ui/color.ludic
@@ -1,19 +1,18 @@
# color.ludic - CSS colours: #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(r, g, b), rgba(r, g, b, a), the
# basic names, transparent and none. A colour is 0xRRGGBB; its alpha is left in cl_alpha.
-var cl_alpha: float = 1.0
const CL_NAMES: string = " black=000000 white=ffffff red=ff0000 green=008000 lime=00ff00 blue=0000ff yellow=ffff00 cyan=00ffff aqua=00ffff magenta=ff00ff fuchsia=ff00ff gray=808080 grey=808080 silver=c0c0c0 maroon=800000 olive=808000 navy=000080 purple=800080 teal=008080 orange=ffa500 "
-function ui_color(name0: string) -> int {
+function ui_color(ui_st: mut UiState, name0: string) -> int {
let name = tpl_words(name0)
- cl_alpha = 1.0
+ ui_st.cl_alpha = 1.0
if name == "none" or name == "" or name == "transparent" { return UI_NONE }
- if name[0] == 35 { return cl_hex(name[1..len(name)]) }
- if len(name) > 4 and (name[0..4] == "rgb(" or name[0..5] == "rgba(") { return cl_rgb(name) }
+ if name[0] == 35 { return cl_hex(ui_st, name[1..len(name)]) }
+ if len(name) > 4 and (name[0..4] == "rgb(" or name[0..5] == "rgba(") { return cl_rgb(ui_st, name) }
let pat = ` {name}=`
let n = len(pat)
for i in 0 .. len(CL_NAMES) - n + 1 {
- if CL_NAMES[i..i + n] == pat { return cl_hex(CL_NAMES[i + n..i + n + 6]) }
+ if CL_NAMES[i..i + n] == pat { return cl_hex(ui_st, CL_NAMES[i + n..i + n + 6]) }
}
- ui_err(`{name}: not a colour - #rrggbb, rgb(), rgba(), a name, or var(--a-colour)`)
+ ui_err(ui_st, `{name}: not a colour - #rrggbb, rgb(), rgba(), a name, or var(--a-colour)`)
return 15658734
}
function cl_digit(c: int) -> int {
@@ -21,26 +20,26 @@ function cl_digit(c: int) -> int {
if c >= 65 { return c - 55 }
return c - 48
}
-function cl_hex(h: string) -> int {
+function cl_hex(ui_st: mut UiState, h: string) -> int {
var c = 0
let n = len(h)
if n == 3 or n == 4 {
for i in 0 .. 3 { c = c * 256 + cl_digit(h[i]) * 17 }
- if n == 4 { cl_alpha = float(cl_digit(h[3]) * 17) / 255.0 }
+ if n == 4 { ui_st.cl_alpha = float(cl_digit(h[3]) * 17) / 255.0 }
return c
}
for i in 0 .. 3 { c = c * 256 + cl_digit(h[i * 2]) * 16 + cl_digit(h[i * 2 + 1]) }
- if n == 8 { cl_alpha = float(cl_digit(h[6]) * 16 + cl_digit(h[7])) / 255.0 }
+ if n == 8 { ui_st.cl_alpha = float(cl_digit(h[6]) * 16 + cl_digit(h[7])) / 255.0 }
return c
}
# rgb(217, 165, 74) or rgba(0, 0, 0, 0.55)
-function cl_rgb(s: string) -> int {
+function cl_rgb(ui_st: mut UiState, s: string) -> int {
var open = 0
while open < len(s) and s[open] != 40 { open += 1 }
let parts = lss_split(s[open + 1..len(s) - 1], 44)
var c = 0
for i in 0 .. Math.min(3, len(parts)) { c = c * 256 + Math.clamp(int(el_num(tpl_words(parts[i]))), 0, 255) }
- if len(parts) > 3 { cl_alpha = el_num(tpl_words(parts[3])) }
+ if len(parts) > 3 { ui_st.cl_alpha = el_num(tpl_words(parts[3])) }
return c
}
# a colour as #rrggbb, for a dump or a test
diff --git a/packages/ludic.ui/controls.ludic b/packages/ludic.ui/controls.ludic
index eefd6c5f..abb41f86 100644
--- a/packages/ludic.ui/controls.ludic
+++ b/packages/ludic.ui/controls.ludic
@@ -11,45 +11,45 @@ function ct_type(n: UiNode) -> string {
}
function ct_takes_arrows(n: UiNode) -> bool { return ct_type(n) == "range" or ct_type(n) == "select" or ct_type(n) == "number" }
function ct_takes_space(n: UiNode) -> bool { return ct_type(n) == "text" }
-function ct_change(n: UiNode, v: Val) -> void {
- ui_sound(ct_sound(n))
- ui_fire(n, "change", v)
+function ct_change(ui_st: mut UiState, n: UiNode, v: Val) -> void {
+ ui_sound(ui_st, ct_sound(n))
+ ui_fire(ui_st, n, "change", v)
}
function ct_sound(n: UiNode) -> string {
let s = ui_attr_text(n, "sound")
if s == "" { return "click" }
return s
}
-function ct_activate(n: UiNode) -> void {
+function ct_activate(ui_st: mut UiState, n: UiNode) -> void {
if not n.enabled { return }
let t = ct_type(n)
- if t == "checkbox" { ct_change(n, vl_bool(not ui_attr_on(n, "checked"))) } else if t == "radio" { ct_change(n, ui_attr(n, "value")) } else if t == "select" { ct_step(n, 1) } else if t == "key" { ct_key_start(n) } else if t == "number" { ct_num_commit(n) } else if t == "text" { ct_submit(n) } else if t == "" {
- ui_sound(ct_sound(n))
- ui_fire(n, "press", Value.null())
+ if t == "checkbox" { ct_change(ui_st, n, vl_bool(not ui_attr_on(n, "checked"))) } else if t == "radio" { ct_change(ui_st, n, ui_attr(n, "value")) } else if t == "select" { ct_step(ui_st, n, 1) } else if t == "key" { ct_key_start(ui_st, n) } else if t == "number" { ct_num_commit(ui_st, n) } else if t == "text" { ct_submit(ui_st, n) } else if t == "" {
+ ui_sound(ui_st, ct_sound(n))
+ ui_fire(ui_st, n, "press", Value.null())
}
}
-function ct_step(n: UiNode, d: int) -> void {
+function ct_step(ui_st: mut UiState, n: UiNode, d: int) -> void {
let t = ct_type(n)
if t == "select" and n.opts != null and len(n.opts) > 0 {
let at = (ct_option_at(n, n.opts) + d + len(n.opts)) % len(n.opts)
let ov = ui_attr(n.opts[at], "value")
- if Value.kind(ov) == 0 { ct_change(n, Value.int(at)) } else { ct_change(n, ov) }
+ if Value.kind(ov) == 0 { ct_change(ui_st, n, Value.int(at)) } else { ct_change(ui_st, n, ov) }
}
- if t == "number" { ct_num_step(n, d) }
+ if t == "number" { ct_num_step(ui_st, n, d) }
if t == "range" {
var step = Value.as_float(ui_attr(n, "step"))
if step <= 0.0 { step = 1.0 }
- ct_range_set(n, Value.as_float(ui_attr(n, "value")) + step * float(d))
+ ct_range_set(ui_st, n, Value.as_float(ui_attr(n, "value")) + step * float(d))
}
}
-function ct_range_set(n: UiNode, v0: float) -> void {
+function ct_range_set(ui_st: mut UiState, n: UiNode, v0: float) -> void {
let lo = Value.as_float(ui_attr(n, "min"))
let hi = Value.as_float(ui_attr(n, "max"))
let v = Math.clamp(v0, lo, Math.max(hi, lo))
- if v != Value.as_float(ui_attr(n, "value")) { ui_fire(n, "change", Value.float(v)) }
+ if v != Value.as_float(ui_attr(n, "value")) { ui_fire(ui_st, n, "change", Value.float(v)) }
}
# a range's track pressed or dragged: the value where the pointer is
-function ct_press(n: UiNode, x: float, y: float) -> void {
+function ct_press(ui_st: mut UiState, n: UiNode, x: float, y: float) -> void {
if ct_type(n) != "range" { return }
for i in 0 .. len(n.children) {
let t = n.children[i]
@@ -59,26 +59,26 @@ function ct_press(n: UiNode, x: float, y: float) -> void {
var step = Value.as_float(ui_attr(n, "step"))
if step <= 0.0 { step = 1.0 }
let raw = lo + Math.clamp((x - t.x) / t.cw, 0.0, 1.0) * (hi - lo)
- ct_range_set(n, lo + float(int((raw - lo) / step + 0.5)) * step)
+ ct_range_set(ui_st, n, lo + float(int((raw - lo) / step + 0.5)) * step)
}
}
}
-function ct_drag(root: UiNode, key: string, x: float, y: float) -> void {
+function ct_drag(ui_st: mut UiState, root: UiNode, key: string, x: float, y: float) -> void {
let n = fr_node(root, key)
- if n != null { ct_press(n, x, y) }
+ if n != null { ct_press(ui_st, n, x, y) }
}
# typing into a focused text field, or the key a key field is waiting for
-function ct_typed(n: UiNode) -> void {
+function ct_typed(ui_st: mut UiState, n: UiNode) -> void {
let t = ct_type(n)
- if t == "key" { ct_key_typed(n) }
- if t == "number" { ct_num_typed(n) }
- if t != "text" or (in_now.typed == "" and not in_now.backspace) { return }
+ if t == "key" { ct_key_typed(ui_st, n) }
+ if t == "number" { ct_num_typed(ui_st, n) }
+ if t != "text" or (ui_st.in_now.typed == "" and not ui_st.in_now.backspace) { return }
var s = ui_attr_text(n, "value")
- if in_now.backspace and len(s) > 0 { s = s[0..ct_char_start(s)] }
+ if ui_st.in_now.backspace and len(s) > 0 { s = s[0..ct_char_start(s)] }
var max = Value.as_int(ui_attr(n, "maxlength"))
if max <= 0 { max = 1000 }
- if len(in_now.typed) > 0 and len(s) < max { s = s + in_now.typed[0..Math.min(len(in_now.typed), max - len(s))] }
- ui_fire(n, "change", Value.str(s))
+ if len(ui_st.in_now.typed) > 0 and len(s) < max { s = s + ui_st.in_now.typed[0..Math.min(len(ui_st.in_now.typed), max - len(s))] }
+ ui_fire(ui_st, n, "change", Value.str(s))
}
# where the last UTF-8 character starts, so Backspace takes a whole letter
function ct_char_start(s: string) -> int {
diff --git a/packages/ludic.ui/controls_build.ludic b/packages/ludic.ui/controls_build.ludic
index b5177c68..450d8350 100644
--- a/packages/ludic.ui/controls_build.ludic
+++ b/packages/ludic.ui/controls_build.ludic
@@ -32,7 +32,7 @@ function ct_build_bar(n: UiNode) -> void {
fill.w_pct = Math.clamp((Value.as_float(ui_attr(n, "value")) - lo) / (hi - lo), 0.0, 1.0) * 100.0
push(n.children, fill)
}
-function ct_build(n: UiNode) -> void {
+function ct_build(ui_st: UiState, n: UiNode) -> void {
if n.tag == "progress" or n.tag == "meter" {
ct_build_bar(n)
return
@@ -41,10 +41,10 @@ function ct_build(n: UiNode) -> void {
n.focusable = true
let t = ui_attr_text(n, "type")
if n.tag == "select" {
- ct_build_select(n)
+ ct_build_select(ui_st, n)
return
}
- ct_label(n)
+ ct_label(ui_st, n)
if t == "checkbox" or t == "radio" {
let track = ct_part(n, "ui-track", "")
push(track.children, ct_part(n, "ui-knob", ""))
@@ -53,8 +53,8 @@ function ct_build(n: UiNode) -> void {
ct_build_range(n)
} else {
let field = ct_part(n, "ui-field", "")
- push(field.children, ct_part(n, "ui-value", ct_field_text(n)))
- if fc_key == n.key and t != "key" { push(field.children, ct_part(n, "ui-caret", "")) }
+ push(field.children, ct_part(n, "ui-value", ct_field_text(ui_st, n)))
+ if ui_st.fc_key == n.key and t != "key" { push(field.children, ct_part(n, "ui-caret", "")) }
push(n.children, field)
}
}
@@ -71,10 +71,10 @@ function ct_build_range(n: UiNode) -> void {
push(n.children, track)
push(n.children, ct_part(n, "ui-value", ct_value_text(n)))
}
-function ct_build_select(n: UiNode) -> void {
+function ct_build_select(ui_st: UiState, n: UiNode) -> void {
let opts = n.children
n.children = new []UiNode
- ct_label(n)
+ ct_label(ui_st, n)
var shown = ""
let at = ct_option_at(n, opts)
if at >= 0 { shown = opts[at].text }
@@ -92,9 +92,9 @@ function ct_option_at(n: UiNode, opts: []UiNode) -> int {
}
return -1
}
-function ct_field_text(n: UiNode) -> string {
+function ct_field_text(ui_st: UiState, n: UiNode) -> string {
let t = ui_attr_text(n, "type")
- if t == "key" { return ct_key_text(n) }
- if t == "number" { return ct_num_shown(n) }
+ if t == "key" { return ct_key_text(ui_st, n) }
+ if t == "number" { return ct_num_shown(ui_st, n) }
return ui_attr_text(n, "value")
}
diff --git a/packages/ludic.ui/controls_key.ludic b/packages/ludic.ui/controls_key.ludic
index 837e8a51..d8db1f02 100644
--- a/packages/ludic.ui/controls_key.ludic
+++ b/packages/ludic.ui/controls_key.ludic
@@ -2,54 +2,51 @@
# the next key pressed is its value - Tab, the arrows and Enter included, which move nothing while it
# listens. Esc stops listening and keeps the value; Backspace, when it is not listening, clears it (0).
# `shown` is what it says for its value (a host's own name for the key), else the keyboard's label.
-var ct_capture: string = ""
-var ct_capture_at: int = -1 # the frame listening began, whose own key started it
-var ct_capture_seen: int = -1 # the last frame that began listening, for ui_capturing
-function ct_key_start(n: UiNode) -> void {
- ct_capture = n.key
- ct_capture_at = an_frame
- ct_capture_seen = an_frame
+function ct_key_start(ui_st: mut UiState, n: UiNode) -> void {
+ ui_st.ct_capture = n.key
+ ui_st.ct_capture_at = ui_st.an_frame
+ ui_st.ct_capture_seen = ui_st.an_frame
}
# while it listens, the frame's keys are the field's: nothing else reads them (fc_keys)
-function ct_key_listening() -> bool { return ct_capture != "" and ct_capture_at != an_frame }
+function ct_key_listening(ui_st: UiState) -> bool { return ui_st.ct_capture != "" and ui_st.ct_capture_at != ui_st.an_frame }
# the field listening, given this frame's key; false when there is none to give it to
-function ct_key_capture(list: []UiNode) -> bool {
- if ct_capture == "" { return false }
- ct_capture_seen = an_frame
+function ct_key_capture(ui_st: mut UiState, list: []UiNode) -> bool {
+ if ui_st.ct_capture == "" { return false }
+ ui_st.ct_capture_seen = ui_st.an_frame
var n: UiNode = null
for i in 0 .. len(list) {
- if list[i].key == ct_capture { n = list[i] }
+ if list[i].key == ui_st.ct_capture { n = list[i] }
}
if n == null {
- ct_capture = ""
+ ui_st.ct_capture = ""
return false
}
- if ct_capture_at == an_frame { return true }
- if in_now.escape {
- ct_capture = ""
+ if ui_st.ct_capture_at == ui_st.an_frame { return true }
+ if ui_st.in_now.escape {
+ ui_st.ct_capture = ""
return true
}
- if in_now.key != 0 {
- ct_capture = ""
- ct_change(n, Value.int(in_now.key))
+ if ui_st.in_now.key != 0 {
+ ui_st.ct_capture = ""
+ ct_change(ui_st, n, Value.int(ui_st.in_now.key))
}
return true
}
# focused, not listening: Backspace unbinds it
-function ct_key_typed(n: UiNode) -> void {
- if ct_capture != n.key and in_now.backspace { ct_change(n, Value.int(0)) }
+function ct_key_typed(ui_st: mut UiState, n: UiNode) -> void {
+ if ui_st.ct_capture != n.key and ui_st.in_now.backspace { ct_change(ui_st, n, Value.int(0)) }
}
# a press anywhere but the field stops it listening
-function ct_key_pressed_away(hit: UiNode) -> void {
- if ct_capture != "" and (hit == null or hit.key != ct_capture) { ct_capture = "" }
+function ct_key_pressed_away(ui_st: mut UiState, hit: UiNode) -> void {
+ if ui_st.ct_capture != "" and (hit == null or hit.key != ui_st.ct_capture) { ui_st.ct_capture = "" }
}
-function ct_key_text(n: UiNode) -> string {
- if ct_capture == n.key { return ui_tr("press a key...") }
- if Value.kind(ui_attr(n, "shown")) != 0 { return ui_tr(ui_attr_text(n, "shown")) }
+function ct_key_text(ui_st: UiState, n: UiNode) -> string {
+ if ui_st.ct_capture == n.key { return ui_tr(ui_st, "press a key...") }
+ if Value.kind(ui_attr(n, "shown")) != 0 { return ui_tr(ui_st, ui_attr_text(n, "shown")) }
let v = Value.as_int(ui_attr(n, "value"))
- if v >= UI_MOUSE_LEFT and v <= UI_MOUSE_MIDDLE { return ct_mouse_name(v) }
+ if v >= UI_MOUSE_LEFT and v <= UI_MOUSE_MIDDLE { return ct_mouse_name(ui_st, v) }
return Input.key_label(v)
}
# a key field is listening: the host should leave the keys alone until it is done (it stays true
# for the frame in which Esc or a key ended it, so that key is not read twice)
-export function ui_capturing() -> bool { return ct_capture != "" or ct_capture_seen == an_frame }
+export function ui_capturing(ui_st: UiState) -> bool { return ui_st.ct_capture != "" or ui_st.ct_capture_seen == ui_st.an_frame }
diff --git a/packages/ludic.ui/controls_key_mouse.ludic b/packages/ludic.ui/controls_key_mouse.ludic
index 7c021afe..6c8fc59f 100644
--- a/packages/ludic.ui/controls_key_mouse.ludic
+++ b/packages/ludic.ui/controls_key_mouse.ludic
@@ -4,19 +4,19 @@
export const UI_MOUSE_LEFT: int = 256
export const UI_MOUSE_RIGHT: int = 257
export const UI_MOUSE_MIDDLE: int = 258
-function ct_key_mouse(root: UiNode) -> bool {
- if ct_capture == "" or ct_capture_at == an_frame { return false }
- let b = pe_pressed(in_now, in_last)
+function ct_key_mouse(ui_st: mut UiState, root: UiNode) -> bool {
+ if ui_st.ct_capture == "" or ui_st.ct_capture_at == ui_st.an_frame { return false }
+ let b = pe_pressed(ui_st.in_now, ui_st.in_last)
if b < 0 { return false }
- let n = fr_node(root, ct_capture)
- ct_capture = ""
- ct_capture_seen = an_frame
- fr_press_key = ""
- if n != null { ct_change(n, Value.int(UI_MOUSE_LEFT + b)) }
+ let n = fr_node(root, ui_st.ct_capture)
+ ui_st.ct_capture = ""
+ ui_st.ct_capture_seen = ui_st.an_frame
+ ui_st.fr_press_key = ""
+ if n != null { ct_change(ui_st, n, Value.int(UI_MOUSE_LEFT + b)) }
return true
}
-function ct_mouse_name(v: int) -> string {
- if v == UI_MOUSE_RIGHT { return ui_tr("Right click") }
- if v == UI_MOUSE_MIDDLE { return ui_tr("Middle click") }
- return ui_tr("Left click")
+function ct_mouse_name(ui_st: UiState, v: int) -> string {
+ if v == UI_MOUSE_RIGHT { return ui_tr(ui_st, "Right click") }
+ if v == UI_MOUSE_MIDDLE { return ui_tr(ui_st, "Middle click") }
+ return ui_tr(ui_st, "Left click")
}
diff --git a/packages/ludic.ui/controls_number.ludic b/packages/ludic.ui/controls_number.ludic
index 4b78f80b..0c782d32 100644
--- a/packages/ludic.ui/controls_number.ludic
+++ b/packages/ludic.ui/controls_number.ludic
@@ -3,8 +3,6 @@
# the field commits, held between min and max; Backspace takes a digit back (of the value, when it is
# the first key), and the left and right arrows step it. Each commit is one on-change with the
# number: an int when step and the value are whole.
-var ct_num_key: string = ""
-var ct_num_text: string = ""
function ct_num_bound(n: UiNode, key: string) -> bool { return Value.kind(ui_attr(n, key)) != 0 }
function ct_num_step_of(n: UiNode) -> float {
let s = Value.as_float(ui_attr(n, "step"))
@@ -17,57 +15,57 @@ function ct_num_clamp(n: UiNode, v0: float) -> float {
if ct_num_bound(n, "min") { v = Math.max(v, Value.as_float(ui_attr(n, "min"))) }
return v
}
-function ct_num_set(n: UiNode, v0: float) -> void {
+function ct_num_set(ui_st: mut UiState, n: UiNode, v0: float) -> void {
let v = ct_num_clamp(n, v0)
let step = ct_num_step_of(n)
let was = ui_attr(n, "value")
if Value.kind(was) != 0 and Value.as_float(was) == v { return }
- if step == float(int(step)) and v == float(int(v)) { ct_change(n, Value.int(int(v))) } else { ct_change(n, Value.float(v)) }
+ if step == float(int(step)) and v == float(int(v)) { ct_change(ui_st, n, Value.int(int(v))) } else { ct_change(ui_st, n, Value.float(v)) }
}
# Enter, or the focus leaving it: the draft becomes the value (a draft of nothing changes nothing)
-function ct_num_commit(n: UiNode) -> void {
- if ct_num_key != n.key { return }
- let s = ct_num_text
- ct_num_key = ""
+function ct_num_commit(ui_st: mut UiState, n: UiNode) -> void {
+ if ui_st.ct_num_key != n.key { return }
+ let s = ui_st.ct_num_text
+ ui_st.ct_num_key = ""
if s == "" or s == "-" or s == "." or s == "-." { return }
- ct_num_set(n, el_num(s))
+ ct_num_set(ui_st, n, el_num(s))
}
-function ct_num_step(n: UiNode, d: int) -> void {
+function ct_num_step(ui_st: mut UiState, n: UiNode, d: int) -> void {
var base = Value.as_float(ui_attr(n, "value"))
- if ct_num_key == n.key and ct_num_text != "" and ct_num_text != "-" { base = el_num(ct_num_text) }
- ct_num_key = ""
- ct_num_set(n, base + ct_num_step_of(n) * float(d))
+ if ui_st.ct_num_key == n.key and ui_st.ct_num_text != "" and ui_st.ct_num_text != "-" { base = el_num(ui_st.ct_num_text) }
+ ui_st.ct_num_key = ""
+ ct_num_set(ui_st, n, base + ct_num_step_of(n) * float(d))
}
# what was typed this frame, the characters a number can hold; Backspace takes one back
-function ct_num_typed(n: UiNode) -> void {
- if fc_key != n.key or (in_now.typed == "" and not in_now.backspace) { return }
- if ct_num_key != n.key {
- ct_num_key = n.key
- ct_num_text = ""
- if in_now.typed == "" { ct_num_text = ui_attr_text(n, "value") }
+function ct_num_typed(ui_st: mut UiState, n: UiNode) -> void {
+ if ui_st.fc_key != n.key or (ui_st.in_now.typed == "" and not ui_st.in_now.backspace) { return }
+ if ui_st.ct_num_key != n.key {
+ ui_st.ct_num_key = n.key
+ ui_st.ct_num_text = ""
+ if ui_st.in_now.typed == "" { ui_st.ct_num_text = ui_attr_text(n, "value") }
}
- if in_now.backspace and len(ct_num_text) > 0 { ct_num_text = ct_num_text[0..len(ct_num_text) - 1] }
+ if ui_st.in_now.backspace and len(ui_st.ct_num_text) > 0 { ui_st.ct_num_text = ui_st.ct_num_text[0..len(ui_st.ct_num_text) - 1] }
let step = ct_num_step_of(n)
let lo = Value.as_float(ui_attr(n, "min"))
- for i in 0 .. len(in_now.typed) {
- let c = in_now.typed[i]
+ for i in 0 .. len(ui_st.in_now.typed) {
+ let c = ui_st.in_now.typed[i]
let digit = c >= 48 and c <= 57
- let minus = c == 45 and ct_num_text == "" and (not ct_num_bound(n, "min") or lo < 0.0)
- let point = c == 46 and step != float(int(step)) and not ui_has(ct_num_text, ".")
- if (digit or minus or point) and len(ct_num_text) < 24 { ct_num_text = ct_num_text + in_now.typed[i..i + 1] }
+ let minus = c == 45 and ui_st.ct_num_text == "" and (not ct_num_bound(n, "min") or lo < 0.0)
+ let point = c == 46 and step != float(int(step)) and not ui_has(ui_st.ct_num_text, ".")
+ if (digit or minus or point) and len(ui_st.ct_num_text) < 24 { ui_st.ct_num_text = ui_st.ct_num_text + ui_st.in_now.typed[i..i + 1] }
}
}
# the focus has gone from the field being typed in: what was typed is kept
-function ct_num_blur(root: UiNode) -> void {
- if ct_num_key == "" or ct_num_key == fc_key { return }
- let n = fr_node(root, ct_num_key)
+function ct_num_blur(ui_st: mut UiState, root: UiNode) -> void {
+ if ui_st.ct_num_key == "" or ui_st.ct_num_key == ui_st.fc_key { return }
+ let n = fr_node(root, ui_st.ct_num_key)
if n == null {
- ct_num_key = ""
+ ui_st.ct_num_key = ""
return
}
- ct_num_commit(n)
+ ct_num_commit(ui_st, n)
}
-function ct_num_shown(n: UiNode) -> string {
- if ct_num_key == n.key { return ct_num_text }
+function ct_num_shown(ui_st: UiState, n: UiNode) -> string {
+ if ui_st.ct_num_key == n.key { return ui_st.ct_num_text }
return ct_value_text(n)
}
diff --git a/packages/ludic.ui/controls_parts.ludic b/packages/ludic.ui/controls_parts.ludic
index 1205681d..e9bd0d41 100644
--- a/packages/ludic.ui/controls_parts.ludic
+++ b/packages/ludic.ui/controls_parts.ludic
@@ -1,16 +1,16 @@
# controls_parts.ludic - what a control shows besides its own parts: its label, with a `note` (a line
# of help under it) in .ui-labels > .ui-label + .ui-note, and a range's or a number's value as text:
# `decimals="1"` places, `format="percent"` its place between min and max, and a `unit` after it
-function ct_label(n: UiNode) -> void {
+function ct_label(ui_st: UiState, n: UiNode) -> void {
let label = ui_attr_text(n, "label")
let note = ui_attr_text(n, "note")
if note == "" {
- if label != "" { push(n.children, ct_part(n, "ui-label", ui_tr(label))) }
+ if label != "" { push(n.children, ct_part(n, "ui-label", ui_tr(ui_st, label))) }
return
}
let both = ct_part(n, "ui-labels", "")
- if label != "" { push(both.children, ct_part(n, "ui-label", ui_tr(label))) }
- push(both.children, ct_part(n, "ui-note", ui_tr(note)))
+ if label != "" { push(both.children, ct_part(n, "ui-label", ui_tr(ui_st, label))) }
+ push(both.children, ct_part(n, "ui-note", ui_tr(ui_st, note)))
push(n.children, both)
}
function ct_value_text(n: UiNode) -> string {
@@ -46,7 +46,7 @@ function ct_fixed(f: float, places: int) -> string {
}
# Enter (or A) on a text field: on-submit with what it holds; the focus stays, and the text too
# unless it says clear-on-submit
-function ct_submit(n: UiNode) -> void {
- ui_fire(n, "submit", ui_attr(n, "value"))
- if ui_attr_on(n, "clear-on-submit") { ui_fire(n, "change", Value.str("")) }
+function ct_submit(ui_st: mut UiState, n: UiNode) -> void {
+ ui_fire(ui_st, n, "submit", ui_attr(n, "value"))
+ if ui_attr_on(n, "clear-on-submit") { ui_fire(ui_st, n, "change", Value.str("")) }
}
diff --git a/packages/ludic.ui/dev.ludic b/packages/ludic.ui/dev.ludic
index ae187996..2c74c1a1 100644
--- a/packages/ludic.ui/dev.ludic
+++ b/packages/ludic.ui/dev.ludic
@@ -1,24 +1,21 @@
# dev.ludic - a developer's build: templates and stylesheets read again when they change (once a
# second), template errors drawn over the screen, and LUDIC_UI_DUMP= printing that screen's
# tree once, with every element's box and computed style
-var dv_on: bool = false
-var dv_frames: int = 0
-var dv_dumped: bool = false
-export function ui_dev(on: bool) -> void { dv_on = on }
-function dv_before() -> void {
- if not dv_on { return }
- dv_frames += 1
- if dv_frames % 60 == 0 { ui_reload() }
+export function ui_dev(ui_st: mut UiState, on: bool) -> void { ui_st.dv_on = on }
+function dv_before(ui_st: mut UiState) -> void {
+ if not ui_st.dv_on { return }
+ ui_st.dv_frames += 1
+ if ui_st.dv_frames % 60 == 0 { ui_reload(ui_st) }
}
-function dv_after(screen: string, root: UiNode) -> void {
- if not dv_on { return }
- if not dv_dumped and Os.has_env("LUDIC_UI_DUMP") and Os.env("LUDIC_UI_DUMP") == screen {
- dv_dumped = true
+function dv_after(ui_st: mut UiState, screen: string, root: UiNode) -> void {
+ if not ui_st.dv_on { return }
+ if not ui_st.dv_dumped and Os.has_env("LUDIC_UI_DUMP") and Os.env("LUDIC_UI_DUMP") == screen {
+ ui_st.dv_dumped = true
print(ui_dump(root, 0))
}
- if len(ui_errs) == 0 or ui_be == null or ui_be.rect == null { return }
- let n = Math.min(len(ui_errs), 8)
- let line = ui_px(26.0)
- ui_be.rect(0.0, 0.0, 4000.0, line * float(n) + ui_px(8.0), 4194304, 0.85)
- for i in 0 .. n { ui_be.text(ui_px(12.0), ui_px(6.0) + line * float(i), ui_errs[len(ui_errs) - n + i], ui_px(18.0), 16767449, 1.0) }
+ if len(ui_st.ui_errs) == 0 or ui_st.ui_be == null or ui_st.ui_be.rect == null { return }
+ let n = Math.min(len(ui_st.ui_errs), 8)
+ let line = ui_px(ui_st, 26.0)
+ ui_st.ui_be.rect(0.0, 0.0, 4000.0, line * float(n) + ui_px(ui_st, 8.0), 4194304, 0.85)
+ for i in 0 .. n { ui_st.ui_be.text(ui_px(ui_st, 12.0), ui_px(ui_st, 6.0) + line * float(i), ui_st.ui_errs[len(ui_st.ui_errs) - n + i], ui_px(ui_st, 18.0), 16767449, 1.0) }
}
diff --git a/packages/ludic.ui/draw.ludic b/packages/ludic.ui/draw.ludic
index 7a6cb55b..f136efd1 100644
--- a/packages/ludic.ui/draw.ludic
+++ b/packages/ludic.ui/draw.ludic
@@ -1,84 +1,83 @@
# draw.ludic - the laid-out nodes through the renderer: the background (rounded by border-radius),
# the border, the content, the children in painting order (a scroll box's clipped to it, with its
# bar), then the outline, which is where a focus ring is drawn
-var fr_ga: float = 1.0 # the opacity of everything around this element, which it multiplies
-function fr_draw(n: UiNode) -> void {
- let outer = fr_ga
- fr_ga = outer * n.alpha
- fr_draw_in(n)
- fr_ga = outer
+function fr_draw(ui_st: mut UiState, n: UiNode) -> void {
+ let outer = ui_st.fr_ga
+ ui_st.fr_ga = outer * n.alpha
+ fr_draw_in(ui_st, n)
+ ui_st.fr_ga = outer
}
-function fr_draw_in(n: UiNode) -> void {
+function fr_draw_in(ui_st: mut UiState, n: UiNode) -> void {
if n.kind == UI_NATIVE {
n.native.draw(n)
return
}
- fr_background(n)
- fr_border(n)
- if n.kind == UI_TEXT { fr_text(n) }
- if n.kind == UI_IMAGE and ui_be.image != null { fr_image(n) }
- let clipped = n.kind == UI_SCROLL and ui_be.clip != null
- if clipped { fr_clip_push(n.x, n.y, n.cw, n.ch) }
- if n.children != null { fr_children(n) }
- if clipped { fr_clip_pop() }
- if n.kind == UI_SCROLL { sc_bar(n) }
- if n.outline_w > 0.0 { fr_outline(n) }
+ fr_background(ui_st, n)
+ fr_border(ui_st, n)
+ if n.kind == UI_TEXT { fr_text(ui_st, n) }
+ if n.kind == UI_IMAGE and ui_st.ui_be.image != null { fr_image(ui_st, n) }
+ let clipped = n.kind == UI_SCROLL and ui_st.ui_be.clip != null
+ if clipped { fr_clip_push(ui_st, n.x, n.y, n.cw, n.ch) }
+ if n.children != null { fr_children(ui_st, n) }
+ if clipped { fr_clip_pop(ui_st) }
+ if n.kind == UI_SCROLL { sc_bar(ui_st, n) }
+ if n.outline_w > 0.0 { fr_outline(ui_st, n) }
}
# the shadow, then the background: the colour (rounded), or a border-image's nine-slice - drawn as
# painted, as CSS draws one, or tinted by a background colour when there is one (so `transparent`,
# or any colour with no alpha, leaves nothing to see) - then a picture over it
-function fr_background(n: UiNode) -> void {
- if n.sh_c >= 0 { fr_fill(n, n.x + n.sh_x, n.y + n.sh_y, n.sh_c, fr_ga * n.sh_a) }
+function fr_background(ui_st: mut UiState, n: UiNode) -> void {
+ if n.sh_c >= 0 { fr_fill(ui_st, n, n.x + n.sh_x, n.y + n.sh_y, n.sh_c, ui_st.fr_ga * n.sh_a) }
if n.bg >= 0 or n.bi_src != "" {
var c = n.bg
- var a = fr_ga * n.bg_a
+ var a = ui_st.fr_ga * n.bg_a
if c < 0 {
c = 16777215
- a = fr_ga
+ a = ui_st.fr_ga
}
- if a > 0.0 { fr_fill(n, n.x, n.y, c, a) }
+ if a > 0.0 { fr_fill(ui_st, n, n.x, n.y, c, a) }
}
- if n.ex != null and n.ex.grad != null { gr_draw(n.ex.grad, n.x, n.y, n.cw, n.ch, fr_ga) }
- if n.bg_img != "" and ui_be.image != null { ui_be.image(n.bg_img, n.x, n.y, n.cw, n.ch, 16777215, fr_ga) }
+ if n.ex != null and n.ex.grad != null { gr_draw(ui_st, n.ex.grad, n.x, n.y, n.cw, n.ch, ui_st.fr_ga) }
+ if n.bg_img != "" and ui_st.ui_be.image != null { ui_st.ui_be.image(n.bg_img, n.x, n.y, n.cw, n.ch, 16777215, ui_st.fr_ga) }
}
-function fr_fill(n: UiNode, x: float, y: float, c: int, a: float) -> void {
- if n.bi_src != "" and ui_be.nine != null {
- ui_be.nine(n.bi_src, n.bi_slice, x, y, n.cw, n.ch, n.bi_width, c, a)
+function fr_fill(ui_st: UiState, n: UiNode, x: float, y: float, c: int, a: float) -> void {
+ if n.bi_src != "" and ui_st.ui_be.nine != null {
+ ui_st.ui_be.nine(n.bi_src, n.bi_slice, x, y, n.cw, n.ch, n.bi_width, c, a)
return
}
- fr_box(x, y, n.cw, n.ch, n.radius, c, a)
+ fr_box(ui_st, x, y, n.cw, n.ch, n.radius, c, a)
}
-function fr_box(x: float, y: float, w: float, h: float, r: float, c: int, a: float) -> void {
- if r > 0.0 and ui_be.round != null { ui_be.round(x, y, w, h, r, c, a) } else if ui_be.rect != null { ui_be.rect(x, y, w, h, c, a) }
+function fr_box(ui_st: UiState, x: float, y: float, w: float, h: float, r: float, c: int, a: float) -> void {
+ if r > 0.0 and ui_st.ui_be.round != null { ui_st.ui_be.round(x, y, w, h, r, c, a) } else if ui_st.ui_be.rect != null { ui_st.ui_be.rect(x, y, w, h, c, a) }
}
-function fr_border(n: UiNode) -> void {
- if n.bt + n.br + n.bb + n.bl <= 0.0 or ui_be.rect == null { return }
+function fr_border(ui_st: UiState, n: UiNode) -> void {
+ if n.bt + n.br + n.bb + n.bl <= 0.0 or ui_st.ui_be.rect == null { return }
var c = n.border_c
- var ca = fr_ga * n.border_a
+ var ca = ui_st.fr_ga * n.border_a
if c < 0 {
c = n.rfg
- ca = fr_ga * n.rfg_a
+ ca = ui_st.fr_ga * n.rfg_a
}
- if n.bt > 0.0 { ui_be.rect(n.x, n.y, n.cw, n.bt, c, ca) }
- if n.bb > 0.0 { ui_be.rect(n.x, n.y + n.ch - n.bb, n.cw, n.bb, c, ca) }
- if n.bl > 0.0 { ui_be.rect(n.x, n.y + n.bt, n.bl, n.ch - n.bt - n.bb, c, ca) }
- if n.br > 0.0 { ui_be.rect(n.x + n.cw - n.br, n.y + n.bt, n.br, n.ch - n.bt - n.bb, c, ca) }
+ if n.bt > 0.0 { ui_st.ui_be.rect(n.x, n.y, n.cw, n.bt, c, ca) }
+ if n.bb > 0.0 { ui_st.ui_be.rect(n.x, n.y + n.ch - n.bb, n.cw, n.bb, c, ca) }
+ if n.bl > 0.0 { ui_st.ui_be.rect(n.x, n.y + n.bt, n.bl, n.ch - n.bt - n.bb, c, ca) }
+ if n.br > 0.0 { ui_st.ui_be.rect(n.x + n.cw - n.br, n.y + n.bt, n.br, n.ch - n.bt - n.bb, c, ca) }
}
-function fr_frame(x: float, y: float, w: float, h: float, b: float, c: int, a: float) -> void {
- ui_be.rect(x, y, w, b, c, a)
- ui_be.rect(x, y + h - b, w, b, c, a)
- ui_be.rect(x, y + b, b, h - b * 2.0, c, a)
- ui_be.rect(x + w - b, y + b, b, h - b * 2.0, c, a)
+function fr_frame(ui_st: UiState, x: float, y: float, w: float, h: float, b: float, c: int, a: float) -> void {
+ ui_st.ui_be.rect(x, y, w, b, c, a)
+ ui_st.ui_be.rect(x, y + h - b, w, b, c, a)
+ ui_st.ui_be.rect(x, y + b, b, h - b * 2.0, c, a)
+ ui_st.ui_be.rect(x + w - b, y + b, b, h - b * 2.0, c, a)
}
# an outline follows the box's corners, as CSS's does: rounded by border-radius, grown by its offset
-function fr_outline(n: UiNode) -> void {
+function fr_outline(ui_st: UiState, n: UiNode) -> void {
var c = n.outline_c
if c < 0 { c = n.rfg }
let o = n.outline_off + n.outline_w
- let a = fr_ga * n.outline_a
- if n.radius > 0.0 and ui_be.ring != null {
- ui_be.ring(n.x - o, n.y - o, n.cw + o * 2.0, n.ch + o * 2.0, n.radius + o, n.outline_w, c, a)
+ let a = ui_st.fr_ga * n.outline_a
+ if n.radius > 0.0 and ui_st.ui_be.ring != null {
+ ui_st.ui_be.ring(n.x - o, n.y - o, n.cw + o * 2.0, n.ch + o * 2.0, n.radius + o, n.outline_w, c, a)
return
}
- if ui_be.rect != null { fr_frame(n.x - o, n.y - o, n.cw + o * 2.0, n.ch + o * 2.0, n.outline_w, c, a) }
+ if ui_st.ui_be.rect != null { fr_frame(ui_st, n.x - o, n.y - o, n.cw + o * 2.0, n.ch + o * 2.0, n.outline_w, c, a) }
}
diff --git a/packages/ludic.ui/draw_content.ludic b/packages/ludic.ui/draw_content.ludic
index a0defe3e..83130d83 100644
--- a/packages/ludic.ui/draw_content.ludic
+++ b/packages/ludic.ui/draw_content.ludic
@@ -1,23 +1,23 @@
# draw_content.ludic - what is inside a box drawn: a text's lines, placed by text-align, each over its
# text-shadow, and a picture
# a text's lines (one, or the wrapped ones), each placed by text-align, the last cut by an ellipsis
-function fr_text(n: UiNode) -> void {
+function fr_text(ui_st: mut UiState, n: UiNode) -> void {
let lh = ly_line_h(n)
let y = n.y + n.pt + n.bt + (lh - n.rsize * 1.25) / 2.0
- let a = n.rfg_a * fr_ga
+ let a = n.rfg_a * ui_st.fr_ga
let sh = fr_text_shadow(n)
if n.lines == null {
- var s = tf_fit(n, n.text)
- if n.ellipsis { s = ly_ellipsis(s, tf_size, n.cw - ly_hin(n)) }
- fr_line(n, sh, fr_line_x(n, s, tf_size), y, s, a, tf_size)
+ var s = tf_fit(ui_st, n, n.text)
+ if n.ellipsis { s = ly_ellipsis(ui_st, s, ui_st.tf_size, n.cw - ly_hin(n)) }
+ fr_line(ui_st, n, sh, fr_line_x(ui_st, n, s, ui_st.tf_size), y, s, a, ui_st.tf_size)
return
}
- for i in 0 .. len(n.lines) { fr_line(n, sh, fr_line_x(n, n.lines[i], n.rsize), y + float(i) * lh, n.lines[i], a, n.rsize) }
+ for i in 0 .. len(n.lines) { fr_line(ui_st, n, sh, fr_line_x(ui_st, n, n.lines[i], n.rsize), y + float(i) * lh, n.lines[i], a, n.rsize) }
}
# one line, over its shadow when it has one
-function fr_line(n: UiNode, sh: UiNodeX, x: float, y: float, s: string, a: float, size: float) -> void {
- if sh != null { ui_be.text(x + sh.tsh_x, y + sh.tsh_y, s, size, sh.tsh_c, sh.tsh_a * fr_ga) }
- ui_be.text(x, y, s, size, n.rfg, a)
+function fr_line(ui_st: UiState, n: UiNode, sh: UiNodeX, x: float, y: float, s: string, a: float, size: float) -> void {
+ if sh != null { ui_st.ui_be.text(x + sh.tsh_x, y + sh.tsh_y, s, size, sh.tsh_c, sh.tsh_a * ui_st.fr_ga) }
+ ui_st.ui_be.text(x, y, s, size, n.rfg, a)
}
# text-shadow is inherited: the nearest element that says it, or null (none)
function fr_text_shadow(n: UiNode) -> UiNodeX {
@@ -32,35 +32,35 @@ function fr_text_shadow(n: UiNode) -> UiNodeX {
return null
}
# the opacity a native's draw is at: every group opacity around it, its own included
-export function ui_opacity() -> float { return fr_ga }
+export function ui_opacity(ui_st: UiState) -> float { return ui_st.fr_ga }
# where a line starts: text-align within the room the box gives it
-function fr_line_x(n: UiNode, s: string, size: float) -> float {
+function fr_line_x(ui_st: UiState, n: UiNode, s: string, size: float) -> float {
let x = n.x + n.pl + n.bl
if n.talign == UI_START { return x }
- let spare = n.cw - ly_hin(n) - ly_text_w(s, size)
+ let spare = n.cw - ly_hin(n) - ly_text_w(ui_st, s, size)
if n.talign == UI_CENTER { return x + spare / 2.0 }
return x + spare
}
# an
: a picture file drawn as it is, as a browser draws one (unless the img sets its own
# color); a cell of an atlas ("icon:tent") is a glyph, and takes the color around it as text does
-function fr_image(n: UiNode) -> void {
+function fr_image(ui_st: mut UiState, n: UiNode) -> void {
var c = 16777215
- var a = fr_ga
+ var a = ui_st.fr_ga
if n.fg != UI_INHERIT or fr_is_cell(n.src) {
c = n.rfg
- a = fr_ga * n.rfg_a
+ a = ui_st.fr_ga * n.rfg_a
}
var nat_w = 0.0
var nat_h = 0.0
- if ui_be.image_w != null and ui_be.image_h != null {
- nat_w = ui_be.image_w(n.src)
- nat_h = ui_be.image_h(n.src)
+ if ui_st.ui_be.image_w != null and ui_st.ui_be.image_h != null {
+ nat_w = ui_st.ui_be.image_w(n.src)
+ nat_h = ui_st.ui_be.image_h(n.src)
}
let r = ui_object_fit(n, nat_w, nat_h)
- let clipped = ui_be.clip != null and (r[2] > n.cw - ly_hin(n) or r[3] > n.ch - ly_vin(n))
- if clipped { fr_clip_push(n.x + n.pl + n.bl, n.y + n.pt + n.bt, n.cw - ly_hin(n), n.ch - ly_vin(n)) }
- ui_be.image(n.src, r[0], r[1], r[2], r[3], c, a)
- if clipped { fr_clip_pop() }
+ let clipped = ui_st.ui_be.clip != null and (r[2] > n.cw - ly_hin(n) or r[3] > n.ch - ly_vin(n))
+ if clipped { fr_clip_push(ui_st, n.x + n.pl + n.bl, n.y + n.pt + n.bt, n.cw - ly_hin(n), n.ch - ly_vin(n)) }
+ ui_st.ui_be.image(n.src, r[0], r[1], r[2], r[3], c, a)
+ if clipped { fr_clip_pop(ui_st) }
}
# prefix:name, and not a path (C:/... is a drive, not an atlas)
function fr_is_cell(src: string) -> bool {
diff --git a/packages/ludic.ui/draw_gradient.ludic b/packages/ludic.ui/draw_gradient.ludic
index d98fde69..3edd7163 100644
--- a/packages/ludic.ui/draw_gradient.ludic
+++ b/packages/ludic.ui/draw_gradient.ludic
@@ -8,7 +8,7 @@ export property UiGradient {
at: []float = null # each stop's place, 0 to 1
}
function gr_is(s: string) -> bool { return len(s) > 16 and s[0..16] == "linear-gradient(" }
-function gr_parse(s0: string) -> UiGradient {
+function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient {
let s = tpl_words(s0)
let parts = el_commas(s[16..len(s) - 1])
let g = new UiGradient
@@ -26,8 +26,8 @@ function gr_parse(s0: string) -> UiGradient {
at = el_num(last) / 100.0
col = tpl_words(col[0..len(col) - len(last)])
}
- push(g.cols, ui_color(col))
- push(g.alphas, cl_alpha)
+ push(g.cols, ui_color(ui_st, col))
+ push(g.alphas, ui_st.cl_alpha)
push(g.at, at)
}
for i in 0 .. len(g.at) {
@@ -46,8 +46,8 @@ function gr_dir(g: UiGradient, w: string) -> bool {
return true
}
# the gradient over the box, a band every two pixels at most
-function gr_draw(g: UiGradient, x: float, y: float, w: float, h: float, a: float) -> void {
- if ui_be.rect == null or len(g.cols) == 0 { return }
+function gr_draw(ui_st: mut UiState, g: UiGradient, x: float, y: float, w: float, h: float, a: float) -> void {
+ if ui_st.ui_be.rect == null or len(g.cols) == 0 { return }
let across = g.dir >= 2
var span = h
if across { span = w }
@@ -57,20 +57,19 @@ function gr_draw(g: UiGradient, x: float, y: float, w: float, h: float, a: float
let e1 = Math.floor(span * float(i + 1) / float(bands) + 0.5)
var t = (e0 + e1) / 2.0 / span
if g.dir == 1 or g.dir == 3 { t = 1.0 - t }
- let c = gr_at(g, t)
- if across { ui_be.rect(x + e0, y, e1 - e0, h, c, gr_a * a) } else { ui_be.rect(x, y + e0, w, e1 - e0, c, gr_a * a) }
+ let c = gr_at(ui_st, g, t)
+ if across { ui_st.ui_be.rect(x + e0, y, e1 - e0, h, c, ui_st.gr_a * a) } else { ui_st.ui_be.rect(x, y + e0, w, e1 - e0, c, ui_st.gr_a * a) }
}
}
-var gr_a: float = 1.0
# the colour at `t` between the stops around it, its alpha in gr_a
-function gr_at(g: UiGradient, t: float) -> int {
+function gr_at(ui_st: mut UiState, g: UiGradient, t: float) -> int {
var k = 0
while k + 1 < len(g.at) and g.at[k + 1] < t { k += 1 }
let j = Math.min(k + 1, len(g.at) - 1)
var f = 0.0
if g.at[j] > g.at[k] { f = Math.clamp((t - g.at[k]) / (g.at[j] - g.at[k]), 0.0, 1.0) }
if t <= g.at[0] { f = 0.0 }
- gr_a = g.alphas[k] + (g.alphas[j] - g.alphas[k]) * f
+ ui_st.gr_a = g.alphas[k] + (g.alphas[j] - g.alphas[k]) * f
return cl_mix(g.cols[k], g.cols[j], f)
}
function cl_mix(a: int, b: int, f: float) -> int {
diff --git a/packages/ludic.ui/draw_order.ludic b/packages/ludic.ui/draw_order.ludic
index 15a4d7b5..eab01678 100644
--- a/packages/ludic.ui/draw_order.ludic
+++ b/packages/ludic.ui/draw_order.ludic
@@ -1,35 +1,34 @@
# draw_order.ludic - painting order, and clipping that nests
-var fr_clips: []float = new []float
-function fr_clip_push(x: float, y: float, w: float, h: float) -> void {
+function fr_clip_push(ui_st: mut UiState, x: float, y: float, w: float, h: float) -> void {
var x0 = x
var y0 = y
var x1 = x + w
var y1 = y + h
- let n = len(fr_clips)
+ let n = len(ui_st.fr_clips)
if n >= 4 {
- x0 = Math.max(x0, fr_clips[n - 4])
- y0 = Math.max(y0, fr_clips[n - 3])
- x1 = Math.min(x1, fr_clips[n - 2])
- y1 = Math.min(y1, fr_clips[n - 1])
+ x0 = Math.max(x0, ui_st.fr_clips[n - 4])
+ y0 = Math.max(y0, ui_st.fr_clips[n - 3])
+ x1 = Math.min(x1, ui_st.fr_clips[n - 2])
+ y1 = Math.min(y1, ui_st.fr_clips[n - 1])
}
- push(fr_clips, x0)
- push(fr_clips, y0)
- push(fr_clips, x1)
- push(fr_clips, y1)
- ui_be.clip(x0, y0, Math.max(x1 - x0, 0.0), Math.max(y1 - y0, 0.0))
+ push(ui_st.fr_clips, x0)
+ push(ui_st.fr_clips, y0)
+ push(ui_st.fr_clips, x1)
+ push(ui_st.fr_clips, y1)
+ ui_st.ui_be.clip(x0, y0, Math.max(x1 - x0, 0.0), Math.max(y1 - y0, 0.0))
}
-function fr_clip_pop() -> void {
+function fr_clip_pop(ui_st: mut UiState) -> void {
var keep = new []float
- for i in 0 .. len(fr_clips) - 4 { push(keep, fr_clips[i]) }
- fr_clips = keep
- let n = len(fr_clips)
- if n >= 4 { ui_be.clip(fr_clips[n - 4], fr_clips[n - 3], fr_clips[n - 2] - fr_clips[n - 4], fr_clips[n - 1] - fr_clips[n - 3]) } else if ui_be.unclip != null { ui_be.unclip() }
+ for i in 0 .. len(ui_st.fr_clips) - 4 { push(keep, ui_st.fr_clips[i]) }
+ ui_st.fr_clips = keep
+ let n = len(ui_st.fr_clips)
+ if n >= 4 { ui_st.ui_be.clip(ui_st.fr_clips[n - 4], ui_st.fr_clips[n - 3], ui_st.fr_clips[n - 2] - ui_st.fr_clips[n - 4], ui_st.fr_clips[n - 1] - ui_st.fr_clips[n - 3]) } else if ui_st.ui_be.unclip != null { ui_st.ui_be.unclip() }
}
# the children in painting order: those in the flow first, then positioned ones by z-index
-function fr_children(n: UiNode) -> void {
+function fr_children(ui_st: mut UiState, n: UiNode) -> void {
var positioned = 0
for i in 0 .. len(n.children) {
- if n.children[i].popover { push(pop_list, n.children[i]) } else if n.children[i].pos == 0 { fr_draw(n.children[i]) } else { positioned += 1 }
+ if n.children[i].popover { push(ui_st.pop_list, n.children[i]) } else if n.children[i].pos == 0 { fr_draw(ui_st, n.children[i]) } else { positioned += 1 }
}
if positioned == 0 { return }
var lo = 1000000
@@ -42,7 +41,7 @@ function fr_children(n: UiNode) -> void {
}
for z in lo .. hi + 1 {
for i in 0 .. len(n.children) {
- if n.children[i].pos != 0 and not n.children[i].popover and n.children[i].z == z { fr_draw(n.children[i]) }
+ if n.children[i].pos != 0 and not n.children[i].popover and n.children[i].z == z { fr_draw(ui_st, n.children[i]) }
}
}
}
diff --git a/packages/ludic.ui/elem.ludic b/packages/ludic.ui/elem.ludic
index e11b1094..1a7d4e76 100644
--- a/packages/ludic.ui/elem.ludic
+++ b/packages/ludic.ui/elem.ludic
@@ -2,8 +2,8 @@
# and attributes (kept for selectors, applied after every rule), its text, and its children
const EL_BOXES: string = " col div progress meter section header footer nav main article aside ul ol li form box "
const EL_TEXTS: string = " text span p label h1 h2 h3 h4 h5 h6 strong em small b i a option "
-function bd_elem(k: UiTpl, e: UiEnv, key: string) -> UiNode {
- ui_at = k.where
+function bd_elem(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string) -> UiNode {
+ ui_st.ui_at = k.where
let n = new UiNode
n.key = key
n.tag = k.tag
@@ -15,34 +15,34 @@ function bd_elem(k: UiTpl, e: UiEnv, key: string) -> UiNode {
n.akeys = new []string
n.avals = new []string
n.vals = new []Val
- el_kind(n, k.tag)
- for i in 0 .. len(k.keys) { el_keep(n, k.keys[i], ui_eval(k.vals[i], e)) }
- n.hovered = fr_was_hovered(key)
+ el_kind(ui_st, n, k.tag)
+ for i in 0 .. len(k.keys) { el_keep(n, k.keys[i], ui_eval(ui_st, k.vals[i], e)) }
+ n.hovered = fr_was_hovered(ui_st, key)
n.on_names = k.on_keys
n.on_acts = k.on_acts
for i in 0 .. len(k.on_keys) {
if k.on_keys[i] == "press" { n.act = k.on_acts[i] }
}
- lc_note(key, k.on_keys, k.on_acts, e)
- let was_no = tr_no
+ lc_note(ui_st, key, k.on_keys, k.on_acts, e)
+ let was_no = ui_st.tr_no
let say = ui_attr_text(n, "translate")
- if say == "no" { tr_no = true } else if say == "yes" { tr_no = false }
- bd_kids(k.kids, e, key, n.children)
- if k.text != null { el_text(n, ui_tr(ui_eval_text(k.text, e))) }
+ if say == "no" { ui_st.tr_no = true } else if say == "yes" { ui_st.tr_no = false }
+ bd_kids(ui_st, k.kids, e, key, n.children)
+ if k.text != null { el_text(n, ui_tr(ui_st, ui_eval_text(ui_st, k.text, e))) }
el_inline(n)
- if k.tag == "input" or k.tag == "select" or k.tag == "progress" or k.tag == "meter" { ct_build(n) }
- tr_no = was_no
+ if k.tag == "input" or k.tag == "select" or k.tag == "progress" or k.tag == "meter" { ct_build(ui_st, n) }
+ ui_st.tr_no = was_no
return n
}
-function el_kind(n: UiNode, tag: string) -> void {
- let nv = nt_find(tag)
+function el_kind(ui_st: mut UiState, n: UiNode, tag: string) -> void {
+ let nv = nt_find(ui_st, tag)
if nv != null {
n.kind = UI_NATIVE
n.native = nv
} else if lk_in(EL_TEXTS, tag) { n.kind = UI_TEXT } else if tag == "row" { n.dir = UI_ROW } else if tag == "button" {
n.dir = UI_ROW
n.focusable = true
- } else if tag == "input" or tag == "select" { n.focusable = true } else if tag == "spacer" { n.kind = UI_SPACER } else if tag == "scroll" { n.kind = UI_SCROLL } else if tag == "img" { n.kind = UI_IMAGE } else if tag == "hr" { n.kind = UI_RULE } else if not lk_in(EL_BOXES, tag) { ui_err(`<{tag}>: there is no such element`) }
+ } else if tag == "input" or tag == "select" { n.focusable = true } else if tag == "spacer" { n.kind = UI_SPACER } else if tag == "scroll" { n.kind = UI_SCROLL } else if tag == "img" { n.kind = UI_IMAGE } else if tag == "hr" { n.kind = UI_RULE } else if not lk_in(EL_BOXES, tag) { ui_err(ui_st, `<{tag}>: there is no such element`) }
}
# a box with words in it holds them as a text of its own, as words
does
function el_text(n: UiNode, s: string) -> void {
diff --git a/packages/ludic.ui/env.ludic b/packages/ludic.ui/env.ludic
index ea35bee9..79214a21 100644
--- a/packages/ludic.ui/env.ludic
+++ b/packages/ludic.ui/env.ludic
@@ -15,8 +15,6 @@ export property UiEnv {
ctx: UiEnv = null # what a above gave, seen through components too
inst: UiInst = null # the Ludic component whose template this is
}
-var ui_view: UiView = null
-var ui_errs: []string = new []string
function env_new(parent: UiEnv) -> UiEnv {
let e = new UiEnv
@@ -33,7 +31,7 @@ function env_bind(e: UiEnv, name: string, v: Val) -> void {
push(e.names, name)
push(e.vals, v)
}
-function env_get(e: UiEnv, name: string) -> Val {
+function env_get(ui_st: mut UiState, e: UiEnv, name: string) -> Val {
var at = e
while at != null {
var i = len(at.names) - 1
@@ -52,11 +50,11 @@ function env_get(e: UiEnv, name: string) -> Val {
cx = cx.parent
}
if e.model != null and Value.has(e.model, name) != 0 { return Value.get(e.model, name) }
- ui_err(`no {name} here: not a loop's, a prop, a state or in the view`)
+ ui_err(ui_st, `no {name} here: not a loop's, a prop, a state or in the view`)
return Value.null()
}
# `set name = value`: the nearest component's state of that name, or that holds it
-function env_set(e: UiEnv, name: string, v: Val) -> void {
+function env_set(ui_st: mut UiState, e: UiEnv, name: string, v: Val) -> void {
var at = e
while at != null {
if at.inst != null {
@@ -70,18 +68,17 @@ function env_set(e: UiEnv, name: string, v: Val) -> void {
}
at = at.parent
}
- ui_err(`set {name}: no state here holds it - declare it with `)
+ ui_err(ui_st, `set {name}: no state here holds it - declare it with `)
}
# an error is said once, however many frames make it, and names the template line being built
-var ui_at: string = ""
# said by the runtime, or by a host about something a template asked for
-export function ui_err(msg0: string) -> void {
+export function ui_err(ui_st: mut UiState, msg0: string) -> void {
var msg = msg0
- if ui_at != "" and not ui_has(msg, ".xml:") { msg = `{ui_at}: {msg}` }
- for i in 0 .. len(ui_errs) {
- if ui_errs[i] == msg { return }
+ if ui_st.ui_at != "" and not ui_has(msg, ".xml:") { msg = `{ui_st.ui_at}: {msg}` }
+ for i in 0 .. len(ui_st.ui_errs) {
+ if ui_st.ui_errs[i] == msg { return }
}
- push(ui_errs, msg)
+ push(ui_st.ui_errs, msg)
print(`ui: {msg}`)
}
function ui_has(s: string, part: string) -> bool {
@@ -92,4 +89,4 @@ function ui_has(s: string, part: string) -> bool {
return false
}
# every error said so far - for a host to show them where a developer will see them
-export function ui_errors() -> []string { return ui_errs }
+export function ui_errors(ui_st: UiState) -> []string { return ui_st.ui_errs }
diff --git a/packages/ludic.ui/eval.ludic b/packages/ludic.ui/eval.ludic
index 2c757675..9b88cf47 100644
--- a/packages/ludic.ui/eval.ludic
+++ b/packages/ludic.ui/eval.ludic
@@ -1,41 +1,41 @@
# eval.ludic - an expression's value, here
-function ui_eval(x: UiExpr, e: UiEnv) -> Val {
+function ui_eval(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
if x.op == X_LIT { return x.v }
- if x.op == X_NAME { return env_get(e, x.s) }
+ if x.op == X_NAME { return env_get(ui_st, e, x.s) }
if x.op == X_MEMBER {
- let o = ui_eval(x.a, e)
+ let o = ui_eval(ui_st, x.a, e)
if Value.kind(o) == 6 and Value.has(o, x.s) != 0 { return Value.get(o, x.s) }
if Value.kind(o) == 5 and x.s == "length" { return Value.int(Value.count(o)) }
- ui_err(`.{x.s}: there is no such field`)
+ ui_err(ui_st, `.{x.s}: there is no such field`)
return Value.null()
}
- if x.op == X_INDEX { return Value.at(ui_eval(x.a, e), Value.as_int(ui_eval(x.b, e))) }
- if x.op == X_CALL { return ev_call(x, e) }
- if x.op == X_NOT { return vl_bool(not vl_truthy(ui_eval(x.a, e))) }
- if x.op == X_NEG { return vl_arith("-", Value.int(0), ui_eval(x.a, e)) }
+ if x.op == X_INDEX { return Value.at(ui_eval(ui_st, x.a, e), Value.as_int(ui_eval(ui_st, x.b, e))) }
+ if x.op == X_CALL { return ev_call(ui_st, x, e) }
+ if x.op == X_NOT { return vl_bool(not vl_truthy(ui_eval(ui_st, x.a, e))) }
+ if x.op == X_NEG { return vl_arith("-", Value.int(0), ui_eval(ui_st, x.a, e)) }
if x.op == X_AND {
- if not vl_truthy(ui_eval(x.a, e)) { return vl_bool(false) }
- return vl_bool(vl_truthy(ui_eval(x.b, e)))
+ if not vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(false) }
+ return vl_bool(vl_truthy(ui_eval(ui_st, x.b, e)))
}
if x.op == X_OR {
- if vl_truthy(ui_eval(x.a, e)) { return vl_bool(true) }
- return vl_bool(vl_truthy(ui_eval(x.b, e)))
+ if vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(true) }
+ return vl_bool(vl_truthy(ui_eval(ui_st, x.b, e)))
}
if x.op == X_COND {
- if vl_truthy(ui_eval(x.a, e)) { return ui_eval(x.b, e) }
- return ui_eval(x.c, e)
+ if vl_truthy(ui_eval(ui_st, x.a, e)) { return ui_eval(ui_st, x.b, e) }
+ return ui_eval(ui_st, x.c, e)
}
if x.op == X_TEXT {
var out = ""
- for i in 0 .. len(x.args) { out = out + vl_text(ui_eval(x.args[i], e)) }
+ for i in 0 .. len(x.args) { out = out + vl_text(ui_eval(ui_st, x.args[i], e)) }
return Value.str(out)
}
- return vl_binary(x.s, ui_eval(x.a, e), ui_eval(x.b, e))
+ return vl_binary(x.s, ui_eval(ui_st, x.a, e), ui_eval(ui_st, x.b, e))
}
# a call: len() and range() are the template's own, anything else is the view's
-function ev_call(x: UiExpr, e: UiEnv) -> Val {
+function ev_call(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
let args = Value.list()
- for i in 0 .. len(x.args) { Value.add(args, ui_eval(x.args[i], e)) }
+ for i in 0 .. len(x.args) { Value.add(args, ui_eval(ui_st, x.args[i], e)) }
if x.s == "len" and len(x.args) == 1 { return Value.int(Value.count(Value.at(args, 0))) }
if x.s == "range" and len(x.args) == 1 {
let out = Value.list()
@@ -50,11 +50,11 @@ function ev_call(x: UiExpr, e: UiEnv) -> Val {
}
at = at.parent
}
- if ui_view == null or ui_view.call == null {
- ui_err(`{x.s}(): there is no function or event of that name here`)
+ if ui_st.ui_view == null or ui_st.ui_view.call == null {
+ ui_err(ui_st, `{x.s}(): there is no function or event of that name here`)
return Value.null()
}
- return ui_view.call(x.s, args)
+ return ui_st.ui_view.call(x.s, args)
}
-function ui_eval_text(x: UiExpr, e: UiEnv) -> string { return vl_text(ui_eval(x, e)) }
-function ui_eval_bool(x: UiExpr, e: UiEnv) -> bool { return vl_truthy(ui_eval(x, e)) }
+function ui_eval_text(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> string { return vl_text(ui_eval(ui_st, x, e)) }
+function ui_eval_bool(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> bool { return vl_truthy(ui_eval(ui_st, x, e)) }
diff --git a/packages/ludic.ui/focus.ludic b/packages/ludic.ui/focus.ludic
index 31d06728..c73e6c25 100644
--- a/packages/ludic.ui/focus.ludic
+++ b/packages/ludic.ui/focus.ludic
@@ -1,75 +1,72 @@
# focus.ludic - the keyboard's place: focus moves through the controls in document order (Tab and
# Shift+Tab, or the arrows), Enter and Space activate what has it, and a pointer press focuses what
# it presses; a key field that is listening has every key to itself. A key that is gone from the screen leaves nothing focused.
-var fc_key: string = ""
-var fc_visible: bool = false # focus came from the keyboard: :focus-visible, and the ring
-var fc_list: []UiNode = new []UiNode
-function fc_collect(n: UiNode) -> void {
- if n.focusable and n.enabled { push(fc_list, n) }
- for i in 0 .. len(n.children) { fc_collect(n.children[i]) }
+function fc_collect(ui_st: mut UiState, n: UiNode) -> void {
+ if n.focusable and n.enabled { push(ui_st.fc_list, n) }
+ for i in 0 .. len(n.children) { fc_collect(ui_st, n.children[i]) }
}
-function fc_find() -> int {
- for i in 0 .. len(fc_list) {
- if fc_list[i].key == fc_key { return i }
+function fc_find(ui_st: UiState) -> int {
+ for i in 0 .. len(ui_st.fc_list) {
+ if ui_st.fc_list[i].key == ui_st.fc_key { return i }
}
return -1
}
-function fc_move(step: int) -> void {
- if len(fc_list) == 0 { return }
- var at = fc_find()
+function fc_move(ui_st: mut UiState, step: int) -> void {
+ if len(ui_st.fc_list) == 0 { return }
+ var at = fc_find(ui_st)
if at < 0 {
at = 0
- if step < 0 { at = len(fc_list) - 1 }
- } else { at = (at + step + len(fc_list)) % len(fc_list) }
- fc_key = fc_list[at].key
- fc_visible = true
+ if step < 0 { at = len(ui_st.fc_list) - 1 }
+ } else { at = (at + step + len(ui_st.fc_list)) % len(ui_st.fc_list) }
+ ui_st.fc_key = ui_st.fc_list[at].key
+ ui_st.fc_visible = true
}
# the keys, after the tree is laid out: moving, then acting on what has the focus
-function fc_keys(root: UiNode) -> void {
- fc_list = new []UiNode
+function fc_keys(ui_st: mut UiState, root: UiNode) -> void {
+ ui_st.fc_list = new []UiNode
let top = pp_top(root)
- if top != null { fc_collect(top) } else { fc_collect(root) }
- if ct_key_capture(fc_list) { return }
- if top != null and in_now.escape { ui_fire(top, "close", Value.null()) }
- let i = in_now
- if fc_find() < 0 { fc_autofocus() }
- let at = fc_find()
+ if top != null { fc_collect(ui_st, top) } else { fc_collect(ui_st, root) }
+ if ct_key_capture(ui_st, ui_st.fc_list) { return }
+ if top != null and ui_st.in_now.escape { ui_fire(ui_st, top, "close", Value.null()) }
+ let i = ui_st.in_now
+ if fc_find(ui_st) < 0 { fc_autofocus(ui_st) }
+ let at = fc_find(ui_st)
var n: UiNode = null
- if at >= 0 { n = fc_list[at] }
- if i.tab and i.shift { fc_move(-1) } else if i.tab { fc_move(1) }
+ if at >= 0 { n = ui_st.fc_list[at] }
+ if i.tab and i.shift { fc_move(ui_st, -1) } else if i.tab { fc_move(ui_st, 1) }
let horizontal = n != null and ct_takes_arrows(n)
- if i.up or (i.left and not horizontal) { fc_move(-1) }
- if i.down_key or (i.right and not horizontal) { fc_move(1) }
- ct_num_blur(root)
+ if i.up or (i.left and not horizontal) { fc_move(ui_st, -1) }
+ if i.down_key or (i.right and not horizontal) { fc_move(ui_st, 1) }
+ ct_num_blur(ui_st, root)
if n == null { return }
- fc_hold(root, n)
- if i.enter or (i.space and not ct_takes_space(n)) { ct_activate(n) }
- if horizontal and i.left { ct_step(n, -1) }
- if horizontal and i.right { ct_step(n, 1) }
- ct_typed(n)
+ fc_hold(ui_st, root, n)
+ if i.enter or (i.space and not ct_takes_space(n)) { ct_activate(ui_st, n) }
+ if horizontal and i.left { ct_step(ui_st, n, -1) }
+ if horizontal and i.right { ct_step(ui_st, n, 1) }
+ ct_typed(ui_st, n)
}
# Enter or A held on a control: its on-down, and on-up when let go (or the focus moves away)
-function fc_hold(root: UiNode, n: UiNode) -> void {
- if fr_key_press != "" and (in_act_up or n.key != fr_key_press) {
- let was = fr_node(root, fr_key_press)
- if was != null { ui_fire(was, "up", Value.null()) }
- fr_key_press = ""
+function fc_hold(ui_st: mut UiState, root: UiNode, n: UiNode) -> void {
+ if ui_st.fr_key_press != "" and (ui_st.in_act_up or n.key != ui_st.fr_key_press) {
+ let was = fr_node(root, ui_st.fr_key_press)
+ if was != null { ui_fire(ui_st, was, "up", Value.null()) }
+ ui_st.fr_key_press = ""
}
- if in_act_down and n.enabled {
- fr_key_press = n.key
- ui_fire(n, "down", Value.null())
+ if ui_st.in_act_down and n.enabled {
+ ui_st.fr_key_press = n.key
+ ui_fire(ui_st, n, "down", Value.null())
}
}
-export function ui_focus(key: string) -> void {
- fc_key = key
- fc_visible = false
+export function ui_focus(ui_st: mut UiState, key: string) -> void {
+ ui_st.fc_key = key
+ ui_st.fc_visible = false
}
# nothing focused (a screen just opened): the control that says autofocus, as HTML's does
-function fc_autofocus() -> void {
- for i in 0 .. len(fc_list) {
- if ui_attr_on(fc_list[i], "autofocus") {
- fc_key = fc_list[i].key
- fc_visible = false
+function fc_autofocus(ui_st: mut UiState) -> void {
+ for i in 0 .. len(ui_st.fc_list) {
+ if ui_attr_on(ui_st.fc_list[i], "autofocus") {
+ ui_st.fc_key = ui_st.fc_list[i].key
+ ui_st.fc_visible = false
return
}
}
diff --git a/packages/ludic.ui/frame.ludic b/packages/ludic.ui/frame.ludic
index bfe61f2f..8e9c6ecd 100644
--- a/packages/ludic.ui/frame.ludic
+++ b/packages/ludic.ui/frame.ludic
@@ -1,46 +1,46 @@
# frame.ludic - a screen shown: built from the view's model, laid out, drawn, and its presses
# run once the frame is drawn, so nothing the view does can change a frame half way through it
-export function ui_show(screen: string, view: UiView, x: float, y: float, w: float, h: float) -> void {
- in_read()
- dv_before()
- an_frame += 1
- ui_viewport(w, h)
- let root = ui_nodes(screen, view)
+export function ui_show(ui_st: mut UiState, screen: string, view: UiView, x: float, y: float, w: float, h: float) -> void {
+ in_read(ui_st)
+ dv_before(ui_st)
+ ui_st.an_frame += 1
+ ui_viewport(ui_st, w, h)
+ let root = ui_nodes(ui_st, screen, view)
if root == null { return }
- ui_place(root, x, y, w, h)
- nt_fired_clear()
+ ui_place(ui_st, root, x, y, w, h)
+ nt_fired_clear(ui_st)
sc_undo(root)
- sc_update(root)
- fr_pointer(root)
- fc_keys(root)
- hd_tick(root)
- tt_track(root, in_now.x, in_now.y)
- fb_keep(screen, root)
- if ui_be != null and ui_be.text != null {
- fr_draw(root)
- pp_draw_top()
- tt_draw(root)
+ sc_update(ui_st, root)
+ fr_pointer(ui_st, root)
+ fc_keys(ui_st, root)
+ hd_tick(ui_st, root)
+ tt_track(ui_st, root, ui_st.in_now.x, ui_st.in_now.y)
+ fb_keep(ui_st, screen, root)
+ if ui_st.ui_be != null and ui_st.ui_be.text != null {
+ fr_draw(ui_st, root)
+ pp_draw_top(ui_st)
+ tt_draw(ui_st, root)
}
- dv_after(screen, root)
- lc_run(screen)
- nt_fired_run()
+ dv_after(ui_st, screen, root)
+ lc_run(ui_st, screen)
+ nt_fired_run(ui_st)
}
# the screen's nodes for this frame, not yet laid out - for a test, or a host that places them
-export function ui_nodes(screen: string, view: UiView) -> UiNode {
- let si = lib_find(lib_screen_names, screen)
- let cls = uc_find(screen)
- if si < 0 and cls != null { return fr_class_nodes(cls) }
+export function ui_nodes(ui_st: mut UiState, screen: string, view: UiView) -> UiNode {
+ let si = lib_find(ui_st.lib_screen_names, screen)
+ let cls = uc_find(ui_st, screen)
+ if si < 0 and cls != null { return fr_class_nodes(ui_st, cls) }
if si < 0 {
- ui_err(`there is no screen {screen} - ui_load the file that has it`)
+ ui_err(ui_st, `there is no screen {screen} - ui_load the file that has it`)
return null
}
- let t = lib_screens[si]
- lc_begin()
- ui_view = view
+ let t = ui_st.lib_screens[si]
+ lc_begin(ui_st)
+ ui_st.ui_view = view
let e = env_new(null)
if view != null and view.model != null { e.model = view.model() } else { e.model = Value.object() }
- e.state = st_for(screen, t, e)
+ e.state = st_for(ui_st, screen, t, e)
let root = new UiNode
root.key = screen
root.children = new []UiNode
@@ -52,32 +52,32 @@ export function ui_nodes(screen: string, view: UiView) -> UiNode {
root.vals = new []Val
root.tag = "screen"
for i in 0 .. len(t.keys) {
- if t.keys[i] != "name" { el_keep(root, t.keys[i], ui_eval(t.vals[i], e)) }
+ if t.keys[i] != "name" { el_keep(root, t.keys[i], ui_eval(ui_st, t.vals[i], e)) }
}
- bd_kids(t.kids, e, screen, root.children)
- cs_tree(root)
+ bd_kids(ui_st, t.kids, e, screen, root.children)
+ cs_tree(ui_st, root)
return root
}
# the room vw, vh and @media measure against; ui_show sets it to the screen's rectangle
-export function ui_viewport(w: float, h: float) -> void {
- ly_vp_w = w
- ly_vp_h = h
+export function ui_viewport(ui_st: mut UiState, w: float, h: float) -> void {
+ ui_st.ly_vp_w = w
+ ui_st.ly_vp_h = h
}
-export function ui_place(root: UiNode, x: float, y: float, w: float, h: float) -> void {
- ly_vp_x = x
- ly_vp_y = y
+export function ui_place(ui_st: mut UiState, root: UiNode, x: float, y: float, w: float, h: float) -> void {
+ ui_st.ly_vp_x = x
+ ui_st.ly_vp_y = y
root.w = UI_FILL
root.h = UI_FILL
let fg = 15658734
- ly_fg_a = 1.0
- ly_measure(root, w, ui_px(16.0), fg)
- ly_place(root, x, y, w, h)
- sc_apply(root)
- pp_anchor_all(root)
+ ui_st.ly_fg_a = 1.0
+ ly_measure(ui_st, root, w, ui_px(ui_st, 16.0), fg)
+ ly_place(ui_st, root, x, y, w, h)
+ sc_apply(ui_st, root)
+ pp_anchor_all(ui_st, root)
}
# a press, as if the pointer had made it
-export function ui_press(n: UiNode) -> void {
- if n.act != null and n.enabled { act_run(n.act, n.env) }
+export function ui_press(ui_st: mut UiState, n: UiNode) -> void {
+ if n.act != null and n.enabled { act_run(ui_st, n.act, n.env) }
}
# any event, as if the element had fired it - for a test
-export function ui_event(n: UiNode, name: string, value: Val) -> void { nt_run(n, name, value) }
+export function ui_event(ui_st: mut UiState, n: UiNode, name: string, value: Val) -> void { nt_run(ui_st, n, name, value) }
diff --git a/packages/ludic.ui/frame_box.ludic b/packages/ludic.ui/frame_box.ludic
index 2b67eb92..d44682f1 100644
--- a/packages/ludic.ui/frame_box.ludic
+++ b/packages/ludic.ui/frame_box.ludic
@@ -6,22 +6,21 @@ export property UiBox {
w: float = 0.0
h: float = 0.0
}
-var fb_roots: []UiNode = new []UiNode
# the screen pixels per design pixel: the renderer's scale (render3d's includes ui_render3d_scale)
-export function ui_scale() -> float { return ui_px(1.0) }
-function fb_keep(screen: string, root: UiNode) -> void {
- for i in 0 .. len(fb_roots) {
- if fb_roots[i].key == screen {
- fb_roots[i] = root
+export function ui_scale(ui_st: UiState) -> float { return ui_px(ui_st, 1.0) }
+function fb_keep(ui_st: mut UiState, screen: string, root: UiNode) -> void {
+ for i in 0 .. len(ui_st.fb_roots) {
+ if ui_st.fb_roots[i].key == screen {
+ ui_st.fb_roots[i] = root
return
}
}
- push(fb_roots, root)
+ push(ui_st.fb_roots, root)
}
# the box of the element with that id (or key) on any screen shown, in screen pixels; null if none
-export function ui_box(id: string) -> UiBox {
- for i in 0 .. len(fb_roots) {
- let n = fb_find(fb_roots[i], id)
+export function ui_box(ui_st: UiState, id: string) -> UiBox {
+ for i in 0 .. len(ui_st.fb_roots) {
+ let n = fb_find(ui_st.fb_roots[i], id)
if n != null {
let b = new UiBox
b.x = n.x
diff --git a/packages/ludic.ui/hold.ludic b/packages/ludic.ui/hold.ludic
index e1601db3..cf2dad52 100644
--- a/packages/ludic.ui/hold.ludic
+++ b/packages/ludic.ui/hold.ludic
@@ -1,28 +1,25 @@
# hold.ludic - on-hold: every frame a control is held down - by the pointer (wherever it goes until it
# is let go), or by Enter or the pad's A on the focus - with event.dt, the seconds since the last
# frame, and event.t, how long it has been held, on the ui clock
-var hd_key: string = ""
-var hd_t0: float = 0.0
-var hd_last: float = 0.0
-function hd_tick(root: UiNode) -> void {
- var key = fr_key_press
- if fr_press_key != "" and in_now.down { key = fr_press_key }
+function hd_tick(ui_st: mut UiState, root: UiNode) -> void {
+ var key = ui_st.fr_key_press
+ if ui_st.fr_press_key != "" and ui_st.in_now.down { key = ui_st.fr_press_key }
if key == "" {
- hd_key = ""
+ ui_st.hd_key = ""
return
}
- let now = an_now()
- if key != hd_key {
- hd_key = key
- hd_t0 = now
- hd_last = now
+ let now = an_now(ui_st)
+ if key != ui_st.hd_key {
+ ui_st.hd_key = key
+ ui_st.hd_t0 = now
+ ui_st.hd_last = now
}
let n = fr_node(root, key)
if n != null {
let v = Value.object()
- Value.put(v, "dt", Value.float(now - hd_last))
- Value.put(v, "t", Value.float(now - hd_t0))
- ui_fire(n, "hold", v)
+ Value.put(v, "dt", Value.float(now - ui_st.hd_last))
+ Value.put(v, "t", Value.float(now - ui_st.hd_t0))
+ ui_fire(ui_st, n, "hold", v)
}
- hd_last = now
+ ui_st.hd_last = now
}
diff --git a/packages/ludic.ui/input.ludic b/packages/ludic.ui/input.ludic
index 5a10ed83..7aef8c9f 100644
--- a/packages/ludic.ui/input.ludic
+++ b/packages/ludic.ui/input.ludic
@@ -28,19 +28,15 @@ export property UiInput {
down_right: bool = false # the other mouse buttons held
down_middle: bool = false
}
-var in_now: UiInput = new UiInput
-var in_was_down: bool = false
-var in_last: UiInput = new UiInput
-var in_given: bool = false
-function in_read() -> void {
- if in_given {
- in_given = false
+function in_read(ui_st: mut UiState) -> void {
+ if ui_st.in_given {
+ ui_st.in_given = false
return
}
- in_was_down = in_now.down
- in_last = in_now
+ ui_st.in_was_down = ui_st.in_now.down
+ ui_st.in_last = ui_st.in_now
let i = new UiInput
- if ui_be != null and ui_be.input {
+ if ui_st.ui_be != null and ui_st.ui_be.input {
i.x = float(Input.mouse_x())
i.y = float(Input.mouse_y())
i.down = Input.mouse_down(0)
@@ -61,8 +57,8 @@ function in_read() -> void {
i.key = in_any_key()
in_held(i)
}
- in_repeat(i)
- in_now = i
+ in_repeat(ui_st, i)
+ ui_st.in_now = i
}
function in_any_key() -> int {
for k in 8 .. 167 {
@@ -71,10 +67,10 @@ function in_any_key() -> int {
return 0
}
# this frame's input, given by a test or a host that reads its own
-export function ui_input(i: UiInput) -> void {
- in_was_down = in_now.down
- in_last = in_now
- in_repeat(i)
- in_now = i
- in_given = true
+export function ui_input(ui_st: mut UiState, i: UiInput) -> void {
+ ui_st.in_was_down = ui_st.in_now.down
+ ui_st.in_last = ui_st.in_now
+ in_repeat(ui_st, i)
+ ui_st.in_now = i
+ ui_st.in_given = true
}
diff --git a/packages/ludic.ui/input_pad.ludic b/packages/ludic.ui/input_pad.ludic
index faeca0eb..e88b4a56 100644
--- a/packages/ludic.ui/input_pad.ludic
+++ b/packages/ludic.ui/input_pad.ludic
@@ -4,12 +4,6 @@
const RP_FIRST: float = 0.42
const RP_EVERY: float = 0.11
const PAD_STICK: float = 0.5
-var rp_next: []float = [-1.0, -1.0, -1.0, -1.0]
-var rp_a: bool = false
-var rp_b: bool = false
-var rp_act: bool = false
-var in_act_down: bool = false # A or Enter went down this frame, or came up
-var in_act_up: bool = false
# what is held this frame: the arrow keys, the d-pad (SDL's order: 11 up, 12 down, 13 left, 14
# right) and the stick past half way along its larger axis
function in_held(i: UiInput) -> void {
@@ -30,32 +24,32 @@ function in_held(i: UiInput) -> void {
i.pad_b = Input.pad_button(0, 1)
}
# the held directions and buttons as this frame's presses
-function in_repeat(i: UiInput) -> void {
- let now = an_now()
- if rp_fire(0, i.held_up, now) { i.up = true }
- if rp_fire(1, i.held_down, now) { i.down_key = true }
- if rp_fire(2, i.held_left, now) { i.left = true }
- if rp_fire(3, i.held_right, now) { i.right = true }
- if i.pad_a and not rp_a { i.enter = true }
- if i.pad_b and not rp_b { i.escape = true }
- rp_a = i.pad_a
- rp_b = i.pad_b
+function in_repeat(ui_st: mut UiState, i: UiInput) -> void {
+ let now = an_now(ui_st)
+ if rp_fire(ui_st, 0, i.held_up, now) { i.up = true }
+ if rp_fire(ui_st, 1, i.held_down, now) { i.down_key = true }
+ if rp_fire(ui_st, 2, i.held_left, now) { i.left = true }
+ if rp_fire(ui_st, 3, i.held_right, now) { i.right = true }
+ if i.pad_a and not ui_st.rp_a { i.enter = true }
+ if i.pad_b and not ui_st.rp_b { i.escape = true }
+ ui_st.rp_a = i.pad_a
+ ui_st.rp_b = i.pad_b
let act = i.pad_a or i.held_enter
- in_act_down = act and not rp_act
- in_act_up = rp_act and not act
- rp_act = act
+ ui_st.in_act_down = act and not ui_st.rp_act
+ ui_st.in_act_up = ui_st.rp_act and not act
+ ui_st.rp_act = act
}
-function rp_fire(k: int, held: bool, now: float) -> bool {
+function rp_fire(ui_st: mut UiState, k: int, held: bool, now: float) -> bool {
if not held {
- rp_next[k] = -1.0
+ ui_st.rp_next[k] = -1.0
return false
}
- if rp_next[k] < 0.0 {
- rp_next[k] = now + RP_FIRST
+ if ui_st.rp_next[k] < 0.0 {
+ ui_st.rp_next[k] = now + RP_FIRST
return true
}
- if now + 0.000001 < rp_next[k] { return false }
- rp_next[k] = rp_next[k] + RP_EVERY
- if rp_next[k] <= now { rp_next[k] = now + RP_EVERY }
+ if now + 0.000001 < ui_st.rp_next[k] { return false }
+ ui_st.rp_next[k] = ui_st.rp_next[k] + RP_EVERY
+ if ui_st.rp_next[k] <= now { ui_st.rp_next[k] = now + RP_EVERY }
return true
}
diff --git a/packages/ludic.ui/instance.ludic b/packages/ludic.ui/instance.ludic
index dc9b7acf..57bbec41 100644
--- a/packages/ludic.ui/instance.ludic
+++ b/packages/ludic.ui/instance.ludic
@@ -6,13 +6,11 @@ property UiInst {
ptr: pointer = null
seen: int = 0
}
-var in_list: []UiInst = new []UiInst
-var in_build: int = 0
-function in_get(key: string, k: UiClass) -> UiInst {
- for i in 0 .. len(in_list) {
- let it = in_list[i]
+function in_get(ui_st: mut UiState, key: string, k: UiClass) -> UiInst {
+ for i in 0 .. len(ui_st.in_list) {
+ let it = ui_st.in_list[i]
if it.key == key and it.cls.name == k.name {
- it.seen = in_build
+ it.seen = ui_st.in_build
return it
}
}
@@ -20,16 +18,16 @@ function in_get(key: string, k: UiClass) -> UiInst {
it.key = key
it.cls = k
it.ptr = k.make()
- it.seen = in_build
- push(in_list, it)
+ it.seen = ui_st.in_build
+ push(ui_st.in_list, it)
return it
}
# after a build of `screen`: its instances the build did not reach are unmounted
-function in_sweep(screen: string) -> void {
+function in_sweep(ui_st: mut UiState, screen: string) -> void {
let keep = new []UiInst
- for i in 0 .. len(in_list) {
- let it = in_list[i]
- if it.seen == in_build or not lc_mine(it.key, screen) { push(keep, it) }
+ for i in 0 .. len(ui_st.in_list) {
+ let it = ui_st.in_list[i]
+ if it.seen == ui_st.in_build or not lc_mine(it.key, screen) { push(keep, it) }
}
- in_list = keep
+ ui_st.in_list = keep
}
diff --git a/packages/ludic.ui/layout.ludic b/packages/ludic.ui/layout.ludic
index 395fe941..e5a9f5ee 100644
--- a/packages/ludic.ui/layout.ludic
+++ b/packages/ludic.ui/layout.ludic
@@ -1,19 +1,19 @@
# layout.ludic - flex layout on the CSS box model, top-down: a box is told how wide it may be,
# measures its content at that width (so a wrapping row knows how tall it is), then places each
# child. A size is the border box: padding and border inside it, margin outside it.
-function ly_measure(n: UiNode, avail: float, size: float, fg: int) -> void {
+function ly_measure(ui_st: mut UiState, n: UiNode, avail: float, size: float, fg: int) -> void {
n.rsize = size * zm_own(n)
if n.size > 0.0 { n.rsize = n.size }
n.rfg = fg
- n.rfg_a = ly_fg_a
+ n.rfg_a = ui_st.ly_fg_a
if n.fg != UI_INHERIT {
n.rfg = n.fg
n.rfg_a = n.fg_a
}
- ly_fg_a = n.rfg_a
+ ui_st.ly_fg_a = n.rfg_a
n.mw = ly_hin(n)
n.mh = ly_vin(n)
- if n.kind == UI_TEXT { ly_measure_text(n, avail) }
+ if n.kind == UI_TEXT { ly_measure_text(ui_st, n, avail) }
if n.kind == UI_NATIVE and n.native.measure != null {
let base_w = n.mw
let base_h = n.mh
@@ -33,7 +33,7 @@ function ly_measure(n: UiNode, avail: float, size: float, fg: int) -> void {
var inner = avail - n.ml - n.mr
if fixed_w >= 0.0 { inner = fixed_w }
inner = ly_clamp(inner, n.min_w, n.max_w) - ly_hin(n)
- if n.dir == UI_ROW and n.wrap { ly_measure_wrap(n, inner) } else { ly_measure_box(n, inner) }
+ if n.dir == UI_ROW and n.wrap { ly_measure_wrap(ui_st, n, inner) } else { ly_measure_box(ui_st, n, inner) }
}
n.content_h = n.mh
if fixed_w >= 0.0 { n.mw = fixed_w }
@@ -43,7 +43,6 @@ function ly_measure(n: UiNode, avail: float, size: float, fg: int) -> void {
n.mh = ly_clamp(n.mh, n.min_h, n.max_h)
ly_min_note(n)
}
-var ly_fg_a: float = 1.0
function ly_hin(n: UiNode) -> float { return n.pl + n.pr + n.bl + n.br }
function ly_vin(n: UiNode) -> float { return n.pt + n.pb + n.bt + n.bb }
function ly_clamp(v: float, lo: float, hi: float) -> float {
diff --git a/packages/ludic.ui/layout_box.ludic b/packages/ludic.ui/layout_box.ludic
index de68db28..687022ca 100644
--- a/packages/ludic.ui/layout_box.ludic
+++ b/packages/ludic.ui/layout_box.ludic
@@ -1,11 +1,11 @@
# layout_box.ludic - a box's content measured: its children in a line along its direction (a
# row's text wrapped into the room its siblings leave), or a wrapping row's lines
-function ly_measure_box(n: UiNode, inner: float) -> void {
+function ly_measure_box(ui_st: mut UiState, n: UiNode, inner: float) -> void {
for i in 0 .. len(n.children) {
- ly_fg_a = n.rfg_a
- ly_measure(n.children[i], inner, n.rsize, n.rfg)
+ ui_st.ly_fg_a = n.rfg_a
+ ly_measure(ui_st, n.children[i], inner, n.rsize, n.rfg)
}
- if n.dir == UI_ROW and inner > 0.0 { ly_row_fit(n, inner) }
+ if n.dir == UI_ROW and inner > 0.0 { ly_row_fit(ui_st, n, inner) }
var main = 0.0
var cross = 0.0
var count = 0
@@ -28,7 +28,7 @@ function ly_measure_box(n: UiNode, inner: float) -> void {
}
# a row wider than its room: its texts (and boxes of their own width) measured again in the room the
# rest leave them, each giving up a share of the excess as wide as it is, so a line wraps beside an icon
-function ly_row_fit(n: UiNode, inner: float) -> void {
+function ly_row_fit(ui_st: mut UiState, n: UiNode, inner: float) -> void {
var used = 0.0
var flex = 0.0
var count = 0
@@ -46,8 +46,8 @@ function ly_row_fit(n: UiNode, inner: float) -> void {
let c = n.children[i]
let share = ly_main(c, UI_ROW) - over * ly_main(c, UI_ROW) / flex
if ly_flexes(c) and share > 0.0 {
- ly_fg_a = n.rfg_a
- ly_measure(c, share, n.rsize, n.rfg)
+ ui_st.ly_fg_a = n.rfg_a
+ ly_measure(ui_st, c, share, n.rsize, n.rfg)
}
}
}
@@ -57,7 +57,7 @@ function ly_flexes(c: UiNode) -> bool {
return c.kind == UI_BOX and c.children != null and len(c.children) > 0
}
# a wrapping row: children in lines no wider than `inner`, the lines stacked `gap` apart
-function ly_measure_wrap(n: UiNode, inner: float) -> void {
+function ly_measure_wrap(ui_st: mut UiState, n: UiNode, inner: float) -> void {
var line_w = 0.0
var line_h = 0.0
var widest = 0.0
@@ -65,8 +65,8 @@ function ly_measure_wrap(n: UiNode, inner: float) -> void {
var count = 0
for i in 0 .. len(n.children) {
let c = n.children[i]
- ly_fg_a = n.rfg_a
- ly_measure(c, inner, n.rsize, n.rfg)
+ ui_st.ly_fg_a = n.rfg_a
+ ly_measure(ui_st, c, inner, n.rsize, n.rfg)
if c.pos >= 2 { continue }
let cw = ly_main(c, UI_ROW)
if count > 0 and line_w + n.gap + cw > inner {
diff --git a/packages/ludic.ui/layout_run.ludic b/packages/ludic.ui/layout_run.ludic
index ff12d114..7bcc2b51 100644
--- a/packages/ludic.ui/layout_run.ludic
+++ b/packages/ludic.ui/layout_run.ludic
@@ -1,8 +1,6 @@
# layout_run.ludic - a box's children given their places: along its direction by size, grow and
# justify, across it by align (or each child's own `self`), a wrapping row one line at a time
-var ly_lead: float = 0.0
-var ly_extra: float = 0.0
-function ly_place(n: UiNode, x: float, y: float, w: float, h: float) -> void {
+function ly_place(ui_st: mut UiState, n: UiNode, x: float, y: float, w: float, h: float) -> void {
n.x = x
n.y = y
n.cw = w
@@ -12,9 +10,9 @@ function ly_place(n: UiNode, x: float, y: float, w: float, h: float) -> void {
var inner_h = h - ly_vin(n)
if n.kind == UI_SCROLL { inner_h = Math.max(n.content_h, h) - ly_vin(n) }
let top = n.y + n.pt + n.bt
- if n.dir == UI_ROW and n.wrap { ly_place_wrap(n, top, inner_w) } else if n.dir == UI_ROW { ly_place_line(n, 0, len(n.children), top, inner_w, inner_h) } else { ly_place_line(n, 0, len(n.children), top, inner_h, inner_w) }
+ if n.dir == UI_ROW and n.wrap { ly_place_wrap(ui_st, n, top, inner_w) } else if n.dir == UI_ROW { ly_place_line(ui_st, n, 0, len(n.children), top, inner_w, inner_h) } else { ly_place_line(ui_st, n, 0, len(n.children), top, inner_h, inner_w) }
for i in 0 .. len(n.children) {
- if n.children[i].pos >= 2 { ly_place_abs(n, n.children[i]) }
+ if n.children[i].pos >= 2 { ly_place_abs(ui_st, n, n.children[i]) }
}
}
function ly_fills(c: UiNode, dir: int) -> bool {
@@ -39,7 +37,7 @@ function ly_stretches(n: UiNode, c: UiNode) -> bool {
return a == UI_STRETCH
}
# children from..to along one line starting at `top`: sizes, the spare room, then each place
-function ly_place_line(n: UiNode, from: int, to: int, top: float, main_room: float, cross_room: float) -> void {
+function ly_place_line(ui_st: mut UiState, n: UiNode, from: int, to: int, top: float, main_room: float, cross_room: float) -> void {
var used = 0.0
var grows = 0.0
var count = 0
@@ -55,21 +53,21 @@ function ly_place_line(n: UiNode, from: int, to: int, top: float, main_room: flo
used = used + n.gap * float(Math.max(count - 1, 0))
let free = main_room - used
if free < 0.0 { ly_shrink_line(n, from, sizes, 0.0 - free) }
- ly_justify(n, free, count, grows)
- let extra = ly_extra
- var at = ly_lead
+ ly_justify(ui_st, n, free, count, grows)
+ let extra = ui_st.ly_extra
+ var at = ui_st.ly_lead
for i in from .. to {
let c = n.children[i]
if c.pos >= 2 { continue }
var m = sizes[i - from]
if grows > 0.0 and free > 0.0 { m = m + free * ly_grow(c, n.dir) / grows }
- ly_place_child(n, c, at, top, m, cross_room)
+ ly_place_child(ui_st, n, c, at, top, m, cross_room)
if c.pos == 1 { ly_nudge(n, c) }
at = at + m + n.gap + extra
}
}
# one child: `m` along the run and its own size (or the whole room, stretched) across, less margins
-function ly_place_child(n: UiNode, c: UiNode, at: float, top: float, m: float, cross_room: float) -> void {
+function ly_place_child(ui_st: mut UiState, n: UiNode, c: UiNode, at: float, top: float, m: float, cross_room: float) -> void {
var cr = ly_cross(c, n.dir)
if n.dir == UI_ROW and c.h_pct > 0.0 { cr = ly_ph(c, cross_room) + c.mt + c.mb }
if n.dir == UI_COL and c.w_pct > 0.0 { cr = ly_pw(c, cross_room) + c.ml + c.mr }
@@ -82,9 +80,9 @@ function ly_place_child(n: UiNode, c: UiNode, at: float, top: float, m: float, c
let left = n.x + n.pl + n.bl
if n.dir == UI_ROW {
let cw = ly_clamp(m - c.ml - c.mr, c.min_w, c.max_w)
- ly_place(c, left + at + c.ml, top + off + c.mt, cw, ly_clamp(cr - c.mt - c.mb, c.min_h, c.max_h))
+ ly_place(ui_st, c, left + at + c.ml, top + off + c.mt, cw, ly_clamp(cr - c.mt - c.mb, c.min_h, c.max_h))
} else {
let chh = ly_clamp(m - c.mt - c.mb, c.min_h, c.max_h)
- ly_place(c, left + off + c.ml, top + at + c.mt, ly_clamp(cr - c.ml - c.mr, c.min_w, c.max_w), chh)
+ ly_place(ui_st, c, left + off + c.ml, top + at + c.mt, ly_clamp(cr - c.ml - c.mr, c.min_w, c.max_w), chh)
}
}
diff --git a/packages/ludic.ui/layout_text.ludic b/packages/ludic.ui/layout_text.ludic
index a44f913d..f69f2552 100644
--- a/packages/ludic.ui/layout_text.ludic
+++ b/packages/ludic.ui/layout_text.ludic
@@ -1,9 +1,9 @@
# layout_text.ludic - a text's size: one line, or as many as it takes to fit its room (white-space
# normal), breaking between words; nowrap keeps one line, and text-overflow: ellipsis cuts it
-function ly_measure_text(n: UiNode, avail: float) -> void {
+function ly_measure_text(ui_st: UiState, n: UiNode, avail: float) -> void {
n.lines = null
let lh = ly_line_h(n)
- var one = ly_text_w(n.text, n.rsize)
+ var one = ly_text_w(ui_st, n.text, n.rsize)
var room = avail - n.ml - n.mr - ly_hin(n)
if n.w >= 0.0 { room = n.w - ly_hin(n) }
let fits = n.ex != null and n.ex.fit_min > 0.0
@@ -13,13 +13,13 @@ function ly_measure_text(n: UiNode, avail: float) -> void {
n.mh = n.mh + lh
return
}
- n.lines = ly_wrap(n.text, n.rsize, room)
+ n.lines = ly_wrap(ui_st, n.text, n.rsize, room)
var widest = 0.0
- for i in 0 .. len(n.lines) { widest = Math.max(widest, ly_text_w(n.lines[i], n.rsize)) }
+ for i in 0 .. len(n.lines) { widest = Math.max(widest, ly_text_w(ui_st, n.lines[i], n.rsize)) }
n.mw = n.mw + widest
n.mh = n.mh + lh * float(len(n.lines))
}
-function ly_wrap(s: string, size: float, room: float) -> []string {
+function ly_wrap(ui_st: UiState, s: string, size: float, room: float) -> []string {
let words = lss_split(s, 32)
let out = new []string
var line = ""
@@ -27,7 +27,7 @@ function ly_wrap(s: string, size: float, room: float) -> []string {
if len(words[i]) == 0 { continue }
var next = words[i]
if len(line) > 0 { next = line + " " + words[i] }
- if len(line) > 0 and ly_text_w(next, size) > room {
+ if len(line) > 0 and ly_text_w(ui_st, next, size) > room {
push(out, line)
line = words[i]
} else { line = next }
@@ -36,14 +36,14 @@ function ly_wrap(s: string, size: float, room: float) -> []string {
return out
}
# a line cut to `room` with an ellipsis, when it does not fit
-function ly_ellipsis(s: string, size: float, room: float) -> string {
- if ly_text_w(s, size) <= room { return s }
+function ly_ellipsis(ui_st: UiState, s: string, size: float, room: float) -> string {
+ if ly_text_w(ui_st, s, size) <= room { return s }
var n = len(s)
- while n > 0 and ly_text_w(s[0..n] + "...", size) > room { n -= 1 }
+ while n > 0 and ly_text_w(ui_st, s[0..n] + "...", size) > room { n -= 1 }
return s[0..n] + "..."
}
-function ly_text_w(s: string, size: float) -> float {
- if ui_be != null and ui_be.measure != null { return ui_be.measure(s, size) }
+function ly_text_w(ui_st: UiState, s: string, size: float) -> float {
+ if ui_st.ui_be != null and ui_st.ui_be.measure != null { return ui_st.ui_be.measure(s, size) }
return float(len(s)) * size * 0.5
}
# a child's size along and across a direction, margins included
diff --git a/packages/ludic.ui/layout_wrap.ludic b/packages/ludic.ui/layout_wrap.ludic
index 39430496..fa086a1b 100644
--- a/packages/ludic.ui/layout_wrap.ludic
+++ b/packages/ludic.ui/layout_wrap.ludic
@@ -1,5 +1,5 @@
# layout_wrap.ludic - a wrapping row, line by line: each line is as tall as its tallest child
-function ly_place_wrap(n: UiNode, top0: float, inner_w: float) -> void {
+function ly_place_wrap(ui_st: mut UiState, n: UiNode, top0: float, inner_w: float) -> void {
var top = top0
var from = 0
while from < len(n.children) {
@@ -16,23 +16,23 @@ function ly_place_wrap(n: UiNode, top0: float, inner_w: float) -> void {
line_h = Math.max(line_h, ly_cross(n.children[to], UI_ROW))
to += 1
}
- ly_place_line(n, from, to, top, inner_w, line_h)
+ ly_place_line(ui_st, n, from, to, top, inner_w, line_h)
top = top + line_h + n.gap
from = to
}
}
# position: absolute (in the parent's padding box) or fixed (in the screen): the insets say where,
# and two opposite ones say how big; relative is placed as usual and moved by its insets
-function ly_place_abs(n: UiNode, c: UiNode) -> void {
+function ly_place_abs(ui_st: mut UiState, n: UiNode, c: UiNode) -> void {
var bx = n.x + n.bl
var by = n.y + n.bt
var bw = n.cw - n.bl - n.br
var bh = n.ch - n.bt - n.bb
if c.pos == 3 {
- bx = ly_vp_x
- by = ly_vp_y
- bw = ly_vp_w
- bh = ly_vp_h
+ bx = ui_st.ly_vp_x
+ by = ui_st.ly_vp_y
+ bw = ui_st.ly_vp_w
+ bh = ui_st.ly_vp_h
}
var w = c.mw
var h = c.mh
@@ -48,7 +48,7 @@ function ly_place_abs(n: UiNode, c: UiNode) -> void {
if l != UI_AUTO { x = bx + l + c.ml } else if r != UI_AUTO { x = bx + bw - r - c.mr - w }
var y = by + n.pt + c.mt
if t != UI_AUTO { y = by + t + c.mt } else if b != UI_AUTO { y = by + bh - b - c.mb - h }
- ly_place(c, x, y, w, h)
+ ly_place(ui_st, c, x, y, w, h)
}
function ly_or0(v: float) -> float {
if v == UI_AUTO { return 0.0 }
@@ -60,19 +60,19 @@ function ly_offset(n: UiNode, dx: float, dy: float) -> void {
for i in 0 .. len(n.children) { ly_offset(n.children[i], dx, dy) }
}
# where the first child starts and what goes between, when nothing grows into the spare room
-function ly_justify(n: UiNode, free: float, count: int, grows: float) -> void {
- ly_lead = 0.0
- ly_extra = 0.0
+function ly_justify(ui_st: mut UiState, n: UiNode, free: float, count: int, grows: float) -> void {
+ ui_st.ly_lead = 0.0
+ ui_st.ly_extra = 0.0
if grows > 0.0 or free <= 0.0 or count == 0 { return }
- if n.justify == UI_CENTER { ly_lead = free / 2.0 }
- if n.justify == UI_END { ly_lead = free }
- if n.justify == UI_BETWEEN and count > 1 { ly_extra = free / float(count - 1) }
+ if n.justify == UI_CENTER { ui_st.ly_lead = free / 2.0 }
+ if n.justify == UI_END { ui_st.ly_lead = free }
+ if n.justify == UI_BETWEEN and count > 1 { ui_st.ly_extra = free / float(count - 1) }
if n.justify == UI_AROUND {
- ly_extra = free / float(count)
- ly_lead = ly_extra / 2.0
+ ui_st.ly_extra = free / float(count)
+ ui_st.ly_lead = ui_st.ly_extra / 2.0
}
if n.justify == UI_EVENLY {
- ly_extra = free / float(count + 1)
- ly_lead = ly_extra
+ ui_st.ly_extra = free / float(count + 1)
+ ui_st.ly_lead = ui_st.ly_extra
}
}
diff --git a/packages/ludic.ui/lib.ludic b/packages/ludic.ui/lib.ludic
index 665e40c9..caf71545 100644
--- a/packages/ludic.ui/lib.ludic
+++ b/packages/ludic.ui/lib.ludic
@@ -14,51 +14,48 @@ export property UiFile {
src: string = "" # the text it was read from
disk: bool = false # read from a file (ui_reload looks again), or handed over as text
}
-var lib_files: []UiFile = new []UiFile
-var lib_screen_names: []string = new []string
-var lib_screens: []UiTpl = new []UiTpl
# the file a path names, read once
-export function ui_load(path: string) -> bool { return lib_file(path) != null }
-function lib_file(path: string) -> UiFile {
- for i in 0 .. len(lib_files) {
- if lib_files[i].path == path { return lib_files[i] }
+export function ui_load(ui_st: mut UiState, path: string) -> bool { return lib_file(ui_st, path) != null }
+function lib_file(ui_st: mut UiState, path: string) -> UiFile {
+ for i in 0 .. len(ui_st.lib_files) {
+ if ui_st.lib_files[i].path == path { return ui_st.lib_files[i] }
}
- let src = lib_text(path)
+ let src = lib_text(ui_st, path)
if len(src) == 0 {
- ui_err(`{path}: cannot read it`)
+ ui_err(ui_st, `{path}: cannot read it`)
return null
}
var f: UiFile = null
- if lss_is(path) { f = lss_file(src, path) } else { f = lib_read(src, path) }
+ if lss_is(path) { f = lss_file(ui_st, src, path) } else { f = lib_read(ui_st, src, path) }
f.disk = true
return f
}
# a file's text, as if read from `path` (its imports are found beside it)
-export function ui_load_text(src: string, path: string) -> void {
- if lss_is(path) { lss_file(src, path) } else { lib_read(src, path) }
+export function ui_load_text(ui_st: mut UiState, src: string, path: string) -> void {
+ if lss_is(path) { lss_file(ui_st, src, path) } else { lib_read(ui_st, src, path) }
}
-function lib_read(src: string, path: string) -> UiFile {
- let f = lib_new(path)
+function lib_read(ui_st: mut UiState, src: string, path: string) -> UiFile {
+ let f = lib_new(ui_st, path)
f.src = src
- tp_lines(src)
- let root = tpl_read(Xml.parse(src), path)
+ tp_lines(ui_st, src)
+ let root = tpl_read(ui_st, Xml.parse(src), path)
var tops = root.kids
if root.tag != "ui" { tops = [root] }
- for i in 0 .. len(tops) { lib_top(f, tops[i]) }
+ for i in 0 .. len(tops) { lib_top(ui_st, f, tops[i]) }
f.sheet = sheet_of(f)
- for i in 0 .. len(f.comps) { lib_link(f, f.comps[i]) }
+ for i in 0 .. len(f.comps) { lib_link(ui_st, f, f.comps[i]) }
for i in 0 .. len(tops) {
- if tops[i].tag == "screen" { lib_link(f, tops[i]) }
+ if tops[i].tag == "screen" { lib_link(ui_st, f, tops[i]) }
}
return f
}
-function lib_top(f: UiFile, t: UiTpl) -> void {
+function lib_top(ui_st: mut UiState, f: UiFile, t: UiTpl) -> void {
if t.tag == "style" {
- sheet_read(f, t)
+ sheet_read(ui_st, f, t)
return
}
if t.tag == "import" {
- let other = lib_file(lib_beside(f.path, bd_name(t, "src", "")))
+ let other = lib_file(ui_st, lib_beside(f.path, bd_name(t, "src", "")))
if other != null {
push(f.imp, other)
push(f.imp_as, bd_name(t, "as", ""))
@@ -67,14 +64,14 @@ function lib_top(f: UiFile, t: UiTpl) -> void {
}
let name = bd_name(t, "name", "")
if name == "" or (t.tag != "component" and t.tag != "screen") {
- ui_err(`{f.path}: <{t.tag}> - a file holds s,