ludic.ui: string matching compares in place - at_alias, lk_in, colours and every s[a..b] == p made a string per position tried, per rule, per element, per frame (a benchmark page: ~3 MB -> 256 KB a frame)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-27 23:27:02 +03:00
parent 8808dda605
commit 83200a453b
17 changed files with 84 additions and 50 deletions

View file

@ -1,6 +1,5 @@
# attr.ludic - what a property sets, by its CSS name or the short one (width or w, background or # attr.ludic - what a property sets, by its CSS name or the short one (width or w, background or
# bg, font-size or size, ...). An attribute on an element is the same property, applied last. # bg, font-size or size, ...). An attribute on an element is the same property, applied last.
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 " 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 # a stylesheet's property: one that means nothing is said
function el_attr(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> void { function el_attr(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> void {
@ -12,7 +11,7 @@ function el_apply(ui_st: mut UiState, n: UiNode, key: string, v0: Val) -> bool {
let k = at_alias(key) let k = at_alias(key)
ui_st.el_cur = n ui_st.el_cur = n
ui_st.el_key = k ui_st.el_key = k
if len(k) > 2 and k[0..2] == "--" { if len(k) > 2 and sat(k, 0, "--") {
cs_var_set(n, k, vl_text(v0)) cs_var_set(n, k, vl_text(v0))
return true return true
} }
@ -20,15 +19,28 @@ function el_apply(ui_st: mut UiState, n: UiNode, key: string, v0: Val) -> bool {
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(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 { function at_alias(key: string) -> string {
let pat = ` {key}=` # the CSS name to the short one; literals, so asking allocates nothing (asked per rule per element)
let n = len(pat) if key == "width" { return "w" }
for i in 0 .. len(AT_ALIAS) - n + 1 { if key == "height" { return "h" }
if AT_ALIAS[i..i + n] == pat { if key == "min-width" { return "min-w" }
var j = i + n if key == "max-width" { return "max-w" }
while AT_ALIAS[j] != 32 { j += 1 } if key == "min-height" { return "min-h" }
return AT_ALIAS[i + n..j] if key == "max-height" { return "max-h" }
} if key == "padding" { return "pad" }
} if key == "background" { return "bg" }
if key == "background-color" { return "bg" }
if key == "font-size" { return "size" }
if key == "opacity" { return "alpha" }
if key == "flex-grow" { return "grow" }
if key == "flex-shrink" { return "shrink" }
if key == "flex-direction" { return "dir" }
if key == "flex-wrap" { return "wrap" }
if key == "justify-content" { return "justify" }
if key == "align-items" { return "align" }
if key == "align-self" { return "self" }
if key == "row-gap" { return "gap" }
if key == "column-gap" { return "gap" }
if key == "text-align" { return "talign" }
return key return key
} }
function el_size(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool { function el_size(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
@ -49,7 +61,7 @@ function el_look(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
n.fg = ui_color(ui_st, s) n.fg = ui_color(ui_st, s)
n.fg_a = ui_st.cl_alpha n.fg_a = ui_st.cl_alpha
} else if key == "bg" { } else if key == "bg" {
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" { if gr_is(tpl_words(s)) { nx(n).grad = gr_parse(ui_st, s) } else if len(s) > 4 and sat(s, 0, "url(") { n.bg_img = el_url(s) } else if tpl_words(s) == "transparent" {
n.bg = 0 n.bg = 0
n.bg_a = 0.0 n.bg_a = 0.0
} else { } else {

View file

@ -67,8 +67,8 @@ function el_num(s: string) -> float {
# start, center, end, stretch; for justify also between, around, evenly - with CSS's spellings # start, center, end, stretch; for justify also between, around, evenly - with CSS's spellings
function el_place(v: Val) -> int { function el_place(v: Val) -> int {
var s = vl_text(v) var s = vl_text(v)
if len(s) > 6 and s[0..6] == "space-" { s = s[6..len(s)] } if len(s) > 6 and sat(s, 0, "space-") { s = s[6..len(s)] }
if len(s) > 5 and s[0..5] == "flex-" { s = s[5..len(s)] } if len(s) > 5 and sat(s, 0, "flex-") { s = s[5..len(s)] }
if s == "center" { return UI_CENTER } if s == "center" { return UI_CENTER }
if s == "end" or s == "right" { return UI_END } if s == "end" or s == "right" { return UI_END }
if s == "stretch" or s == "between" { return 3 } if s == "stretch" or s == "between" { return 3 }

View file

@ -14,7 +14,7 @@ function el_edge(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
# url(path) or url("path") -> path # url(path) or url("path") -> path
function el_url(s: string) -> string { function el_url(s: string) -> string {
var t = tpl_words(s) var t = tpl_words(s)
if len(t) > 5 and t[0..4] == "url(" { t = tpl_words(t[4..len(t) - 1]) } if len(t) > 5 and sat(t, 0, "url(") { t = tpl_words(t[4..len(t) - 1]) }
if len(t) >= 2 and (t[0] == 34 or t[0] == 39) { t = t[1..len(t) - 1] } if len(t) >= 2 and (t[0] == 34 or t[0] == 39) { t = t[1..len(t) - 1] }
if t == "none" { return "" } if t == "none" { return "" }
return t return t
@ -78,7 +78,7 @@ function el_secs(s: string) -> float {
for i in 0 .. len(parts) { for i in 0 .. len(parts) {
let p = parts[i] let p = parts[i]
if len(p) > 1 and p[0] >= 48 and p[0] <= 57 or len(p) > 1 and p[0] == 46 { if len(p) > 1 and p[0] >= 48 and p[0] <= 57 or len(p) > 1 and p[0] == 46 {
if len(p) > 2 and p[len(p) - 2..len(p)] == "ms" { return el_num(p) / 1000.0 } if len(p) > 2 and sat(p, len(p) - 2, "ms") { return el_num(p) / 1000.0 }
return el_num(p) return el_num(p)
} }
} }

View file

@ -8,7 +8,7 @@ function ca_fn_at(s: string, at: int) -> string {
if ca_word(s, at, "clamp(") { return "clamp" } if ca_word(s, at, "clamp(") { return "clamp" }
return "" 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_word(s: string, at: int, w: string) -> bool { return at + len(w) <= len(s) and sat(s, at, w) }
function ca_fn(ui_st: mut UiState, f: string) -> CaVal { function ca_fn(ui_st: mut UiState, f: string) -> CaVal {
ui_st.ca_i = ui_st.ca_i + len(f) + 1 ui_st.ca_i = ui_st.ca_i + len(f) + 1
let args = new []CaVal let args = new []CaVal

View file

@ -51,10 +51,10 @@ function cs_rule(ui_st: mut UiState, n: UiNode, r: UiRule) -> void { cs_rule_in(
# a rule's custom properties first, so its other declarations can read them; values read in `e` # a rule's custom properties first, so its other declarations can read them; values read in `e`
function cs_rule_in(ui_st: mut UiState, 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) { for j in 0 .. len(r.keys) {
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)) } if len(r.keys[j]) > 2 and sat(r.keys[j], 0, "--") { el_attr(ui_st, n, r.keys[j], ui_eval(ui_st, r.vals[j], e)) }
} }
for j in 0 .. len(r.keys) { for j in 0 .. len(r.keys) {
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)) } if not (len(r.keys[j]) > 2 and sat(r.keys[j], 0, "--")) 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)) }
} }
} }
function ua_sheet(ui_st: mut UiState) -> UiSheet { function ua_sheet(ui_st: mut UiState) -> UiSheet {

View file

@ -5,12 +5,11 @@ function ui_color(ui_st: mut UiState, name0: string) -> int {
let name = tpl_words(name0) let name = tpl_words(name0)
ui_st.cl_alpha = 1.0 ui_st.cl_alpha = 1.0
if name == "none" or name == "" or name == "transparent" { return UI_NONE } if name == "none" or name == "" or name == "transparent" { return UI_NONE }
if name[0] == 35 { return cl_hex(ui_st, name[1..len(name)]) } if name[0] == 35 { return cl_hex_at(ui_st, name, 1, len(name) - 1) }
if len(name) > 4 and (name[0..4] == "rgb(" or name[0..5] == "rgba(") { return cl_rgb(ui_st, name) } if len(name) > 4 and (sat(name, 0, "rgb(") or sat(name, 0, "rgba(")) { return cl_rgb(ui_st, name) }
let pat = ` {name}=` let n = len(name)
let n = len(pat) for i in 0 .. len(CL_NAMES) - n - 1 {
for i in 0 .. len(CL_NAMES) - n + 1 { if CL_NAMES[i] == 32 and CL_NAMES[i + n + 1] == 61 and sat(CL_NAMES, i + 1, name) { return cl_hex_at(ui_st, CL_NAMES, i + n + 2, 6) }
if CL_NAMES[i..i + n] == pat { return cl_hex(ui_st, CL_NAMES[i + n..i + n + 6]) }
} }
ui_err(ui_st, `{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 return 15658734
@ -20,16 +19,17 @@ function cl_digit(c: int) -> int {
if c >= 65 { return c - 55 } if c >= 65 { return c - 55 }
return c - 48 return c - 48
} }
function cl_hex(ui_st: mut UiState, h: string) -> int { function cl_hex(ui_st: mut UiState, h: string) -> int { return cl_hex_at(ui_st, h, 0, len(h)) }
# the n hex digits of `s` from `at`, read where they stand
function cl_hex_at(ui_st: mut UiState, s: string, at: int, n: int) -> int {
var c = 0 var c = 0
let n = len(h)
if n == 3 or n == 4 { if n == 3 or n == 4 {
for i in 0 .. 3 { c = c * 256 + cl_digit(h[i]) * 17 } for i in 0 .. 3 { c = c * 256 + cl_digit(s[at + i]) * 17 }
if n == 4 { ui_st.cl_alpha = float(cl_digit(h[3]) * 17) / 255.0 } if n == 4 { ui_st.cl_alpha = float(cl_digit(s[at + 3]) * 17) / 255.0 }
return c return c
} }
for i in 0 .. 3 { c = c * 256 + cl_digit(h[i * 2]) * 16 + cl_digit(h[i * 2 + 1]) } for i in 0 .. 3 { c = c * 256 + cl_digit(s[at + i * 2]) * 16 + cl_digit(s[at + i * 2 + 1]) }
if n == 8 { ui_st.cl_alpha = float(cl_digit(h[6]) * 16 + cl_digit(h[7])) / 255.0 } if n == 8 { ui_st.cl_alpha = float(cl_digit(s[at + 6]) * 16 + cl_digit(s[at + 7])) / 255.0 }
return c return c
} }
# rgb(217, 165, 74) or rgba(0, 0, 0, 0.55) # rgb(217, 165, 74) or rgba(0, 0, 0, 0.55)

View file

@ -7,7 +7,7 @@ export property UiGradient {
alphas: []float = null alphas: []float = null
at: []float = null # each stop's place, 0 to 1 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_is(s: string) -> bool { return len(s) > 16 and sat(s, 0, "linear-gradient(") }
function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient { function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient {
let s = tpl_words(s0) let s = tpl_words(s0)
let parts = el_commas(s[16..len(s) - 1]) let parts = el_commas(s[16..len(s) - 1])
@ -36,7 +36,7 @@ function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient {
return g return g
} }
function gr_dir(g: UiGradient, w: string) -> bool { function gr_dir(g: UiGradient, w: string) -> bool {
if w == "to bottom" { g.dir = 0 } else if w == "to top" { g.dir = 1 } else if w == "to right" { g.dir = 2 } else if w == "to left" { g.dir = 3 } else if len(w) > 3 and w[len(w) - 3..len(w)] == "deg" { if w == "to bottom" { g.dir = 0 } else if w == "to top" { g.dir = 1 } else if w == "to right" { g.dir = 2 } else if w == "to left" { g.dir = 3 } else if len(w) > 3 and sat(w, len(w) - 3, "deg") {
let d = int(Math.round(el_num(w) / 90.0)) % 4 let d = int(Math.round(el_num(w) / 90.0)) % 4
g.dir = 1 g.dir = 1
if d == 1 or d == -3 { g.dir = 2 } if d == 1 or d == -3 { g.dir = 2 }

View file

@ -84,7 +84,7 @@ export function ui_err(ui_st: mut UiState, msg0: string) -> void {
function ui_has(s: string, part: string) -> bool { function ui_has(s: string, part: string) -> bool {
let n = len(part) let n = len(part)
for i in 0 .. len(s) - n + 1 { for i in 0 .. len(s) - n + 1 {
if s[i..i + n] == part { return true } if sat(s, i, part) { return true }
} }
return false return false
} }

View file

@ -51,7 +51,7 @@ function rd_op(r: UiRd, op: string) -> bool {
rd_ws(r) rd_ws(r)
let n = len(op) let n = len(op)
if r.i + n > len(r.s) { return false } if r.i + n > len(r.s) { return false }
if r.s[r.i..r.i + n] != op { return false } if not sat(r.s, r.i, op) { return false }
r.i += n r.i += n
return true return true
} }

View file

@ -39,3 +39,13 @@ function lib_beside(path: string, src: string) -> string {
if cut < 0 { return src } if cut < 0 { return src }
return path[0..cut + 1] + src return path[0..cut + 1] + src
} }
# `s` has `p` starting at `at`: compared in place, where s[at..at + len(p)] == p made a new string
# for every place it looked (Ludic frees nothing, and the cascade asks this per element per frame)
function sat(s: string, at: int, p: string) -> bool {
let n = len(p)
if at < 0 or at + n > len(s) { return false }
for i in 0 .. n {
if s[at + i] != p[i] { return false }
}
return true
}

View file

@ -22,7 +22,7 @@ function lc_note(ui_st: mut UiState, key: string, names: []string, acts: []UiAct
} }
function lc_mine(key: string, screen: string) -> bool { function lc_mine(key: string, screen: string) -> bool {
let n = len(screen) let n = len(screen)
return key == screen or (len(key) > n and key[0..n] == screen and key[n] == 47) return key == screen or (len(key) > n and sat(key, 0, screen) and key[n] == 47)
} }
# the screen's mounts and unmounts since its last frame; other screens' records are kept # the screen's mounts and unmounts since its last frame; other screens' records are kept
function lc_run(ui_st: mut UiState, screen: string) -> void { function lc_run(ui_st: mut UiState, screen: string) -> void {

View file

@ -20,10 +20,9 @@ function lk_kids(ui_st: mut UiState, f: UiFile, t: UiTpl) -> void {
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 } 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 " # a word in a list of words written " a b c "
function lk_in(words: string, w: string) -> bool { function lk_in(words: string, w: string) -> bool {
let pat = ` {w} ` let n = len(w)
let n = len(pat) for i in 0 .. len(words) - n - 1 {
for i in 0 .. len(words) - n + 1 { if words[i] == 32 and words[i + n + 1] == 32 and sat(words, i + 1, w) { return true }
if words[i..i + n] == pat { return true }
} }
return false return false
} }

View file

@ -73,7 +73,7 @@ function r3u_image_h(render3d_st: mut Render3dState, ui_st: mut UiState, src: st
function r3u_is_cell(ui_st: UiState, src: string) -> bool { function r3u_is_cell(ui_st: UiState, src: string) -> bool {
for k in 0 .. len(ui_st.r3u_atlas) { for k in 0 .. len(ui_st.r3u_atlas) {
let p = ui_st.r3u_atlas[k] 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 } if len(src) > len(p) and sat(src, 0, p) and src[len(p)] == 58 { return true }
} }
return false return false
} }

View file

@ -21,7 +21,7 @@ export function ui_forget(ui_st: mut UiState, screen: string) -> void {
let vals = new []Val let vals = new []Val
for i in 0 .. len(ui_st.st_keys) { for i in 0 .. len(ui_st.st_keys) {
let k = ui_st.st_keys[i] 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) let mine = len(k) >= len(screen) and sat(k, 0, screen) and (len(k) == len(screen) or k[len(screen)] == 47)
if not mine { if not mine {
push(keys, k) push(keys, k)
push(vals, ui_st.st_vals[i]) push(vals, ui_st.st_vals[i])

View file

@ -18,7 +18,7 @@ function sheet_read(ui_st: mut UiState, f: UiFile, t: UiTpl) -> void {
if from != "" { src = lib_text(ui_st, lib_beside(f.path, from)) } if from != "" { src = lib_text(ui_st, lib_beside(f.path, from)) }
lss_text(ui_st, f, src) lss_text(ui_st, f, src)
} }
function lss_is(path: string) -> bool { return len(path) > 4 and path[len(path) - 4..len(path)] == ".lss" } function lss_is(path: string) -> bool { return len(path) > 4 and sat(path, len(path) - 4, ".lss") }
# a stylesheet file: a file with rules and imports and nothing else # a stylesheet file: a file with rules and imports and nothing else
function lss_file(ui_st: mut UiState, src: string, path: string) -> UiFile { function lss_file(ui_st: mut UiState, src: string, path: string) -> UiFile {
let f = lib_new(ui_st, path) let f = lib_new(ui_st, path)
@ -64,7 +64,7 @@ function lss_block(ui_st: mut UiState, f: UiFile, src: string, md: UiMedia, err:
# @import "base.lss"; - its rules come before this file's own # @import "base.lss"; - its rules come before this file's own
function lss_import(ui_st: mut UiState, f: UiFile, what: string) -> void { function lss_import(ui_st: mut UiState, f: UiFile, what: string) -> void {
var p = tpl_words(what) var p = tpl_words(what)
if len(p) > 5 and p[0..4] == "url(" { p = p[4..len(p) - 1] } if len(p) > 5 and sat(p, 0, "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] } if len(p) >= 2 and (p[0] == 34 or p[0] == 39) { p = p[1..len(p) - 1] }
let other = lib_file(ui_st, lib_beside(f.path, p)) let other = lib_file(ui_st, lib_beside(f.path, p))
if other != null { if other != null {

View file

@ -29,7 +29,7 @@ function tpl_read(ui_st: mut UiState, x: Xml, file: string) -> UiTpl {
for i in 0 .. len(x.akeys) { for i in 0 .. len(x.akeys) {
let k: string = x.akeys[i] let k: string = x.akeys[i]
let v: string = x.avals[i] let v: string = x.avals[i]
if len(k) > 3 and k[0..3] == "on-" { if len(k) > 3 and sat(k, 0, "on-") {
push(t.on_keys, tpl_event(k[3..len(k)])) push(t.on_keys, tpl_event(k[3..len(k)]))
push(t.on_acts, act_parse(v, err)) push(t.on_acts, act_parse(v, err))
} else if k == "onclick" { } else if k == "onclick" {
@ -68,6 +68,7 @@ function tpl_value(v: string, err: []string) -> UiExpr {
} }
# text as a reader sees it: the ends trimmed and every run of white space one space # text as a reader sees it: the ends trimmed and every run of white space one space
function tpl_words(s: string) -> string { function tpl_words(s: string) -> string {
if tpl_clean(s) { return s }
var out = "" var out = ""
var gap = false var gap = false
for i in 0 .. len(s) { for i in 0 .. len(s) {
@ -86,3 +87,15 @@ function tpl_attr(t: UiTpl, key: string) -> UiExpr {
} }
return null return null
} }
# no space at either end, no two together, no tab or line break: tpl_words would give it back as is
function tpl_clean(s: string) -> bool {
let n = len(s)
if n == 0 { return true }
if s[0] == 32 or s[n - 1] == 32 { return false }
for i in 0 .. n {
let c = s[i]
if c == 9 or c == 10 or c == 13 { return false }
if c == 32 and s[i + 1] == 32 { return false }
}
return true
}

View file

@ -4,14 +4,14 @@
function el_unit(ui_st: UiState, s: string) -> float { function el_unit(ui_st: UiState, s: string) -> float {
let f = el_num(s) let f = el_num(s)
let n = len(s) let n = len(s)
if n > 3 and s[n - 3..n] == "rem" { return f * el_px(ui_st, 16.0) } if n > 3 and sat(s, n - 3, "rem") { return f * el_px(ui_st, 16.0) }
if n > 2 and s[n - 2..n] == "em" { if n > 2 and sat(s, n - 2, "em") {
if ui_st.el_cur == null { return f * el_px(ui_st, 16.0) } 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) } 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) return f * cs_fs(ui_st, ui_st.el_cur)
} }
if n > 2 and s[n - 2..n] == "vw" { return f * ui_st.ly_vp_w / 100.0 } if n > 2 and sat(s, n - 2, "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 } if n > 2 and sat(s, n - 2, "vh") { return f * ui_st.ly_vp_h / 100.0 }
return el_px(ui_st, f) return el_px(ui_st, f)
} }
# the font size an element has so far in its cascade (its own, else its parent's) # the font size an element has so far in its cascade (its own, else its parent's)
@ -55,7 +55,7 @@ function cs_vars_in(ui_st: mut UiState, n: UiNode, v: Val) -> Val {
var found = false var found = false
var i = 0 var i = 0
while i + 4 <= len(s) { while i + 4 <= len(s) {
if s[i..i + 4] == "var(" { if sat(s, i, "var(") {
found = true found = true
var j = i + 4 var j = i + 4
var depth = 1 var depth = 1