ludic.ui: var() values found by their pieces, tpl_words/url/border-image by their text, nine-slices, picking, lifecycle, focus, hover, the event queue and input without a list or record made a frame
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
edaec666d1
commit
08ac2f1125
21 changed files with 281 additions and 98 deletions
|
|
@ -61,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 sat(s, 0, "url(") { n.bg_img = el_url(s) } else if tpl_words(s) == "transparent" {
|
||||
if gr_is(tw(ui_st, s)) { nx(n).grad = gr_parse(ui_st, s) } else if len(s) > 4 and sat(s, 0, "url(") { n.bg_img = el_url_u(ui_st, s) } else if tw(ui_st, s) == "transparent" {
|
||||
n.bg = 0
|
||||
n.bg_a = 0.0
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ function el_edge(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
|||
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(ui_st, n, s) } else if key == "transition" { n.tr_dur = el_secs(s) } else { return false }
|
||||
} else if key == "background-image" and gr_is(tw(ui_st, s)) { nx(n).grad = gr_parse(ui_st, s) } else if key == "background-image" { n.bg_img = el_url_u(ui_st, 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
|
||||
|
|
@ -23,15 +23,15 @@ function el_url(s: string) -> string {
|
|||
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))])
|
||||
n.bi_src = el_url_u(ui_st, s[0..Math.min(close + 1, len(s))])
|
||||
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_slice = el_num(tw(ui_st, 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])) }
|
||||
if len(rest) > 1 { n.bi_width = el_unit(ui_st, tw(ui_st, rest[1])) }
|
||||
}
|
||||
# box-shadow: 6px 10px 0 rgba(0, 0, 0, 0.45) - x, y, a blur this renderer draws sharp, the colour
|
||||
function el_shadow(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
||||
if tpl_words(s) == "none" {
|
||||
if tw(ui_st, s) == "none" {
|
||||
n.sh_c = -2
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# 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_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) }
|
||||
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 = tw(ui_st, 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)
|
||||
|
|
@ -71,7 +71,7 @@ function el_text_shadow(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
|||
let x = nx(n)
|
||||
x.tsh_set = true
|
||||
x.tsh_c = -2
|
||||
if tpl_words(s) == "none" { return }
|
||||
if tw(ui_st, s) == "none" { return }
|
||||
let parts = mm_words(ui_st, s)
|
||||
var nums = 0
|
||||
var col = ""
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ export state UiState {
|
|||
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
|
||||
cv_texts: []string = new []string # units_pieces.ludic: a value's var() pieces, kept
|
||||
r3n_cut: []float = new []float # render3d_nine.ludic: a nine-slice's edges, kept
|
||||
r3n_xs: []float = new []float
|
||||
r3n_ys: []float = new []float
|
||||
r3n_us: []float = new []float
|
||||
r3n_vs: []float = new []float
|
||||
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
|
||||
|
|
@ -90,6 +96,8 @@ export state UiState {
|
|||
in_now: UiInput = new UiInput
|
||||
in_was_down: bool = false
|
||||
in_last: UiInput = new UiInput
|
||||
in_rec_a: UiInput = new UiInput
|
||||
in_rec_b: UiInput = new UiInput
|
||||
in_given: bool = false
|
||||
rp_next: []float = [-1.0, -1.0, -1.0, -1.0]
|
||||
rp_a: bool = false
|
||||
|
|
@ -109,14 +117,21 @@ export state UiState {
|
|||
lc_mounts: []UiAct = new []UiAct
|
||||
lc_unmounts: []UiAct = new []UiAct
|
||||
lc_envs: []UiEnv = new []UiEnv
|
||||
lc_sp_keys: []string = new []string # life.ludic: the spare set lc_run fills and swaps in
|
||||
lc_sp_unmounts: []UiAct = new []UiAct
|
||||
lc_sp_envs: []UiEnv = new []UiEnv
|
||||
lc_prev_keys: []string = new []string
|
||||
lc_prev_unmounts: []UiAct = new []UiAct
|
||||
lc_prev_envs: []UiEnv = new []UiEnv
|
||||
nt_list: []UiNative = new []UiNative
|
||||
nt_sp_nodes: []UiNode = new []UiNode # native.ludic: the queue's spare, swapped while it runs
|
||||
nt_sp_names: []string = new []string
|
||||
nt_sp_vals: []Val = new []Val
|
||||
nt_q_nodes: []UiNode = new []UiNode
|
||||
nt_q_names: []string = new []string
|
||||
nt_q_vals: []Val = new []Val
|
||||
fr_hover: []string = new []string
|
||||
fr_hover_sp: []string = new []string
|
||||
fr_press_key: string = ""
|
||||
fr_down_now: bool = false
|
||||
fr_key_press: string = "" # the control Enter or the pad's A is holding down
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# basic names, transparent and none. A colour is 0xRRGGBB; its alpha is left in cl_alpha.
|
||||
const CL_NAMES: string = " black=000000 white=ffffff red=ff0000 green=008000 lime=00ff00 blue=0000ff yellow=ffff00 cyan=00ffff aqua=00ffff magenta=ff00ff fuchsia=ff00ff gray=808080 grey=808080 silver=c0c0c0 maroon=800000 olive=808000 navy=000080 purple=800080 teal=008080 orange=ffa500 "
|
||||
function ui_color(ui_st: mut UiState, name0: string) -> int {
|
||||
let name = tpl_words(name0)
|
||||
let name = tw(ui_st, 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_at(ui_st, name, 1, len(name) - 1) }
|
||||
|
|
@ -38,8 +38,8 @@ function cl_rgb(ui_st: mut UiState, s: string) -> int {
|
|||
while open < len(s) and s[open] != 40 { open += 1 }
|
||||
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])) }
|
||||
for i in 0 .. Math.min(3, len(parts)) { c = c * 256 + Math.clamp(int(el_num(tw(ui_st, parts[i]))), 0, 255) }
|
||||
if len(parts) > 3 { ui_st.cl_alpha = el_num(tw(ui_st, parts[3])) }
|
||||
return c
|
||||
}
|
||||
# a colour as #rrggbb, for a dump or a test
|
||||
|
|
|
|||
|
|
@ -19,22 +19,22 @@ function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient {
|
|||
return g
|
||||
}
|
||||
function gr_parse_new(ui_st: mut UiState, s0: string) -> UiGradient {
|
||||
let s = tpl_words(s0)
|
||||
let s = tw(ui_st, s0)
|
||||
let parts = el_commas(s[16..len(s) - 1])
|
||||
let g = new UiGradient
|
||||
g.cols = new []int
|
||||
g.alphas = new []float
|
||||
g.at = new []float
|
||||
var from = 0
|
||||
if len(parts) > 0 and gr_dir(g, tpl_words(parts[0])) { from = 1 }
|
||||
if len(parts) > 0 and gr_dir(g, tw(ui_st, parts[0])) { from = 1 }
|
||||
for i in from .. len(parts) {
|
||||
let words = mm_words(ui_st, tpl_words(parts[i]))
|
||||
var col = tpl_words(parts[i])
|
||||
let words = mm_words(ui_st, tw(ui_st, parts[i]))
|
||||
var col = tw(ui_st, parts[i])
|
||||
var at = -1.0
|
||||
let last = words[len(words) - 1]
|
||||
if len(words) > 1 and last[len(last) - 1] == 37 {
|
||||
at = el_num(last) / 100.0
|
||||
col = tpl_words(col[0..len(col) - len(last)])
|
||||
col = tw(ui_st, col[0..len(col) - len(last)])
|
||||
}
|
||||
push(g.cols, ui_color(ui_st, col))
|
||||
push(g.alphas, ui_st.cl_alpha)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
# more than half the box across or half of it down (each direction on its own, so a small key cap
|
||||
# keeps its round ends and has no seam), on whole pixels so the nine pieces meet exactly
|
||||
# x0 x1 x2 x3 then y0 y1 y2 y3: the columns' and the rows' edges
|
||||
export function ui_nine_cuts(x: float, y: float, w: float, h: float, dst: float) -> []float {
|
||||
export function ui_nine_cuts(x: float, y: float, w: float, h: float, dst: float) -> []float { return ui_nine_cuts_into(new []float, x, y, w, h, dst) }
|
||||
# the same into a list the caller keeps, emptied first
|
||||
function ui_nine_cuts_into(out: []float, x: float, y: float, w: float, h: float, dst: float) -> []float {
|
||||
let dx = Math.max(Math.min(dst, w / 2.0), 0.0)
|
||||
let dy = Math.max(Math.min(dst, h / 2.0), 0.0)
|
||||
let out = new []float
|
||||
List.clear(out)
|
||||
push(out, nn_px(x))
|
||||
push(out, nn_px(x + dx))
|
||||
push(out, nn_px(x + w - dx))
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function fc_move(ui_st: mut UiState, step: int) -> void {
|
|||
}
|
||||
# the keys, after the tree is laid out: moving, then acting on what has the focus
|
||||
function fc_keys(ui_st: mut UiState, root: UiNode) -> void {
|
||||
ui_st.fc_list = new []UiNode
|
||||
List.clear(ui_st.fc_list)
|
||||
let top = pp_top(root)
|
||||
if top != null { fc_collect(ui_st, top) } else { fc_collect(ui_st, root) }
|
||||
if ct_key_capture(ui_st, ui_st.fc_list) { return }
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import "pool_reset.ludic"
|
|||
import "pool_reset_more.ludic"
|
||||
import "memo.ludic"
|
||||
import "memo_join.ludic"
|
||||
import "units_pieces.ludic"
|
||||
import "expr.ludic"
|
||||
import "expr_parse.ludic"
|
||||
import "expr_prim.ludic"
|
||||
|
|
|
|||
|
|
@ -34,8 +34,10 @@ function in_read(ui_st: mut UiState) -> void {
|
|||
return
|
||||
}
|
||||
ui_st.in_was_down = ui_st.in_now.down
|
||||
var i = ui_st.in_rec_a # the package's own two records, in turn (never a host's)
|
||||
if ui_st.in_now == ui_st.in_rec_a { i = ui_st.in_rec_b }
|
||||
ui_st.in_last = ui_st.in_now
|
||||
let i = new UiInput
|
||||
in_reset(i)
|
||||
if ui_st.ui_be != null and ui_st.ui_be.input {
|
||||
let ps = in_pointer_scale(ui_st)
|
||||
i.x = float(Input.mouse_x()) * ps
|
||||
|
|
@ -81,3 +83,31 @@ export function ui_input(ui_st: mut UiState, i: UiInput) -> void {
|
|||
ui_st.in_now = i
|
||||
ui_st.in_given = true
|
||||
}
|
||||
# every field back to its default (generated from UiInput)
|
||||
function in_reset(i: UiInput) -> void {
|
||||
i.x = -1.0
|
||||
i.y = -1.0
|
||||
i.down = false
|
||||
i.wheel = 0.0
|
||||
i.tab = false
|
||||
i.shift = false
|
||||
i.up = false
|
||||
i.down_key = false
|
||||
i.left = false
|
||||
i.right = false
|
||||
i.enter = false
|
||||
i.space = false
|
||||
i.escape = false
|
||||
i.backspace = false
|
||||
i.typed = ""
|
||||
i.key = 0
|
||||
i.held_up = false
|
||||
i.held_down = false
|
||||
i.held_left = false
|
||||
i.held_right = false
|
||||
i.pad_a = false
|
||||
i.pad_b = false
|
||||
i.held_enter = false
|
||||
i.down_right = false
|
||||
i.down_middle = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,10 +25,13 @@ function in_get(ui_st: mut UiState, key: string, k: UiClass) -> UiInst {
|
|||
}
|
||||
# after a build of `screen`: its instances the build did not reach are unmounted
|
||||
function in_sweep(ui_st: mut UiState, screen: string) -> void {
|
||||
let keep = new []UiInst
|
||||
var kept = 0
|
||||
for i in 0 .. len(ui_st.in_list) {
|
||||
let it = ui_st.in_list[i]
|
||||
if it.seen == ui_st.in_build or not lc_mine(it.key, screen) { push(keep, it) }
|
||||
if it.seen == ui_st.in_build or not lc_mine(it.key, screen) {
|
||||
ui_st.in_list[kept] = it
|
||||
kept += 1
|
||||
}
|
||||
}
|
||||
ui_st.in_list = keep
|
||||
while len(ui_st.in_list) > kept { List.pop(ui_st.in_list) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
# in last frame is mounted, and one that is gone is unmounted, each once, after the frame is drawn
|
||||
# a build starts its own records: a screen built twice keeps only the last build's
|
||||
function lc_begin(ui_st: mut UiState) -> void {
|
||||
ui_st.lc_keys = new []string
|
||||
ui_st.lc_mounts = new []UiAct
|
||||
ui_st.lc_unmounts = new []UiAct
|
||||
ui_st.lc_envs = new []UiEnv
|
||||
List.clear(ui_st.lc_keys)
|
||||
List.clear(ui_st.lc_mounts)
|
||||
List.clear(ui_st.lc_unmounts)
|
||||
List.clear(ui_st.lc_envs)
|
||||
}
|
||||
function lc_note(ui_st: mut UiState, key: string, names: []string, acts: []UiAct, e: UiEnv) -> void {
|
||||
var m: UiAct = null
|
||||
|
|
@ -29,9 +29,12 @@ function lc_run(ui_st: mut UiState, screen: string) -> void {
|
|||
for i in 0 .. len(ui_st.lc_keys) {
|
||||
if ui_st.lc_mounts[i] != null and not sel_has(ui_st.lc_prev_keys, ui_st.lc_keys[i]) { act_run(ui_st, ui_st.lc_mounts[i], ui_st.lc_envs[i]) }
|
||||
}
|
||||
let keys = new []string
|
||||
let unmounts = new []UiAct
|
||||
let envs = new []UiEnv
|
||||
let keys = ui_st.lc_sp_keys # the spare set, emptied: the records carried over
|
||||
let unmounts = ui_st.lc_sp_unmounts
|
||||
let envs = ui_st.lc_sp_envs
|
||||
List.clear(keys)
|
||||
List.clear(unmounts)
|
||||
List.clear(envs)
|
||||
for i in 0 .. len(ui_st.lc_prev_keys) {
|
||||
let k = ui_st.lc_prev_keys[i]
|
||||
if lc_mine(k, screen) {
|
||||
|
|
@ -47,11 +50,14 @@ function lc_run(ui_st: mut UiState, screen: string) -> void {
|
|||
push(unmounts, ui_st.lc_unmounts[i])
|
||||
push(envs, ui_st.lc_envs[i])
|
||||
}
|
||||
ui_st.lc_sp_keys = ui_st.lc_prev_keys
|
||||
ui_st.lc_sp_unmounts = ui_st.lc_prev_unmounts
|
||||
ui_st.lc_sp_envs = ui_st.lc_prev_envs
|
||||
ui_st.lc_prev_keys = keys
|
||||
ui_st.lc_prev_unmounts = unmounts
|
||||
ui_st.lc_prev_envs = envs
|
||||
ui_st.lc_keys = new []string
|
||||
ui_st.lc_mounts = new []UiAct
|
||||
ui_st.lc_unmounts = new []UiAct
|
||||
ui_st.lc_envs = new []UiEnv
|
||||
List.clear(ui_st.lc_keys)
|
||||
List.clear(ui_st.lc_mounts)
|
||||
List.clear(ui_st.lc_unmounts)
|
||||
List.clear(ui_st.lc_envs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,17 @@ export property UiMemo {
|
|||
tags: []int = null # what kind of result: a separator, or a kind below
|
||||
lists: [][]string = null
|
||||
grads: []UiGradient = null
|
||||
texts: []string = null # a text worked out from the key: tw, el_url
|
||||
vals: []Val = null # a Value made from it: cs_vars_in's
|
||||
vtpls: []UiVarTpl = 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
|
||||
const MM_TW: int = -4 # tpl_words
|
||||
const MM_URL: int = -5 # el_url
|
||||
const MM_VARS: int = -6 # cs_vars_in: keys[i] is the text with every var() put in
|
||||
const MM_VTPL: int = -7 # a value's var()s parsed (units_pieces.ludic)
|
||||
function mm_new() -> UiMemo {
|
||||
let m = new UiMemo
|
||||
m.slots = words(MM_CAP)
|
||||
|
|
@ -20,6 +27,9 @@ function mm_new() -> UiMemo {
|
|||
m.tags = new []int
|
||||
m.lists = new [][]string
|
||||
m.grads = new []UiGradient
|
||||
m.texts = new []string
|
||||
m.vals = new []Val
|
||||
m.vtpls = new []UiVarTpl
|
||||
return m
|
||||
}
|
||||
function mm_of(ui_st: mut UiState) -> UiMemo {
|
||||
|
|
@ -51,6 +61,9 @@ function mm_add(m: UiMemo, tag: int, s: string) -> int {
|
|||
push(m.tags, tag)
|
||||
push(m.lists, null)
|
||||
push(m.grads, null)
|
||||
push(m.texts, null)
|
||||
push(m.vals, null)
|
||||
push(m.vtpls, null)
|
||||
m.slots[at] = len(m.keys)
|
||||
return len(m.keys) - 1
|
||||
}
|
||||
|
|
@ -66,3 +79,24 @@ function mm_split(ui_st: mut UiState, s: string, c: int) -> []string {
|
|||
return parts
|
||||
}
|
||||
function mm_words(ui_st: mut UiState, s: string) -> []string { return mm_split(ui_st, s, MM_WORDS) }
|
||||
# tpl_words, made once per text (a clean one is its own answer, and nothing is kept for it)
|
||||
function tw(ui_st: mut UiState, s: string) -> string {
|
||||
if tpl_clean(s) { return s }
|
||||
let m = mm_of(ui_st)
|
||||
let i = mm_find(m, MM_TW, s)
|
||||
if i >= 0 { return m.texts[i] }
|
||||
let t = tpl_words(s)
|
||||
let j = mm_add(m, MM_TW, s)
|
||||
if j >= 0 { m.texts[j] = t }
|
||||
return t
|
||||
}
|
||||
# el_url, made once per text
|
||||
function el_url_u(ui_st: mut UiState, s: string) -> string {
|
||||
let m = mm_of(ui_st)
|
||||
let i = mm_find(m, MM_URL, s)
|
||||
if i >= 0 { return m.texts[i] }
|
||||
let t = el_url(s)
|
||||
let j = mm_add(m, MM_URL, s)
|
||||
if j >= 0 { m.texts[j] = t }
|
||||
return t
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ function mm_join(ui_st: mut UiState, a: string, sep: string, b: string) -> strin
|
|||
push(m.tags, MM_JOIN)
|
||||
push(m.lists, null)
|
||||
push(m.grads, null)
|
||||
push(m.texts, null)
|
||||
push(m.vals, null)
|
||||
push(m.vtpls, null)
|
||||
m.slots[at] = len(m.keys)
|
||||
return k
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ function nt_find(ui_st: UiState, tag: string) -> UiNative {
|
|||
return null
|
||||
}
|
||||
function nt_fired_clear(ui_st: mut UiState) -> void {
|
||||
ui_st.nt_q_nodes = new []UiNode
|
||||
ui_st.nt_q_names = new []string
|
||||
ui_st.nt_q_vals = new []Val
|
||||
List.clear(ui_st.nt_q_nodes)
|
||||
List.clear(ui_st.nt_q_names)
|
||||
List.clear(ui_st.nt_q_vals)
|
||||
}
|
||||
export function ui_fire(ui_st: mut UiState, n: UiNode, name: string, value: Val) -> void {
|
||||
push(ui_st.nt_q_nodes, n)
|
||||
|
|
@ -49,11 +49,17 @@ export function ui_fire(ui_st: mut UiState, n: UiNode, name: string, value: Val)
|
|||
push(ui_st.nt_q_vals, value)
|
||||
}
|
||||
function nt_fired_run(ui_st: mut UiState) -> void {
|
||||
let nodes = ui_st.nt_q_nodes
|
||||
let nodes = ui_st.nt_q_nodes # run from these while what they fire goes to the spares
|
||||
let names = ui_st.nt_q_names
|
||||
let vals = ui_st.nt_q_vals
|
||||
ui_st.nt_q_nodes = ui_st.nt_sp_nodes
|
||||
ui_st.nt_q_names = ui_st.nt_sp_names
|
||||
ui_st.nt_q_vals = ui_st.nt_sp_vals
|
||||
nt_fired_clear(ui_st)
|
||||
for i in 0 .. len(nodes) { nt_run(ui_st, nodes[i], names[i], vals[i]) }
|
||||
ui_st.nt_sp_nodes = nodes
|
||||
ui_st.nt_sp_names = names
|
||||
ui_st.nt_sp_vals = vals
|
||||
}
|
||||
# the element's on-<name>, with `event` bound to what happened
|
||||
function nt_run(ui_st: mut UiState, n: UiNode, name: string, value: Val) -> void {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ function fr_was_hovered(ui_st: UiState, key: string) -> bool { return sel_has(ui
|
|||
function fr_pointer(ui_st: mut UiState, root: UiNode) -> void {
|
||||
let i = ui_st.in_now
|
||||
ui_st.fr_down_now = i.down
|
||||
let keys = new []string
|
||||
let keys = ui_st.fr_hover_sp # the spare list, emptied; last frame's goes spare
|
||||
List.clear(keys)
|
||||
let top = pp_top(root)
|
||||
var scope = root
|
||||
if top != null { scope = top }
|
||||
fr_under(scope, i.x, i.y, keys)
|
||||
ui_st.fr_hover_sp = ui_st.fr_hover
|
||||
ui_st.fr_hover = keys
|
||||
if top != null and pp_pointer(ui_st, top) { return }
|
||||
if ui_st.sc_drag != "" or ct_key_mouse(ui_st, root) { return }
|
||||
|
|
|
|||
|
|
@ -8,29 +8,11 @@ function fr_in(n: UiNode, px: float, py: float) -> bool {
|
|||
if n.ex != null and n.ex.no_pointer { return false }
|
||||
return px >= n.x and py >= n.y and px < n.x + n.cw and py < n.y + n.ch
|
||||
}
|
||||
# the nearest element that wants `kind`, out from the topmost one under the point
|
||||
# the nearest element that wants `kind`, out from the topmost one under the point: the children are
|
||||
# walked the other way from how they are painted (positioned ones by z-index, highest first, then
|
||||
# the flow, last first; a popover is on the top layer, not here), with no list made for it
|
||||
function fr_pick(n: UiNode, px: float, py: float, kind: int) -> UiNode {
|
||||
let kids = fr_paint_rev(n)
|
||||
for i in 0 .. len(kids) {
|
||||
if fr_in(kids[i], px, py) {
|
||||
let r = fr_pick(kids[i], px, py, kind)
|
||||
if r != null { return r }
|
||||
return fr_wants(n, kind)
|
||||
}
|
||||
}
|
||||
return fr_wants(n, kind)
|
||||
}
|
||||
function fr_wants(n: UiNode, kind: int) -> UiNode {
|
||||
if kind == PK_CONTROL and (n.focusable or n.act != null) { return n }
|
||||
if kind == PK_POINTER and pe_wants(n, false) { return n }
|
||||
if kind == PK_WHEEL and pe_wants(n, true) { return n }
|
||||
return null
|
||||
}
|
||||
# the children the other way from how they are painted: positioned ones by z-index, highest first,
|
||||
# then the flow, last first; a popover is on the top layer, not here
|
||||
function fr_paint_rev(n: UiNode) -> []UiNode {
|
||||
let out = new []UiNode
|
||||
if n.children == null { return out }
|
||||
if n.children == null { return fr_wants(n, kind) }
|
||||
var lo = 1000000
|
||||
var hi = -1000000
|
||||
for i in 0 .. len(n.children) {
|
||||
|
|
@ -45,15 +27,27 @@ function fr_paint_rev(n: UiNode) -> []UiNode {
|
|||
var i = len(n.children) - 1
|
||||
while i >= 0 {
|
||||
let c = n.children[i]
|
||||
if c.pos != 0 and not c.popover and c.z == z { push(out, c) }
|
||||
if c.pos != 0 and not c.popover and c.z == z and fr_in(c, px, py) { return fr_pick_in(n, c, px, py, kind) }
|
||||
i -= 1
|
||||
}
|
||||
z -= 1
|
||||
}
|
||||
var k = len(n.children) - 1
|
||||
while k >= 0 {
|
||||
if n.children[k].pos == 0 and not n.children[k].popover { push(out, n.children[k]) }
|
||||
let c = n.children[k]
|
||||
if c.pos == 0 and not c.popover and fr_in(c, px, py) { return fr_pick_in(n, c, px, py, kind) }
|
||||
k -= 1
|
||||
}
|
||||
return out
|
||||
return fr_wants(n, kind)
|
||||
}
|
||||
function fr_pick_in(n: UiNode, c: UiNode, px: float, py: float, kind: int) -> UiNode {
|
||||
let r = fr_pick(c, px, py, kind)
|
||||
if r != null { return r }
|
||||
return fr_wants(n, kind)
|
||||
}
|
||||
function fr_wants(n: UiNode, kind: int) -> UiNode {
|
||||
if kind == PK_CONTROL and (n.focusable or n.act != null) { return n }
|
||||
if kind == PK_POINTER and pe_wants(n, false) { return n }
|
||||
if kind == PK_WHEEL and pe_wants(n, true) { return n }
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ function r3u_nine(render3d_st: mut Render3dState, ui_st: mut UiState, src: strin
|
|||
if t == 0 { return }
|
||||
let su = slice / float(Math.max(tex_width(render3d_st, t), 1))
|
||||
let sv = slice / float(Math.max(tex_height(render3d_st, t), 1))
|
||||
let cut = ui_nine_cuts(x, y, w, h, dst)
|
||||
let xs = r3u_four(cut[0], cut[1], cut[2], cut[3])
|
||||
let ys = r3u_four(cut[4], cut[5], cut[6], cut[7])
|
||||
let us = r3u_four(0.0, su, 1.0 - su, 1.0)
|
||||
let vs = r3u_four(0.0, sv, 1.0 - sv, 1.0)
|
||||
let cut = ui_nine_cuts_into(ui_st.r3n_cut, x, y, w, h, dst)
|
||||
let xs = r3u_four(ui_st.r3n_xs, cut[0], cut[1], cut[2], cut[3])
|
||||
let ys = r3u_four(ui_st.r3n_ys, cut[4], cut[5], cut[6], cut[7])
|
||||
let us = r3u_four(ui_st.r3n_us, 0.0, su, 1.0 - su, 1.0)
|
||||
let vs = r3u_four(ui_st.r3n_vs, 0.0, sv, 1.0 - sv, 1.0)
|
||||
for j in 0 .. 3 {
|
||||
for i in 0 .. 3 {
|
||||
let x0 = int(xs[i])
|
||||
|
|
@ -21,8 +21,8 @@ function r3u_nine(render3d_st: mut Render3dState, ui_st: mut UiState, src: strin
|
|||
}
|
||||
}
|
||||
}
|
||||
function r3u_four(a: float, b: float, c: float, d: float) -> []float {
|
||||
let out = new []float
|
||||
function r3u_four(out: []float, a: float, b: float, c: float, d: float) -> []float {
|
||||
List.clear(out)
|
||||
push(out, a)
|
||||
push(out, b)
|
||||
push(out, c)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
# 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: mut UiState, n: UiNode, s: string) -> void {
|
||||
let parts = mm_split(ui_st, tpl_words(s), 32)
|
||||
let parts = mm_split(ui_st, tw(ui_st, 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) }
|
||||
}
|
||||
function el_line_h(ui_st: UiState, n: UiNode, s0: string) -> void {
|
||||
let s = tpl_words(s0)
|
||||
function el_line_h(ui_st: mut UiState, n: UiNode, s0: string) -> void {
|
||||
let s = tw(ui_st, s0)
|
||||
let x = nx(n)
|
||||
x.lh_kind = 0
|
||||
if s == "normal" or s == "" { return }
|
||||
|
|
|
|||
|
|
@ -46,37 +46,31 @@ function cs_var(n: UiNode, name: string) -> string {
|
|||
}
|
||||
return null
|
||||
}
|
||||
# a value with var(--name) or var(--name, fallback) in it, with each replaced by what it names
|
||||
# a value with var(--name) or var(--name, fallback) in it, with each replaced by what it names. What
|
||||
# it becomes is found by its pieces - stretches of the value and each var's text - compared in place,
|
||||
# so a result seen before (every frame's, nearly always) is its kept Value, and nothing is built
|
||||
function cs_vars_in(ui_st: mut UiState, n: UiNode, v: Val) -> Val {
|
||||
if Value.kind(v) != 4 { return v }
|
||||
let s = Value.as_str(v)
|
||||
var at = 0
|
||||
var out = ""
|
||||
var found = false
|
||||
var i = 0
|
||||
while i + 4 <= len(s) {
|
||||
if sat(s, i, "var(") {
|
||||
found = true
|
||||
var j = i + 4
|
||||
var depth = 1
|
||||
while j < len(s) and depth > 0 {
|
||||
if s[j] == 40 { depth += 1 }
|
||||
if s[j] == 41 { depth -= 1 }
|
||||
j += 1
|
||||
}
|
||||
out = out + s[at..i] + cs_var_ref(ui_st, n, s[i + 4..j - 1])
|
||||
at = j
|
||||
i = j
|
||||
} else { i += 1 }
|
||||
if not cv_pieces(ui_st, n, s) { return v }
|
||||
let m = mm_of(ui_st)
|
||||
var at = cv_hash(ui_st) & (MM_CAP - 1)
|
||||
while m.slots[at] != 0 {
|
||||
let i = m.slots[at] - 1
|
||||
if m.tags[i] == MM_VARS and cv_same(ui_st, m.keys[i]) { return m.vals[i] }
|
||||
at = (at + 1) & (MM_CAP - 1)
|
||||
}
|
||||
if not found { return v }
|
||||
return Value.str(tpl_words(out + s[at..len(s)]))
|
||||
let raw = cv_join(ui_st)
|
||||
let r = Value.str(tpl_words(raw))
|
||||
let j = mm_add(m, MM_VARS, raw)
|
||||
if j >= 0 { m.vals[j] = r }
|
||||
return r
|
||||
}
|
||||
function cs_var_ref(ui_st: mut UiState, n: UiNode, inner: string) -> string {
|
||||
let parts = mm_split(ui_st, inner, 44)
|
||||
let v = cs_var(n, tpl_words(parts[0]))
|
||||
let v = cs_var(n, tw(ui_st, parts[0]))
|
||||
if v != null { return v }
|
||||
if len(parts) > 1 { return tpl_words(inner[len(parts[0]) + 1..len(inner)]) }
|
||||
ui_err(ui_st, `var({tpl_words(parts[0])}): no element here or above sets it`)
|
||||
if len(parts) > 1 { return tw(ui_st, inner[len(parts[0]) + 1..len(inner)]) }
|
||||
ui_err(ui_st, `var({tw(ui_st, parts[0])}): no element here or above sets it`)
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
93
packages/ludic.ui/units_pieces.ludic
Normal file
93
packages/ludic.ui/units_pieces.ludic
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# units_pieces.ludic - a value with var()s, parsed once by its text into its stretches, the names it
|
||||
# asks for and their fallbacks; each frame its pieces are those stretches and what the names hold
|
||||
# here, in a list kept on the state, hashed and compared against the memo without a copy
|
||||
export property UiVarTpl {
|
||||
lits: []string = null # the text around the var()s: one more than there are names
|
||||
names: []string = null
|
||||
falls: []string = null # a var's fallback, or null
|
||||
}
|
||||
function cv_tpl(ui_st: mut UiState, s: string) -> UiVarTpl {
|
||||
let m = mm_of(ui_st)
|
||||
let i = mm_find(m, MM_VTPL, s)
|
||||
if i >= 0 { return m.vtpls[i] }
|
||||
let t = cv_parse(ui_st, s)
|
||||
let j = mm_add(m, MM_VTPL, s)
|
||||
if j >= 0 { m.vtpls[j] = t }
|
||||
return t
|
||||
}
|
||||
function cv_parse(ui_st: mut UiState, s: string) -> UiVarTpl {
|
||||
let t = new UiVarTpl
|
||||
t.lits = new []string
|
||||
t.names = new []string
|
||||
t.falls = new []string
|
||||
var at = 0
|
||||
var i = 0
|
||||
while i + 4 <= len(s) {
|
||||
if sat(s, i, "var(") {
|
||||
var j = i + 4
|
||||
var depth = 1
|
||||
while j < len(s) and depth > 0 {
|
||||
if s[j] == 40 { depth += 1 }
|
||||
if s[j] == 41 { depth -= 1 }
|
||||
j += 1
|
||||
}
|
||||
push(t.lits, s[at..i])
|
||||
let inner = s[i + 4..j - 1]
|
||||
let parts = lss_split(inner, 44)
|
||||
push(t.names, tpl_words(parts[0]))
|
||||
if len(parts) > 1 { push(t.falls, tpl_words(inner[len(parts[0]) + 1..len(inner)])) } else { push(t.falls, null) }
|
||||
at = j
|
||||
i = j
|
||||
} else { i += 1 }
|
||||
}
|
||||
push(t.lits, s[at..len(s)])
|
||||
return t
|
||||
}
|
||||
# this element's pieces for `s` into cv_texts; false when it has no var() at all
|
||||
function cv_pieces(ui_st: mut UiState, n: UiNode, s: string) -> bool {
|
||||
if not cv_has(s) { return false }
|
||||
let t = cv_tpl(ui_st, s)
|
||||
List.clear(ui_st.cv_texts)
|
||||
for k in 0 .. len(t.names) {
|
||||
push(ui_st.cv_texts, t.lits[k])
|
||||
push(ui_st.cv_texts, cv_value(ui_st, n, t, k))
|
||||
}
|
||||
push(ui_st.cv_texts, t.lits[len(t.names)])
|
||||
return true
|
||||
}
|
||||
function cv_has(s: string) -> bool {
|
||||
for i in 0 .. len(s) - 3 {
|
||||
if s[i] == 118 and sat(s, i, "var(") { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
function cv_value(ui_st: mut UiState, n: UiNode, t: UiVarTpl, k: int) -> string {
|
||||
let v = cs_var(n, t.names[k])
|
||||
if v != null { return v }
|
||||
if t.falls[k] != null { return t.falls[k] }
|
||||
ui_err(ui_st, `var({t.names[k]}): no element here or above sets it`)
|
||||
return ""
|
||||
}
|
||||
function cv_hash(ui_st: UiState) -> int {
|
||||
var h = 216613626 ^ (MM_VARS & 255)
|
||||
for p in 0 .. len(ui_st.cv_texts) {
|
||||
let t = ui_st.cv_texts[p]
|
||||
for i in 0 .. len(t) { h = ((h ^ t[i]) * 16777619) & 2147483647 }
|
||||
}
|
||||
return h
|
||||
}
|
||||
# the pieces, one after another, are exactly `k`
|
||||
function cv_same(ui_st: UiState, k: string) -> bool {
|
||||
var at = 0
|
||||
for p in 0 .. len(ui_st.cv_texts) {
|
||||
let t = ui_st.cv_texts[p]
|
||||
if not sat(k, at, t) { return false }
|
||||
at += len(t)
|
||||
}
|
||||
return at == len(k)
|
||||
}
|
||||
function cv_join(ui_st: UiState) -> string {
|
||||
var out = ""
|
||||
for p in 0 .. len(ui_st.cv_texts) { out = out + ui_st.cv_texts[p] }
|
||||
return out
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue