--- @section UI --- Draws the top bar. --- @within UI --- @param title string The title text to display.
function UI.draw_top_bar(title) rect(0, 0, Config.screen.width, 10, Config.colors.dark_grey) Print.text(title, 3, 2, Config.colors.light_blue) end --- Draws a menu. --- @within UI --- @param items table A table of menu items.
--- @param selected_item number The index of the currently selected item.
--- @param x number The x-coordinate for the menu.
--- @param y number The y-coordinate for the menu.
function UI.draw_menu(items, selected_item, x, y) for i, item in ipairs(items) do local current_y = y + (i-1)*10 if i == selected_item then Print.text(">", x - 8, current_y, Config.colors.light_blue) end Print.text(item.label, x, current_y, Config.colors.light_blue) end end --- Updates menu selection. --- @within UI --- @param items table A table of menu items.
--- @param selected_item number The current index of the selected item.
--- @return number selected_item The updated index of the selected item. function UI.update_menu(items, selected_item) if Input.up() then Audio.sfx_beep() selected_item = selected_item - 1 if selected_item < 1 then selected_item = #items end elseif Input.down() then Audio.sfx_beep() selected_item = selected_item + 1 if selected_item > #items then selected_item = 1 end end return selected_item end --- Draws a bordered textbox with scrolling text. --- @within UI --- @param text string The text to display (multi-line supported).
--- @param box_x number The x-coordinate of the box.
--- @param box_y number The y-coordinate of the box.
--- @param box_w number The width of the box.
--- @param box_h number The height of the box.
--- @param scroll_y number The vertical scroll offset for the text (0 = top, increases to scroll up).
--- @param[opt] color number The text color (default: Config.colors.white).
--- @param[opt] bg_color number The background fill color (default: Config.colors.dark_grey).
--- @param[opt] border_color number The border color (default: Config.colors.white).
--- @param[opt] center_text boolean Whether to center each line inside the box. Defaults to false.
function UI.draw_textbox(text, box_x, box_y, box_w, box_h, scroll_y, color, bg_color, border_color, center_text) color = color or Config.colors.white bg_color = bg_color or Config.colors.dark_grey border_color = border_color or Config.colors.white center_text = center_text or false local padding = 4 local line_height = 8 local inner_x = box_x + padding local inner_y = box_y + padding local inner_center_x = box_x + (box_w / 2) local visible_height = box_h - padding * 2 local lines = UI.word_wrap(text, 30) local text_height = #lines * line_height local base_y = inner_y if center_text and text_height < visible_height then base_y = inner_y + math.floor((visible_height - text_height) / 2) end rect(box_x, box_y, box_w, box_h, bg_color) for i, line in ipairs(lines) do local ly = base_y + (i - 1) * line_height - scroll_y if ly >= inner_y and ly + line_height <= inner_y + visible_height then if center_text then Print.text_center(line, inner_center_x, ly, color) else Print.text(line, inner_x, ly, color) end end end rectb(box_x, box_y, box_w, box_h, border_color) end --- Wraps text. --- @within UI --- @param text string The text to wrap.
--- @param max_chars_per_line number The maximum characters per line.
--- @return result table A table of wrapped lines. function UI.word_wrap(text, max_chars_per_line) if text == nil then return {""} end local lines = {} local function trim(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end local function previous_whitespace_index(s, target) if s:sub(target, target):match("%s") then return target end for i = target - 1, 1, -1 do if s:sub(i, i):match("%s") then return i end end return nil end for input_line in (text .. "\n"):gmatch("(.-)\n") do local remaining = trim(input_line) if remaining == "" then table.insert(lines, "") else while #remaining > max_chars_per_line do local split_at = previous_whitespace_index(remaining, max_chars_per_line) local line = trim(remaining:sub(1, split_at)) if not split_at or line == "" then line = remaining:sub(1, max_chars_per_line) split_at = max_chars_per_line end table.insert(lines, line) remaining = trim(remaining:sub(split_at + 1)) end table.insert(lines, remaining) end end if #lines == 0 then return {""} end return lines end