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