ludic.ui: splits, words, gradients and keys are made once per text (a bounded memo), sides without a list - benchmark 110 -> 63 KB a frame
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
978f128755
commit
1d40f6d95a
18 changed files with 172 additions and 41 deletions
|
|
@ -55,7 +55,7 @@ function an_apply(ui_st: mut UiState, n: UiNode) -> void {
|
|||
ui_err(ui_st, `animation {n.anim}: there is no @keyframes of that name`)
|
||||
return
|
||||
}
|
||||
var t = (an_now(ui_st) - an_began(ui_st, `{n.key}@{n.anim}`)) / n.anim_dur
|
||||
var t = (an_now(ui_st) - an_began(ui_st, mm_join(ui_st, n.key, "@", n.anim))) / n.anim_dur
|
||||
if n.anim_loop {
|
||||
let lap = int(t)
|
||||
t = t - float(lap)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ function el_apply(ui_st: mut UiState, n: UiNode, key: string, v0: Val) -> bool {
|
|||
return true
|
||||
}
|
||||
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)
|
||||
return el_size(ui_st, n, k, v) or el_box(ui_st, n, k, v) or el_flex(ui_st, 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 {
|
||||
# the CSS name to the short one; literals, so asking allocates nothing (asked per rule per element)
|
||||
|
|
@ -47,12 +47,12 @@ 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 {
|
||||
function el_flex(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
||||
let s = vl_text(v)
|
||||
if key == "dir" {
|
||||
n.dir = UI_COL
|
||||
if s == "row" { n.dir = UI_ROW }
|
||||
} 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 }
|
||||
} else if key == "grow" { n.grow = el_num(s) } else if key == "flex" { el_flex_short(ui_st, 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(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
||||
|
|
@ -76,7 +76,7 @@ function el_look(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
|||
}
|
||||
# border: 1 #ffcc00, or 1px solid accent
|
||||
function el_border(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
||||
let parts = lss_split(s, 32)
|
||||
let parts = mm_split(ui_st, 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(ui_st, p)) } else if p == "none" { el_sides_border(n, 0.0) } else if len(p) > 0 and p != "solid" {
|
||||
|
|
|
|||
|
|
@ -18,20 +18,27 @@ function el_box(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
|||
}
|
||||
# top right bottom left out of 1 to 4 lengths
|
||||
function el_sides(ui_st: mut UiState, v: Val) -> void {
|
||||
let parts = new []float
|
||||
if vl_num(v) { push(parts, el_px(ui_st, Value.as_float(v))) } else {
|
||||
let words = el_words(vl_text(v))
|
||||
var n = 0
|
||||
if vl_num(v) {
|
||||
ui_st.el_s0 = el_px(ui_st, Value.as_float(v))
|
||||
n = 1
|
||||
} else {
|
||||
let words = mm_words(ui_st, vl_text(v))
|
||||
for i in 0 .. len(words) {
|
||||
if len(words[i]) > 0 { push(parts, el_word_len(ui_st, words[i])) }
|
||||
if len(words[i]) > 0 and n < 4 {
|
||||
el_side_set(ui_st, n, el_word_len(ui_st, words[i]))
|
||||
n += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
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]) }
|
||||
}
|
||||
ui_st.el_s0 = parts[0]
|
||||
ui_st.el_s1 = parts[1]
|
||||
ui_st.el_s2 = parts[2]
|
||||
ui_st.el_s3 = parts[3]
|
||||
if n == 0 { ui_st.el_s0 = 0.0 }
|
||||
if n < 2 { ui_st.el_s1 = ui_st.el_s0 }
|
||||
if n < 3 { ui_st.el_s2 = ui_st.el_s0 }
|
||||
if n < 4 { ui_st.el_s3 = ui_st.el_s1 }
|
||||
}
|
||||
# CSS's one to four sides, top right bottom left, without a list made for them
|
||||
function el_side_set(ui_st: mut UiState, i: int, v: float) -> void {
|
||||
if i == 0 { ui_st.el_s0 = v } else if i == 1 { ui_st.el_s1 = v } else if i == 2 { ui_st.el_s2 = v } else { ui_st.el_s3 = v }
|
||||
}
|
||||
# a length: a number, 10px, 1.5em, 2rem, 50vw, calc(...), fill, fit or auto (a percentage is el_pct's)
|
||||
function el_len(ui_st: mut UiState, v: Val) -> float {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ function el_edge(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
|||
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)
|
||||
let parts = mm_split(ui_st, s, 32)
|
||||
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 }
|
||||
} 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(ui_st, n, s) } else if key == "transition" { n.tr_dur = el_secs(s) } else { return false }
|
||||
return true
|
||||
}
|
||||
# url(path) or url("path") -> path
|
||||
|
|
@ -20,11 +20,11 @@ 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(ui_st: UiState, n: UiNode, s: string) -> void {
|
||||
function el_border_image(ui_st: mut 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)
|
||||
let rest = mm_split(ui_st, s[Math.min(close + 1, len(s))..len(s)], 47)
|
||||
n.bi_slice = el_num(tpl_words(rest[0]))
|
||||
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])) }
|
||||
|
|
@ -35,7 +35,7 @@ function el_shadow(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
|||
n.sh_c = -2
|
||||
return
|
||||
}
|
||||
let parts = lss_split(s, 32)
|
||||
let parts = mm_split(ui_st, s, 32)
|
||||
var nums = 0
|
||||
var col = ""
|
||||
for i in 0 .. len(parts) {
|
||||
|
|
@ -53,7 +53,7 @@ function el_shadow(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
|||
}
|
||||
# outline: 2px solid #ffcc00, or none
|
||||
function el_outline(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
||||
let parts = lss_split(s, 32)
|
||||
let parts = mm_split(ui_st, s, 32)
|
||||
n.outline_w = 0.0
|
||||
for i in 0 .. len(parts) {
|
||||
let p = parts[i]
|
||||
|
|
@ -64,8 +64,8 @@ function el_outline(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
|||
}
|
||||
}
|
||||
# animation: fade 0.25s infinite alternate
|
||||
function el_animation(n: UiNode, s: string) -> void {
|
||||
let parts = lss_split(s, 32)
|
||||
function el_animation(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
||||
let parts = mm_split(ui_st, s, 32)
|
||||
n.anim = ""
|
||||
for i in 0 .. len(parts) {
|
||||
let p = parts[i]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# for pictures and natives, zoom (zoom.ludic), and a list split at its top-level commas
|
||||
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)
|
||||
let parts = mm_split(ui_st, s, 47)
|
||||
var r = el_num(tpl_words(parts[0]))
|
||||
if len(parts) > 1 and el_num(tpl_words(parts[1])) > 0.0 { r = r / el_num(tpl_words(parts[1])) }
|
||||
if tpl_words(s) == "auto" { r = 0.0 }
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ function el_calc_len(ui_st: mut UiState, s: string) -> float {
|
|||
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 {
|
||||
let parts = el_words(s)
|
||||
function el_flex_short(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
||||
let parts = mm_words(ui_st, s)
|
||||
if s == "none" {
|
||||
n.grow = 0.0
|
||||
nx(n).shrink = 0.0
|
||||
|
|
@ -72,7 +72,7 @@ function el_text_shadow(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
|||
x.tsh_set = true
|
||||
x.tsh_c = -2
|
||||
if tpl_words(s) == "none" { return }
|
||||
let parts = el_words(s)
|
||||
let parts = mm_words(ui_st, s)
|
||||
var nums = 0
|
||||
var col = ""
|
||||
for i in 0 .. len(parts) {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ function ui_default_backend() -> UiBackend {
|
|||
export state UiState {
|
||||
pl_pools: []UiPool = new []UiPool # pool.ludic: each screen's nodes and envs, used again
|
||||
pl_cur: UiPool = null
|
||||
mm: UiMemo = null # memo.ludic: splits, gradients and keys by their text
|
||||
mm_ints: []string = null
|
||||
an_frames: []UiKeyframes = new []UiKeyframes
|
||||
an_keys: []string = new []string
|
||||
an_start: []float = new []float
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ function bd_kids(ui_st: mut UiState, kids: []UiTpl, e0: UiEnv, key: string, out:
|
|||
for i in 0 .. len(kids) {
|
||||
let k = kids[i]
|
||||
ui_st.ui_at = k.where
|
||||
let ck = `{key}/{i}`
|
||||
let ck = mm_join(ui_st, key, "/", mm_int(ui_st, i))
|
||||
if k.tag == "state" or k.tag == "style" { continue }
|
||||
if k.tag == "let" {
|
||||
e = bd_let(ui_st, k, e)
|
||||
|
|
@ -50,9 +50,9 @@ function bd_each(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiN
|
|||
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)
|
||||
var id = mm_int(ui_st, j)
|
||||
if by != null { id = ui_eval_text(ui_st, by, le) }
|
||||
bd_kids(ui_st, k.kids, le, `{key}#{id}`, out)
|
||||
bd_kids(ui_st, k.kids, le, mm_join(ui_st, key, "#", id), out)
|
||||
}
|
||||
}
|
||||
# <slot/> is what the component's user put inside it; <slot name="x"/> their <template slot="x">
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ function bd_class(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []Ui
|
|||
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(ui_st, c, e), 32)
|
||||
let parts = mm_split(ui_st, ui_eval_text(ui_st, c, e), 32)
|
||||
for i in 0 .. len(parts) {
|
||||
if len(parts[i]) > 0 { push(n.classes, parts[i]) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ function cl_hex_at(ui_st: mut UiState, s: string, at: int, n: int) -> 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)
|
||||
let parts = mm_split(ui_st, 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 { ui_st.cl_alpha = el_num(tpl_words(parts[3])) }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
# with a note="...", the label is .ui-labels > .ui-label + .ui-note
|
||||
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.key = mm_join(ui_st, n.key, "/", cls)
|
||||
p.tag = "span"
|
||||
p.env = n.env
|
||||
p.sheet = n.sheet
|
||||
|
|
|
|||
|
|
@ -8,7 +8,17 @@ export property UiGradient {
|
|||
at: []float = null # each stop's place, 0 to 1
|
||||
}
|
||||
function gr_is(s: string) -> bool { return len(s) > 16 and sat(s, 0, "linear-gradient(") }
|
||||
# a gradient is parsed once per text and shared by every element that wears it - never changed after
|
||||
function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient {
|
||||
let m = mm_of(ui_st)
|
||||
let i = mm_find(m, MM_GRAD, s0)
|
||||
if i >= 0 { return m.grads[i] }
|
||||
let g = gr_parse_new(ui_st, s0)
|
||||
let j = mm_add(m, MM_GRAD, s0)
|
||||
if j >= 0 { m.grads[j] = g }
|
||||
return g
|
||||
}
|
||||
function gr_parse_new(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
|
||||
|
|
@ -18,7 +28,7 @@ function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient {
|
|||
var from = 0
|
||||
if len(parts) > 0 and gr_dir(g, tpl_words(parts[0])) { from = 1 }
|
||||
for i in from .. len(parts) {
|
||||
let words = el_words(tpl_words(parts[i]))
|
||||
let words = mm_words(ui_st, tpl_words(parts[i]))
|
||||
var col = tpl_words(parts[i])
|
||||
var at = -1.0
|
||||
let last = words[len(words) - 1]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import "backend.ludic"
|
|||
import "pool.ludic"
|
||||
import "pool_reset.ludic"
|
||||
import "pool_reset_more.ludic"
|
||||
import "memo.ludic"
|
||||
import "memo_join.ludic"
|
||||
import "expr.ludic"
|
||||
import "expr_parse.ludic"
|
||||
import "expr_prim.ludic"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 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(ui_st: UiState, n: UiNode, avail: float) -> void {
|
||||
function ly_measure_text(ui_st: mut UiState, n: UiNode, avail: float) -> void {
|
||||
n.lines = null
|
||||
let lh = ly_line_h(n)
|
||||
var one = ly_text_w(ui_st, n.text, n.rsize)
|
||||
|
|
@ -19,8 +19,8 @@ function ly_measure_text(ui_st: UiState, n: UiNode, avail: float) -> void {
|
|||
n.mw = n.mw + widest
|
||||
n.mh = n.mh + lh * float(len(n.lines))
|
||||
}
|
||||
function ly_wrap(ui_st: UiState, s: string, size: float, room: float) -> []string {
|
||||
let words = lss_split(s, 32)
|
||||
function ly_wrap(ui_st: mut UiState, s: string, size: float, room: float) -> []string {
|
||||
let words = mm_split(ui_st, s, 32)
|
||||
let out = new []string
|
||||
var line = ""
|
||||
for i in 0 .. len(words) {
|
||||
|
|
|
|||
68
packages/ludic.ui/memo.ludic
Normal file
68
packages/ludic.ui/memo.ludic
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# memo.ludic - what a frame would work out again from the same text is kept by that text: a value
|
||||
# split into its words, a gradient, a key joined from its parent's. Ludic frees nothing, so parsing
|
||||
# "2px 6px" per element per frame was a leak; the table is bounded, and past it a result is made
|
||||
# (and lost) as before rather than the table growing without end.
|
||||
const MM_CAP: int = 8192 # slots; at most three quarters are used
|
||||
export property UiMemo {
|
||||
slots: words = null # index + 1 into keys, 0 empty
|
||||
keys: []string = null # the text a result was made from (or the joined text itself)
|
||||
tags: []int = null # what kind of result: a separator, or a kind below
|
||||
lists: [][]string = null
|
||||
grads: []UiGradient = null
|
||||
}
|
||||
const MM_WORDS: int = -1 # el_words: spaces, a bracket's own kept
|
||||
const MM_GRAD: int = -2
|
||||
const MM_JOIN: int = -3 # keys[i] is the joined text itself
|
||||
function mm_new() -> UiMemo {
|
||||
let m = new UiMemo
|
||||
m.slots = words(MM_CAP)
|
||||
m.keys = new []string
|
||||
m.tags = new []int
|
||||
m.lists = new [][]string
|
||||
m.grads = new []UiGradient
|
||||
return m
|
||||
}
|
||||
function mm_of(ui_st: mut UiState) -> UiMemo {
|
||||
if ui_st.mm == null { ui_st.mm = mm_new() }
|
||||
return ui_st.mm
|
||||
}
|
||||
# FNV-1a over the text, the tag mixed in first
|
||||
function mm_hash(tag: int, s: string) -> int {
|
||||
var h = 216613626 ^ (tag & 255)
|
||||
for i in 0 .. len(s) { h = ((h ^ s[i]) * 16777619) & 2147483647 }
|
||||
return h
|
||||
}
|
||||
# the entry for (tag, s), or -1
|
||||
function mm_find(m: UiMemo, tag: int, s: string) -> int {
|
||||
var at = mm_hash(tag, s) & (MM_CAP - 1)
|
||||
while m.slots[at] != 0 {
|
||||
let i = m.slots[at] - 1
|
||||
if m.tags[i] == tag and m.keys[i] == s { return i }
|
||||
at = (at + 1) & (MM_CAP - 1)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
# a new entry for (tag, s) with its results empty, or -1 when the table is full
|
||||
function mm_add(m: UiMemo, tag: int, s: string) -> int {
|
||||
if len(m.keys) * 4 >= MM_CAP * 3 { return -1 }
|
||||
var at = mm_hash(tag, s) & (MM_CAP - 1)
|
||||
while m.slots[at] != 0 { at = (at + 1) & (MM_CAP - 1) }
|
||||
push(m.keys, s)
|
||||
push(m.tags, tag)
|
||||
push(m.lists, null)
|
||||
push(m.grads, null)
|
||||
m.slots[at] = len(m.keys)
|
||||
return len(m.keys) - 1
|
||||
}
|
||||
# lss_split's parts of `s` at `c`, made once per text; the list is shared - read it, never change it
|
||||
function mm_split(ui_st: mut UiState, s: string, c: int) -> []string {
|
||||
let m = mm_of(ui_st)
|
||||
let i = mm_find(m, c, s)
|
||||
if i >= 0 { return m.lists[i] }
|
||||
var parts: []string = null
|
||||
if c == MM_WORDS { parts = el_words(s) } else { parts = lss_split(s, c) }
|
||||
let j = mm_add(m, c, s)
|
||||
if j >= 0 { m.lists[j] = parts }
|
||||
return parts
|
||||
}
|
||||
function mm_words(ui_st: mut UiState, s: string) -> []string { return mm_split(ui_st, s, MM_WORDS) }
|
||||
42
packages/ludic.ui/memo_join.ludic
Normal file
42
packages/ludic.ui/memo_join.ludic
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# memo_join.ludic - a key joined from its parent's key, a separator and a part ("App/3",
|
||||
# "App/3#7", "App/3/ui-thumb"), found by comparing in place, so an element's key is made once
|
||||
# the element first appears and never again
|
||||
function mm_hash_join(a: string, sep: string, b: string) -> int {
|
||||
var h = 216613626 ^ (MM_JOIN & 255)
|
||||
for i in 0 .. len(a) { h = ((h ^ a[i]) * 16777619) & 2147483647 }
|
||||
h = ((h ^ sep[0]) * 16777619) & 2147483647
|
||||
for i in 0 .. len(b) { h = ((h ^ b[i]) * 16777619) & 2147483647 }
|
||||
return h
|
||||
}
|
||||
function mm_is_join(k: string, a: string, sep: string, b: string) -> bool {
|
||||
let na = len(a)
|
||||
if len(k) != na + 1 + len(b) or k[na] != sep[0] { return false }
|
||||
return sat(k, 0, a) and sat(k, na + 1, b)
|
||||
}
|
||||
# a + sep + b, the same string every frame
|
||||
function mm_join(ui_st: mut UiState, a: string, sep: string, b: string) -> string {
|
||||
let m = mm_of(ui_st)
|
||||
var at = mm_hash_join(a, sep, b) & (MM_CAP - 1)
|
||||
while m.slots[at] != 0 {
|
||||
let i = m.slots[at] - 1
|
||||
if m.tags[i] == MM_JOIN and mm_is_join(m.keys[i], a, sep, b) { return m.keys[i] }
|
||||
at = (at + 1) & (MM_CAP - 1)
|
||||
}
|
||||
let k = a + sep + b
|
||||
if len(m.keys) * 4 >= MM_CAP * 3 { return k }
|
||||
push(m.keys, k)
|
||||
push(m.tags, MM_JOIN)
|
||||
push(m.lists, null)
|
||||
push(m.grads, null)
|
||||
m.slots[at] = len(m.keys)
|
||||
return k
|
||||
}
|
||||
# a small whole number's text, made once (a key's index, a loop's count)
|
||||
function mm_int(ui_st: mut UiState, i: int) -> string {
|
||||
if i < 0 or i >= 1024 { return string(i) }
|
||||
if ui_st.mm_ints == null {
|
||||
ui_st.mm_ints = new []string
|
||||
for k in 0 .. 1024 { push(ui_st.mm_ints, string(k)) }
|
||||
}
|
||||
return ui_st.mm_ints[i]
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# 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.
|
||||
function el_text_fit(ui_st: UiState, n: UiNode, s: string) -> void {
|
||||
let parts = lss_split(tpl_words(s), 32)
|
||||
function el_text_fit(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
||||
let parts = mm_split(ui_st, 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(ui_st, parts[1]), 1.0) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ function cs_vars_in(ui_st: mut UiState, n: UiNode, v: Val) -> Val {
|
|||
return Value.str(tpl_words(out + s[at..len(s)]))
|
||||
}
|
||||
function cs_var_ref(ui_st: mut UiState, n: UiNode, inner: string) -> string {
|
||||
let parts = lss_split(inner, 44)
|
||||
let parts = mm_split(ui_st, 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)]) }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue