ludic/runtime/native/ui.ludic
Orkuncakilkaya d3301684f1 Phase 8b: modern names for the raw-memory and OS/IO primitives
Groups B and C of the leftover-primitive cleanup — renames, not new machinery,
and deliberately NO unsafe_ prefix (a __-prefix is itself a C convention, and an
`unsafe` marker carries no signal in a fully-manual-memory language with no safe
subset to contrast against).

  memory:  mem_free -> free   mem_realloc -> resize   mem_set -> fill
           ptr_add -> offset
  process: os_argc -> arg_count   os_arg -> arg   os_exit -> exit
           os_system -> run   os_getenv -> getenv   read_byte -> read_char
  dead:    mem_copy, os_time, write_byte (0 uses) — deleted

Two reseeds: accept both old and new names in the intrinsic dispatch, then
migrate every call site and drop the old names. file_open/read/write/seek/tell/
close are left as-is — they're the domain-prefixed syscall layer wrapped by
read_file, not the argc/argv-style C-ness the audit targeted; a `File` type is a
separate, larger design if wanted.

test.sh's CLI smoke updated (os_exit -> exit); check-vocabulary's grammar marker
moved off the deleted names. Reseeded (22243 lines); C-free fixpoint holds;
goldens identical; 18/18; vocab + doc-fences clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-28 02:55:24 +03:00

312 lines
7.9 KiB
Text

# ============================================================================
# ui.ludic — the retained UI engine, written in Ludic.
#
# A `ui Name { … }` block in a game is DATA: the compiler flattens the widget
# tree and emits a build function that calls rt_ui_set() for each property.
# Everything after that — measure/arrange layout, 9-slice skins, text, keyboard
# focus and activation — is this file.
#
# Widgets are parallel arrays rather than a struct per widget, because Ludic
# has components for per-entity data and raw memory for everything else, and a
# widget is not an entity.
# ============================================================================
const UI_MAX: int = 256
# widget types, matching the compiler's flattener
const WT_PANEL: int = 0
const WT_COL: int = 1
const WT_ROW: int = 2
const WT_LABEL: int = 3
const WT_BUTTON: int = 4
const WT_IMAGE: int = 5
const WT_SPACER: int = 6
# property keys — the private protocol between ludicc and this runtime
const K_TYPE: int = 0
const K_PARENT: int = 1
const K_W: int = 2
const K_H: int = 3
const K_X: int = 4
const K_Y: int = 5
const K_HASPOS: int = 6
const K_PAD: int = 7
const K_GAP: int = 8
const K_BG: int = 9
const K_FG: int = 10
const K_BORDER: int = 11
const K_ALIGN: int = 12
const K_GROW: int = 13
const K_FONT: int = 14
const K_SIZE: int = 15
const K_SKININSET: int = 16
const K_FOCUSABLE: int = 17
const K_VISIBLE: int = 18
const K_IMG: int = 19
const K_SKIN: int = 20
var ui_type: words = null
var ui_parent: words = null
var ui_w: words = null
var ui_h: words = null
var ui_x: words = null
var ui_y: words = null
var ui_haspos: words = null
var ui_pad: words = null
var ui_gap: words = null
var ui_bg: words = null
var ui_fg: words = null
var ui_border: words = null
var ui_align: words = null
var ui_grow: words = null
var ui_font: words = null
var ui_size: words = null
var ui_skin: words = null
var ui_inset: words = null
var ui_img: words = null
var ui_focusable: words = null
var ui_rx: words = null
var ui_ry: words = null
var ui_rw: words = null
var ui_rh: words = null
var ui_visible: words = null
var ui_fired: words = null
var ui_hasdyn: words = null
var ui_dyn: ptr = null # UI_MAX * 96 bytes
var ui_text: ptrs = null # UI_MAX static string pointers
var ui_n: int = 0
var ui_active: int = -1
var ui_focus: int = -1
fn rt_ui_init() -> void {
ui_type = words(UI_MAX)
ui_parent = words(UI_MAX)
ui_w = words(UI_MAX)
ui_h = words(UI_MAX)
ui_x = words(UI_MAX)
ui_y = words(UI_MAX)
ui_haspos = words(UI_MAX)
ui_pad = words(UI_MAX)
ui_gap = words(UI_MAX)
ui_bg = words(UI_MAX)
ui_fg = words(UI_MAX)
ui_border = words(UI_MAX)
ui_align = words(UI_MAX)
ui_grow = words(UI_MAX)
ui_font = words(UI_MAX)
ui_size = words(UI_MAX)
ui_skin = words(UI_MAX)
ui_inset = words(UI_MAX)
ui_img = words(UI_MAX)
ui_focusable = words(UI_MAX)
ui_rx = words(UI_MAX)
ui_ry = words(UI_MAX)
ui_rw = words(UI_MAX)
ui_rh = words(UI_MAX)
ui_visible = words(UI_MAX)
ui_fired = words(UI_MAX)
ui_hasdyn = words(UI_MAX)
ui_dyn = bytes(UI_MAX * 96)
ui_text = bytes(UI_MAX * 8)
}
# ---- build-time interface (called by compiler-emitted code) ---------------
fn rt_ui_reset(n: int) -> void {
ui_n = n
for i in 0 .. n {
ui_type[i] = 0
ui_parent[i] = 0 - 1
ui_w[i] = 0
ui_h[i] = 0
ui_x[i] = 0
ui_y[i] = 0
ui_haspos[i] = 0
ui_pad[i] = 0
ui_gap[i] = 0
ui_bg[i] = 0 - 1
ui_fg[i] = 0 - 1
ui_border[i] = 0 - 1
ui_align[i] = 0 - 1
ui_grow[i] = 0
ui_font[i] = 0 - 1
ui_size[i] = 8
ui_skin[i] = 0 - 1
ui_inset[i] = 6
ui_img[i] = 0 - 1
ui_focusable[i] = 0
ui_visible[i] = 1
ui_fired[i] = 0
ui_hasdyn[i] = 0
ui_text[i] = null
}
}
fn rt_ui_set(i: int, k: int, v: int) -> void {
if i < 0 { return }
if i >= UI_MAX { return }
if k == K_TYPE { ui_type[i] = v }
if k == K_PARENT { ui_parent[i] = v }
if k == K_W { ui_w[i] = v }
if k == K_H { ui_h[i] = v }
if k == K_X { ui_x[i] = v }
if k == K_Y { ui_y[i] = v }
if k == K_HASPOS { ui_haspos[i] = v }
if k == K_PAD { ui_pad[i] = v }
if k == K_GAP { ui_gap[i] = v }
if k == K_BG { ui_bg[i] = v }
if k == K_FG { ui_fg[i] = v }
if k == K_BORDER { ui_border[i] = v }
if k == K_ALIGN { ui_align[i] = v }
if k == K_GROW { ui_grow[i] = v }
if k == K_FONT { ui_font[i] = v }
if k == K_SIZE { ui_size[i] = v }
if k == K_SKININSET { ui_inset[i] = v }
if k == K_FOCUSABLE { ui_focusable[i] = v }
if k == K_VISIBLE { ui_visible[i] = v }
if k == K_IMG { ui_img[i] = v }
if k == K_SKIN { ui_skin[i] = v }
}
fn rt_ui_static_text(i: int, s: str) -> void {
if i < 0 { return }
if i >= UI_MAX { return }
ui_text[i] = s
}
# ---- text helpers: TrueType when a font is loaded, bitmap otherwise -------
fn ui_str(i: int) -> ptr {
if ui_hasdyn[i] == 1 { return offset(ui_dyn, i * 96) }
let t = ui_text[i]
if (t == null) { return offset(ui_dyn, i * 96) }
return t
}
fn ui_draw_text(font: int, x: int, y: int, s: ptr, colour: int, size: int) -> void {
if font >= 0 {
if font < tt_n {
rt_text_ttf(font, x, y, s, colour, size)
return
}
}
rt_text(x, y, s, colour, max(size / 8, 1))
}
fn ui_tw(font: int, s: ptr, size: int) -> int {
if font >= 0 {
if font < tt_n { return rt_text_w(font, s, size) }
}
var n = 0
while s[n] != 0 { n = n + 1 }
return n * 6 * max(size / 8, 1)
}
fn ui_th(font: int, size: int) -> int {
if font >= 0 {
if font < tt_n { return rt_text_h(font, size) }
}
return 7 * max(size / 8, 1)
}
fn ui_is_container(t: int) -> bool {
if t == WT_PANEL { return true }
if t == WT_COL { return true }
if t == WT_ROW { return true }
return false
}
fn ui_dir(t: int) -> int {
if t == WT_ROW { return 0 }
return 1
}
fn ui_under(i: int, root: int) -> bool {
var k = i
while k >= 0 {
if k == root { return true }
k = ui_parent[k]
}
return false
}
# ---- measure: content sizes, children before parents ----------------------
fn ui_measure() -> void {
var i = ui_n - 1
while i >= 0 {
if ui_visible[i] == 0 {
ui_rw[i] = 0
ui_rh[i] = 0
} else {
let t = ui_type[i]
let pad = ui_pad[i]
var cw = 0
var ch = 0
if t == WT_LABEL { cw = 0 - 1 }
if t == WT_BUTTON { cw = 0 - 1 }
if cw == 0 - 1 {
let s = ui_str(i)
cw = ui_tw(ui_font[i], s, ui_size[i]) + 2 * pad
ch = ui_th(ui_font[i], ui_size[i]) + 2 * pad
if t == WT_BUTTON {
cw = cw + 10
ch = ch + 6
}
} else {
if t == WT_IMAGE {
let im = ui_img[i]
if im >= 0 {
cw = img_w[im]
ch = img_h[im]
}
} else {
if t == WT_SPACER {
cw = ui_w[i]
ch = ui_h[i]
} else {
let dir = ui_dir(t)
var mainsz = 0
var cross = 0
var nc = 0
for c in 0 .. ui_n {
if ui_parent[c] == i {
if ui_visible[c] == 1 {
if dir == 1 {
mainsz = mainsz + ui_rh[c]
cross = max(cross, ui_rw[c])
} else {
mainsz = mainsz + ui_rw[c]
cross = max(cross, ui_rh[c])
}
nc = nc + 1
}
}
}
if nc > 1 { mainsz = mainsz + ui_gap[i] * (nc - 1) }
mainsz = mainsz + 2 * pad
cross = cross + 2 * pad
if dir == 1 {
cw = cross
ch = mainsz
} else {
cw = mainsz
ch = cross
}
}
}
}
var rw = ui_w[i]
var rh = ui_h[i]
if rw == 0 { rw = cw }
if rh == 0 { rh = ch }
ui_rw[i] = rw
ui_rh[i] = rh
}
i = i - 1
}
}
fn ui_eff_align(child: int, parent: int) -> int {
if ui_align[child] >= 0 { return ui_align[child] }
if ui_align[parent] >= 0 { return ui_align[parent] }
return 0
}
import "ui_draw.ludic"