wip(0.S3): packages and examples migrated in one run - ludic migrate state packages <every example program> packages/ludic.lab/example/plate.ludic
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d490d4f9f1
commit
3eda72e8c2
172 changed files with 3094 additions and 2966 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,47 +2,47 @@
|
|||
# <each in= as= index=> 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: <component name="Card"
|
||||
# sub="">), its on-* as what `emit` runs, and its content as <slot/>.
|
||||
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("<if> needs a test=\"{...}\"")
|
||||
ui_err(ui_st, "<if> 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("<each> needs an in=\"{...}\"")
|
||||
ui_err(ui_st, "<each> 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)
|
||||
}
|
||||
}
|
||||
# <slot/> is what the component's user put inside it; <slot name="x"/> their <template slot="x">
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
# <Card class="wide" style="margin: 4px" id="c"> 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
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
# build_more.ludic - <let name="{value}"/> names a value for the siblings after it, and
|
||||
# <provide name="{value}"> 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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("")) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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=<screen> 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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <img>: 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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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]) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <div>words</div> does
|
||||
function el_text(n: UiNode, s: string) -> void {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ export property UiEnv {
|
|||
ctx: UiEnv = null # what a <provide> 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 <state> 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 <state {name}="..."/>`)
|
||||
ui_err(ui_st, `set {name}: no state here holds it - declare it with <state {name}="..."/>`)
|
||||
}
|
||||
# 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 }
|
||||
|
|
|
|||
|
|
@ -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)) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <import>s, <style>s, <component name=...>s and <screen name=...>s`)
|
||||
ui_err(ui_st, `{f.path}: <{t.tag}> - a file holds <import>s, <style>s, <component name=...>s and <screen name=...>s`)
|
||||
return
|
||||
}
|
||||
if t.tag == "screen" {
|
||||
let at = lib_find(lib_screen_names, name)
|
||||
if at >= 0 { lib_screens[at] = t } else {
|
||||
push(lib_screen_names, name)
|
||||
push(lib_screens, t)
|
||||
let at = lib_find(ui_st.lib_screen_names, name)
|
||||
if at >= 0 { ui_st.lib_screens[at] = t } else {
|
||||
push(ui_st.lib_screen_names, name)
|
||||
push(ui_st.lib_screens, t)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ function lib_find(names: []string, name: string) -> int {
|
|||
}
|
||||
return -1
|
||||
}
|
||||
function lib_new(path: string) -> UiFile {
|
||||
function lib_new(ui_st: mut UiState, path: string) -> UiFile {
|
||||
let f = lib_blank(path)
|
||||
push(lib_files, f)
|
||||
push(ui_st.lib_files, f)
|
||||
return f
|
||||
}
|
||||
# a file that is not one of the loaded ones (a component's own)
|
||||
|
|
@ -23,9 +23,9 @@ function lib_blank(path: string) -> UiFile {
|
|||
f.own = new []UiRule
|
||||
return f
|
||||
}
|
||||
function lib_text(path: string) -> string {
|
||||
function lib_text(ui_st: UiState, path: string) -> string {
|
||||
var src = ""
|
||||
if ui_be != null and ui_be.read != null { src = ui_be.read(path) } else { src = Fs.read_text(path) }
|
||||
if ui_st.ui_be != null and ui_st.ui_be.read != null { src = ui_st.ui_be.read(path) } else { src = Fs.read_text(path) }
|
||||
if src == null { return "" }
|
||||
return src
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
# life.ludic - on-mount and on-unmount: an element or component that appears in a frame it was not
|
||||
# in last frame is mounted, and one that is gone is unmounted, each once, after the frame is drawn
|
||||
var lc_keys: []string = new []string
|
||||
var lc_mounts: []UiAct = new []UiAct
|
||||
var lc_unmounts: []UiAct = new []UiAct
|
||||
var lc_envs: []UiEnv = new []UiEnv
|
||||
var lc_prev_keys: []string = new []string
|
||||
var lc_prev_unmounts: []UiAct = new []UiAct
|
||||
var lc_prev_envs: []UiEnv = new []UiEnv
|
||||
# a build starts its own records: a screen built twice keeps only the last build's
|
||||
function lc_begin() -> void {
|
||||
lc_keys = new []string
|
||||
lc_mounts = new []UiAct
|
||||
lc_unmounts = new []UiAct
|
||||
lc_envs = new []UiEnv
|
||||
function lc_begin(ui_st: mut UiState) -> void {
|
||||
ui_st.lc_keys = new []string
|
||||
ui_st.lc_mounts = new []UiAct
|
||||
ui_st.lc_unmounts = new []UiAct
|
||||
ui_st.lc_envs = new []UiEnv
|
||||
}
|
||||
function lc_note(key: string, names: []string, acts: []UiAct, e: UiEnv) -> void {
|
||||
function lc_note(ui_st: mut UiState, key: string, names: []string, acts: []UiAct, e: UiEnv) -> void {
|
||||
var m: UiAct = null
|
||||
var u: UiAct = null
|
||||
for i in 0 .. len(names) {
|
||||
|
|
@ -22,43 +15,43 @@ function lc_note(key: string, names: []string, acts: []UiAct, e: UiEnv) -> void
|
|||
if names[i] == "unmount" { u = acts[i] }
|
||||
}
|
||||
if m == null and u == null { return }
|
||||
push(lc_keys, key)
|
||||
push(lc_mounts, m)
|
||||
push(lc_unmounts, u)
|
||||
push(lc_envs, e)
|
||||
push(ui_st.lc_keys, key)
|
||||
push(ui_st.lc_mounts, m)
|
||||
push(ui_st.lc_unmounts, u)
|
||||
push(ui_st.lc_envs, e)
|
||||
}
|
||||
function lc_mine(key: string, screen: string) -> bool {
|
||||
let n = len(screen)
|
||||
return key == screen or (len(key) > n and key[0..n] == screen and key[n] == 47)
|
||||
}
|
||||
# the screen's mounts and unmounts since its last frame; other screens' records are kept
|
||||
function lc_run(screen: string) -> void {
|
||||
for i in 0 .. len(lc_keys) {
|
||||
if lc_mounts[i] != null and not sel_has(lc_prev_keys, lc_keys[i]) { act_run(lc_mounts[i], lc_envs[i]) }
|
||||
function lc_run(ui_st: mut UiState, screen: string) -> void {
|
||||
for i in 0 .. len(ui_st.lc_keys) {
|
||||
if ui_st.lc_mounts[i] != null and not sel_has(ui_st.lc_prev_keys, ui_st.lc_keys[i]) { act_run(ui_st, ui_st.lc_mounts[i], ui_st.lc_envs[i]) }
|
||||
}
|
||||
let keys = new []string
|
||||
let unmounts = new []UiAct
|
||||
let envs = new []UiEnv
|
||||
for i in 0 .. len(lc_prev_keys) {
|
||||
let k = lc_prev_keys[i]
|
||||
for i in 0 .. len(ui_st.lc_prev_keys) {
|
||||
let k = ui_st.lc_prev_keys[i]
|
||||
if lc_mine(k, screen) {
|
||||
if lc_prev_unmounts[i] != null and not sel_has(lc_keys, k) { act_run(lc_prev_unmounts[i], lc_prev_envs[i]) }
|
||||
if ui_st.lc_prev_unmounts[i] != null and not sel_has(ui_st.lc_keys, k) { act_run(ui_st, ui_st.lc_prev_unmounts[i], ui_st.lc_prev_envs[i]) }
|
||||
} else {
|
||||
push(keys, k)
|
||||
push(unmounts, lc_prev_unmounts[i])
|
||||
push(envs, lc_prev_envs[i])
|
||||
push(unmounts, ui_st.lc_prev_unmounts[i])
|
||||
push(envs, ui_st.lc_prev_envs[i])
|
||||
}
|
||||
}
|
||||
for i in 0 .. len(lc_keys) {
|
||||
push(keys, lc_keys[i])
|
||||
push(unmounts, lc_unmounts[i])
|
||||
push(envs, lc_envs[i])
|
||||
for i in 0 .. len(ui_st.lc_keys) {
|
||||
push(keys, ui_st.lc_keys[i])
|
||||
push(unmounts, ui_st.lc_unmounts[i])
|
||||
push(envs, ui_st.lc_envs[i])
|
||||
}
|
||||
lc_prev_keys = keys
|
||||
lc_prev_unmounts = unmounts
|
||||
lc_prev_envs = envs
|
||||
lc_keys = new []string
|
||||
lc_mounts = new []UiAct
|
||||
lc_unmounts = new []UiAct
|
||||
lc_envs = new []UiEnv
|
||||
ui_st.lc_prev_keys = keys
|
||||
ui_st.lc_prev_unmounts = unmounts
|
||||
ui_st.lc_prev_envs = envs
|
||||
ui_st.lc_keys = new []string
|
||||
ui_st.lc_mounts = new []UiAct
|
||||
ui_st.lc_unmounts = new []UiAct
|
||||
ui_st.lc_envs = new []UiEnv
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
# link.ludic - each tag in a file that is not an element names a component: the file's own, an
|
||||
# unnamed import's exported one, or `as:Name` from the import called `as`
|
||||
const LK_OWN: string = " row button input select textarea progress meter spacer scroll img hr if else each slot state style component screen import let fragment template provide #text "
|
||||
function lib_link(f: UiFile, t: UiTpl) -> void {
|
||||
function lib_link(ui_st: mut UiState, f: UiFile, t: UiTpl) -> void {
|
||||
t.sheet = f.sheet
|
||||
if not lk_element(t.tag) {
|
||||
if not lk_element(ui_st, t.tag) {
|
||||
t.comp = lk_find(f, t.tag, false)
|
||||
if t.comp == null { t.cls = uc_find(t.tag) }
|
||||
if t.comp == null { t.cls = uc_find(ui_st, t.tag) }
|
||||
if t.cls != null {
|
||||
lk_kids(f, t)
|
||||
lk_kids(ui_st, f, t)
|
||||
return
|
||||
}
|
||||
if t.comp == null and lk_find(f, t.tag, true) != null { ui_err(`{t.where}: <{t.tag}>: that component is not exported - export="true" on it lets other files use it`) } else if t.comp == null { ui_err(`{t.where}: <{t.tag}>: there is no such element, and no component of that name here or exported by an import`) }
|
||||
if t.comp == null and lk_find(f, t.tag, true) != null { ui_err(ui_st, `{t.where}: <{t.tag}>: that component is not exported - export="true" on it lets other files use it`) } else if t.comp == null { ui_err(ui_st, `{t.where}: <{t.tag}>: there is no such element, and no component of that name here or exported by an import`) }
|
||||
}
|
||||
lk_kids(f, t)
|
||||
lk_kids(ui_st, f, t)
|
||||
}
|
||||
function lk_kids(f: UiFile, t: UiTpl) -> void {
|
||||
for i in 0 .. len(t.kids) { lib_link(f, t.kids[i]) }
|
||||
function lk_kids(ui_st: mut UiState, f: UiFile, t: UiTpl) -> void {
|
||||
for i in 0 .. len(t.kids) { lib_link(ui_st, f, t.kids[i]) }
|
||||
}
|
||||
function lk_element(tag: string) -> bool { return lk_in(LK_OWN, tag) or lk_in(EL_BOXES, tag) or lk_in(EL_TEXTS, tag) or nt_find(tag) != null }
|
||||
function lk_element(ui_st: UiState, tag: string) -> bool { return lk_in(LK_OWN, tag) or lk_in(EL_BOXES, tag) or lk_in(EL_TEXTS, tag) or nt_find(ui_st, tag) != null }
|
||||
# a word in a list of words written " a b c "
|
||||
function lk_in(words: string, w: string) -> bool {
|
||||
let pat = ` {w} `
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ function md_parse(cond: string) -> UiMedia {
|
|||
}
|
||||
return m
|
||||
}
|
||||
function md_ok(r: UiRule) -> bool {
|
||||
function md_ok(ui_st: UiState, r: UiRule) -> bool {
|
||||
let m = r.md
|
||||
if m == null { return true }
|
||||
if m.min_w > 0.0 and ly_vp_w < m.min_w { return false }
|
||||
if m.max_w > 0.0 and ly_vp_w > m.max_w { return false }
|
||||
if m.min_h > 0.0 and ly_vp_h < m.min_h { return false }
|
||||
if m.max_h > 0.0 and ly_vp_h > m.max_h { return false }
|
||||
if m.min_w > 0.0 and ui_st.ly_vp_w < m.min_w { return false }
|
||||
if m.max_w > 0.0 and ui_st.ly_vp_w > m.max_w { return false }
|
||||
if m.min_h > 0.0 and ui_st.ly_vp_h < m.min_h { return false }
|
||||
if m.max_h > 0.0 and ui_st.ly_vp_h > m.max_h { return false }
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,59 +8,55 @@ export property UiNative {
|
|||
draw: fn(UiNode) -> void = null
|
||||
input: fn(UiNode, UiPointer) -> bool = null # the pointer on it (pointer_events.ludic)
|
||||
}
|
||||
var nt_list: []UiNative = new []UiNative
|
||||
export function ui_native(tag: string, measure: fn(UiNode, float) -> void, draw: fn(UiNode) -> void) -> void {
|
||||
export function ui_native(ui_st: mut UiState, tag: string, measure: fn(UiNode, float) -> void, draw: fn(UiNode) -> void) -> void {
|
||||
let nv = new UiNative
|
||||
nv.tag = tag
|
||||
nv.measure = measure
|
||||
nv.draw = draw
|
||||
for i in 0 .. len(nt_list) {
|
||||
if nt_list[i].tag == tag {
|
||||
nv.input = nt_list[i].input
|
||||
nt_list[i] = nv
|
||||
for i in 0 .. len(ui_st.nt_list) {
|
||||
if ui_st.nt_list[i].tag == tag {
|
||||
nv.input = ui_st.nt_list[i].input
|
||||
ui_st.nt_list[i] = nv
|
||||
return
|
||||
}
|
||||
}
|
||||
push(nt_list, nv)
|
||||
push(ui_st.nt_list, nv)
|
||||
}
|
||||
# the pointer on a native: its presses, moves, drags, releases and the wheel, while it is over it or
|
||||
# has it captured; answering false to a pointerdown leaves the pointer free
|
||||
export function ui_native_input(tag: string, f: fn(UiNode, UiPointer) -> bool) -> void {
|
||||
let nv = nt_find(tag)
|
||||
export function ui_native_input(ui_st: mut UiState, tag: string, f: fn(UiNode, UiPointer) -> bool) -> void {
|
||||
let nv = nt_find(ui_st, tag)
|
||||
if nv == null {
|
||||
ui_err(`ui_native_input: there is no native <{tag}> - ui_native it first`)
|
||||
ui_err(ui_st, `ui_native_input: there is no native <{tag}> - ui_native it first`)
|
||||
return
|
||||
}
|
||||
nv.input = f
|
||||
}
|
||||
function nt_find(tag: string) -> UiNative {
|
||||
for i in 0 .. len(nt_list) {
|
||||
if nt_list[i].tag == tag { return nt_list[i] }
|
||||
function nt_find(ui_st: UiState, tag: string) -> UiNative {
|
||||
for i in 0 .. len(ui_st.nt_list) {
|
||||
if ui_st.nt_list[i].tag == tag { return ui_st.nt_list[i] }
|
||||
}
|
||||
return null
|
||||
}
|
||||
var nt_q_nodes: []UiNode = new []UiNode
|
||||
var nt_q_names: []string = new []string
|
||||
var nt_q_vals: []Val = new []Val
|
||||
function nt_fired_clear() -> void {
|
||||
nt_q_nodes = new []UiNode
|
||||
nt_q_names = new []string
|
||||
nt_q_vals = new []Val
|
||||
function nt_fired_clear(ui_st: mut UiState) -> void {
|
||||
ui_st.nt_q_nodes = new []UiNode
|
||||
ui_st.nt_q_names = new []string
|
||||
ui_st.nt_q_vals = new []Val
|
||||
}
|
||||
export function ui_fire(n: UiNode, name: string, value: Val) -> void {
|
||||
push(nt_q_nodes, n)
|
||||
push(nt_q_names, name)
|
||||
push(nt_q_vals, value)
|
||||
export function ui_fire(ui_st: mut UiState, n: UiNode, name: string, value: Val) -> void {
|
||||
push(ui_st.nt_q_nodes, n)
|
||||
push(ui_st.nt_q_names, name)
|
||||
push(ui_st.nt_q_vals, value)
|
||||
}
|
||||
function nt_fired_run() -> void {
|
||||
let nodes = nt_q_nodes
|
||||
let names = nt_q_names
|
||||
let vals = nt_q_vals
|
||||
nt_fired_clear()
|
||||
for i in 0 .. len(nodes) { nt_run(nodes[i], names[i], vals[i]) }
|
||||
function nt_fired_run(ui_st: mut UiState) -> void {
|
||||
let nodes = ui_st.nt_q_nodes
|
||||
let names = ui_st.nt_q_names
|
||||
let vals = ui_st.nt_q_vals
|
||||
nt_fired_clear(ui_st)
|
||||
for i in 0 .. len(nodes) { nt_run(ui_st, nodes[i], names[i], vals[i]) }
|
||||
}
|
||||
# the element's on-<name>, with `event` bound to what happened
|
||||
function nt_run(n: UiNode, name: string, value: Val) -> void {
|
||||
function nt_run(ui_st: mut UiState, n: UiNode, name: string, value: Val) -> void {
|
||||
if n.on_names == null or not n.enabled { return }
|
||||
for i in 0 .. len(n.on_names) {
|
||||
if n.on_names[i] == name {
|
||||
|
|
@ -70,7 +66,7 @@ function nt_run(n: UiNode, name: string, value: Val) -> void {
|
|||
Value.put(ev, "value", value)
|
||||
let e = env_new(n.env)
|
||||
env_bind(e, "event", ev)
|
||||
act_run(n.on_acts[i], e)
|
||||
act_run(ui_st, n.on_acts[i], e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,43 @@
|
|||
# pointer.ludic - the pointer: what it is over is :hover (and all around it, as in CSS), a press on a
|
||||
# control focuses it and makes it :active, letting go over it is a click. A dragged thumb or scrollbar
|
||||
# keeps the pointer until let go; a press that takes a scrollbar or closes a popover presses nothing else.
|
||||
var fr_hover: []string = new []string
|
||||
var fr_press_key: string = ""
|
||||
var fr_down_now: bool = false
|
||||
var fr_key_press: string = "" # the control Enter or the pad's A is holding down
|
||||
function fr_was_hovered(key: string) -> bool { return sel_has(fr_hover, key) }
|
||||
function fr_pointer(root: UiNode) -> void {
|
||||
let i = in_now
|
||||
fr_down_now = i.down
|
||||
function fr_was_hovered(ui_st: UiState, key: string) -> bool { return sel_has(ui_st.fr_hover, key) }
|
||||
function fr_pointer(ui_st: mut UiState, root: UiNode) -> void {
|
||||
let i = ui_st.in_now
|
||||
ui_st.fr_down_now = i.down
|
||||
let keys = new []string
|
||||
let top = pp_top(root)
|
||||
var scope = root
|
||||
if top != null { scope = top }
|
||||
fr_under(scope, i.x, i.y, keys)
|
||||
fr_hover = keys
|
||||
if top != null and pp_pointer(top) { return }
|
||||
if sc_drag != "" or ct_key_mouse(root) { return }
|
||||
ui_st.fr_hover = keys
|
||||
if top != null and pp_pointer(ui_st, top) { return }
|
||||
if ui_st.sc_drag != "" or ct_key_mouse(ui_st, root) { return }
|
||||
let hit = fr_hit(scope, i.x, i.y)
|
||||
pe_frame(root, scope)
|
||||
if i.down and not in_was_down { fr_press(hit, i.x, i.y) }
|
||||
if i.down and fr_press_key != "" { ct_drag(root, fr_press_key, i.x, i.y) }
|
||||
if not i.down and in_was_down {
|
||||
let was = fr_node(root, fr_press_key)
|
||||
if was != null { ui_fire(was, "up", Value.null()) }
|
||||
if hit != null and hit.key == fr_press_key and hit.enabled { ct_activate(hit) }
|
||||
fr_press_key = ""
|
||||
pe_frame(ui_st, root, scope)
|
||||
if i.down and not ui_st.in_was_down { fr_press(ui_st, hit, i.x, i.y) }
|
||||
if i.down and ui_st.fr_press_key != "" { ct_drag(ui_st, root, ui_st.fr_press_key, i.x, i.y) }
|
||||
if not i.down and ui_st.in_was_down {
|
||||
let was = fr_node(root, ui_st.fr_press_key)
|
||||
if was != null { ui_fire(ui_st, was, "up", Value.null()) }
|
||||
if hit != null and hit.key == ui_st.fr_press_key and hit.enabled { ct_activate(ui_st, hit) }
|
||||
ui_st.fr_press_key = ""
|
||||
}
|
||||
}
|
||||
function fr_press(hit: UiNode, x: float, y: float) -> void {
|
||||
ct_key_pressed_away(hit)
|
||||
fr_press_key = ""
|
||||
function fr_press(ui_st: mut UiState, hit: UiNode, x: float, y: float) -> void {
|
||||
ct_key_pressed_away(ui_st, hit)
|
||||
ui_st.fr_press_key = ""
|
||||
if hit == null { return }
|
||||
fr_press_key = hit.key
|
||||
if hit.focusable { ui_focus(hit.key) }
|
||||
if hit.enabled { ui_fire(hit, "down", Value.null()) }
|
||||
ct_press(hit, x, y)
|
||||
ui_st.fr_press_key = hit.key
|
||||
if hit.focusable { ui_focus(ui_st, hit.key) }
|
||||
if hit.enabled { ui_fire(ui_st, hit, "down", Value.null()) }
|
||||
ct_press(ui_st, hit, x, y)
|
||||
}
|
||||
# :active - the press began on it, or inside it, and is still held (by the pointer, Enter or A)
|
||||
function fr_active(n: UiNode) -> bool {
|
||||
if fr_down_now and fr_press_key != "" and lc_mine(fr_press_key, n.key) { return true }
|
||||
if pe_key != "" and lc_mine(pe_key, n.key) { return true }
|
||||
return fr_key_press != "" and lc_mine(fr_key_press, n.key)
|
||||
function fr_active(ui_st: UiState, n: UiNode) -> bool {
|
||||
if ui_st.fr_down_now and ui_st.fr_press_key != "" and lc_mine(ui_st.fr_press_key, n.key) { return true }
|
||||
if ui_st.pe_key != "" and lc_mine(ui_st.pe_key, n.key) { return true }
|
||||
return ui_st.fr_key_press != "" and lc_mine(ui_st.fr_key_press, n.key)
|
||||
}
|
||||
function fr_under(n: UiNode, px: float, py: float, keys: []string) -> void {
|
||||
if px < n.x or py < n.y or px >= n.x + n.cw or py >= n.y + n.ch { return }
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ export property UiPointer {
|
|||
wheel: float = 0.0
|
||||
}
|
||||
const PE_NAMES: string = " pointerdown pointermove pointerup wheel drag "
|
||||
var pe_key: string = "" # the element the pointer is captured by
|
||||
var pe_btn: int = 0
|
||||
function pe_wants(n: UiNode, wheel: bool) -> bool {
|
||||
if n.kind == UI_NATIVE and n.native != null and n.native.input != null { return true }
|
||||
if n.on_names == null { return false }
|
||||
|
|
@ -23,25 +21,25 @@ function pe_wants(n: UiNode, wheel: bool) -> bool {
|
|||
return false
|
||||
}
|
||||
# the pointer's events this frame: to what captured it, else to what it is over
|
||||
function pe_frame(root: UiNode, scope: UiNode) -> void {
|
||||
let i = in_now
|
||||
let p = in_last
|
||||
function pe_frame(ui_st: mut UiState, root: UiNode, scope: UiNode) -> void {
|
||||
let i = ui_st.in_now
|
||||
let p = ui_st.in_last
|
||||
let moved = p.x >= 0.0 and (i.x != p.x or i.y != p.y)
|
||||
var over: UiNode = null
|
||||
if fr_in(scope, i.x, i.y) { over = fr_pick(scope, i.x, i.y, PK_POINTER) }
|
||||
if pe_key != "" {
|
||||
let c = fr_node(root, pe_key)
|
||||
if c != null and moved { pe_send(c, "pointermove", pe_btn) }
|
||||
if c != null and moved { pe_send(c, "drag", pe_btn) }
|
||||
if ui_st.pe_key != "" {
|
||||
let c = fr_node(root, ui_st.pe_key)
|
||||
if c != null and moved { pe_send(ui_st, c, "pointermove", ui_st.pe_btn) }
|
||||
if c != null and moved { pe_send(ui_st, c, "drag", ui_st.pe_btn) }
|
||||
if not (i.down or i.down_right or i.down_middle) {
|
||||
if c != null { pe_send(c, "pointerup", pe_btn) }
|
||||
pe_key = ""
|
||||
if c != null { pe_send(ui_st, c, "pointerup", ui_st.pe_btn) }
|
||||
ui_st.pe_key = ""
|
||||
}
|
||||
} else if over != null {
|
||||
let b = pe_pressed(i, p)
|
||||
if b >= 0 { pe_down(over, b) } else if moved { pe_send(over, "pointermove", 0) }
|
||||
if b >= 0 { pe_down(ui_st, over, b) } else if moved { pe_send(ui_st, over, "pointermove", 0) }
|
||||
}
|
||||
if i.wheel != 0.0 and over != null { pe_send(over, "wheel", 0) }
|
||||
if i.wheel != 0.0 and over != null { pe_send(ui_st, over, "wheel", 0) }
|
||||
}
|
||||
function pe_pressed(i: UiInput, p: UiInput) -> int {
|
||||
if i.down and not p.down { return 0 }
|
||||
|
|
@ -49,24 +47,24 @@ function pe_pressed(i: UiInput, p: UiInput) -> int {
|
|||
if i.down_middle and not p.down_middle { return 2 }
|
||||
return -1
|
||||
}
|
||||
function pe_down(n: UiNode, b: int) -> void {
|
||||
pe_key = n.key
|
||||
pe_btn = b
|
||||
if not pe_send(n, "pointerdown", b) { pe_key = "" }
|
||||
function pe_down(ui_st: mut UiState, n: UiNode, b: int) -> void {
|
||||
ui_st.pe_key = n.key
|
||||
ui_st.pe_btn = b
|
||||
if not pe_send(ui_st, n, "pointerdown", b) { ui_st.pe_key = "" }
|
||||
}
|
||||
# one event: a native's input answers at once (false: not its to take); an element's on-<kind> runs
|
||||
# once the frame is drawn, with the pointer's fields on `event`
|
||||
function pe_send(n: UiNode, kind: string, b: int) -> bool {
|
||||
function pe_send(ui_st: mut UiState, n: UiNode, kind: string, b: int) -> bool {
|
||||
let e = new UiPointer
|
||||
e.kind = kind
|
||||
e.x = in_now.x - n.x
|
||||
e.y = in_now.y - n.y
|
||||
if in_last.x >= 0.0 {
|
||||
e.dx = in_now.x - in_last.x
|
||||
e.dy = in_now.y - in_last.y
|
||||
e.x = ui_st.in_now.x - n.x
|
||||
e.y = ui_st.in_now.y - n.y
|
||||
if ui_st.in_last.x >= 0.0 {
|
||||
e.dx = ui_st.in_now.x - ui_st.in_last.x
|
||||
e.dy = ui_st.in_now.y - ui_st.in_last.y
|
||||
}
|
||||
e.button = b
|
||||
e.wheel = in_now.wheel
|
||||
e.wheel = ui_st.in_now.wheel
|
||||
if n.kind == UI_NATIVE and n.native != null and n.native.input != null { return n.native.input(n, e) }
|
||||
let v = Value.object()
|
||||
Value.put(v, "x", Value.float(e.x))
|
||||
|
|
@ -75,7 +73,7 @@ function pe_send(n: UiNode, kind: string, b: int) -> bool {
|
|||
Value.put(v, "dy", Value.float(e.dy))
|
||||
Value.put(v, "button", Value.int(b))
|
||||
Value.put(v, "wheel", Value.float(e.wheel))
|
||||
ui_fire(n, kind, v)
|
||||
ui_fire(ui_st, n, kind, v)
|
||||
return true
|
||||
}
|
||||
# a scroll box leaves the wheel to what is under the pointer inside it and takes it itself
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
# (absolutely, in its parent) and is drawn on the top layer, over everything. While one is up the
|
||||
# pointer and the keyboard are its: a press outside it, or Esc, closes it (on-close), and Tab
|
||||
# walks only its own controls.
|
||||
var pop_list: []UiNode = new []UiNode
|
||||
function pp_find(n: UiNode, out: []UiNode) -> void {
|
||||
if n.popover {
|
||||
push(out, n)
|
||||
|
|
@ -18,16 +17,16 @@ function pp_top(root: UiNode) -> UiNode {
|
|||
return all[len(all) - 1]
|
||||
}
|
||||
# the pointer against the popover: inside it is as usual; a press outside closes it and is spent
|
||||
function pp_pointer(top: UiNode) -> bool {
|
||||
let i = in_now
|
||||
if not (i.down and not in_was_down) { return false }
|
||||
function pp_pointer(ui_st: mut UiState, top: UiNode) -> bool {
|
||||
let i = ui_st.in_now
|
||||
if not (i.down and not ui_st.in_was_down) { return false }
|
||||
if i.x >= top.x and i.y >= top.y and i.x < top.x + top.cw and i.y < top.y + top.ch { return false }
|
||||
fr_press_key = ""
|
||||
ui_fire(top, "close", Value.null())
|
||||
ui_st.fr_press_key = ""
|
||||
ui_fire(ui_st, top, "close", Value.null())
|
||||
return true
|
||||
}
|
||||
function pp_draw_top() -> void {
|
||||
let list = pop_list
|
||||
pop_list = new []UiNode
|
||||
for i in 0 .. len(list) { fr_draw(list[i]) }
|
||||
function pp_draw_top(ui_st: mut UiState) -> void {
|
||||
let list = ui_st.pop_list
|
||||
ui_st.pop_list = new []UiNode
|
||||
for i in 0 .. len(list) { fr_draw(ui_st, list[i]) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
# popover_anchor.ludic - `<div popover anchor="cell">` sits beside the element with that id (the
|
||||
# nearest), a bare `anchor` beside the one before it: `placement` right, left, bottom or top (its margin
|
||||
# is the gap), `align` start, center or end along that side, flipped and kept within its bounds.
|
||||
function pp_anchor_all(n: UiNode) -> void {
|
||||
function pp_anchor_all(ui_st: mut UiState, n: UiNode) -> void {
|
||||
if n.children == null { return }
|
||||
for i in 0 .. len(n.children) {
|
||||
let c = n.children[i]
|
||||
if c.popover and Value.kind(ui_attr(c, "anchor")) != 0 { pp_anchor(c, pp_anchor_of(n, i)) }
|
||||
pp_anchor_all(c)
|
||||
if c.popover and Value.kind(ui_attr(c, "anchor")) != 0 { pp_anchor(ui_st, c, pp_anchor_of(ui_st, n, i)) }
|
||||
pp_anchor_all(ui_st, c)
|
||||
}
|
||||
}
|
||||
# the element a popover (the i-th child of `parent`) is anchored to, or null
|
||||
function pp_anchor_of(parent: UiNode, i: int) -> UiNode {
|
||||
function pp_anchor_of(ui_st: mut UiState, parent: UiNode, i: int) -> UiNode {
|
||||
let pop = parent.children[i]
|
||||
let id = ui_attr_text(pop, "anchor")
|
||||
if id == "" or id == "true" {
|
||||
|
|
@ -25,7 +25,7 @@ function pp_anchor_of(parent: UiNode, i: int) -> UiNode {
|
|||
if f != null { return f }
|
||||
at = at.parent
|
||||
}
|
||||
ui_err(`anchor="{id}": no element has that id`)
|
||||
ui_err(ui_st, `anchor="{id}": no element has that id`)
|
||||
return null
|
||||
}
|
||||
function pp_by_id(n: UiNode, id: string, skip: UiNode) -> UiNode {
|
||||
|
|
@ -38,11 +38,11 @@ function pp_by_id(n: UiNode, id: string, skip: UiNode) -> UiNode {
|
|||
}
|
||||
return null
|
||||
}
|
||||
function pp_anchor(p: UiNode, a: UiNode) -> void {
|
||||
function pp_anchor(ui_st: mut UiState, p: UiNode, a: UiNode) -> void {
|
||||
if a == null { return }
|
||||
let side = ui_attr_text(p, "placement")
|
||||
let align = ui_attr_text(p, "align")
|
||||
pp_bounds(p)
|
||||
pp_bounds(ui_st, p)
|
||||
let w = p.cw
|
||||
let h = p.ch
|
||||
var x = a.x
|
||||
|
|
@ -50,18 +50,18 @@ function pp_anchor(p: UiNode, a: UiNode) -> void {
|
|||
if side == "top" or side == "bottom" {
|
||||
let below = a.y + a.ch + p.mt
|
||||
let above = a.y - h - p.mb
|
||||
y = pp_side(side == "top", above, below, h, pp_by, pp_bh)
|
||||
y = pp_side(side == "top", above, below, h, ui_st.pp_by, ui_st.pp_bh)
|
||||
x = pp_along(align, a.x, a.cw, w) + p.ml
|
||||
} else {
|
||||
let right = a.x + a.cw + p.ml
|
||||
let left = a.x - w - p.mr
|
||||
x = pp_side(side == "left", left, right, w, pp_bx, pp_bw)
|
||||
x = pp_side(side == "left", left, right, w, ui_st.pp_bx, ui_st.pp_bw)
|
||||
y = pp_along(align, a.y, a.ch, h) + p.mt
|
||||
}
|
||||
x = Math.max(pp_bx, Math.min(x, pp_bx + pp_bw - w))
|
||||
y = Math.max(pp_by, Math.min(y, pp_by + pp_bh - h))
|
||||
ly_place(p, x, y, w, h)
|
||||
sc_apply(p)
|
||||
x = Math.max(ui_st.pp_bx, Math.min(x, ui_st.pp_bx + ui_st.pp_bw - w))
|
||||
y = Math.max(ui_st.pp_by, Math.min(y, ui_st.pp_by + ui_st.pp_bh - h))
|
||||
ly_place(ui_st, p, x, y, w, h)
|
||||
sc_apply(ui_st, p)
|
||||
}
|
||||
# where along one axis: the side asked for (`low` first when `first_low`), else the other when the
|
||||
# first leaves the screen and the other does not
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
# popover_within.ludic - what an anchored popover is kept inside: the element named by within="id",
|
||||
# else the nearest scroll box (or overflow box) around it, else the screen; it flips against the same
|
||||
var pp_bx: float = 0.0
|
||||
var pp_by: float = 0.0
|
||||
var pp_bw: float = 0.0
|
||||
var pp_bh: float = 0.0
|
||||
function pp_bounds(p: UiNode) -> void {
|
||||
pp_bx = ly_vp_x
|
||||
pp_by = ly_vp_y
|
||||
pp_bw = ly_vp_w
|
||||
pp_bh = ly_vp_h
|
||||
function pp_bounds(ui_st: mut UiState, p: UiNode) -> void {
|
||||
ui_st.pp_bx = ui_st.ly_vp_x
|
||||
ui_st.pp_by = ui_st.ly_vp_y
|
||||
ui_st.pp_bw = ui_st.ly_vp_w
|
||||
ui_st.pp_bh = ui_st.ly_vp_h
|
||||
var box: UiNode = null
|
||||
let id = ui_attr_text(p, "within")
|
||||
if id != "" {
|
||||
|
|
@ -17,7 +13,7 @@ function pp_bounds(p: UiNode) -> void {
|
|||
box = pp_by_id(at, id, p)
|
||||
at = at.parent
|
||||
}
|
||||
if box == null { ui_err(`within="{id}": no element has that id`) }
|
||||
if box == null { ui_err(ui_st, `within="{id}": no element has that id`) }
|
||||
} else {
|
||||
var at = p.parent
|
||||
while at != null and box == null {
|
||||
|
|
@ -26,10 +22,10 @@ function pp_bounds(p: UiNode) -> void {
|
|||
}
|
||||
}
|
||||
if box == null { return }
|
||||
pp_bx = box.x
|
||||
pp_by = box.y
|
||||
pp_bw = box.cw
|
||||
pp_bh = box.ch
|
||||
ui_st.pp_bx = box.x
|
||||
ui_st.pp_by = box.y
|
||||
ui_st.pp_bw = box.cw
|
||||
ui_st.pp_bh = box.ch
|
||||
}
|
||||
# where a popover of `size` starts along its anchor's side: level with its start, centred, or its end
|
||||
function pp_along(align: string, at: float, span: float, size: float) -> float {
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
# reload.ludic - a developer's templates and stylesheets read again when they change on disk: every
|
||||
# file is re-read in the order it was first loaded (so an import is there before its importer), and
|
||||
# the screens' state is kept, as React's fast refresh keeps it. True when anything changed.
|
||||
export function ui_reload() -> bool {
|
||||
var changed = uc_reload()
|
||||
for i in 0 .. len(lib_files) {
|
||||
let f = lib_files[i]
|
||||
if f.disk and lib_text(f.path) != f.src { changed = true }
|
||||
export function ui_reload(ui_st: mut UiState) -> bool {
|
||||
var changed = uc_reload(ui_st)
|
||||
for i in 0 .. len(ui_st.lib_files) {
|
||||
let f = ui_st.lib_files[i]
|
||||
if f.disk and lib_text(ui_st, f.path) != f.src { changed = true }
|
||||
}
|
||||
if not changed { return false }
|
||||
ui_errs = new []string
|
||||
if len(lib_files) == 0 {
|
||||
ui_st.ui_errs = new []string
|
||||
if len(ui_st.lib_files) == 0 {
|
||||
print(`ui: templates reloaded`)
|
||||
return true
|
||||
}
|
||||
let old = lib_files
|
||||
lib_files = new []UiFile
|
||||
lib_screen_names = new []string
|
||||
lib_screens = new []UiTpl
|
||||
ui_errs = new []string
|
||||
let old = ui_st.lib_files
|
||||
ui_st.lib_files = new []UiFile
|
||||
ui_st.lib_screen_names = new []string
|
||||
ui_st.lib_screens = new []UiTpl
|
||||
ui_st.ui_errs = new []string
|
||||
for i in 0 .. len(old) {
|
||||
let f = old[i]
|
||||
var have = false
|
||||
for k in 0 .. len(lib_files) {
|
||||
if lib_files[k].path == f.path { have = true }
|
||||
for k in 0 .. len(ui_st.lib_files) {
|
||||
if ui_st.lib_files[k].path == f.path { have = true }
|
||||
}
|
||||
if have { continue }
|
||||
if f.disk { lib_file(f.path) } else { ui_load_text(f.src, f.path) }
|
||||
if f.disk { lib_file(ui_st, f.path) } else { ui_load_text(ui_st, f.src, f.path) }
|
||||
}
|
||||
uc_invalidate()
|
||||
uc_invalidate(ui_st)
|
||||
print(`ui: templates reloaded`)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,19 +7,9 @@
|
|||
module ludic_ui
|
||||
import "render3d_image.ludic"
|
||||
import "render3d_nine.ludic"
|
||||
var r3u_user: float = 1.0
|
||||
var r3u_tex_paths: []string = new []string
|
||||
var r3u_tex_ids: []int = new []int
|
||||
var r3u_tex_sizes: []int = new []int
|
||||
var r3u_tex_seen: []float = new []float
|
||||
var r3u_atlas: []string = new []string
|
||||
var r3u_atlas_tex: []int = new []int
|
||||
var r3u_atlas_cols: []int = new []int
|
||||
var r3u_atlas_rows: []int = new []int
|
||||
var r3u_atlas_names: [][]string = new [][]string
|
||||
# importing this file is enough: it is the renderer from the program's first frame
|
||||
def UiRenderers render3d { make: fn r3u_make }
|
||||
export function ui_render3d() -> void { ui_backend(r3u_make()) }
|
||||
export function ui_render3d(ui_st: mut UiState) -> void { ui_backend(ui_st, r3u_make()) }
|
||||
function r3u_make() -> UiBackend {
|
||||
let b = new UiBackend
|
||||
b.rect = fn r3u_rect
|
||||
|
|
@ -38,18 +28,18 @@ function r3u_make() -> UiBackend {
|
|||
return b
|
||||
}
|
||||
# the player's interface size, 1.0 as designed
|
||||
export function ui_render3d_scale(s: float) -> void { r3u_user = s }
|
||||
export function ui_render3d_scale(ui_st: mut UiState, s: float) -> void { ui_st.r3u_user = s }
|
||||
# a grid atlas: `names` are its cells, row by row, `cols` across and `rows` down; <img
|
||||
# src="prefix:name"> draws one (by its name, or by its number: "item:12")
|
||||
export function ui_atlas(prefix: string, tex: int, cols: int, rows: int, names: []string) -> void {
|
||||
push(r3u_atlas, prefix)
|
||||
push(r3u_atlas_tex, tex)
|
||||
push(r3u_atlas_cols, cols)
|
||||
push(r3u_atlas_rows, Math.max(rows, 1))
|
||||
push(r3u_atlas_names, names)
|
||||
export function ui_atlas(ui_st: mut UiState, prefix: string, tex: int, cols: int, rows: int, names: []string) -> void {
|
||||
push(ui_st.r3u_atlas, prefix)
|
||||
push(ui_st.r3u_atlas_tex, tex)
|
||||
push(ui_st.r3u_atlas_cols, cols)
|
||||
push(ui_st.r3u_atlas_rows, Math.max(rows, 1))
|
||||
push(ui_st.r3u_atlas_names, names)
|
||||
}
|
||||
function r3u_now() -> float { return float(Time.now_us() / 1000) / 1000.0 }
|
||||
function r3u_scale() -> float { return float(gl_h) / 1080.0 * r3u_user }
|
||||
function r3u_scale(ui_st: UiState) -> float { return float(gl_h) / 1080.0 * ui_st.r3u_user }
|
||||
function r3u_r(c: int) -> float { return float((c / 65536) % 256) / 255.0 }
|
||||
function r3u_g(c: int) -> float { return float((c / 256) % 256) / 255.0 }
|
||||
function r3u_b(c: int) -> float { return float(c % 256) / 255.0 }
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
# render3d_image.ludic - pictures for the render3d backend: a texture by path (its sRGB bytes as they
|
||||
# are, clamped at its edges, loaded again when the file changes) and a cell of a named atlas
|
||||
module ludic_ui
|
||||
function r3u_tex(path: string) -> int {
|
||||
for i in 0 .. len(r3u_tex_paths) {
|
||||
if r3u_tex_paths[i] == path { return r3u_tex_fresh(i) }
|
||||
function r3u_tex(ui_st: mut UiState, path: string) -> int {
|
||||
for i in 0 .. len(ui_st.r3u_tex_paths) {
|
||||
if ui_st.r3u_tex_paths[i] == path { return r3u_tex_fresh(ui_st, i) }
|
||||
}
|
||||
push(r3u_tex_paths, path)
|
||||
push(r3u_tex_ids, r3u_tex_load(path))
|
||||
push(r3u_tex_sizes, Fs.size(path))
|
||||
push(r3u_tex_seen, r3u_now())
|
||||
return r3u_tex_ids[len(r3u_tex_ids) - 1]
|
||||
push(ui_st.r3u_tex_paths, path)
|
||||
push(ui_st.r3u_tex_ids, r3u_tex_load(path))
|
||||
push(ui_st.r3u_tex_sizes, Fs.size(path))
|
||||
push(ui_st.r3u_tex_seen, r3u_now())
|
||||
return ui_st.r3u_tex_ids[len(ui_st.r3u_tex_ids) - 1]
|
||||
}
|
||||
# a picture is looked at again every half second: a file that has changed (or that was not there,
|
||||
# and is now) is loaded again, so an <img> of a photograph written over the old one shows the new one
|
||||
function r3u_tex_fresh(i: int) -> int {
|
||||
function r3u_tex_fresh(ui_st: mut UiState, i: int) -> int {
|
||||
let now = r3u_now()
|
||||
if now - r3u_tex_seen[i] < 0.5 and now >= r3u_tex_seen[i] { return r3u_tex_ids[i] }
|
||||
r3u_tex_seen[i] = now
|
||||
let size = Fs.size(r3u_tex_paths[i])
|
||||
if size == r3u_tex_sizes[i] and (r3u_tex_ids[i] != 0 or size < 0) { return r3u_tex_ids[i] }
|
||||
if r3u_tex_ids[i] != 0 { gpu_tex_free(r3u_tex_ids[i]) }
|
||||
r3u_tex_ids[i] = r3u_tex_load(r3u_tex_paths[i])
|
||||
r3u_tex_sizes[i] = size
|
||||
return r3u_tex_ids[i]
|
||||
if now - ui_st.r3u_tex_seen[i] < 0.5 and now >= ui_st.r3u_tex_seen[i] { return ui_st.r3u_tex_ids[i] }
|
||||
ui_st.r3u_tex_seen[i] = now
|
||||
let size = Fs.size(ui_st.r3u_tex_paths[i])
|
||||
if size == ui_st.r3u_tex_sizes[i] and (ui_st.r3u_tex_ids[i] != 0 or size < 0) { return ui_st.r3u_tex_ids[i] }
|
||||
if ui_st.r3u_tex_ids[i] != 0 { gpu_tex_free(ui_st.r3u_tex_ids[i]) }
|
||||
ui_st.r3u_tex_ids[i] = r3u_tex_load(ui_st.r3u_tex_paths[i])
|
||||
ui_st.r3u_tex_sizes[i] = size
|
||||
return ui_st.r3u_tex_ids[i]
|
||||
}
|
||||
# the interface is drawn in sRGB, and a picture's bytes are sRGB, so they are taken as they are
|
||||
# (not decoded to linear, which would darken it); clamped at its edges
|
||||
|
|
@ -36,64 +36,64 @@ function r3u_tex_load(path: string) -> int {
|
|||
return t
|
||||
}
|
||||
# a picture the program has written again, loaded again the next time it is drawn
|
||||
export function ui_image_reload(path: string) -> void {
|
||||
for i in 0 .. len(r3u_tex_paths) {
|
||||
if r3u_tex_paths[i] == path {
|
||||
r3u_tex_seen[i] = -1000000.0
|
||||
r3u_tex_sizes[i] = -2
|
||||
export function ui_image_reload(ui_st: mut UiState, path: string) -> void {
|
||||
for i in 0 .. len(ui_st.r3u_tex_paths) {
|
||||
if ui_st.r3u_tex_paths[i] == path {
|
||||
ui_st.r3u_tex_seen[i] = -1000000.0
|
||||
ui_st.r3u_tex_sizes[i] = -2
|
||||
}
|
||||
}
|
||||
}
|
||||
function r3u_image(src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void {
|
||||
function r3u_image(ui_st: mut UiState, src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void {
|
||||
var colon = -1
|
||||
for i in 0 .. len(src) {
|
||||
if colon < 0 and src[i] == 58 { colon = i }
|
||||
}
|
||||
if colon > 0 {
|
||||
let prefix: string = src[0..colon]
|
||||
for k in 0 .. len(r3u_atlas) {
|
||||
if r3u_atlas[k] == prefix {
|
||||
r3u_cell(k, src[colon + 1..len(src)], x, y, w, h, c, a)
|
||||
for k in 0 .. len(ui_st.r3u_atlas) {
|
||||
if ui_st.r3u_atlas[k] == prefix {
|
||||
r3u_cell(ui_st, k, src[colon + 1..len(src)], x, y, w, h, c, a)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
let t = r3u_tex(src)
|
||||
let t = r3u_tex(ui_st, src)
|
||||
if t != 0 { ov_sub(t, int(x), int(y), int(w), int(h), 0.0, 0.0, 1.0, 1.0, r3u_r(c), r3u_g(c), r3u_b(c), a) }
|
||||
}
|
||||
# a picture's own size, for object-fit; an atlas cell is drawn square
|
||||
function r3u_image_w(src: string) -> float {
|
||||
if r3u_is_cell(src) { return 1.0 }
|
||||
return float(tex_width(r3u_tex(src)))
|
||||
function r3u_image_w(ui_st: mut UiState, src: string) -> float {
|
||||
if r3u_is_cell(ui_st, src) { return 1.0 }
|
||||
return float(tex_width(r3u_tex(ui_st, src)))
|
||||
}
|
||||
function r3u_image_h(src: string) -> float {
|
||||
if r3u_is_cell(src) { return 1.0 }
|
||||
return float(tex_height(r3u_tex(src)))
|
||||
function r3u_image_h(ui_st: mut UiState, src: string) -> float {
|
||||
if r3u_is_cell(ui_st, src) { return 1.0 }
|
||||
return float(tex_height(r3u_tex(ui_st, src)))
|
||||
}
|
||||
function r3u_is_cell(src: string) -> bool {
|
||||
for k in 0 .. len(r3u_atlas) {
|
||||
let p = r3u_atlas[k]
|
||||
function r3u_is_cell(ui_st: UiState, src: string) -> bool {
|
||||
for k in 0 .. len(ui_st.r3u_atlas) {
|
||||
let p = ui_st.r3u_atlas[k]
|
||||
if len(src) > len(p) and src[0..len(p)] == p and src[len(p)] == 58 { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
function r3u_cell(k: int, name: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void {
|
||||
let names = r3u_atlas_names[k]
|
||||
function r3u_cell(ui_st: mut UiState, k: int, name: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void {
|
||||
let names = ui_st.r3u_atlas_names[k]
|
||||
var idx = -1
|
||||
for i in 0 .. len(names) {
|
||||
if names[i] == name { idx = i }
|
||||
}
|
||||
if idx < 0 and len(name) > 0 and name[0] >= 48 and name[0] <= 57 { idx = int(el_num(name)) }
|
||||
if idx < 0 {
|
||||
ui_err(`{r3u_atlas[k]}:{name}: the atlas has no such picture`)
|
||||
ui_err(ui_st, `{ui_st.r3u_atlas[k]}:{name}: the atlas has no such picture`)
|
||||
return
|
||||
}
|
||||
let cols = r3u_atlas_cols[k]
|
||||
let rows = r3u_atlas_rows[k]
|
||||
let cols = ui_st.r3u_atlas_cols[k]
|
||||
let rows = ui_st.r3u_atlas_rows[k]
|
||||
let cx = idx % cols
|
||||
let cy = idx / cols
|
||||
let u0 = float(cx) / float(cols)
|
||||
let v0 = float(cy) / float(rows)
|
||||
let s = Math.min(w, h)
|
||||
ov_sub(r3u_atlas_tex[k], int(x + (w - s) / 2.0), int(y + (h - s) / 2.0), int(s), int(s), u0, v0, u0 + 1.0 / float(cols), v0 + 1.0 / float(rows), r3u_r(c), r3u_g(c), r3u_b(c), a)
|
||||
ov_sub(ui_st.r3u_atlas_tex[k], int(x + (w - s) / 2.0), int(y + (h - s) / 2.0), int(s), int(s), u0, v0, u0 + 1.0 / float(cols), v0 + 1.0 / float(rows), r3u_r(c), r3u_g(c), r3u_b(c), a)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ module ludic_ui
|
|||
# border-image: the texture's `slice`-pixel corners kept, the rest stretched, drawn `dst` wide (at
|
||||
# most half the box each way, ui_nine_cuts); the slice is a share of the texture's own width across
|
||||
# and its own height down, so it need not be square
|
||||
function r3u_nine(src: string, slice: float, x: float, y: float, w: float, h: float, dst: float, c: int, a: float) -> void {
|
||||
let t = r3u_tex(src)
|
||||
function r3u_nine(ui_st: mut UiState, src: string, slice: float, x: float, y: float, w: float, h: float, dst: float, c: int, a: float) -> void {
|
||||
let t = r3u_tex(ui_st, src)
|
||||
if t == 0 { return }
|
||||
let su = slice / float(Math.max(tex_width(t), 1))
|
||||
let sv = slice / float(Math.max(tex_height(t), 1))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# screen_class.ludic - a Ludic component shown as a whole screen
|
||||
# a Ludic component shown as a screen: an instance at the top, keyed by its name
|
||||
function fr_class_nodes(cls: UiClass) -> UiNode {
|
||||
lc_begin()
|
||||
in_build += 1
|
||||
ui_view = null
|
||||
uc_load(cls)
|
||||
function fr_class_nodes(ui_st: mut UiState, cls: UiClass) -> UiNode {
|
||||
lc_begin(ui_st)
|
||||
ui_st.in_build += 1
|
||||
ui_st.ui_view = null
|
||||
uc_load(ui_st, cls)
|
||||
let root = fr_root(cls.name, cls.sheet)
|
||||
let use = new UiTpl
|
||||
use.tag = cls.name
|
||||
|
|
@ -17,9 +17,9 @@ function fr_class_nodes(cls: UiClass) -> UiNode {
|
|||
let e = env_new(null)
|
||||
e.model = Value.object()
|
||||
root.env = e
|
||||
bd_class(use, e, cls.name, root.children)
|
||||
in_sweep(cls.name)
|
||||
cs_tree(root)
|
||||
bd_class(ui_st, use, e, cls.name, root.children)
|
||||
in_sweep(ui_st, cls.name)
|
||||
cs_tree(ui_st, root)
|
||||
return root
|
||||
}
|
||||
function fr_root(key: string, sheet: UiSheet) -> UiNode {
|
||||
|
|
|
|||
|
|
@ -1,60 +1,56 @@
|
|||
# scroll.ludic - a box with overflow (or <scroll>) scrolls itself: the wheel over it, its scrollbar
|
||||
# held and dragged, and the focused control pulled into view. Its content is shifted by how far it is
|
||||
# scrolled and clipped to it, and the offset is kept by its key from frame to frame.
|
||||
var sc_keys: []string = new []string
|
||||
var sc_offs: []float = new []float
|
||||
var sc_drag: string = ""
|
||||
var sc_scope: UiNode = null # the popover that has the pointer this frame, if one is up
|
||||
function sc_get(key: string) -> float {
|
||||
for i in 0 .. len(sc_keys) {
|
||||
if sc_keys[i] == key { return sc_offs[i] }
|
||||
function sc_get(ui_st: UiState, key: string) -> float {
|
||||
for i in 0 .. len(ui_st.sc_keys) {
|
||||
if ui_st.sc_keys[i] == key { return ui_st.sc_offs[i] }
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
function sc_put(key: string, v: float) -> void {
|
||||
for i in 0 .. len(sc_keys) {
|
||||
if sc_keys[i] == key {
|
||||
sc_offs[i] = v
|
||||
function sc_put(ui_st: mut UiState, key: string, v: float) -> void {
|
||||
for i in 0 .. len(ui_st.sc_keys) {
|
||||
if ui_st.sc_keys[i] == key {
|
||||
ui_st.sc_offs[i] = v
|
||||
return
|
||||
}
|
||||
}
|
||||
push(sc_keys, key)
|
||||
push(sc_offs, v)
|
||||
push(ui_st.sc_keys, key)
|
||||
push(ui_st.sc_offs, v)
|
||||
}
|
||||
# every scroll box, outermost first: its offset (from input, when `live`), then its content moved
|
||||
function sc_update(n: UiNode) -> void {
|
||||
sc_scope = pp_top(n)
|
||||
if not in_now.down { sc_drag = "" }
|
||||
sc_walk(n, true)
|
||||
function sc_update(ui_st: mut UiState, n: UiNode) -> void {
|
||||
ui_st.sc_scope = pp_top(n)
|
||||
if not ui_st.in_now.down { ui_st.sc_drag = "" }
|
||||
sc_walk(ui_st, n, true)
|
||||
}
|
||||
function sc_apply(n: UiNode) -> void { sc_walk(n, false) }
|
||||
function sc_walk(n: UiNode, live: bool) -> void {
|
||||
function sc_apply(ui_st: mut UiState, n: UiNode) -> void { sc_walk(ui_st, n, false) }
|
||||
function sc_walk(ui_st: mut UiState, n: UiNode, live: bool) -> void {
|
||||
if n.kind == UI_SCROLL {
|
||||
let room = n.ch - ly_vin(n)
|
||||
let most = Math.max(n.content_h - ly_vin(n) - room, 0.0)
|
||||
let held = sc_held(n, sc_get(n.key), live)
|
||||
let held = sc_held(ui_st, n, sc_get(ui_st, n.key), live)
|
||||
var off = held
|
||||
if live { off = sc_input(n, off, room, most) }
|
||||
if live { off = sc_input(ui_st, n, off, room, most) }
|
||||
off = Math.clamp(off, 0.0, most)
|
||||
if live and off != Math.clamp(held, 0.0, most) { ui_fire(n, "scroll", Value.float(off / ui_px(1.0))) }
|
||||
sc_put(n.key, off)
|
||||
if live and off != Math.clamp(held, 0.0, most) { ui_fire(ui_st, n, "scroll", Value.float(off / ui_px(ui_st, 1.0))) }
|
||||
sc_put(ui_st, n.key, off)
|
||||
n.scroll_y = off
|
||||
if off != 0.0 {
|
||||
for k in 0 .. len(n.children) { ly_offset(n.children[k], 0.0, 0.0 - off) }
|
||||
}
|
||||
}
|
||||
for k in 0 .. len(n.children) { sc_walk(n.children[k], live) }
|
||||
for k in 0 .. len(n.children) { sc_walk(ui_st, n.children[k], live) }
|
||||
}
|
||||
# the wheel over it, its bar held and dragged (scroll_bar.ludic), the keyboard's focus pulled into
|
||||
# view; while a popover is up, only a box inside it answers the pointer
|
||||
function sc_input(n: UiNode, off0: float, room: float, most: float) -> float {
|
||||
function sc_input(ui_st: mut UiState, n: UiNode, off0: float, room: float, most: float) -> float {
|
||||
var off = off0
|
||||
if sc_scope != null and not sc_within(n, sc_scope) { return sc_into_view(n, off, room) }
|
||||
let i = in_now
|
||||
if ui_st.sc_scope != null and not sc_within(n, ui_st.sc_scope) { return sc_into_view(ui_st, n, off, room) }
|
||||
let i = ui_st.in_now
|
||||
let over = i.x >= n.x and i.y >= n.y and i.x < n.x + n.cw and i.y < n.y + n.ch
|
||||
if over and i.wheel != 0.0 and not pe_takes_wheel(n, i.x, i.y + off0) { off = off - i.wheel * ui_px(40.0) }
|
||||
off = sb_input(n, off, room, most)
|
||||
return sc_into_view(n, off, room)
|
||||
if over and i.wheel != 0.0 and not pe_takes_wheel(n, i.x, i.y + off0) { off = off - i.wheel * ui_px(ui_st, 40.0) }
|
||||
off = sb_input(ui_st, n, off, room, most)
|
||||
return sc_into_view(ui_st, n, off, room)
|
||||
}
|
||||
function sc_within(n: UiNode, top: UiNode) -> bool {
|
||||
var at = n
|
||||
|
|
@ -65,13 +61,13 @@ function sc_within(n: UiNode, top: UiNode) -> bool {
|
|||
return false
|
||||
}
|
||||
# the focused control, moved by the keyboard, is brought into the box's view
|
||||
function sc_into_view(n: UiNode, off: float, room: float) -> float {
|
||||
if not fc_visible or fc_key == "" or not lc_mine(fc_key, n.key) { return off }
|
||||
let f = fr_node(n, fc_key)
|
||||
function sc_into_view(ui_st: UiState, n: UiNode, off: float, room: float) -> float {
|
||||
if not ui_st.fc_visible or ui_st.fc_key == "" or not lc_mine(ui_st.fc_key, n.key) { return off }
|
||||
let f = fr_node(n, ui_st.fc_key)
|
||||
if f == null { return off }
|
||||
let top = f.y - n.y - n.pt
|
||||
if top < off { return top - ui_px(8.0) }
|
||||
if top + f.ch > off + room { return top + f.ch - room + ui_px(8.0) }
|
||||
if top < off { return top - ui_px(ui_st, 8.0) }
|
||||
if top + f.ch > off + room { return top + f.ch - room + ui_px(ui_st, 8.0) }
|
||||
return off
|
||||
}
|
||||
# ui_place moved every box by its stored offset; the frame takes that back before moving it again
|
||||
|
|
|
|||
|
|
@ -1,63 +1,57 @@
|
|||
# scroll_bar.ludic - a scroll box's bar: .ui-scrollbar, the track down its right edge, and its
|
||||
# .ui-thumb, styled by the default sheet or a theme as the box's own parts. A press on the thumb holds
|
||||
# it where it was taken; a press on the track jumps the thumb's middle to the pointer and holds it there.
|
||||
var sb_x: float = 0.0
|
||||
var sb_w: float = 0.0
|
||||
var sb_ty: float = 0.0
|
||||
var sb_th: float = 0.0
|
||||
var sb_bar: UiNode = null
|
||||
var sc_grab: float = 0.0 # how far below the thumb's top the pointer holds it
|
||||
# the bar's parts, styled as descendants of the box, and where they are at this offset
|
||||
function sb_geo(n: UiNode, off: float, room: float, most: float) -> void {
|
||||
function sb_geo(ui_st: mut UiState, n: UiNode, off: float, room: float, most: float) -> void {
|
||||
let bar = ct_part(n, "ui-scrollbar", "")
|
||||
bar.tag = "div"
|
||||
bar.parent = n
|
||||
push(bar.children, ct_part(n, "ui-thumb", ""))
|
||||
cs_tree(bar)
|
||||
sb_bar = bar
|
||||
sb_w = ui_px(6.0)
|
||||
if bar.w >= 0.0 { sb_w = bar.w }
|
||||
sb_x = n.x + n.cw - sb_w - bar.mr
|
||||
var least = ui_px(24.0)
|
||||
cs_tree(ui_st, bar)
|
||||
ui_st.sb_bar = bar
|
||||
ui_st.sb_w = ui_px(ui_st, 6.0)
|
||||
if bar.w >= 0.0 { ui_st.sb_w = bar.w }
|
||||
ui_st.sb_x = n.x + n.cw - ui_st.sb_w - bar.mr
|
||||
var least = ui_px(ui_st, 24.0)
|
||||
if len(bar.children) > 0 and bar.children[0].min_h > 0.0 { least = bar.children[0].min_h }
|
||||
sb_th = Math.min(Math.max(n.ch * room / (room + most), least), n.ch)
|
||||
sb_ty = n.y
|
||||
if most > 0.0 { sb_ty = n.y + (n.ch - sb_th) * Math.clamp(off, 0.0, most) / most }
|
||||
ui_st.sb_th = Math.min(Math.max(n.ch * room / (room + most), least), n.ch)
|
||||
ui_st.sb_ty = n.y
|
||||
if most > 0.0 { ui_st.sb_ty = n.y + (n.ch - ui_st.sb_th) * Math.clamp(off, 0.0, most) / most }
|
||||
}
|
||||
# the pointer on the bar: taken on a press over it, moved while held, let go on release
|
||||
function sb_input(n: UiNode, off: float, room: float, most: float) -> float {
|
||||
let i = in_now
|
||||
function sb_input(ui_st: mut UiState, n: UiNode, off: float, room: float, most: float) -> float {
|
||||
let i = ui_st.in_now
|
||||
if most <= 0.0 or room <= 0.0 { return off }
|
||||
if i.down and not in_was_down and sc_drag == "" {
|
||||
sb_geo(n, off, room, most)
|
||||
let reach = Math.min(sb_x, n.x + n.cw - ui_px(14.0))
|
||||
if i.down and not ui_st.in_was_down and ui_st.sc_drag == "" {
|
||||
sb_geo(ui_st, n, off, room, most)
|
||||
let reach = Math.min(ui_st.sb_x, n.x + n.cw - ui_px(ui_st, 14.0))
|
||||
let over = i.x >= reach and i.y >= n.y and i.x < n.x + n.cw and i.y < n.y + n.ch
|
||||
if over and not sb_bar.hidden {
|
||||
sc_drag = n.key
|
||||
sc_grab = sb_th / 2.0
|
||||
if i.y >= sb_ty and i.y < sb_ty + sb_th { sc_grab = i.y - sb_ty }
|
||||
if over and not ui_st.sb_bar.hidden {
|
||||
ui_st.sc_drag = n.key
|
||||
ui_st.sc_grab = ui_st.sb_th / 2.0
|
||||
if i.y >= ui_st.sb_ty and i.y < ui_st.sb_ty + ui_st.sb_th { ui_st.sc_grab = i.y - ui_st.sb_ty }
|
||||
}
|
||||
}
|
||||
if sc_drag != n.key { return off }
|
||||
sb_geo(n, off, room, most)
|
||||
let span = n.ch - sb_th
|
||||
if ui_st.sc_drag != n.key { return off }
|
||||
sb_geo(ui_st, n, off, room, most)
|
||||
let span = n.ch - ui_st.sb_th
|
||||
if span <= 0.0 { return off }
|
||||
return (i.y - sc_grab - n.y) / span * most
|
||||
return (i.y - ui_st.sc_grab - n.y) / span * most
|
||||
}
|
||||
# the track, then the thumb over it; scrollbar-color on the box wins over the parts' backgrounds
|
||||
function sc_bar(n: UiNode) -> void {
|
||||
function sc_bar(ui_st: mut UiState, n: UiNode) -> void {
|
||||
let room = n.ch - ly_vin(n)
|
||||
let most = n.content_h - ly_vin(n) - room
|
||||
if most <= 0.0 or ui_be.rect == null { return }
|
||||
sb_geo(n, n.scroll_y, room, most)
|
||||
let bar = sb_bar
|
||||
if most <= 0.0 or ui_st.ui_be.rect == null { return }
|
||||
sb_geo(ui_st, n, n.scroll_y, room, most)
|
||||
let bar = ui_st.sb_bar
|
||||
if bar.hidden { return }
|
||||
var track = bar.bg
|
||||
if n.bar_track != -2 { track = n.bar_track }
|
||||
if track >= 0 { fr_box(sb_x, n.y, sb_w, n.ch, bar.radius, track, bar.bg_a * fr_ga) }
|
||||
if track >= 0 { fr_box(ui_st, ui_st.sb_x, n.y, ui_st.sb_w, n.ch, bar.radius, track, bar.bg_a * ui_st.fr_ga) }
|
||||
if len(bar.children) == 0 { return }
|
||||
let t = bar.children[0]
|
||||
var thumb = t.bg
|
||||
if n.bar_c >= 0 { thumb = n.bar_c }
|
||||
if thumb >= 0 { fr_box(sb_x, sb_ty, sb_w, sb_th, t.radius, thumb, t.bg_a * fr_ga) }
|
||||
if thumb >= 0 { fr_box(ui_st, ui_st.sb_x, ui_st.sb_ty, ui_st.sb_w, ui_st.sb_th, t.radius, thumb, t.bg_a * ui_st.fr_ga) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,27 @@
|
|||
# scroll_held.ludic - a scroll box held at an offset: scroll-top="{px}" puts it there every frame
|
||||
# (the wheel, the bar and the focus still move it, and say so with on-scroll and event.value, for
|
||||
# the program to keep or not), and ui_scroll_set(id or key, px) moves it once
|
||||
var sc_want_keys: []string = new []string
|
||||
var sc_want_px: []float = new []float
|
||||
export function ui_scroll_set(key: string, px: float) -> void {
|
||||
push(sc_want_keys, key)
|
||||
push(sc_want_px, px)
|
||||
export function ui_scroll_set(ui_st: mut UiState, key: string, px: float) -> void {
|
||||
push(ui_st.sc_want_keys, key)
|
||||
push(ui_st.sc_want_px, px)
|
||||
}
|
||||
# how far a box is scrolled before this frame's input: what it was, what ui_scroll_set asked for,
|
||||
# or what its scroll-top says
|
||||
function sc_held(n: UiNode, off: float, live: bool) -> float {
|
||||
function sc_held(ui_st: mut UiState, n: UiNode, off: float, live: bool) -> float {
|
||||
var at = off
|
||||
if live {
|
||||
let keys = new []string
|
||||
let pxs = new []float
|
||||
for i in 0 .. len(sc_want_keys) {
|
||||
if sc_want_keys[i] == n.key or (n.id != "" and sc_want_keys[i] == n.id) { at = ui_px(sc_want_px[i]) } else {
|
||||
push(keys, sc_want_keys[i])
|
||||
push(pxs, sc_want_px[i])
|
||||
for i in 0 .. len(ui_st.sc_want_keys) {
|
||||
if ui_st.sc_want_keys[i] == n.key or (n.id != "" and ui_st.sc_want_keys[i] == n.id) { at = ui_px(ui_st, ui_st.sc_want_px[i]) } else {
|
||||
push(keys, ui_st.sc_want_keys[i])
|
||||
push(pxs, ui_st.sc_want_px[i])
|
||||
}
|
||||
}
|
||||
sc_want_keys = keys
|
||||
sc_want_px = pxs
|
||||
ui_st.sc_want_keys = keys
|
||||
ui_st.sc_want_px = pxs
|
||||
}
|
||||
let v = ui_attr(n, "scroll-top")
|
||||
if Value.kind(v) != 0 { at = ui_px(el_num(vl_text(v))) }
|
||||
if Value.kind(v) != 0 { at = ui_px(ui_st, el_num(vl_text(v))) }
|
||||
return at
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,26 +11,26 @@ function sel_spec(s: UiSel) -> int {
|
|||
}
|
||||
return n
|
||||
}
|
||||
function sel_match(s: UiSel, n: UiNode) -> bool {
|
||||
if not sel_one(s, n) { return false }
|
||||
function sel_match(ui_st: mut UiState, s: UiSel, n: UiNode) -> bool {
|
||||
if not sel_one(ui_st, s, n) { return false }
|
||||
if s.prev == null { return true }
|
||||
if s.comb == 1 { return n.parent != null and sel_match(s.prev, n.parent) }
|
||||
if s.comb == 2 { return n.parent != null and n.index > 0 and sel_match(s.prev, n.parent.children[n.index - 1]) }
|
||||
if s.comb == 1 { return n.parent != null and sel_match(ui_st, s.prev, n.parent) }
|
||||
if s.comb == 2 { return n.parent != null and n.index > 0 and sel_match(ui_st, s.prev, n.parent.children[n.index - 1]) }
|
||||
if s.comb == 3 {
|
||||
if n.parent == null { return false }
|
||||
for i in 0 .. n.index {
|
||||
if sel_match(s.prev, n.parent.children[i]) { return true }
|
||||
if sel_match(ui_st, s.prev, n.parent.children[i]) { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
var a = n.parent
|
||||
while a != null {
|
||||
if sel_match(s.prev, a) { return true }
|
||||
if sel_match(ui_st, s.prev, a) { return true }
|
||||
a = a.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
function sel_one(s: UiSel, n: UiNode) -> bool {
|
||||
function sel_one(ui_st: mut UiState, s: UiSel, n: UiNode) -> bool {
|
||||
if s.tag != "" and s.tag != n.tag { return false }
|
||||
if s.id != "" and s.id != n.id { return false }
|
||||
for i in 0 .. len(s.classes) {
|
||||
|
|
@ -41,10 +41,10 @@ function sel_one(s: UiSel, n: UiNode) -> bool {
|
|||
if v == null or (s.aops[i] == 1 and v != s.avals[i]) { return false }
|
||||
}
|
||||
for i in 0 .. len(s.pseudos) {
|
||||
if not sel_pseudo_ok(s.pseudos[i], s.pargs[i], n) { return false }
|
||||
if not sel_pseudo_ok(ui_st, s.pseudos[i], s.pargs[i], n) { return false }
|
||||
}
|
||||
for i in 0 .. len(s.nots) {
|
||||
if sel_one(s.nots[i], n) { return false }
|
||||
if sel_one(ui_st, s.nots[i], n) { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -62,11 +62,11 @@ function sel_attr_of(n: UiNode, key: string) -> string {
|
|||
}
|
||||
return null
|
||||
}
|
||||
function sel_pseudo_ok(p: string, arg: string, n: UiNode) -> bool {
|
||||
function sel_pseudo_ok(ui_st: mut UiState, p: string, arg: string, n: UiNode) -> bool {
|
||||
if p == "hover" { return n.hovered }
|
||||
if p == "focus" { return fc_key != "" and n.key == fc_key }
|
||||
if p == "focus-visible" { return fc_visible and fc_key != "" and n.key == fc_key }
|
||||
if p == "focus-within" { return fc_key != "" and lc_mine(fc_key, n.key) }
|
||||
if p == "focus" { return ui_st.fc_key != "" and n.key == ui_st.fc_key }
|
||||
if p == "focus-visible" { return ui_st.fc_visible and ui_st.fc_key != "" and n.key == ui_st.fc_key }
|
||||
if p == "focus-within" { return ui_st.fc_key != "" and lc_mine(ui_st.fc_key, n.key) }
|
||||
if p == "disabled" { return not n.enabled }
|
||||
if p == "enabled" { return n.enabled }
|
||||
if p == "first-child" { return n.index == 0 }
|
||||
|
|
@ -75,9 +75,9 @@ function sel_pseudo_ok(p: string, arg: string, n: UiNode) -> bool {
|
|||
if p == "empty" { return len(n.children) == 0 and n.text == "" }
|
||||
if p == "root" { return n.parent == null }
|
||||
if p == "checked" { return ui_attr_on(n, "checked") }
|
||||
if p == "active" { return fr_active(n) }
|
||||
if p == "capturing" { return ct_capture != "" and n.key == ct_capture }
|
||||
if p == "active" { return fr_active(ui_st, n) }
|
||||
if p == "capturing" { return ui_st.ct_capture != "" and n.key == ui_st.ct_capture }
|
||||
if p == "nth-child" { return sel_nth(arg, n.index + 1) }
|
||||
ui_err(`:{p}: there is no such pseudo-class here`)
|
||||
ui_err(ui_st, `:{p}: there is no such pseudo-class here`)
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,32 @@
|
|||
# state.ludic - a component's or a screen's own values, <state name="{first}"/>, kept from frame
|
||||
# to frame by where the instance is in the tree, and set by `set name = value`
|
||||
var st_keys: []string = new []string
|
||||
var st_vals: []Val = new []Val
|
||||
function st_for(key: string, t: UiTpl, e: UiEnv) -> Val {
|
||||
function st_for(ui_st: mut UiState, key: string, t: UiTpl, e: UiEnv) -> Val {
|
||||
var decl: UiTpl = null
|
||||
for i in 0 .. len(t.kids) {
|
||||
if t.kids[i].tag == "state" { decl = t.kids[i] }
|
||||
}
|
||||
if decl == null { return null }
|
||||
for i in 0 .. len(st_keys) {
|
||||
if st_keys[i] == key { return st_vals[i] }
|
||||
for i in 0 .. len(ui_st.st_keys) {
|
||||
if ui_st.st_keys[i] == key { return ui_st.st_vals[i] }
|
||||
}
|
||||
let o = Value.object()
|
||||
for i in 0 .. len(decl.keys) { Value.put(o, decl.keys[i], ui_eval(decl.vals[i], e)) }
|
||||
push(st_keys, key)
|
||||
push(st_vals, o)
|
||||
for i in 0 .. len(decl.keys) { Value.put(o, decl.keys[i], ui_eval(ui_st, decl.vals[i], e)) }
|
||||
push(ui_st.st_keys, key)
|
||||
push(ui_st.st_vals, o)
|
||||
return o
|
||||
}
|
||||
# a screen's state starts again the next time it opens
|
||||
export function ui_forget(screen: string) -> void {
|
||||
export function ui_forget(ui_st: mut UiState, screen: string) -> void {
|
||||
let keys = new []string
|
||||
let vals = new []Val
|
||||
for i in 0 .. len(st_keys) {
|
||||
let k = st_keys[i]
|
||||
for i in 0 .. len(ui_st.st_keys) {
|
||||
let k = ui_st.st_keys[i]
|
||||
let mine = len(k) >= len(screen) and k[0..len(screen)] == screen and (len(k) == len(screen) or k[len(screen)] == 47)
|
||||
if not mine {
|
||||
push(keys, k)
|
||||
push(vals, st_vals[i])
|
||||
push(vals, ui_st.st_vals[i])
|
||||
}
|
||||
}
|
||||
st_keys = keys
|
||||
st_vals = vals
|
||||
ui_st.st_keys = keys
|
||||
ui_st.st_vals = vals
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,45 +12,45 @@ export property UiRule {
|
|||
export property UiSheet {
|
||||
rules: []UiRule = null
|
||||
}
|
||||
function sheet_read(f: UiFile, t: UiTpl) -> void {
|
||||
function sheet_read(ui_st: mut UiState, f: UiFile, t: UiTpl) -> void {
|
||||
var src = t.raw
|
||||
let from = bd_name(t, "src", "")
|
||||
if from != "" { src = lib_text(lib_beside(f.path, from)) }
|
||||
lss_text(f, src)
|
||||
if from != "" { src = lib_text(ui_st, lib_beside(f.path, from)) }
|
||||
lss_text(ui_st, f, src)
|
||||
}
|
||||
function lss_is(path: string) -> bool { return len(path) > 4 and path[len(path) - 4..len(path)] == ".lss" }
|
||||
# a stylesheet file: a file with rules and imports and nothing else
|
||||
function lss_file(src: string, path: string) -> UiFile {
|
||||
let f = lib_new(path)
|
||||
function lss_file(ui_st: mut UiState, src: string, path: string) -> UiFile {
|
||||
let f = lib_new(ui_st, path)
|
||||
f.src = src
|
||||
lss_text(f, src)
|
||||
lss_text(ui_st, f, src)
|
||||
f.sheet = sheet_of(f)
|
||||
return f
|
||||
}
|
||||
function lss_text(f: UiFile, src: string) -> void {
|
||||
function lss_text(ui_st: mut UiState, f: UiFile, src: string) -> void {
|
||||
let err = new []string
|
||||
lss_block(f, lss_strip(src), null, err)
|
||||
for i in 0 .. len(err) { ui_err(`{f.path}: <style> {err[i]}`) }
|
||||
lss_block(ui_st, f, lss_strip(src), null, err)
|
||||
for i in 0 .. len(err) { ui_err(ui_st, `{f.path}: <style> {err[i]}`) }
|
||||
}
|
||||
# rules, @imports and @media blocks; the rules in a @media block carry its condition
|
||||
function lss_block(f: UiFile, src: string, md: UiMedia, err: []string) -> void {
|
||||
function lss_block(ui_st: mut UiState, f: UiFile, src: string, md: UiMedia, err: []string) -> void {
|
||||
let r = new UiRd
|
||||
r.s = src
|
||||
while r.err == "" {
|
||||
rd_ws(r)
|
||||
if r.i >= len(r.s) { break }
|
||||
if rd_op(r, "@import") {
|
||||
lss_import(f, lss_upto(r, 59))
|
||||
lss_import(ui_st, f, lss_upto(r, 59))
|
||||
continue
|
||||
}
|
||||
if rd_op(r, "@keyframes") {
|
||||
let name = lss_upto(r, 123)
|
||||
an_read(name, lss_upto(r, 125), err)
|
||||
an_read(ui_st, name, lss_upto(r, 125), err)
|
||||
continue
|
||||
}
|
||||
if rd_op(r, "@media") {
|
||||
let cond = md_parse(lss_upto(r, 123))
|
||||
lss_block(f, lss_upto(r, 125), cond, err)
|
||||
lss_block(ui_st, f, lss_upto(r, 125), cond, err)
|
||||
continue
|
||||
}
|
||||
let sels = lss_upto(r, 123)
|
||||
|
|
@ -62,11 +62,11 @@ function lss_block(f: UiFile, src: string, md: UiMedia, err: []string) -> void {
|
|||
if r.err != "" { push(err, r.err) }
|
||||
}
|
||||
# @import "base.lss"; - its rules come before this file's own
|
||||
function lss_import(f: UiFile, what: string) -> void {
|
||||
function lss_import(ui_st: mut UiState, f: UiFile, what: string) -> void {
|
||||
var p = tpl_words(what)
|
||||
if len(p) > 5 and p[0..4] == "url(" { p = p[4..len(p) - 1] }
|
||||
if len(p) >= 2 and (p[0] == 34 or p[0] == 39) { p = p[1..len(p) - 1] }
|
||||
let other = lib_file(lib_beside(f.path, p))
|
||||
let other = lib_file(ui_st, lib_beside(f.path, p))
|
||||
if other != null {
|
||||
push(f.imp, other)
|
||||
push(f.imp_as, "")
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
# text_fit.ludic - text-fit: shrink 12px draws a line that does not fit its box smaller, down to that
|
||||
# size, and cuts what still does not fit with an ellipsis; line-height (a multiple of the font size,
|
||||
# px or em) spaces a text's lines. Both are inherited, as font properties are.
|
||||
var tf_size: float = 0.0 # the size tf_fit drew at
|
||||
function el_text_fit(n: UiNode, s: string) -> void {
|
||||
function el_text_fit(ui_st: UiState, n: UiNode, s: string) -> void {
|
||||
let parts = lss_split(tpl_words(s), 32)
|
||||
nx(n).fit_min = 0.0
|
||||
if len(parts) >= 2 and parts[0] == "shrink" { nx(n).fit_min = Math.max(el_unit(parts[1]), 1.0) }
|
||||
if len(parts) >= 2 and parts[0] == "shrink" { nx(n).fit_min = Math.max(el_unit(ui_st, parts[1]), 1.0) }
|
||||
}
|
||||
function el_line_h(n: UiNode, s0: string) -> void {
|
||||
function el_line_h(ui_st: UiState, n: UiNode, s0: string) -> void {
|
||||
let s = tpl_words(s0)
|
||||
let x = nx(n)
|
||||
x.lh_kind = 0
|
||||
|
|
@ -19,7 +18,7 @@ function el_line_h(n: UiNode, s0: string) -> void {
|
|||
return
|
||||
}
|
||||
x.lh_kind = 2
|
||||
x.lh_v = el_unit(s)
|
||||
x.lh_v = el_unit(ui_st, s)
|
||||
}
|
||||
# the nearest element, out from this one, that says `what` (0 text-fit, 1 line-height), or null
|
||||
function tf_up(n: UiNode, what: int) -> UiNodeX {
|
||||
|
|
@ -39,14 +38,14 @@ function ly_line_h(n: UiNode) -> float {
|
|||
}
|
||||
# one line to fit its box: at its own size if it does, else smaller down to text-fit's least, then
|
||||
# cut; tf_size says the size it is drawn at
|
||||
function tf_fit(n: UiNode, s: string) -> string {
|
||||
tf_size = n.rsize
|
||||
function tf_fit(ui_st: mut UiState, n: UiNode, s: string) -> string {
|
||||
ui_st.tf_size = n.rsize
|
||||
let x = tf_up(n, 0)
|
||||
let room = n.cw - ly_hin(n)
|
||||
if x == null or room <= 0.0 { return s }
|
||||
let w = ly_text_w(s, tf_size)
|
||||
let w = ly_text_w(ui_st, s, ui_st.tf_size)
|
||||
if w <= room { return s }
|
||||
tf_size = Math.max(x.fit_min, n.rsize * room / w)
|
||||
while tf_size > x.fit_min and ly_text_w(s, tf_size) > room { tf_size = Math.max(x.fit_min, tf_size - 0.5) }
|
||||
return ly_ellipsis(s, tf_size, room)
|
||||
ui_st.tf_size = Math.max(x.fit_min, n.rsize * room / w)
|
||||
while ui_st.tf_size > x.fit_min and ly_text_w(ui_st, s, ui_st.tf_size) > room { ui_st.tf_size = Math.max(x.fit_min, ui_st.tf_size - 0.5) }
|
||||
return ly_ellipsis(ui_st, s, ui_st.tf_size, room)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,29 @@
|
|||
# tooltip.ludic - an element's title="..." shown beside the pointer once it has rested on it for half
|
||||
# a second (or under it, when the keyboard's focus has), in a box styled by .ui-tooltip; a title of
|
||||
# several lines (a newline, or \n) is one .ui-tooltip-text a line
|
||||
var tt_key: string = ""
|
||||
var tt_since: float = 0.0
|
||||
var tt_text: string = ""
|
||||
var tt_x: float = 0.0
|
||||
var tt_y: float = 0.0
|
||||
function tt_track(root: UiNode, x: float, y: float) -> void {
|
||||
function tt_track(ui_st: mut UiState, root: UiNode, x: float, y: float) -> void {
|
||||
var scope = pp_top(root)
|
||||
if scope == null { scope = root }
|
||||
var n = tt_under(scope, x, y)
|
||||
var px = x
|
||||
var py = y
|
||||
let k = tt_focused(scope, x, y)
|
||||
let k = tt_focused(ui_st, scope, x, y)
|
||||
if k != null {
|
||||
n = k
|
||||
px = k.x
|
||||
py = k.y + k.ch - ui_px(14.0)
|
||||
py = k.y + k.ch - ui_px(ui_st, 14.0)
|
||||
}
|
||||
if n == null {
|
||||
tt_key = ""
|
||||
ui_st.tt_key = ""
|
||||
return
|
||||
}
|
||||
if n.key != tt_key {
|
||||
tt_key = n.key
|
||||
tt_since = an_now()
|
||||
if n.key != ui_st.tt_key {
|
||||
ui_st.tt_key = n.key
|
||||
ui_st.tt_since = an_now(ui_st)
|
||||
}
|
||||
tt_text = tt_translate(n, ui_attr_text(n, "title"))
|
||||
tt_x = px
|
||||
tt_y = py
|
||||
ui_st.tt_text = tt_translate(ui_st, n, ui_attr_text(n, "title"))
|
||||
ui_st.tt_x = px
|
||||
ui_st.tt_y = py
|
||||
}
|
||||
# the deepest element under the point that has a title
|
||||
function tt_under(n: UiNode, x: float, y: float) -> UiNode {
|
||||
|
|
@ -42,20 +37,20 @@ function tt_under(n: UiNode, x: float, y: float) -> UiNode {
|
|||
if n.akeys != null and ui_attr_text(n, "title") != "" { return n }
|
||||
return null
|
||||
}
|
||||
function tt_draw(root: UiNode) -> void {
|
||||
if tt_key == "" or tt_text == "" or an_now() - tt_since < 0.5 { return }
|
||||
function tt_draw(ui_st: mut UiState, root: UiNode) -> void {
|
||||
if ui_st.tt_key == "" or ui_st.tt_text == "" or an_now(ui_st) - ui_st.tt_since < 0.5 { return }
|
||||
let n = ct_part(root, "ui-tooltip", "")
|
||||
n.tag = "div"
|
||||
n.parent = root
|
||||
n.children = new []UiNode
|
||||
let lines = tt_lines(tt_text)
|
||||
let lines = tt_lines(ui_st.tt_text)
|
||||
for i in 0 .. len(lines) { push(n.children, ct_part(root, "ui-tooltip-text", lines[i])) }
|
||||
cs_tree(n)
|
||||
ly_measure(n, ly_vp_w * 0.4, ui_px(16.0), root.rfg)
|
||||
let x = Math.min(tt_x + ui_px(16.0), ly_vp_x + ly_vp_w - n.mw)
|
||||
let y = Math.min(tt_y + ui_px(20.0), ly_vp_y + ly_vp_h - n.mh)
|
||||
ly_place(n, x, y, n.mw, n.mh)
|
||||
fr_draw(n)
|
||||
cs_tree(ui_st, n)
|
||||
ly_measure(ui_st, n, ui_st.ly_vp_w * 0.4, ui_px(ui_st, 16.0), root.rfg)
|
||||
let x = Math.min(ui_st.tt_x + ui_px(ui_st, 16.0), ui_st.ly_vp_x + ui_st.ly_vp_w - n.mw)
|
||||
let y = Math.min(ui_st.tt_y + ui_px(ui_st, 20.0), ui_st.ly_vp_y + ui_st.ly_vp_h - n.mh)
|
||||
ly_place(ui_st, n, x, y, n.mw, n.mh)
|
||||
fr_draw(ui_st, n)
|
||||
}
|
||||
# a title's lines: apart at a newline, or at a written \n (which an attribute cannot hold otherwise)
|
||||
function tt_lines(s: string) -> []string {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
# tooltip_text.ludic - a title in the program's language (whole when the translator knows it whole,
|
||||
# else line by line, and not inside translate="no"), and a title for the keyboard's focus
|
||||
var tt_by_keys: bool = false # the focus moved by the keyboard since the pointer last moved
|
||||
var tt_focus_seen: string = ""
|
||||
# the focused element, when the keyboard put the focus there and it has a title; else null
|
||||
function tt_focused(scope: UiNode, x: float, y: float) -> UiNode {
|
||||
if in_last.x != x or in_last.y != y { tt_by_keys = false }
|
||||
if fc_visible and fc_key != "" and fc_key != tt_focus_seen { tt_by_keys = true }
|
||||
tt_focus_seen = fc_key
|
||||
if not tt_by_keys or not fc_visible { return null }
|
||||
let n = fr_node(scope, fc_key)
|
||||
function tt_focused(ui_st: mut UiState, scope: UiNode, x: float, y: float) -> UiNode {
|
||||
if ui_st.in_last.x != x or ui_st.in_last.y != y { ui_st.tt_by_keys = false }
|
||||
if ui_st.fc_visible and ui_st.fc_key != "" and ui_st.fc_key != ui_st.tt_focus_seen { ui_st.tt_by_keys = true }
|
||||
ui_st.tt_focus_seen = ui_st.fc_key
|
||||
if not ui_st.tt_by_keys or not ui_st.fc_visible { return null }
|
||||
let n = fr_node(scope, ui_st.fc_key)
|
||||
if n == null or n.akeys == null or ui_attr_text(n, "title") == "" { return null }
|
||||
return n
|
||||
}
|
||||
function tt_translate(n: UiNode, s: string) -> string {
|
||||
function tt_translate(ui_st: UiState, n: UiNode, s: string) -> string {
|
||||
var at = n
|
||||
while at != null {
|
||||
let say = ui_attr_text(at, "translate")
|
||||
|
|
@ -20,11 +18,11 @@ function tt_translate(n: UiNode, s: string) -> string {
|
|||
if say == "yes" { break }
|
||||
at = at.parent
|
||||
}
|
||||
let whole = ui_tr(s)
|
||||
let whole = ui_tr(ui_st, s)
|
||||
if whole != s { return whole }
|
||||
let lines = tt_lines(s)
|
||||
if len(lines) < 2 { return whole }
|
||||
var out = ui_tr(lines[0])
|
||||
for i in 1 .. len(lines) { out = out + "\n" + ui_tr(lines[i]) }
|
||||
var out = ui_tr(ui_st, lines[0])
|
||||
for i in 1 .. len(lines) { out = out + "\n" + ui_tr(ui_st, lines[i]) }
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export property UiTpl {
|
|||
raw: string = "" # a <style>'s text, which is not a template's
|
||||
where: string = "" # file:line, for an error
|
||||
}
|
||||
function tpl_read(x: Xml, file: string) -> UiTpl {
|
||||
function tpl_read(ui_st: mut UiState, x: Xml, file: string) -> UiTpl {
|
||||
let t = new UiTpl
|
||||
t.tag = Xml.tag(x)
|
||||
t.keys = new []string
|
||||
|
|
@ -24,7 +24,7 @@ function tpl_read(x: Xml, file: string) -> UiTpl {
|
|||
t.on_keys = new []string
|
||||
t.on_acts = new []UiAct
|
||||
t.kids = new []UiTpl
|
||||
t.where = `{file}:{tp_line(x.at)}`
|
||||
t.where = `{file}:{tp_line(ui_st, x.at)}`
|
||||
let err = new []string
|
||||
for i in 0 .. len(x.akeys) {
|
||||
let k: string = x.akeys[i]
|
||||
|
|
@ -40,8 +40,8 @@ function tpl_read(x: Xml, file: string) -> UiTpl {
|
|||
push(t.vals, tpl_value(v, err))
|
||||
}
|
||||
}
|
||||
if tp_mixed(x) { tp_read_mixed(t, x, file, err) } else {
|
||||
for i in 0 .. Xml.child_count(x) { push(t.kids, tpl_read(Xml.child(x, i), file)) }
|
||||
if tp_mixed(x) { tp_read_mixed(ui_st, t, x, file, err) } else {
|
||||
for i in 0 .. Xml.child_count(x) { push(t.kids, tpl_read(ui_st, Xml.child(x, i), file)) }
|
||||
}
|
||||
if t.tag == "style" {
|
||||
t.raw = Xml.text(x)
|
||||
|
|
@ -49,7 +49,7 @@ function tpl_read(x: Xml, file: string) -> UiTpl {
|
|||
}
|
||||
let words = tpl_words(Xml.text(x))
|
||||
if len(words) > 0 and Xml.child_count(x) == 0 { t.text = ex_text(words, err) }
|
||||
for i in 0 .. len(err) { ui_err(`{t.where}: <{t.tag}> {err[i]}`) }
|
||||
for i in 0 .. len(err) { ui_err(ui_st, `{t.where}: <{t.tag}> {err[i]}`) }
|
||||
return t
|
||||
}
|
||||
# on-click is on-press, as a browser would have it
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
# tpl_lines.ludic - which line of its file an element is on
|
||||
# the text being read, and where its lines start, so an element can say which line it is on
|
||||
var tp_nl: []int = new []int
|
||||
function tp_lines(src: string) -> void {
|
||||
tp_nl = new []int
|
||||
function tp_lines(ui_st: mut UiState, src: string) -> void {
|
||||
ui_st.tp_nl = new []int
|
||||
for i in 0 .. len(src) {
|
||||
if src[i] == 10 { push(tp_nl, i) }
|
||||
if src[i] == 10 { push(ui_st.tp_nl, i) }
|
||||
}
|
||||
}
|
||||
function tp_line(at: int) -> int {
|
||||
function tp_line(ui_st: UiState, at: int) -> int {
|
||||
var lo = 0
|
||||
var hi = len(tp_nl)
|
||||
var hi = len(ui_st.tp_nl)
|
||||
while lo < hi {
|
||||
let mid = (lo + hi) / 2
|
||||
if tp_nl[mid] < at { lo = mid + 1 } else { hi = mid }
|
||||
if ui_st.tp_nl[mid] < at { lo = mid + 1 } else { hi = mid }
|
||||
}
|
||||
return lo + 1
|
||||
}
|
||||
|
|
@ -25,7 +24,7 @@ function tp_mixed(x: Xml) -> bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
function tp_read_mixed(t: UiTpl, x: Xml, file: string, err: []string) -> void {
|
||||
function tp_read_mixed(ui_st: mut UiState, t: UiTpl, x: Xml, file: string, err: []string) -> void {
|
||||
for i in 0 .. len(x.mixed) {
|
||||
let m = x.mixed[i]
|
||||
if m.tag == "#text" {
|
||||
|
|
@ -39,6 +38,6 @@ function tp_read_mixed(t: UiTpl, x: Xml, file: string, err: []string) -> void {
|
|||
r.kids = new []UiTpl
|
||||
r.text = ex_text(tpl_words(m.text), err)
|
||||
push(t.kids, r)
|
||||
} else { push(t.kids, tpl_read(m, file)) }
|
||||
} else { push(t.kids, tpl_read(ui_st, m, file)) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,28 @@
|
|||
# transition.ludic - `transition: opacity 0.2s`: when an element's opacity changes from one frame to
|
||||
# the next it eases to the new value over that time instead of jumping (opacity is the property a
|
||||
# game fades most; the others arrive as they are)
|
||||
var tr_keys: []string = new []string
|
||||
var tr_from: []float = new []float
|
||||
var tr_to: []float = new []float
|
||||
var tr_at: []float = new []float
|
||||
function tr_apply(n: UiNode) -> void {
|
||||
function tr_apply(ui_st: mut UiState, n: UiNode) -> void {
|
||||
if n.tr_dur <= 0.0 { return }
|
||||
let now = an_now()
|
||||
let now = an_now(ui_st)
|
||||
let want = n.alpha
|
||||
for i in 0 .. len(tr_keys) {
|
||||
if tr_keys[i] == n.key {
|
||||
if tr_to[i] != want {
|
||||
tr_from[i] = tr_value(i, now, n.tr_dur)
|
||||
tr_to[i] = want
|
||||
tr_at[i] = now
|
||||
for i in 0 .. len(ui_st.tr_keys) {
|
||||
if ui_st.tr_keys[i] == n.key {
|
||||
if ui_st.tr_to[i] != want {
|
||||
ui_st.tr_from[i] = tr_value(ui_st, i, now, n.tr_dur)
|
||||
ui_st.tr_to[i] = want
|
||||
ui_st.tr_at[i] = now
|
||||
}
|
||||
n.alpha = tr_value(i, now, n.tr_dur)
|
||||
n.alpha = tr_value(ui_st, i, now, n.tr_dur)
|
||||
return
|
||||
}
|
||||
}
|
||||
push(tr_keys, n.key)
|
||||
push(tr_from, want)
|
||||
push(tr_to, want)
|
||||
push(tr_at, now)
|
||||
push(ui_st.tr_keys, n.key)
|
||||
push(ui_st.tr_from, want)
|
||||
push(ui_st.tr_to, want)
|
||||
push(ui_st.tr_at, now)
|
||||
}
|
||||
function tr_value(i: int, now: float, dur: float) -> float {
|
||||
let t = Math.clamp((now - tr_at[i]) / dur, 0.0, 1.0)
|
||||
if t >= 1.0 { return tr_to[i] }
|
||||
return tr_from[i] + (tr_to[i] - tr_from[i]) * t
|
||||
function tr_value(ui_st: UiState, i: int, now: float, dur: float) -> float {
|
||||
let t = Math.clamp((now - ui_st.tr_at[i]) / dur, 0.0, 1.0)
|
||||
if t >= 1.0 { return ui_st.tr_to[i] }
|
||||
return ui_st.tr_from[i] + (ui_st.tr_to[i] - ui_st.tr_from[i]) * t
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,24 @@
|
|||
# units.ludic - lengths in px (the renderer's scale), em (the font size here), rem (the root's),
|
||||
# vw and vh (the screen's), and custom properties: --name set on an element, read by var(--name)
|
||||
# in it and everything inside it
|
||||
var el_cur: UiNode = null
|
||||
var el_key: string = ""
|
||||
var ly_vp_x: float = 0.0
|
||||
var ly_vp_y: float = 0.0
|
||||
var ly_vp_w: float = 0.0
|
||||
var ly_vp_h: float = 0.0
|
||||
function el_unit(s: string) -> float {
|
||||
function el_unit(ui_st: UiState, s: string) -> float {
|
||||
let f = el_num(s)
|
||||
let n = len(s)
|
||||
if n > 3 and s[n - 3..n] == "rem" { return f * el_px(16.0) }
|
||||
if n > 3 and s[n - 3..n] == "rem" { return f * el_px(ui_st, 16.0) }
|
||||
if n > 2 and s[n - 2..n] == "em" {
|
||||
if el_cur == null { return f * el_px(16.0) }
|
||||
if el_key == "size" and el_cur.parent != null { return f * cs_fs(el_cur.parent) }
|
||||
return f * cs_fs(el_cur)
|
||||
if ui_st.el_cur == null { return f * el_px(ui_st, 16.0) }
|
||||
if ui_st.el_key == "size" and ui_st.el_cur.parent != null { return f * cs_fs(ui_st, ui_st.el_cur.parent) }
|
||||
return f * cs_fs(ui_st, ui_st.el_cur)
|
||||
}
|
||||
if n > 2 and s[n - 2..n] == "vw" { return f * ly_vp_w / 100.0 }
|
||||
if n > 2 and s[n - 2..n] == "vh" { return f * ly_vp_h / 100.0 }
|
||||
return el_px(f)
|
||||
if n > 2 and s[n - 2..n] == "vw" { return f * ui_st.ly_vp_w / 100.0 }
|
||||
if n > 2 and s[n - 2..n] == "vh" { return f * ui_st.ly_vp_h / 100.0 }
|
||||
return el_px(ui_st, f)
|
||||
}
|
||||
# the font size an element has so far in its cascade (its own, else its parent's)
|
||||
function cs_fs(n: UiNode) -> float {
|
||||
function cs_fs(ui_st: UiState, n: UiNode) -> float {
|
||||
if n.size > 0.0 { return n.size }
|
||||
if n.parent != null { return cs_fs(n.parent) * zm_own(n) }
|
||||
return ui_px(16.0) * zm_own(n)
|
||||
if n.parent != null { return cs_fs(ui_st, n.parent) * zm_own(n) }
|
||||
return ui_px(ui_st, 16.0) * zm_own(n)
|
||||
}
|
||||
function cs_var_set(n: UiNode, name: string, value: string) -> void {
|
||||
if n.var_names == null {
|
||||
|
|
@ -53,7 +47,7 @@ function cs_var(n: UiNode, name: string) -> string {
|
|||
return null
|
||||
}
|
||||
# a value with var(--name) or var(--name, fallback) in it, with each replaced by what it names
|
||||
function cs_vars_in(n: UiNode, v: Val) -> Val {
|
||||
function cs_vars_in(ui_st: mut UiState, n: UiNode, v: Val) -> Val {
|
||||
if Value.kind(v) != 4 { return v }
|
||||
let s = Value.as_str(v)
|
||||
var at = 0
|
||||
|
|
@ -70,7 +64,7 @@ function cs_vars_in(n: UiNode, v: Val) -> Val {
|
|||
if s[j] == 41 { depth -= 1 }
|
||||
j += 1
|
||||
}
|
||||
out = out + s[at..i] + cs_var_ref(n, s[i + 4..j - 1])
|
||||
out = out + s[at..i] + cs_var_ref(ui_st, n, s[i + 4..j - 1])
|
||||
at = j
|
||||
i = j
|
||||
} else { i += 1 }
|
||||
|
|
@ -78,11 +72,11 @@ function cs_vars_in(n: UiNode, v: Val) -> Val {
|
|||
if not found { return v }
|
||||
return Value.str(tpl_words(out + s[at..len(s)]))
|
||||
}
|
||||
function cs_var_ref(n: UiNode, inner: string) -> string {
|
||||
function cs_var_ref(ui_st: mut UiState, n: UiNode, inner: string) -> string {
|
||||
let parts = lss_split(inner, 44)
|
||||
let v = cs_var(n, tpl_words(parts[0]))
|
||||
if v != null { return v }
|
||||
if len(parts) > 1 { return tpl_words(inner[len(parts[0]) + 1..len(inner)]) }
|
||||
ui_err(`var({tpl_words(parts[0])}): no element here or above sets it`)
|
||||
ui_err(ui_st, `var({tpl_words(parts[0])}): no element here or above sets it`)
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
# zoom.ludic - zoom: 0.8 (or 80%, or calc(), min() and the rest: min(1, calc(100vw / 1800px))) makes
|
||||
# an element and everything inside it that much larger or smaller, its lengths, its text and its
|
||||
# layout; zooms multiply down the tree. A length in px is zoomed as it is read (el_px).
|
||||
function el_zoom(n: UiNode, s0: string) -> void {
|
||||
function el_zoom(ui_st: mut UiState, n: UiNode, s0: string) -> void {
|
||||
let s = tpl_words(s0)
|
||||
var z = 1.0
|
||||
if ca_is(s) {
|
||||
if ca_eval(s) {
|
||||
z = ca_num
|
||||
if not ca_bare { ui_err(`zoom: {s} - a zoom is a plain number (divide a length by a length)`) }
|
||||
if ca_eval(ui_st, s) {
|
||||
z = ui_st.ca_num
|
||||
if not ui_st.ca_bare { ui_err(ui_st, `zoom: {s} - a zoom is a plain number (divide a length by a length)`) }
|
||||
}
|
||||
} else if len(s) > 1 and s[len(s) - 1] == 37 { z = el_num(s) / 100.0 } else if s != "normal" { z = el_num(s) }
|
||||
if z <= 0.0 { z = 1.0 }
|
||||
|
|
@ -29,9 +29,9 @@ function zm_all(n: UiNode) -> float {
|
|||
}
|
||||
# a length of `v` design pixels on the element being styled: the renderer's scale and every zoom
|
||||
# around it (its own too, except while its zoom itself is read)
|
||||
function el_px(v: float) -> float {
|
||||
if el_cur == null { return ui_px(v) }
|
||||
if el_key == "zoom" and el_cur.parent != null { return ui_px(v) * zm_all(el_cur.parent) }
|
||||
if el_key == "zoom" { return ui_px(v) }
|
||||
return ui_px(v) * zm_all(el_cur)
|
||||
function el_px(ui_st: UiState, v: float) -> float {
|
||||
if ui_st.el_cur == null { return ui_px(ui_st, v) }
|
||||
if ui_st.el_key == "zoom" and ui_st.el_cur.parent != null { return ui_px(ui_st, v) * zm_all(ui_st.el_cur.parent) }
|
||||
if ui_st.el_key == "zoom" { return ui_px(ui_st, v) }
|
||||
return ui_px(ui_st, v) * zm_all(ui_st.el_cur)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue