diff --git a/packages/ludic.ui/attr.ludic b/packages/ludic.ui/attr.ludic index c7ca60cd..fcd19c22 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,7 +11,7 @@ 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] == "--" { + if len(k) > 2 and sat(k, 0, "--") { cs_var_set(n, k, vl_text(v0)) 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) } 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 { @@ -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_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 { diff --git a/packages/ludic.ui/attr_box.ludic b/packages/ludic.ui/attr_box.ludic index 6b072ff5..ef47a222 100644 --- a/packages/ludic.ui/attr_box.ludic +++ b/packages/ludic.ui/attr_box.ludic @@ -67,8 +67,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..1912fee7 100644 --- a/packages/ludic.ui/attr_edge.ludic +++ b/packages/ludic.ui/attr_edge.ludic @@ -14,7 +14,7 @@ function el_edge(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool { # 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 @@ -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/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..4cbc3530 100644 --- a/packages/ludic.ui/cascade.ludic +++ b/packages/ludic.ui/cascade.ludic @@ -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` 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/color.ludic b/packages/ludic.ui/color.ludic index 88ae3493..7d98456b 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,16 +19,17 @@ 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) diff --git a/packages/ludic.ui/draw_gradient.ludic b/packages/ludic.ui/draw_gradient.ludic index 3edd7163..907e613d 100644 --- a/packages/ludic.ui/draw_gradient.ludic +++ b/packages/ludic.ui/draw_gradient.ludic @@ -7,7 +7,7 @@ export property UiGradient { alphas: []float = null 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 { let s = tpl_words(s0) 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 } 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 g.dir = 1 if d == 1 or d == -3 { g.dir = 2 } diff --git a/packages/ludic.ui/env.ludic b/packages/ludic.ui/env.ludic index 79214a21..45f0603f 100644 --- a/packages/ludic.ui/env.ludic +++ b/packages/ludic.ui/env.ludic @@ -84,7 +84,7 @@ export function ui_err(ui_st: mut UiState, msg0: string) -> void { function ui_has(s: string, part: string) -> bool { let n = len(part) 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 } diff --git a/packages/ludic.ui/expr.ludic b/packages/ludic.ui/expr.ludic index 42da76f9..e4ad8b1f 100644 --- a/packages/ludic.ui/expr.ludic +++ b/packages/ludic.ui/expr.ludic @@ -51,7 +51,7 @@ function rd_op(r: UiRd, op: string) -> bool { rd_ws(r) let n = len(op) 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 return true } diff --git a/packages/ludic.ui/lib_util.ludic b/packages/ludic.ui/lib_util.ludic index 1883f92f..c769f4df 100644 --- a/packages/ludic.ui/lib_util.ludic +++ b/packages/ludic.ui/lib_util.ludic @@ -39,3 +39,13 @@ function lib_beside(path: string, src: string) -> string { if cut < 0 { return 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 +} diff --git a/packages/ludic.ui/life.ludic b/packages/ludic.ui/life.ludic index 277139db..611b920a 100644 --- a/packages/ludic.ui/life.ludic +++ b/packages/ludic.ui/life.ludic @@ -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 { 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 function lc_run(ui_st: mut UiState, screen: string) -> void { diff --git a/packages/ludic.ui/link.ludic b/packages/ludic.ui/link.ludic index 880934f9..c95b0583 100644 --- a/packages/ludic.ui/link.ludic +++ b/packages/ludic.ui/link.ludic @@ -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 } # a word in a list of words written " a b c " function lk_in(words: string, w: string) -> bool { - let pat = ` {w} ` - let n = len(pat) - for i in 0 .. len(words) - n + 1 { - if words[i..i + n] == pat { return true } + let n = len(w) + 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 } } return false } diff --git a/packages/ludic.ui/render3d_image.ludic b/packages/ludic.ui/render3d_image.ludic index 258e7119..fb3d2262 100644 --- a/packages/ludic.ui/render3d_image.ludic +++ b/packages/ludic.ui/render3d_image.ludic @@ -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 { for k in 0 .. len(ui_st.r3u_atlas) { 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 } diff --git a/packages/ludic.ui/state.ludic b/packages/ludic.ui/state.ludic index 3382ea22..827d27c2 100644 --- a/packages/ludic.ui/state.ludic +++ b/packages/ludic.ui/state.ludic @@ -21,7 +21,7 @@ export function ui_forget(ui_st: mut UiState, screen: string) -> void { let vals = new []Val for i in 0 .. len(ui_st.st_keys) { 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 { push(keys, k) push(vals, ui_st.st_vals[i]) diff --git a/packages/ludic.ui/style.ludic b/packages/ludic.ui/style.ludic index d06c771a..25c8f23b 100644 --- a/packages/ludic.ui/style.ludic +++ b/packages/ludic.ui/style.ludic @@ -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)) } 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 function lss_file(ui_st: mut UiState, src: string, path: string) -> UiFile { 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 function lss_import(ui_st: mut UiState, f: UiFile, what: string) -> void { 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] } let other = lib_file(ui_st, lib_beside(f.path, p)) if other != null { diff --git a/packages/ludic.ui/tpl.ludic b/packages/ludic.ui/tpl.ludic index fd1dcdfd..66ae8e14 100644 --- a/packages/ludic.ui/tpl.ludic +++ b/packages/ludic.ui/tpl.ludic @@ -29,7 +29,7 @@ function tpl_read(ui_st: mut UiState, x: Xml, file: string) -> UiTpl { for i in 0 .. len(x.akeys) { let k: string = x.akeys[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_acts, act_parse(v, err)) } 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 function tpl_words(s: string) -> string { + if tpl_clean(s) { return s } var out = "" var gap = false for i in 0 .. len(s) { @@ -86,3 +87,15 @@ function tpl_attr(t: UiTpl, key: string) -> UiExpr { } 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 +} diff --git a/packages/ludic.ui/units.ludic b/packages/ludic.ui/units.ludic index b2679bc8..8b564098 100644 --- a/packages/ludic.ui/units.ludic +++ b/packages/ludic.ui/units.ludic @@ -4,14 +4,14 @@ function el_unit(ui_st: UiState, s: string) -> float { let f = el_num(s) let n = len(s) - if n > 3 and s[n - 3..n] == "rem" { return f * el_px(ui_st, 16.0) } - if n > 2 and s[n - 2..n] == "em" { + if n > 3 and sat(s, n - 3, "rem") { return f * el_px(ui_st, 16.0) } + 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_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) } - if n > 2 and s[n - 2..n] == "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, "vw") { return f * ui_st.ly_vp_w / 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) } # 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 i = 0 while i + 4 <= len(s) { - if s[i..i + 4] == "var(" { + if sat(s, i, "var(") { found = true var j = i + 4 var depth = 1