diff --git a/examples/library/ui_pointer.ludic b/examples/library/ui_pointer.ludic
index 9433e22e..3a6e615d 100644
--- a/examples/library/ui_pointer.ludic
+++ b/examples/library/ui_pointer.ludic
@@ -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}`)
}
}
diff --git a/packages/ludic.ui/anim.ludic b/packages/ludic.ui/anim.ludic
index 22932f41..1b5ed653 100644
--- a/packages/ludic.ui/anim.ludic
+++ b/packages/ludic.ui/anim.ludic
@@ -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)
diff --git a/packages/ludic.ui/attr.ludic b/packages/ludic.ui/attr.ludic
index c7ca60cd..7abacd78 100644
--- a/packages/ludic.ui/attr.ludic
+++ b/packages/ludic.ui/attr.ludic
@@ -1,6 +1,5 @@
# 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.
-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(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> void {
@@ -12,44 +11,57 @@ function el_apply(ui_st: mut UiState, n: UiNode, key: string, v0: Val) -> bool {
let k = at_alias(key)
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))
+ if len(k) > 2 and sat(k, 0, "--") {
+ cs_var_set(n, k, vl_text_u(ui_st, v0))
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 {
- let pat = ` {key}=`
- let n = len(pat)
- for i in 0 .. len(AT_ALIAS) - n + 1 {
- if AT_ALIAS[i..i + n] == pat {
- var j = i + n
- while AT_ALIAS[j] != 32 { j += 1 }
- return AT_ALIAS[i + n..j]
- }
- }
+ # the CSS name to the short one; literals, so asking allocates nothing (asked per rule per element)
+ if key == "width" { return "w" }
+ if key == "height" { return "h" }
+ if key == "min-width" { return "min-w" }
+ if key == "max-width" { return "max-w" }
+ if key == "min-height" { return "min-h" }
+ 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
}
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 {
- let s = vl_text(v)
+function el_flex(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
+ let s = vl_text_u(ui_st, 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 {
- let s = vl_text(v)
+ let s = vl_text_u(ui_st, v)
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(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_a = 0.0
} else {
@@ -64,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" {
diff --git a/packages/ludic.ui/attr_box.ludic b/packages/ludic.ui/attr_box.ludic
index 6b072ff5..16f22735 100644
--- a/packages/ludic.ui/attr_box.ludic
+++ b/packages/ludic.ui/attr_box.ludic
@@ -18,25 +18,32 @@ 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_u(ui_st, 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 {
if vl_num(v) { return el_px(ui_st, Value.as_float(v)) }
- let s = vl_text(v)
+ let s = vl_text_u(ui_st, v)
if s == "fill" { return UI_FILL }
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 }
@@ -67,8 +74,8 @@ function el_num(s: string) -> float {
# start, center, end, stretch; for justify also between, around, evenly - with CSS's spellings
function el_place(v: Val) -> int {
var s = vl_text(v)
- if len(s) > 6 and s[0..6] == "space-" { s = s[6..len(s)] }
- if len(s) > 5 and s[0..5] == "flex-" { s = s[5..len(s)] }
+ if len(s) > 6 and sat(s, 0, "space-") { s = s[6..len(s)] }
+ if len(s) > 5 and sat(s, 0, "flex-") { s = s[5..len(s)] }
if s == "center" { return UI_CENTER }
if s == "end" or s == "right" { return UI_END }
if s == "stretch" or s == "between" { return 3 }
diff --git a/packages/ludic.ui/attr_edge.ludic b/packages/ludic.ui/attr_edge.ludic
index 97adbd08..62df9758 100644
--- a/packages/ludic.ui/attr_edge.ludic
+++ b/packages/ludic.ui/attr_edge.ludic
@@ -1,30 +1,30 @@
# attr_edge.ludic - border-radius, outline (and its width, colour and offset), scrollbar-color
function el_edge(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
- let s = vl_text(v)
+ let s = vl_text_u(ui_st, v)
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)
+ 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
function el_url(s: string) -> string {
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 t == "none" { return "" }
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]
@@ -78,7 +78,7 @@ function el_secs(s: string) -> float {
for i in 0 .. len(parts) {
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) > 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)
}
}
diff --git a/packages/ludic.ui/attr_fit.ludic b/packages/ludic.ui/attr_fit.ludic
index fa083941..b54c1834 100644
--- a/packages/ludic.ui/attr_fit.ludic
+++ b/packages/ludic.ui/attr_fit.ludic
@@ -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 }
diff --git a/packages/ludic.ui/attr_inset.ludic b/packages/ludic.ui/attr_inset.ludic
index b33af5a1..bb6eb47f 100644
--- a/packages/ludic.ui/attr_inset.ludic
+++ b/packages/ludic.ui/attr_inset.ludic
@@ -2,7 +2,7 @@
# 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(ui_st: mut UiState, n: UiNode, side: int, v: Val) -> float {
- let s = vl_text(v)
+ let s = vl_text_u(ui_st, v)
var pct = 0.0
var px = UI_AUTO
if s != "auto" {
diff --git a/packages/ludic.ui/attr_more.ludic b/packages/ludic.ui/attr_more.ludic
index 426b78b0..bf00a328 100644
--- a/packages/ludic.ui/attr_more.ludic
+++ b/packages/ludic.ui/attr_more.ludic
@@ -1,13 +1,13 @@
# 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(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
- let s = vl_text(v)
+ let s = vl_text_u(ui_st, 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(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(ui_st: mut UiState, n: UiNode, wide: bool, v: Val) -> void {
- let s = vl_text(v)
+ let s = vl_text_u(ui_st, v)
var fixed = UI_AUTO
var pct = 0.0
var off = 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) {
diff --git a/packages/ludic.ui/attr_place.ludic b/packages/ludic.ui/attr_place.ludic
index 31263c8e..8abefc97 100644
--- a/packages/ludic.ui/attr_place.ludic
+++ b/packages/ludic.ui/attr_place.ludic
@@ -1,6 +1,6 @@
# attr_place.ludic - position and its insets, z-index, overflow, white-space and text-overflow
function el_place_prop(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
- let s = vl_text(v)
+ let s = vl_text_u(ui_st, v)
if key == "position" {
n.pos = 0
if s == "relative" { n.pos = 1 }
diff --git a/packages/ludic.ui/backend.ludic b/packages/ludic.ui/backend.ludic
index 3b6d3558..309256c3 100644
--- a/packages/ludic.ui/backend.ludic
+++ b/packages/ludic.ui/backend.ludic
@@ -32,6 +32,19 @@ 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
+ mm: UiMemo = null # memo.ludic: splits, gradients and keys by their text
+ mm_ints: []string = null
+ mm_ivals: []Val = null
+ ev_depth: int = 0 # eval.ludic: a call's arguments, in lists kept one a depth
+ ev_args: []Val = new []Val
+ ly_depth: int = 0 # layout_run.ludic: the lists a line lays out with, one a depth
+ ly_sizes: [][]float = new [][]float
+ ly_frozen: [][]bool = new [][]bool
+ cs_rules: []UiRule = new []UiRule # cascade_merge.ludic: a component root's rules, gathered
+ cs_envs: []UiEnv = new []UiEnv
+ cs_seen: []UiSheet = new []UiSheet
an_frames: []UiKeyframes = new []UiKeyframes
an_keys: []string = new []string
an_start: []float = new []float
diff --git a/packages/ludic.ui/build.ludic b/packages/ludic.ui/build.ludic
index 74b12d07..d2e24b37 100644
--- a/packages/ludic.ui/build.ludic
+++ b/packages/ludic.ui/build.ludic
@@ -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)
@@ -47,12 +47,12 @@ 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)
+ env_bind(le, at_name, mm_ival(ui_st, 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)
}
}
# is what the component's user put inside it; their
@@ -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
diff --git a/packages/ludic.ui/build_class.ludic b/packages/ludic.ui/build_class.ludic
index 62e11c71..b4603e57 100644
--- a/packages/ludic.ui/build_class.ludic
+++ b/packages/ludic.ui/build_class.ludic
@@ -5,13 +5,15 @@ function bd_class(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []Ui
let cls = k.cls
uc_load(ui_st, cls)
let it = in_get(ui_st, key, cls)
- let props = Value.object()
+ if it.props == null { it.props = Value.object() }
+ let props = it.props
+ Value.clear(props)
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(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 +21,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)
@@ -33,7 +34,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]) }
}
diff --git a/packages/ludic.ui/build_more.ludic b/packages/ludic.ui/build_more.ludic
index 0e58a44c..e0a2386b 100644
--- a/packages/ludic.ui/build_more.ludic
+++ b/packages/ludic.ui/build_more.ludic
@@ -1,14 +1,14 @@
# build_more.ludic - names a value for the siblings after it, and
# 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 -
Hi there
,
- 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)
}
diff --git a/packages/ludic.ui/calc_fns.ludic b/packages/ludic.ui/calc_fns.ludic
index d3f17233..d55bb1e3 100644
--- a/packages/ludic.ui/calc_fns.ludic
+++ b/packages/ludic.ui/calc_fns.ludic
@@ -8,7 +8,7 @@ function ca_fn_at(s: string, at: int) -> string {
if ca_word(s, at, "clamp(") { return "clamp" }
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 {
ui_st.ca_i = ui_st.ca_i + len(f) + 1
let args = new []CaVal
diff --git a/packages/ludic.ui/cascade.ludic b/packages/ludic.ui/cascade.ludic
index 57670225..6aad6834 100644
--- a/packages/ludic.ui/cascade.ludic
+++ b/packages/ludic.ui/cascade.ludic
@@ -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
@@ -51,10 +54,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`
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(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) {
- 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 {
diff --git a/packages/ludic.ui/cascade_merge.ludic b/packages/ludic.ui/cascade_merge.ludic
index 1210b945..0054543f 100644
--- a/packages/ludic.ui/cascade_merge.ludic
+++ b/packages/ludic.ui/cascade_merge.ludic
@@ -3,9 +3,12 @@
# 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(ui_st: mut UiState, n: UiNode) -> void {
- let rules = new []UiRule
- let envs = new []UiEnv
- let seen = new []UiSheet
+ let rules = ui_st.cs_rules # kept and emptied: a merge runs once an element, never inside another
+ let envs = ui_st.cs_envs
+ let seen = ui_st.cs_seen
+ List.clear(rules)
+ List.clear(envs)
+ List.clear(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(ui_st, n, n.ups[i].sheet, n.ups[i].env, rules, envs, seen) }
diff --git a/packages/ludic.ui/color.ludic b/packages/ludic.ui/color.ludic
index 88ae3493..d3a462a2 100644
--- a/packages/ludic.ui/color.ludic
+++ b/packages/ludic.ui/color.ludic
@@ -5,12 +5,11 @@ function ui_color(ui_st: mut UiState, name0: string) -> int {
let name = tpl_words(name0)
ui_st.cl_alpha = 1.0
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 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(ui_st, CL_NAMES[i + n..i + n + 6]) }
+ if name[0] == 35 { return cl_hex_at(ui_st, name, 1, len(name) - 1) }
+ if len(name) > 4 and (sat(name, 0, "rgb(") or sat(name, 0, "rgba(")) { return cl_rgb(ui_st, name) }
+ let n = len(name)
+ 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) }
}
ui_err(ui_st, `{name}: not a colour - #rrggbb, rgb(), rgba(), a name, or var(--a-colour)`)
return 15658734
@@ -20,23 +19,24 @@ function cl_digit(c: int) -> int {
if c >= 65 { return c - 55 }
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
- 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 { ui_st.cl_alpha = float(cl_digit(h[3]) * 17) / 255.0 }
+ for i in 0 .. 3 { c = c * 256 + cl_digit(s[at + i]) * 17 }
+ if n == 4 { ui_st.cl_alpha = float(cl_digit(s[at + 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 { ui_st.cl_alpha = float(cl_digit(h[6]) * 16 + cl_digit(h[7])) / 255.0 }
+ 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(s[at + 6]) * 16 + cl_digit(s[at + 7])) / 255.0 }
return c
}
# rgb(217, 165, 74) or rgba(0, 0, 0, 0.55)
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])) }
diff --git a/packages/ludic.ui/controls_build.ludic b/packages/ludic.ui/controls_build.ludic
index 450d8350..a690cedd 100644
--- a/packages/ludic.ui/controls_build.ludic
+++ b/packages/ludic.ui/controls_build.ludic
@@ -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