A prompt that comes and goes as a player walks (co-op's netleak: in_get, cmp_*_new, bd_class, value_slot/put) made a new record, props and model on every mount, and an action's call answered into a new Val (ev_call_with). examples/library/ui_remount: two thousand comings and goings hold the heap at 0, and the counter starts at 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
228 lines
10 KiB
Text
228 lines
10 KiB
Text
# backend.ludic - the renderer seam: what a renderer draws, and nothing more. Only rect, text and
|
|
# measure are needed; the rest improve on a default (a rounded rect falls back to a square one, an
|
|
# image to nothing). Input is the runtime's own (Input.*), so no renderer has to supply it.
|
|
export property UiBackend {
|
|
rect: fn(float, float, float, float, int, float) -> void = null # x y w h rgb alpha
|
|
round: fn(float, float, float, float, float, int, float) -> void = null # ... radius rgb alpha
|
|
ring: fn(float, float, float, float, float, float, int, float) -> void = null # x y w h radius width rgb alpha
|
|
text: fn(float, float, string, float, int, float) -> void = null # x y text size rgb alpha
|
|
measure: fn(string, float) -> float = null
|
|
image: fn(string, float, float, float, float, int, float) -> void = null # src x y w h tint alpha
|
|
image_w: fn(string) -> float = null # a picture's own size, for object-fit
|
|
image_h: fn(string) -> float = null
|
|
nine: fn(string, float, float, float, float, float, float, int, float) -> void = null # src slice x y w h width tint alpha (ui_nine_cuts)
|
|
clip: fn(float, float, float, float) -> void = null
|
|
unclip: fn() -> void = null
|
|
scale: fn() -> float = null # screen pixels per design pixel
|
|
pointer_scale: fn() -> float = null # screen pixels per unit of Input.mouse_x/y (2 on Retina)
|
|
translate: fn(string) -> string = null # the program's language, for every text a template shows
|
|
sound: fn(string) -> void = null # a control was used: "click", or its sound="..."
|
|
read: fn(string) -> string = null # a template file's text (else Fs.read_text)
|
|
now: fn() -> float = null # seconds, for animation (else frames at 60 a second)
|
|
input: bool = true # false: take no input from Input.* (a test drives it)
|
|
}
|
|
# a renderer a program installs by importing it (render3d.ludic): the first one is the backend
|
|
property UiRenderer {
|
|
key: string = ""
|
|
make: fn() -> UiBackend = null
|
|
}
|
|
registry UiRenderers of UiRenderer as UIR
|
|
function ui_default_backend() -> UiBackend {
|
|
if len(UiRenderers) == 0 { return null }
|
|
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
|
|
uv_on: bool = false # pool.ludic: a build is under way, so expressions' Values are pooled
|
|
uv_true: Val = null
|
|
uv_false: Val = 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
|
|
ev_ring: []Val = new []Val # eval.ludic: calls' answers while an action runs, EV_RING of them
|
|
ev_ring_at: int = 0
|
|
cv_texts: []string = new []string # units_pieces.ludic: a value's var() pieces, kept
|
|
tx_pieces: [][]string = new [][]string # eval_text.ludic: a text's pieces, a list a depth
|
|
tx_depth: int = 0
|
|
ca_pool: []CaVal = new []CaVal # calc.ludic: an evaluation's terms, used again by the next
|
|
ca_used: int = 0
|
|
ca_args: [][]CaVal = new [][]CaVal
|
|
ca_depth: int = 0
|
|
tt_src: string = null # tooltip.ludic: the title tt_text was translated from
|
|
fit_out: []float = new []float # draw_content.ludic: an image's box, 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
|
|
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
|
|
an_frame: int = 0
|
|
el_s0: float = 0.0
|
|
el_s1: float = 0.0
|
|
el_s2: float = 0.0
|
|
el_s3: float = 0.0
|
|
ui_be: UiBackend = ui_default_backend()
|
|
tr_no: bool = false # inside translate="no" while a frame is built
|
|
ca_s: string = ""
|
|
ca_i: int = 0
|
|
ca_bad: string = ""
|
|
ca_px: float = 0.0 # the result: its lengths, its percentage, and whether it had units
|
|
ca_pct: float = 0.0
|
|
ca_num: float = 0.0 # a plain number's own value, and whether it was one
|
|
ca_bare: bool = false
|
|
cs_pass: int = 0
|
|
ua_sh: UiSheet = null
|
|
uc_list: []UiClass = new []UiClass # filled by the compiler's registrations, before the program's globals
|
|
cl_alpha: float = 1.0
|
|
ct_capture: string = ""
|
|
ct_capture_at: int = -1 # the frame listening began, whose own key started it
|
|
ct_capture_seen: int = -1 # the last frame that began listening, for ui_capturing
|
|
ct_num_key: string = ""
|
|
ct_num_text: string = ""
|
|
dv_on: bool = false
|
|
dv_frames: int = 0
|
|
dv_dumped: bool = false
|
|
fr_ga: float = 1.0 # the opacity of everything around this element, which it multiplies
|
|
gr_a: float = 1.0
|
|
fr_clips: []float = new []float
|
|
ui_view: UiView = null
|
|
ui_errs: []string = new []string
|
|
ui_at: string = ""
|
|
fc_key: string = ""
|
|
fc_visible: bool = false # focus came from the keyboard: :focus-visible, and the ring
|
|
fc_list: []UiNode = new []UiNode
|
|
fb_roots: []UiNode = new []UiNode
|
|
hd_key: string = ""
|
|
hd_t0: float = 0.0
|
|
hd_last: float = 0.0
|
|
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
|
|
rp_b: bool = false
|
|
rp_act: bool = false
|
|
in_act_down: bool = false # A or Enter went down this frame, or came up
|
|
in_act_up: bool = false
|
|
in_list: []UiInst = new []UiInst
|
|
in_free: []UiInst = new []UiInst # unmounted instances, each mounted again as its class's next one
|
|
in_build: int = 0
|
|
ly_fg_a: float = 1.0
|
|
ly_lead: float = 0.0
|
|
ly_extra: float = 0.0
|
|
lib_files: []UiFile = new []UiFile
|
|
lib_screen_names: []string = new []string
|
|
lib_screens: []UiTpl = new []UiTpl
|
|
lc_keys: []string = new []string
|
|
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
|
|
pe_key: string = "" # the element the pointer is captured by
|
|
pe_btn: int = 0
|
|
pop_list: []UiNode = new []UiNode
|
|
pop_spare: []UiNode = new []UiNode
|
|
pp_bx: float = 0.0
|
|
pp_by: float = 0.0
|
|
pp_bw: float = 0.0
|
|
pp_bh: float = 0.0
|
|
r3u_user: float = 1.0
|
|
r3u_tex_paths: []string = new []string
|
|
r3u_tex_ids: []int = new []int
|
|
r3u_tex_sizes: []int = new []int
|
|
r3u_tex_seen: []float = new []float
|
|
r3u_atlas: []string = new []string
|
|
r3u_atlas_tex: []int = new []int
|
|
r3u_atlas_cols: []int = new []int
|
|
r3u_atlas_rows: []int = new []int
|
|
r3u_atlas_names: [][]string = new [][]string
|
|
sc_keys: []string = new []string
|
|
sc_offs: []float = new []float
|
|
sc_drag: string = ""
|
|
sc_scope: UiNode = null # the popover that has the pointer this frame, if one is up
|
|
sb_x: float = 0.0
|
|
sb_w: float = 0.0
|
|
sb_ty: float = 0.0
|
|
sb_th: float = 0.0
|
|
sb_bar: UiNode = null
|
|
sc_grab: float = 0.0 # how far below the thumb's top the pointer holds it
|
|
sc_want_keys: []string = new []string
|
|
sc_want_px: []float = new []float
|
|
st_keys: []string = new []string
|
|
st_vals: []Val = new []Val
|
|
tf_size: float = 0.0 # the size tf_fit drew at
|
|
tt_key: string = ""
|
|
tt_since: float = 0.0
|
|
tt_text: string = ""
|
|
tt_x: float = 0.0
|
|
tt_y: float = 0.0
|
|
tt_by_keys: bool = false # the focus moved by the keyboard since the pointer last moved
|
|
tt_focus_seen: string = ""
|
|
tp_nl: []int = new []int
|
|
tr_keys: []string = new []string
|
|
tr_from: []float = new []float
|
|
tr_to: []float = new []float
|
|
tr_at: []float = new []float
|
|
el_cur: UiNode = null
|
|
el_key: string = ""
|
|
ly_vp_x: float = 0.0
|
|
ly_vp_y: float = 0.0
|
|
ly_vp_w: float = 0.0
|
|
ly_vp_h: float = 0.0
|
|
}
|
|
export function ui_backend(ui_st: mut UiState, b: UiBackend) -> void { ui_st.ui_be = b }
|
|
function ui_px(ui_st: UiState, v: float) -> float {
|
|
if ui_st.ui_be != null and ui_st.ui_be.scale != null { return v * ui_st.ui_be.scale() }
|
|
return v
|
|
}
|
|
function ui_tr(ui_st: UiState, s: string) -> string {
|
|
if ui_st.tr_no { return s }
|
|
if ui_st.ui_be != null and ui_st.ui_be.translate != null { return ui_st.ui_be.translate(s) }
|
|
return s
|
|
}
|
|
function ui_sound(ui_st: UiState, name: string) -> void {
|
|
if ui_st.ui_be != null and ui_st.ui_be.sound != null { ui_st.ui_be.sound(name) }
|
|
}
|
|
# the program's language: every text a template shows goes through `f` before it is measured
|
|
export function ui_translator(ui_st: mut UiState, f: fn(string) -> string) -> void {
|
|
if ui_st.ui_be != null { ui_st.ui_be.translate = f }
|
|
}
|
|
# the program's sounds: "click" when a control is used, or the control's own sound="..."
|
|
export function ui_sounds(ui_st: mut UiState, f: fn(string) -> void) -> void {
|
|
if ui_st.ui_be != null { ui_st.ui_be.sound = f }
|
|
}
|
|
# the program's clock, in seconds, for animation (a game's own time, which a headless run counts)
|
|
export function ui_clock(ui_st: mut UiState, f: fn() -> float) -> void {
|
|
if ui_st.ui_be != null { ui_st.ui_be.now = f }
|
|
}
|