Self-hosted compiler (selfhost/*.ludic), runtime, examples, editor tooling, and docs. Phase 1 of the syntax-redesign cohesion pass has landed: edge-system fix, signature-query, when-alias, and the documentation truth-pass. Suite green (14/14), C-free bootstrap fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
283 lines
7.2 KiB
Text
283 lines
7.2 KiB
Text
# ui_draw.ludic — layout arrange, drawing, focus/activation and the game-facing
|
|
# accessors. Split out of ui.ludic (the storage/build/measure half).
|
|
|
|
# ---- arrange: rects, parents before children ------------------------------
|
|
fn ui_arrange(i: int, x: int, y: int) -> void {
|
|
poke32(ui_rx, i, x)
|
|
poke32(ui_ry, i, y)
|
|
let t = peek32(ui_type, i)
|
|
if ui_is_container(t) == false { return }
|
|
let dir = ui_dir(t)
|
|
let pad = peek32(ui_pad, i)
|
|
let gap = peek32(ui_gap, i)
|
|
let innerw = peek32(ui_rw, i) - 2 * pad
|
|
let innerh = peek32(ui_rh, i) - 2 * pad
|
|
let total = 0
|
|
let ng = 0
|
|
let grows = 0
|
|
for c in 0 .. ui_n {
|
|
if peek32(ui_parent, c) == i {
|
|
if peek32(ui_visible, c) == 1 {
|
|
if dir == 1 { total = total + peek32(ui_rh, c) } else { total = total + peek32(ui_rw, c) }
|
|
ng = ng + 1
|
|
grows = grows + peek32(ui_grow, c)
|
|
}
|
|
}
|
|
}
|
|
if ng > 1 { total = total + gap * (ng - 1) }
|
|
let avail = innerw
|
|
if dir == 1 { avail = innerh }
|
|
let extra = max(avail - total, 0)
|
|
let cx = x + pad
|
|
let cy = y + pad
|
|
for c in 0 .. ui_n {
|
|
if peek32(ui_parent, c) == i {
|
|
if peek32(ui_visible, c) == 1 {
|
|
let kw = peek32(ui_rw, c)
|
|
let kh = peek32(ui_rh, c)
|
|
if peek32(ui_grow, c) != 0 {
|
|
if grows > 0 {
|
|
if dir == 1 { kh = kh + extra / grows } else { kw = kw + extra / grows }
|
|
}
|
|
}
|
|
let al = ui_eff_align(c, i)
|
|
poke32(ui_rw, c, kw)
|
|
poke32(ui_rh, c, kh)
|
|
if dir == 1 {
|
|
let ax = x + pad
|
|
if al == 1 { ax = x + pad + (innerw - kw) / 2 }
|
|
if al == 2 { ax = x + pad + (innerw - kw) }
|
|
ui_arrange(c, ax, cy)
|
|
cy = cy + kh + gap
|
|
} else {
|
|
let ay = y + pad
|
|
if al == 1 { ay = y + pad + (innerh - kh) / 2 }
|
|
if al == 2 { ay = y + pad + (innerh - kh) }
|
|
ui_arrange(c, cx, ay)
|
|
cx = cx + kw + gap
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ui_layout() -> void {
|
|
if ui_active < 0 { return }
|
|
ui_measure()
|
|
let px = (rt_fbw - peek32(ui_rw, ui_active)) / 2
|
|
let py = (rt_fbh - peek32(ui_rh, ui_active)) / 2
|
|
if peek32(ui_haspos, ui_active) == 1 {
|
|
px = peek32(ui_x, ui_active)
|
|
py = peek32(ui_y, ui_active)
|
|
}
|
|
ui_arrange(ui_active, px, py)
|
|
}
|
|
|
|
# ---- drawing --------------------------------------------------------------
|
|
fn ui_lighten(c: int) -> int {
|
|
let r = band(shr(c, 16), 255)
|
|
let g = band(shr(c, 8), 255)
|
|
let b = band(c, 255)
|
|
r = r + (255 - r) * 4 / 10
|
|
g = g + (255 - g) * 4 / 10
|
|
b = b + (255 - b) * 4 / 10
|
|
return bor(bor(shl(r, 16), shl(g, 8)), b)
|
|
}
|
|
|
|
fn ui_draw_node(i: int) -> void {
|
|
if peek32(ui_visible, i) == 0 { return }
|
|
let t = peek32(ui_type, i)
|
|
let rx = peek32(ui_rx, i)
|
|
let ry = peek32(ui_ry, i)
|
|
let rw = peek32(ui_rw, i)
|
|
let rh = peek32(ui_rh, i)
|
|
let skin = peek32(ui_skin, i)
|
|
let focused = 0
|
|
if i == ui_focus { focused = 1 }
|
|
|
|
if t == WT_PANEL {
|
|
if skin >= 0 {
|
|
rt_draw_9slice(skin, rx, ry, rw, rh, peek32(ui_inset, i))
|
|
} else {
|
|
if peek32(ui_bg, i) >= 0 { rt_fill_rect(rx, ry, rw, rh, peek32(ui_bg, i)) }
|
|
if peek32(ui_border, i) >= 0 { rt_frame_rect(rx, ry, rw, rh, peek32(ui_border, i)) }
|
|
}
|
|
}
|
|
if t == WT_BUTTON {
|
|
let bg = peek32(ui_bg, i)
|
|
if bg < 0 { bg = 0x2a2a3a }
|
|
if focused == 1 { bg = ui_lighten(bg) }
|
|
if skin >= 0 {
|
|
rt_draw_9slice(skin, rx, ry, rw, rh, peek32(ui_inset, i))
|
|
} else {
|
|
rt_fill_rect(rx, ry, rw, rh, bg)
|
|
}
|
|
let bc = peek32(ui_border, i)
|
|
if bc < 0 { bc = 0x555577 }
|
|
if focused == 1 { bc = 0xffff00 }
|
|
rt_frame_rect(rx, ry, rw, rh, bc)
|
|
let s = ui_str(i)
|
|
let fg = peek32(ui_fg, i)
|
|
if fg < 0 { fg = 0xffffff }
|
|
let font = peek32(ui_font, i)
|
|
let size = peek32(ui_size, i)
|
|
ui_draw_text(font, rx + (rw - ui_tw(font, s, size)) / 2, ry + (rh - ui_th(font, size)) / 2, s, fg, size)
|
|
}
|
|
if t == WT_LABEL {
|
|
let s = ui_str(i)
|
|
let fg = peek32(ui_fg, i)
|
|
if fg < 0 { fg = 0xffffff }
|
|
let font = peek32(ui_font, i)
|
|
let size = peek32(ui_size, i)
|
|
let pad = peek32(ui_pad, i)
|
|
let tw = ui_tw(font, s, size)
|
|
let tx = rx + pad
|
|
let al = peek32(ui_align, i)
|
|
if al == 1 { tx = rx + (rw - tw) / 2 }
|
|
if al == 2 { tx = rx + rw - pad - tw }
|
|
ui_draw_text(font, tx, ry + pad, s, fg, size)
|
|
}
|
|
if t == WT_IMAGE {
|
|
if peek32(ui_img, i) >= 0 { rt_draw_image_scaled(peek32(ui_img, i), rx, ry, rw, rh) }
|
|
}
|
|
for c in 0 .. ui_n {
|
|
if peek32(ui_parent, c) == i { ui_draw_node(c) }
|
|
}
|
|
}
|
|
|
|
# ---- focus and activation -------------------------------------------------
|
|
var ui_fl: ptr = ptr_null()
|
|
|
|
fn ui_focusables() -> int {
|
|
if ptr_is_null(ui_fl) { ui_fl = mem_alloc(UI_MAX * 4) }
|
|
let n = 0
|
|
for i in 0 .. ui_n {
|
|
if peek32(ui_focusable, i) == 1 {
|
|
if peek32(ui_visible, i) == 1 {
|
|
if ui_under(i, ui_active) {
|
|
poke32(ui_fl, n, i)
|
|
n = n + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
fn rt_ui_open(root: int) -> void {
|
|
ui_active = root
|
|
let n = ui_focusables()
|
|
ui_focus = 0 - 1
|
|
if n > 0 { ui_focus = peek32(ui_fl, 0) }
|
|
}
|
|
|
|
fn rt_ui_tick(k: int) -> void {
|
|
if ui_active < 0 { return }
|
|
for i in 0 .. ui_n { poke32(ui_fired, i, 0) }
|
|
ui_layout()
|
|
let n = ui_focusables()
|
|
if n == 0 {
|
|
ui_focus = 0 - 1
|
|
return
|
|
}
|
|
let cur = 0 - 1
|
|
for i in 0 .. n {
|
|
if peek32(ui_fl, i) == ui_focus { cur = i }
|
|
}
|
|
if cur < 0 {
|
|
cur = 0
|
|
ui_focus = peek32(ui_fl, 0)
|
|
}
|
|
if k == 119 { # 'w'
|
|
cur = (cur - 1 + n) % n
|
|
ui_focus = peek32(ui_fl, cur)
|
|
}
|
|
if k == 115 { # 's'
|
|
cur = (cur + 1) % n
|
|
ui_focus = peek32(ui_fl, cur)
|
|
}
|
|
if k == 32 { poke32(ui_fired, ui_focus, 1) }
|
|
if k == 10 { poke32(ui_fired, ui_focus, 1) }
|
|
if k == 13 { poke32(ui_fired, ui_focus, 1) }
|
|
}
|
|
|
|
fn rt_ui_render() -> void {
|
|
if ui_active < 0 { return }
|
|
ui_layout()
|
|
ui_draw_node(ui_active)
|
|
}
|
|
|
|
# ---- game-facing accessors ------------------------------------------------
|
|
fn rt_ui_clicked(id: int) -> bool {
|
|
if id < 0 { return false }
|
|
if id >= ui_n { return false }
|
|
return peek32(ui_fired, id) == 1
|
|
}
|
|
|
|
fn rt_ui_set_text(id: int, s: str) -> void {
|
|
if id < 0 { return }
|
|
if id >= ui_n { return }
|
|
let base = id * 96
|
|
let i = 0
|
|
let ch = peek8(s, 0)
|
|
while ch != 0 {
|
|
if i >= 95 { ch = 0 }
|
|
if ch != 0 {
|
|
poke8(ui_dyn, base + i, ch)
|
|
i = i + 1
|
|
ch = peek8(s, i)
|
|
}
|
|
}
|
|
poke8(ui_dyn, base + i, 0)
|
|
poke32(ui_hasdyn, id, 1)
|
|
}
|
|
|
|
fn rt_ui_set_int(id: int, n: int) -> void {
|
|
if id < 0 { return }
|
|
if id >= ui_n { return }
|
|
let base = id * 96
|
|
let at = 0
|
|
let v = n
|
|
if v < 0 {
|
|
poke8(ui_dyn, base, 45)
|
|
at = 1
|
|
v = 0 - v
|
|
}
|
|
if v == 0 {
|
|
poke8(ui_dyn, base + at, 48)
|
|
at = at + 1
|
|
} else {
|
|
let digits = 0
|
|
let t = v
|
|
while t > 0 {
|
|
digits = digits + 1
|
|
t = t / 10
|
|
}
|
|
let p = digits
|
|
while p > 0 {
|
|
let div = 1
|
|
for k in 1 .. p { div = div * 10 }
|
|
poke8(ui_dyn, base + at, 48 + (v / div) % 10)
|
|
at = at + 1
|
|
p = p - 1
|
|
}
|
|
}
|
|
poke8(ui_dyn, base + at, 0)
|
|
poke32(ui_hasdyn, id, 1)
|
|
}
|
|
|
|
fn rt_ui_focus(id: int) -> void {
|
|
if id < 0 { return }
|
|
if id >= ui_n { return }
|
|
ui_focus = id
|
|
}
|
|
|
|
fn rt_ui_focused() -> int {
|
|
return ui_focus
|
|
}
|
|
|
|
fn rt_ui_visible(id: int, v: int) -> void {
|
|
if id < 0 { return }
|
|
if id >= ui_n { return }
|
|
poke32(ui_visible, id, v)
|
|
}
|