# 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 ------------------------------ function ui_arrange(rt_ui_st: mut RtUiState, i: int, x: int, y: int) -> void { rt_ui_st.ui_rx[i] = x rt_ui_st.ui_ry[i] = y let t = rt_ui_st.ui_type[i] if ui_is_container(t) == false { return } let dir = ui_dir(t) let pad = rt_ui_st.ui_pad[i] let gap = rt_ui_st.ui_gap[i] let innerw = rt_ui_st.ui_rw[i] - 2 * pad let innerh = rt_ui_st.ui_rh[i] - 2 * pad var total = 0 var ng = 0 var grows = 0 for c in 0 .. rt_ui_st.ui_n { if rt_ui_st.ui_parent[c] == i { if rt_ui_st.ui_visible[c] == 1 { if dir == 1 { total += rt_ui_st.ui_rh[c] } else { total += rt_ui_st.ui_rw[c] } ng += 1 grows += rt_ui_st.ui_grow[c] } } } if ng > 1 { total = total + gap * (ng - 1) } var avail = innerw if dir == 1 { avail = innerh } let extra = max(avail - total, 0) var cx = x + pad var cy = y + pad for c in 0 .. rt_ui_st.ui_n { if rt_ui_st.ui_parent[c] == i { if rt_ui_st.ui_visible[c] == 1 { var kw = rt_ui_st.ui_rw[c] var kh = rt_ui_st.ui_rh[c] if rt_ui_st.ui_grow[c] != 0 { if grows > 0 { if dir == 1 { kh = kh + extra / grows } else { kw = kw + extra / grows } } } let al = ui_eff_align(rt_ui_st, c, i) rt_ui_st.ui_rw[c] = kw rt_ui_st.ui_rh[c] = kh if dir == 1 { var ax = x + pad if al == 1 { ax = x + pad + (innerw - kw) / 2 } if al == 2 { ax = x + pad + (innerw - kw) } ui_arrange(rt_ui_st, c, ax, cy) cy = cy + kh + gap } else { var ay = y + pad if al == 1 { ay = y + pad + (innerh - kh) / 2 } if al == 2 { ay = y + pad + (innerh - kh) } ui_arrange(rt_ui_st, c, cx, ay) cx = cx + kw + gap } } } } } function ui_layout(rt_image_st: RtImageState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, rt_ui_st: mut RtUiState) -> void { if rt_ui_st.ui_active < 0 { return } ui_measure(rt_image_st, rt_truetype_raster_st, rt_truetype_st, rt_ui_st) var px = (rt_fbw - rt_ui_st.ui_rw[rt_ui_st.ui_active]) / 2 var py = (rt_fbh - rt_ui_st.ui_rh[rt_ui_st.ui_active]) / 2 if rt_ui_st.ui_haspos[rt_ui_st.ui_active] == 1 { px = rt_ui_st.ui_x[rt_ui_st.ui_active] py = rt_ui_st.ui_y[rt_ui_st.ui_active] } ui_arrange(rt_ui_st, rt_ui_st.ui_active, px, py) } # ---- drawing -------------------------------------------------------------- function ui_lighten(c: int) -> int { var r = ((c >> 16) & 255) var g = ((c >> 8) & 255) var b = (c & 255) r = r + (255 - r) * 4 / 10 g = g + (255 - g) * 4 / 10 b = b + (255 - b) * 4 / 10 return (((r << 16) | (g << 8)) | b) } function ui_draw_node(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, rt_ui_st: RtUiState, i: int) -> void { if rt_ui_st.ui_visible[i] == 0 { return } let t = rt_ui_st.ui_type[i] let rx = rt_ui_st.ui_rx[i] let ry = rt_ui_st.ui_ry[i] let rw = rt_ui_st.ui_rw[i] let rh = rt_ui_st.ui_rh[i] let skin = rt_ui_st.ui_skin[i] var focused = 0 if i == rt_ui_st.ui_focus { focused = 1 } if t == WT_PANEL { if skin >= 0 { rt_draw_9slice(rt_core_st, rt_image_st, skin, rx, ry, rw, rh, rt_ui_st.ui_inset[i]) } else { if rt_ui_st.ui_bg[i] >= 0 { rt_fill_rect(rt_core_st, rx, ry, rw, rh, rt_ui_st.ui_bg[i]) } if rt_ui_st.ui_border[i] >= 0 { rt_frame_rect(rt_core_st, rx, ry, rw, rh, rt_ui_st.ui_border[i]) } } } if t == WT_BUTTON { var bg = rt_ui_st.ui_bg[i] if bg < 0 { bg = 0x2a2a3a } if focused == 1 { bg = ui_lighten(bg) } if skin >= 0 { rt_draw_9slice(rt_core_st, rt_image_st, skin, rx, ry, rw, rh, rt_ui_st.ui_inset[i]) } else { rt_fill_rect(rt_core_st, rx, ry, rw, rh, bg) } var bc = rt_ui_st.ui_border[i] if bc < 0 { bc = 0x555577 } if focused == 1 { bc = 0xffff00 } rt_frame_rect(rt_core_st, rx, ry, rw, rh, bc) let s = ui_str(rt_ui_st, i) var fg = rt_ui_st.ui_fg[i] if fg < 0 { fg = 0xffffff } let font = rt_ui_st.ui_font[i] let size = rt_ui_st.ui_size[i] ui_draw_text(rt_core_st, rt_truetype_raster_st, rt_truetype_st, font, rx + (rw - ui_tw(rt_truetype_raster_st, rt_truetype_st, font, s, size)) / 2, ry + (rh - ui_th(rt_truetype_st, font, size)) / 2, s, fg, size) } if t == WT_LABEL { let s = ui_str(rt_ui_st, i) var fg = rt_ui_st.ui_fg[i] if fg < 0 { fg = 0xffffff } let font = rt_ui_st.ui_font[i] let size = rt_ui_st.ui_size[i] let pad = rt_ui_st.ui_pad[i] let tw = ui_tw(rt_truetype_raster_st, rt_truetype_st, font, s, size) var tx = rx + pad let al = rt_ui_st.ui_align[i] if al == 1 { tx = rx + (rw - tw) / 2 } if al == 2 { tx = rx + rw - pad - tw } ui_draw_text(rt_core_st, rt_truetype_raster_st, rt_truetype_st, font, tx, ry + pad, s, fg, size) } if t == WT_IMAGE { if rt_ui_st.ui_img[i] >= 0 { rt_draw_image_scaled(rt_core_st, rt_image_st, rt_ui_st.ui_img[i], rx, ry, rw, rh) } } for c in 0 .. rt_ui_st.ui_n { if rt_ui_st.ui_parent[c] == i { ui_draw_node(rt_core_st, rt_image_st, rt_truetype_raster_st, rt_truetype_st, rt_ui_st, c) } } } # ---- focus and activation ------------------------------------------------- export state RtUiDrawState { ui_fl: words = null } function ui_focusables(rt_ui_draw_st: mut RtUiDrawState, rt_ui_st: RtUiState) -> int { if (rt_ui_draw_st.ui_fl == null) { rt_ui_draw_st.ui_fl = words(UI_MAX) } var n = 0 for i in 0 .. rt_ui_st.ui_n { if rt_ui_st.ui_focusable[i] == 1 { if rt_ui_st.ui_visible[i] == 1 { if ui_under(rt_ui_st, i, rt_ui_st.ui_active) { rt_ui_draw_st.ui_fl[n] = i n += 1 } } } } return n } # a focused control was activated (Enter / Space / pad A): the event-driven form of # ui_clicked — `@On(UiClicked) handler { if id == UI_Play { ... } }`. event UiClicked { id: int = 0 } # Ui.close(): no menu is active (the same as opening root -1) function rt_ui_close(rt_ui_draw_st: mut RtUiDrawState, rt_ui_st: mut RtUiState) -> void { rt_ui_open(rt_ui_draw_st, rt_ui_st, -1) } function rt_ui_open(rt_ui_draw_st: mut RtUiDrawState, rt_ui_st: mut RtUiState, root: int) -> void { rt_ui_st.ui_active = root let n = ui_focusables(rt_ui_draw_st, rt_ui_st) rt_ui_st.ui_focus = -1 if n > 0 { rt_ui_st.ui_focus = rt_ui_draw_st.ui_fl[0] } } function rt_ui_tick(rt_image_st: RtImageState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, rt_ui_draw_st: mut RtUiDrawState, rt_ui_st: mut RtUiState, k: int) -> void { for i in 0 .. rt_ui_st.ui_n { rt_ui_st.ui_fired[i] = 0 } # a closed UI clears clicks too, so a stale if rt_ui_st.ui_active < 0 { return } # activation never leaks to the next frame/scene ui_layout(rt_image_st, rt_truetype_raster_st, rt_truetype_st, rt_ui_st) let n = ui_focusables(rt_ui_draw_st, rt_ui_st) if n == 0 { rt_ui_st.ui_focus = -1 return } var cur = -1 for i in 0 .. n { if rt_ui_draw_st.ui_fl[i] == rt_ui_st.ui_focus { cur = i } } if cur < 0 { cur = 0 rt_ui_st.ui_focus = rt_ui_draw_st.ui_fl[0] } if k == 'w' { # 'w' cur = (cur - 1 + n) % n rt_ui_st.ui_focus = rt_ui_draw_st.ui_fl[cur] } if k == 's' { # 's' cur = (cur + 1) % n rt_ui_st.ui_focus = rt_ui_draw_st.ui_fl[cur] } var fire = 0 if k == ' ' { fire = 1 } if k == '\n' { fire = 1 } if k == '\r' { fire = 1 } if fire == 1 { rt_ui_st.ui_fired[rt_ui_st.ui_focus] = 1; emit UiClicked(id: rt_ui_st.ui_focus) } } function rt_ui_render(rt_core_st: mut RtCoreState, rt_image_st: RtImageState, rt_truetype_raster_st: mut RtTruetypeRasterState, rt_truetype_st: mut RtTruetypeState, rt_ui_st: mut RtUiState) -> void { if rt_ui_st.ui_active < 0 { return } ui_layout(rt_image_st, rt_truetype_raster_st, rt_truetype_st, rt_ui_st) ui_draw_node(rt_core_st, rt_image_st, rt_truetype_raster_st, rt_truetype_st, rt_ui_st, rt_ui_st.ui_active) } # ---- game-facing accessors ------------------------------------------------ function rt_ui_clicked(rt_ui_st: RtUiState, id: int) -> bool { if id < 0 { return false } if id >= rt_ui_st.ui_n { return false } return rt_ui_st.ui_fired[id] == 1 } function rt_ui_set_text(rt_ui_st: mut RtUiState, id: int, s: string) -> void { if id < 0 { return } if id >= rt_ui_st.ui_n { return } let base = id * 96 var i = 0 var ch = s[0] while ch != 0 { if i >= 95 { ch = 0 } if ch != 0 { rt_ui_st.ui_dyn[base + i] = ch i += 1 ch = s[i] } } rt_ui_st.ui_dyn[base + i] = 0 rt_ui_st.ui_hasdyn[id] = 1 } function rt_ui_set_int(rt_ui_st: mut RtUiState, id: int, n: int) -> void { if id < 0 { return } if id >= rt_ui_st.ui_n { return } let base = id * 96 var at = 0 var v = n if v < 0 { rt_ui_st.ui_dyn[base] = '-' at = 1 v = -v } if v == 0 { rt_ui_st.ui_dyn[base + at] = '0' at += 1 } else { var digits = 0 var t = v while t > 0 { digits += 1 t /= 10 } var p = digits while p > 0 { var div = 1 for k in 1 .. p { div *= 10 } rt_ui_st.ui_dyn[base + at] = 48 + (v / div) % 10 at += 1 p -= 1 } } rt_ui_st.ui_dyn[base + at] = 0 rt_ui_st.ui_hasdyn[id] = 1 } function rt_ui_focus(rt_ui_st: mut RtUiState, id: int) -> void { if id < 0 { return } if id >= rt_ui_st.ui_n { return } rt_ui_st.ui_focus = id } function rt_ui_focused(rt_ui_st: RtUiState) -> int { return rt_ui_st.ui_focus } function rt_ui_visible(rt_ui_st: mut RtUiState, id: int, v: int) -> void { if id < 0 { return } if id >= rt_ui_st.ui_n { return } rt_ui_st.ui_visible[id] = v }