# 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) said: fn(string) -> void = null # a template error, for a host that reports it itself (else printed) emitted: fn(string) -> void = null # `emit name` ran, whether or not a handler took it (a host's log) } # 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 { @max(512) 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_negs: []string = new []string # memo_join.ludic: negative numbers' texts, and the last big ones mm_big_v: []int = new []int mm_big_s: []string = new []string mm_big_at: int = 0 fc_none: Val = Value.null() # focus.ludic: the value a key's down and up carry, made once 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 @max(64) ev_ring: []Val = ev_ring_new() # eval.ludic: calls' answers while an action runs, EV_RING of them, made with the state ev_ring_at: int = 0 nt_evs: []Val = nt_evs_new() # native.ludic: fired events' objects, NT_EVS of them, made with the state nt_ev_at: int = 0 nt_nums: []Val = nt_nums_new() # fired.ludic: the numbers a scroll or a slider reports, a ring nt_num_at: int = 0 ct_clamp_n: []UiNode = new []UiNode # controls_opts.ludic: selects whose value a build clamped ct_clamp_i: []int = new []int hd_ev: Val = hd_ev_new() # hold.ludic: the held control's event, filled in place 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 @max(16) pe_evs: []UiPointer = pe_evs__new() # pointer_events.ludic: an event's record and its Value, a ring @max(16) pe_vals: []Val = pe_vals__new() pe_at: int = 0 nth_a: int = 0 # select_nth.ludic: the a and b just read nth_b: 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 = nt_nodes__new() # native.ludic: the queue's spare, swapped while it runs; nt_sp_names: []string = nt_names__new() # each list is made with room for a busy frame's events nt_sp_vals: []Val = nt_vals__new() nt_q_nodes: []UiNode = nt_nodes__new() nt_q_names: []string = nt_names__new() nt_q_vals: []Val = nt_vals__new() 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 } }