ludic.ui: a screen's nodes and envs are pooled and used again - two generations a screen, reset in place, their own lists emptied not replaced; a tree is good until its screen is built twice more (benchmark 256 -> 110 KB a frame)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-27 23:33:48 +03:00
parent 83200a453b
commit d1c9359e96
19 changed files with 307 additions and 83 deletions

View file

@ -53,10 +53,10 @@ program UiPointer {
let active = down.children[2].bg
let mid = said(down)
frame(ui_st, hold.x + 5.0, hold.y + 90.0, false, 0.0, false)
let off = frame(ui_st, -1.0, -1.0, false, 0.0, false)
let off = said(frame(ui_st, -1.0, -1.0, false, 0.0, false))
frame(ui_st, -1.0, -1.0, false, 0.0, true)
let keyed = said(frame(ui_st, -1.0, -1.0, false, 0.0, true))
let up = frame(ui_st, -1.0, -1.0, false, 0.0, false)
print(`{ui_pointer_st.log}dragged {int(ui_pointer_st.dragged)} | {mid} active {active} | {said(off)} | {keyed} | {said(up)} {up.children[2].bg}`)
print(`{ui_pointer_st.log}dragged {int(ui_pointer_st.dragged)} | {mid} active {active} | {off} | {keyed} | {said(up)} {up.children[2].bg}`)
}
}

View file

@ -32,6 +32,8 @@ function ui_default_backend() -> UiBackend {
return UiRenderers[0].make()
}
export state UiState {
pl_pools: []UiPool = new []UiPool # pool.ludic: each screen's nodes and envs, used again
pl_cur: UiPool = null
an_frames: []UiKeyframes = new []UiKeyframes
an_keys: []string = new []string
an_start: []float = new []float

View file

@ -47,7 +47,7 @@ function bd_each(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiN
let at_name = bd_name(k, "index", "index")
let by = tpl_attr(k, "key")
for j in 0 .. Value.count(list) {
let le = env_new(e)
let le = env_new(ui_st, e)
env_bind(le, as_name, Value.at(list, j))
env_bind(le, at_name, Value.int(j))
var id = string(j)
@ -77,7 +77,7 @@ function bd_comp(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiN
}
if k.comp == null { return false }
let c = k.comp
let ce = env_new(null)
let ce = env_new(ui_st, null)
ce.model = e.model
ce.ctx = e.ctx
for i in 0 .. len(c.keys) {
@ -86,7 +86,6 @@ function bd_comp(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiN
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
for i in 0 .. len(k.on_keys) { push(ce.on_envs, e) }
ce.slot = k
ce.slot_env = e

View file

@ -11,7 +11,7 @@ function bd_class(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []Ui
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)
let ce = env_new(ui_st, null)
ce.model = cls.model(it.ptr)
ce.inst = it
ce.ctx = e.ctx
@ -19,7 +19,6 @@ function bd_class(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []Ui
ce.slot_env = e
ce.on_names = k.on_keys
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(ui_st, key, k.on_keys, k.on_acts, e)
let at = len(out)

View file

@ -1,14 +1,14 @@
# 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(ui_st: mut UiState, k: UiTpl, e: UiEnv) -> UiEnv {
let le = env_new(e)
let le = env_new(ui_st, 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(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
let cx = env_new(e.ctx)
let cx = env_new(ui_st, e.ctx)
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)
let pe = env_new(ui_st, e)
pe.ctx = cx
bd_kids(ui_st, k.kids, pe, key, out)
}
@ -18,12 +18,11 @@ function bd_group(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []Ui
bd_kids(ui_st, k.kids, e, key, out)
return
}
let t = new UiNode
let t = nd_take(ui_st)
t.kind = UI_TEXT
t.key = key
t.text = ui_tr(ui_st, ui_eval_text(ui_st, k.text, e))
t.env = e
t.children = new []UiNode
push(out, t)
}
# text inside text - <p>Hi <b>there</b></p>, <h3><slot/></h3> - reads as one line of it
@ -37,5 +36,5 @@ function el_inline(n: UiNode) -> void {
s = s + c.text
}
n.text = s
n.children = new []UiNode
List.clear(n.children)
}

View file

@ -9,11 +9,14 @@ function cs_tree(ui_st: mut UiState, n: UiNode) -> void {
}
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
var kept = 0
for i in 0 .. len(n.children) {
if not n.children[i].hidden { push(shown, n.children[i]) }
if not n.children[i].hidden {
n.children[kept] = n.children[i]
kept += 1
}
}
n.children = shown
while len(n.children) > kept { List.pop(n.children) }
cs_order(n)
}
# twice: the font size (and zoom) first, from every rule, so an em anywhere reads the size the

View file

@ -4,18 +4,13 @@
# input[type=text|number|key] .ui-label .ui-field > .ui-value (+ .ui-caret while focused)
# select .ui-label .ui-prev .ui-value .ui-next (its <option>s hidden)
# with a note="...", the label is .ui-labels > .ui-label + .ui-note
function ct_part(n: UiNode, cls: string, text: string) -> UiNode {
let p = new UiNode
function ct_part(ui_st: mut UiState, n: UiNode, cls: string, text: string) -> UiNode {
let p = nd_take(ui_st)
p.key = `{n.key}/{cls}`
p.tag = "span"
p.env = n.env
p.sheet = n.sheet
p.children = new []UiNode
p.classes = new []string
push(p.classes, cls)
p.akeys = new []string
p.avals = new []string
p.vals = new []Val
if text != "" {
p.kind = UI_TEXT
p.text = text
@ -23,18 +18,18 @@ function ct_part(n: UiNode, cls: string, text: string) -> UiNode {
return p
}
# <progress value max> and <meter value min max>: a track with a fill as far as the value
function ct_build_bar(n: UiNode) -> void {
function ct_build_bar(ui_st: mut UiState, n: UiNode) -> void {
n.dir = UI_ROW
let lo = Value.as_float(ui_attr(n, "min"))
var hi = Value.as_float(ui_attr(n, "max"))
if hi <= lo { hi = lo + 1.0 }
let fill = ct_part(n, "ui-fill", "")
let fill = ct_part(ui_st, n, "ui-fill", "")
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(ui_st: UiState, n: UiNode) -> void {
function ct_build(ui_st: mut UiState, n: UiNode) -> void {
if n.tag == "progress" or n.tag == "meter" {
ct_build_bar(n)
ct_build_bar(ui_st, n)
return
}
n.dir = UI_ROW
@ -46,41 +41,41 @@ function ct_build(ui_st: UiState, n: UiNode) -> void {
}
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", ""))
let track = ct_part(ui_st, n, "ui-track", "")
push(track.children, ct_part(ui_st, n, "ui-knob", ""))
push(n.children, track)
} else if t == "range" {
ct_build_range(n)
ct_build_range(ui_st, n)
} else {
let field = ct_part(n, "ui-field", "")
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", "")) }
let field = ct_part(ui_st, n, "ui-field", "")
push(field.children, ct_part(ui_st, n, "ui-value", ct_field_text(ui_st, n)))
if ui_st.fc_key == n.key and t != "key" { push(field.children, ct_part(ui_st, n, "ui-caret", "")) }
push(n.children, field)
}
}
function ct_build_range(n: UiNode) -> void {
function ct_build_range(ui_st: mut UiState, n: UiNode) -> void {
let lo = Value.as_float(ui_attr(n, "min"))
var hi = Value.as_float(ui_attr(n, "max"))
if hi <= lo { hi = lo + 1.0 }
let v = Value.as_float(ui_attr(n, "value"))
let track = ct_part(n, "ui-track", "")
let fill = ct_part(n, "ui-fill", "")
let track = ct_part(ui_st, n, "ui-track", "")
let fill = ct_part(ui_st, n, "ui-fill", "")
fill.w_pct = Math.clamp((v - lo) / (hi - lo), 0.0, 1.0) * 100.0
push(track.children, fill)
push(track.children, ct_part(n, "ui-thumb", ""))
push(track.children, ct_part(ui_st, n, "ui-thumb", ""))
push(n.children, track)
push(n.children, ct_part(n, "ui-value", ct_value_text(n)))
push(n.children, ct_part(ui_st, n, "ui-value", ct_value_text(n)))
}
function ct_build_select(ui_st: UiState, n: UiNode) -> void {
function ct_build_select(ui_st: mut UiState, n: UiNode) -> void {
let opts = n.children
n.children = new []UiNode
ct_label(ui_st, n)
var shown = ""
let at = ct_option_at(n, opts)
if at >= 0 { shown = opts[at].text }
push(n.children, ct_part(n, "ui-prev", "<"))
push(n.children, ct_part(n, "ui-value", shown))
push(n.children, ct_part(n, "ui-next", ">"))
push(n.children, ct_part(ui_st, n, "ui-prev", "<"))
push(n.children, ct_part(ui_st, n, "ui-value", shown))
push(n.children, ct_part(ui_st, n, "ui-next", ">"))
n.opts = opts
}
# which option the select's value names: an option's value="...", else its place

View file

@ -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(ui_st: UiState, n: UiNode) -> void {
function ct_label(ui_st: mut 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(ui_st, label))) }
if label != "" { push(n.children, ct_part(ui_st, 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(ui_st, label))) }
push(both.children, ct_part(n, "ui-note", ui_tr(ui_st, note)))
let both = ct_part(ui_st, n, "ui-labels", "")
if label != "" { push(both.children, ct_part(ui_st, n, "ui-label", ui_tr(ui_st, label))) }
push(both.children, ct_part(ui_st, n, "ui-note", ui_tr(ui_st, note)))
push(n.children, both)
}
function ct_value_text(n: UiNode) -> string {

View file

@ -4,17 +4,12 @@ const EL_BOXES: string = " col div progress meter section header footer nav main
const EL_TEXTS: string = " text span p label h1 h2 h3 h4 h5 h6 strong em small b i a option "
function bd_elem(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string) -> UiNode {
ui_st.ui_at = k.where
let n = new UiNode
let n = nd_take(ui_st)
n.key = key
n.tag = k.tag
n.env = e
n.sheet = k.sheet
n.style = k.style
n.children = new []UiNode
n.classes = new []string
n.akeys = new []string
n.avals = new []string
n.vals = new []Val
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)
@ -28,7 +23,7 @@ function bd_elem(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string) -> UiNode
let say = ui_attr_text(n, "translate")
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))) }
if k.text != null { el_text(ui_st, 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(ui_st, n) }
ui_st.tr_no = was_no
@ -45,16 +40,15 @@ function el_kind(ui_st: mut UiState, n: UiNode, tag: string) -> void {
} 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 {
function el_text(ui_st: mut UiState, n: UiNode, s: string) -> void {
if n.kind != UI_BOX and n.kind != UI_SCROLL {
n.text = s
return
}
let t = new UiNode
let t = nd_take(ui_st)
t.kind = UI_TEXT
t.text = s
t.env = n.env
t.children = new []UiNode
push(n.children, t)
}
# id, class, hidden, disabled and src say what the element is; the rest are applied after the rules

View file

@ -16,10 +16,8 @@ export property UiEnv {
inst: UiInst = null # the Ludic component whose template this is
}
function env_new(parent: UiEnv) -> UiEnv {
let e = new UiEnv
e.names = new []string
e.vals = new []Val
function env_new(ui_st: mut UiState, parent: UiEnv) -> UiEnv {
let e = env_take(ui_st)
e.parent = parent
if parent != null {
e.model = parent.model

View file

@ -39,20 +39,16 @@ export function ui_nodes(ui_st: mut UiState, screen: string, view: UiView) -> Ui
return null
}
let t = ui_st.lib_screens[si]
pl_begin(ui_st, screen)
lc_begin(ui_st)
ui_st.ui_view = view
let e = env_new(null)
let e = env_new(ui_st, null)
if view != null and view.model != null { e.model = view.model() } else { e.model = Value.object() }
e.state = st_for(ui_st, screen, t, e)
let root = new UiNode
let root = nd_take(ui_st)
root.key = screen
root.children = new []UiNode
root.env = e
root.sheet = t.sheet
root.classes = new []string
root.akeys = new []string
root.avals = new []string
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(ui_st, t.vals[i], e)) }

View file

@ -7,6 +7,9 @@ import "node.ludic"
import "node_more.ludic"
import "view.ludic"
import "backend.ludic"
import "pool.ludic"
import "pool_reset.ludic"
import "pool_reset_more.ludic"
import "expr.ludic"
import "expr_parse.ludic"
import "expr_prim.ludic"

View file

@ -64,7 +64,7 @@ function nt_run(ui_st: mut UiState, n: UiNode, name: string, value: Val) -> void
if (lk_in(PE_NAMES, name) or name == "hold") and Value.kind(value) == 6 { ev = value }
Value.put(ev, "name", Value.str(name))
Value.put(ev, "value", value)
let e = env_new(n.env)
let e = env_new(ui_st, n.env)
env_bind(e, "event", ev)
act_run(ui_st, n.on_acts[i], e)
}

View file

@ -0,0 +1,84 @@
# pool.ludic - what a frame is built of is kept and used again: ludic.ui builds a screen's tree anew
# every frame, and Ludic frees nothing, so a node or an env made per element per frame was a leak
# (the valley's HUD: ~100 MB/s). Each screen keeps two generations and alternates, so last frame's
# tree - which an unmount's env, a hover or ui_box still reads - stays whole while this one is built.
# So a host may read a tree until the same screen has been built twice more, and never keep it longer.
export property UiPool {
key: string = ""
gen: int = 0
nodes0: []UiNode = null
nodes1: []UiNode = null
envs0: []UiEnv = null
envs1: []UiEnv = null
un: int = 0
ue: int = 0
}
# a build of `screen` begins: its other generation is handed out again from the start
function pl_begin(ui_st: mut UiState, screen: string) -> void {
var p: UiPool = null
for i in 0 .. len(ui_st.pl_pools) {
if ui_st.pl_pools[i].key == screen { p = ui_st.pl_pools[i] }
}
if p == null {
p = new UiPool
p.key = screen
p.nodes0 = new []UiNode
p.nodes1 = new []UiNode
p.envs0 = new []UiEnv
p.envs1 = new []UiEnv
push(ui_st.pl_pools, p)
}
p.gen = 1 - p.gen
p.un = 0
p.ue = 0
ui_st.pl_cur = p
}
# a node, blank, with its own lists (empty) - from the pool when a build is under way
function nd_take(ui_st: mut UiState) -> UiNode {
let p = ui_st.pl_cur
if p == null { return nd_fresh() }
var list = p.nodes0
if p.gen == 1 { list = p.nodes1 }
if p.un < len(list) {
let n = list[p.un]
p.un += 1
nd_reset(n)
return n
}
let n = nd_fresh()
push(list, n)
p.un += 1
return n
}
function nd_fresh() -> UiNode {
let n = new UiNode
n.children = new []UiNode
n.classes = new []string
n.akeys = new []string
n.avals = new []string
n.vals = new []Val
return n
}
function env_take(ui_st: mut UiState) -> UiEnv {
let p = ui_st.pl_cur
if p == null { return env_fresh() }
var list = p.envs0
if p.gen == 1 { list = p.envs1 }
if p.ue < len(list) {
let e = list[p.ue]
p.ue += 1
env_reset(e)
return e
}
let e = env_fresh()
push(list, e)
p.ue += 1
return e
}
function env_fresh() -> UiEnv {
let e = new UiEnv
e.names = new []string
e.vals = new []Val
e.on_envs = new []UiEnv
return e
}

View file

@ -0,0 +1,61 @@
# pool_reset.ludic - a pooled node put back to its defaults: every field, and the lists it owns
# emptied and kept (generated from node.ludic - a field added there is added here)
function nd_reset(n: UiNode) -> void {
nd_reset_a(n)
nd_reset_b(n)
}
function nd_reset_a(n: UiNode) -> void {
n.kind = 0
n.dir = 0
n.text = ""
n.key = ""
n.tag = ""
n.id = ""
if n.classes != null { List.clear(n.classes) }
if n.akeys != null { List.clear(n.akeys) }
if n.avals != null { List.clear(n.avals) }
if n.vals != null { List.clear(n.vals) }
n.style = null
n.sheet = null
if n.ups != null { List.clear(n.ups) }
n.parent = null
n.index = 0
n.src = ""
n.size = 0.0
n.w = -1000000.0
n.h = -1000000.0
n.w_pct = 0.0
n.h_pct = 0.0
n.pt = 0.0
n.pr = 0.0
n.pb = 0.0
n.pl = 0.0
n.mt = 0.0
n.mr = 0.0
n.mb = 0.0
n.ml = 0.0
n.border = 0.0
n.bt = 0.0
n.br = 0.0
n.bb = 0.0
n.bl = 0.0
n.border_c = -2
n.gap = 0.0
n.align = 0
n.justify = 0
n.talign = 0
n.self = -1
n.grow = 0.0
n.wrap = false
n.min_w = 0.0
n.max_w = 0.0
n.min_h = 0.0
n.max_h = 0.0
n.hidden = false
n.pos = 0
n.top = -1000000.0
n.right = -1000000.0
n.bottom = -1000000.0
n.left = -1000000.0
n.z = 0
}

View file

@ -0,0 +1,96 @@
# pool_reset_more.ludic - the rest of a pooled node's fields, its extras and an env, to their defaults
function nd_reset_b(n: UiNode) -> void {
n.nowrap = false
n.ellipsis = false
if n.lines != null { List.clear(n.lines) }
if n.var_names != null { List.clear(n.var_names) }
if n.var_vals != null { List.clear(n.var_vals) }
n.fg = -1
n.fg_a = 1.0
n.bg = -2
n.bg_a = 1.0
n.border_a = 1.0
n.rfg_a = 1.0
n.alpha = 1.0
n.enabled = true
n.act = null
n.on_names = null
n.on_acts = null
n.native = null
n.env = null
if n.children != null { List.clear(n.children) }
n.mw = 0.0
n.mh = 0.0
n.content_h = 0.0
n.x = 0.0
n.y = 0.0
n.cw = 0.0
n.ch = 0.0
n.rsize = 0.0
n.rfg = 0
n.hovered = false
n.focusable = false
n.radius = 0.0
n.outline_w = 0.0
n.outline_c = -1
n.outline_a = 1.0
n.outline_off = 0.0
n.scroll_y = 0.0
n.bar_c = -1
n.bar_track = -2
n.opts = null
n.bg_img = ""
n.bi_src = ""
n.bi_slice = 0.0
n.bi_width = 0.0
n.sh_x = 0.0
n.sh_y = 0.0
n.sh_c = -2
n.sh_a = 1.0
n.anim = ""
n.anim_dur = 0.0
n.anim_loop = false
n.anim_alt = false
n.tr_dur = 0.0
n.popover = false
if n.ex != null { nx_reset(n.ex) }
}
function nx_reset(x: UiNodeX) -> void {
x.shrink = -1.0
x.order = 0
x.w_off = 0.0
x.h_off = 0.0
x.mh_min = 0.0
x.has_min = false
x.tsh_set = false
x.tsh_x = 0.0
x.tsh_y = 0.0
x.tsh_c = -2
x.tsh_a = 1.0
x.no_pointer = false
x.fit_min = 0.0
x.lh_kind = 0
x.lh_v = 0.0
x.ip_t = 0.0
x.ip_r = 0.0
x.ip_b = 0.0
x.ip_l = 0.0
x.grad = null
x.ratio = 0.0
x.fit_obj = 0
x.zoom = 1.0
}
function env_reset(e: UiEnv) -> void {
if e.names != null { List.clear(e.names) }
if e.vals != null { List.clear(e.vals) }
e.state = null
e.parent = null
e.model = null
e.slot = null
e.slot_env = null
e.on_names = null
e.on_acts = null
if e.on_envs != null { List.clear(e.on_envs) }
e.ctx = null
e.inst = null
}

View file

@ -1,11 +1,12 @@
# 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(ui_st: mut UiState, cls: UiClass) -> UiNode {
pl_begin(ui_st, cls.name)
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 root = fr_root(ui_st, cls.name, cls.sheet)
let use = new UiTpl
use.tag = cls.name
use.cls = cls
@ -14,7 +15,7 @@ function fr_class_nodes(ui_st: mut UiState, cls: UiClass) -> UiNode {
use.on_keys = new []string
use.on_acts = new []UiAct
use.kids = new []UiTpl
let e = env_new(null)
let e = env_new(ui_st, null)
e.model = Value.object()
root.env = e
bd_class(ui_st, use, e, cls.name, root.children)
@ -22,15 +23,10 @@ function fr_class_nodes(ui_st: mut UiState, cls: UiClass) -> UiNode {
cs_tree(ui_st, root)
return root
}
function fr_root(key: string, sheet: UiSheet) -> UiNode {
let root = new UiNode
function fr_root(ui_st: mut UiState, key: string, sheet: UiSheet) -> UiNode {
let root = nd_take(ui_st)
root.key = key
root.tag = "screen"
root.sheet = sheet
root.children = new []UiNode
root.classes = new []string
root.akeys = new []string
root.avals = new []string
root.vals = new []Val
return root
}

View file

@ -3,10 +3,10 @@
# it where it was taken; a press on the track jumps the thumb's middle to the pointer and holds it there.
# the bar's parts, styled as descendants of the box, and where they are at this offset
function sb_geo(ui_st: mut UiState, n: UiNode, off: float, room: float, most: float) -> void {
let bar = ct_part(n, "ui-scrollbar", "")
let bar = ct_part(ui_st, n, "ui-scrollbar", "")
bar.tag = "div"
bar.parent = n
push(bar.children, ct_part(n, "ui-thumb", ""))
push(bar.children, ct_part(ui_st, n, "ui-thumb", ""))
cs_tree(ui_st, bar)
ui_st.sb_bar = bar
ui_st.sb_w = ui_px(ui_st, 6.0)

View file

@ -39,12 +39,11 @@ function tt_under(n: UiNode, x: float, y: float) -> UiNode {
}
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", "")
let n = ct_part(ui_st, root, "ui-tooltip", "")
n.tag = "div"
n.parent = root
n.children = new []UiNode
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])) }
for i in 0 .. len(lines) { push(n.children, ct_part(ui_st, root, "ui-tooltip-text", lines[i])) }
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)