ludic.ui, runtime: the last per-frame allocations in the interface - the top popover found without a list, the popover draw list and the clip stack kept, a select's options in a list the node keeps (and matched to its value without writing the index), a number's text kept on its Value until it changes (value_num_set / Value.num_text), a key's label kept per key code until the layout changes it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 11:11:48 +03:00
parent 3915af34c2
commit 50f255eea4
10 changed files with 98 additions and 40 deletions

View file

@ -149,6 +149,7 @@ export state UiState {
pe_key: string = "" # the element the pointer is captured by
pe_btn: int = 0
pop_list: []UiNode = new []UiNode
pop_spare: []UiNode = new []UiNode
pp_bx: float = 0.0
pp_by: float = 0.0
pp_bw: float = 0.0

View file

@ -67,8 +67,11 @@ function ct_build_range(ui_st: mut UiState, n: UiNode) -> void {
push(n.children, ct_part(ui_st, n, "ui-value", ct_value_text(n)))
}
function ct_build_select(ui_st: mut UiState, n: UiNode) -> void {
let opts = n.children
n.children = new []UiNode
if n.opts == null { n.opts = new []UiNode } # its options moved to a list the node keeps, not a new one
let opts = n.opts
List.clear(opts)
for i in 0 .. len(n.children) { push(opts, n.children[i]) }
List.clear(n.children)
ct_label(ui_st, n)
var shown = ""
let at = ct_option_at(n, opts)
@ -76,14 +79,13 @@ function ct_build_select(ui_st: mut UiState, n: UiNode) -> void {
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
function ct_option_at(n: UiNode, opts: []UiNode) -> int {
let v = ui_attr_text(n, "value")
for i in 0 .. len(opts) {
let ov = ui_attr_text(opts[i], "value")
if ov == v or (ov == "" and string(i) == v) { return i }
if ov == v or (ov == "" and ct_is_int(v, i)) { return i }
}
return -1
}

View file

@ -50,3 +50,16 @@ 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("")) }
}
# the text is exactly i's digits, compared without writing i out
function ct_is_int(s: string, i: int) -> bool {
if i < 0 or len(s) == 0 { return false }
var n = i
var at = len(s) - 1
while at >= 0 {
if s[at] != 48 + n % 10 { return false }
n = n / 10
at -= 1
if n == 0 { return at < 0 }
}
return false
}

View file

@ -18,9 +18,9 @@ function fr_clip_push(ui_st: mut UiState, x: float, y: float, w: float, h: float
ui_st.ui_be.clip(x0, y0, Math.max(x1 - x0, 0.0), Math.max(y1 - y0, 0.0))
}
function fr_clip_pop(ui_st: mut UiState) -> void {
var keep = new []float
for i in 0 .. len(ui_st.fr_clips) - 4 { push(keep, ui_st.fr_clips[i]) }
ui_st.fr_clips = keep
for i in 0 .. 4 { # the top rectangle taken off in place
if len(ui_st.fr_clips) > 0 { List.pop(ui_st.fr_clips) }
}
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() }
}

View file

@ -38,7 +38,7 @@ function nd_reset_b(n: UiNode) -> void {
n.scroll_y = 0.0
n.bar_c = -1
n.bar_track = -2
n.opts = null
if n.opts != null { List.clear(n.opts) }
n.bg_img = ""
n.bi_src = ""
n.bi_slice = 0.0

View file

@ -11,10 +11,13 @@ function pp_find(n: UiNode, out: []UiNode) -> void {
}
# the topmost popover open in this tree, or null
function pp_top(root: UiNode) -> UiNode {
let all = new []UiNode
pp_find(root, all)
if len(all) == 0 { return null }
return all[len(all) - 1]
if root.popover { return root } # the last one pp_find would list, found with no list made
var found: UiNode = null
for i in 0 .. len(root.children) {
let f = pp_top(root.children[i])
if f != null { found = f }
}
return found
}
# the pointer against the popover: inside it is as usual; a press outside closes it and is spent
function pp_pointer(ui_st: mut UiState, top: UiNode) -> bool {
@ -26,7 +29,9 @@ function pp_pointer(ui_st: mut UiState, top: UiNode) -> bool {
return true
}
function pp_draw_top(ui_st: mut UiState) -> void {
let list = ui_st.pop_list
ui_st.pop_list = new []UiNode
let list = ui_st.pop_list # drawn from while what they open goes to the spare, then swapped
ui_st.pop_list = ui_st.pop_spare
List.clear(ui_st.pop_list)
for i in 0 .. len(list) { fr_draw(ui_st, list[i]) }
ui_st.pop_spare = list
}

View file

@ -13,17 +13,12 @@ function vl_truthy(v: Val) -> bool {
function vl_text(v: Val) -> string {
let k = Value.kind(v)
if k == 0 { return "" }
if k == 1 { return string(Value.as_int(v)) }
if k == 1 or k == 2 or k == 7 { return Value.num_text(v) } # kept on the Value until it changes
if k == 3 {
if Value.as_int(v) != 0 { return "true" }
return "false"
}
if k == 4 { return Value.as_str(v) }
if k == 2 or k == 7 {
let f = Value.as_float(v)
if f == float(int(f)) { return string(int(f)) }
return string(f)
}
return Json.encode(v)
}
function vl_arith(ui_st: mut UiState, op: string, a: Val, b: Val) -> Val {