feature/IMP-112-ascension-8-9 #59
@@ -145,6 +145,11 @@ function Ascension.draw_flash()
|
|||||||
local flash_color = (pulse > 0.5) and Config.colors.white or Config.colors.light_grey
|
local flash_color = (pulse > 0.5) and Config.colors.white or Config.colors.light_grey
|
||||||
rect(0, 0, sw, sh, flash_color)
|
rect(0, 0, sw, sh, flash_color)
|
||||||
|
|
||||||
|
local cx = math.floor(sw / 2)
|
||||||
|
local cy = math.floor(sh / 2)
|
||||||
|
Print.text_center("Level Up!", cx, cy - 12, Config.colors.black, false, 2)
|
||||||
|
Print.text_center("One step closer to ascension", cx, cy + 6, Config.colors.black, false, 1)
|
||||||
|
|
||||||
if _flash_timer >= _flash_total then
|
if _flash_timer >= _flash_total then
|
||||||
_flash_active = false
|
_flash_active = false
|
||||||
Ascension.start_fade()
|
Ascension.start_fade()
|
||||||
|
|||||||
@@ -9,53 +9,79 @@ function UI.draw_top_bar(title)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Draws a menu.
|
--- Draws a menu.
|
||||||
|
--- Items with header=true are drawn as non-selectable section headers in small font.
|
||||||
--- @within UI
|
--- @within UI
|
||||||
--- @param items table A table of menu items.<br/>
|
--- @param items table A table of menu items.<br/>
|
||||||
--- @param selected_item number The index of the currently selected item.<br/>
|
--- @param selected_item number The index of the currently selected item.<br/>
|
||||||
--- @param x number The x-coordinate for the menu (ignored if centered is true).<br/>
|
--- @param x number The x-coordinate for the menu (ignored if centered is true).<br/>
|
||||||
--- @param y number The y-coordinate for the menu.<br/>
|
--- @param y number The y-coordinate for the menu.<br/>
|
||||||
--- @param[opt] centered boolean Whether to center the menu block horizontally. Defaults to false.<br/>
|
--- @param[opt] centered boolean Whether to center the menu block horizontally. Defaults to false.<br/>
|
||||||
function UI.draw_menu(items, selected_item, x, y, centered)
|
--- @param[opt] scroll_offset number 0-based index of the first visible item. Defaults to 0.<br/>
|
||||||
|
--- @param[opt] visible_count number Maximum number of items to draw. Defaults to all.<br/>
|
||||||
|
function UI.draw_menu(items, selected_item, x, y, centered, scroll_offset, visible_count)
|
||||||
|
scroll_offset = scroll_offset or 0
|
||||||
|
visible_count = visible_count or #items
|
||||||
|
|
||||||
if centered then
|
if centered then
|
||||||
local max_w = 0
|
local max_w = 0
|
||||||
for _, item in ipairs(items) do
|
for _, item in ipairs(items) do
|
||||||
local w = print(item.label, 0, -10, 0, false, 1, false)
|
if not item.header then
|
||||||
if w > max_w then max_w = w end
|
local w = print(item.label, 0, -10, 0, false, 1, false)
|
||||||
|
if w > max_w then max_w = w end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
x = (Config.screen.width - max_w) / 2
|
x = (Config.screen.width - max_w) / 2
|
||||||
end
|
end
|
||||||
|
|
||||||
for i, item in ipairs(items) do
|
local current_y = y
|
||||||
local current_y = y + (i-1)*10
|
for i = scroll_offset + 1, math.min(#items, scroll_offset + visible_count) do
|
||||||
if i == selected_item then
|
local item = items[i]
|
||||||
Print.text(">", x - 8, current_y, Config.colors.light_blue)
|
if item.header then
|
||||||
|
Print.text(item.label, x, current_y, Config.colors.dark_grey, true, 1)
|
||||||
|
current_y = current_y + 8
|
||||||
|
else
|
||||||
|
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)
|
||||||
|
current_y = current_y + 10
|
||||||
end
|
end
|
||||||
Print.text(item.label, x, current_y, Config.colors.light_blue)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Updates menu selection.
|
--- Updates menu selection. Skips items with header=true during navigation.
|
||||||
--- @within UI
|
--- @within UI
|
||||||
--- @param items table A table of menu items.<br/>
|
--- @param items table A table of menu items.<br/>
|
||||||
--- @param selected_item number The current index of the selected item.<br/>
|
--- @param selected_item number The current index of the selected item.<br/>
|
||||||
--- @param[opt] x number Menu x position (required for mouse support).<br/>
|
--- @param[opt] x number Menu x position (required for mouse support).<br/>
|
||||||
--- @param[opt] y number Menu y position (required for mouse support).<br/>
|
--- @param[opt] y number Menu y position (required for mouse support).<br/>
|
||||||
--- @param[opt] centered boolean Whether the menu is centered horizontally.<br/>
|
--- @param[opt] centered boolean Whether the menu is centered horizontally.<br/>
|
||||||
|
--- @param[opt] scroll_offset number 0-based index of the first visible item. Defaults to 0.<br/>
|
||||||
|
--- @param[opt] visible_count number Number of visible items (for mouse hit zones). Defaults to all.<br/>
|
||||||
--- @return number selected_item The updated index of the selected item.
|
--- @return number selected_item The updated index of the selected item.
|
||||||
--- @return boolean mouse_confirmed True if the user clicked on a menu item.
|
--- @return boolean mouse_confirmed True if the user clicked on a menu item.
|
||||||
function UI.update_menu(items, selected_item, x, y, centered)
|
function UI.update_menu(items, selected_item, x, y, centered, scroll_offset, visible_count)
|
||||||
|
scroll_offset = scroll_offset or 0
|
||||||
|
visible_count = visible_count or #items
|
||||||
|
local n = #items
|
||||||
|
|
||||||
|
local function find_selectable(start, dir)
|
||||||
|
local idx = start
|
||||||
|
for _ = 1, n do
|
||||||
|
if not items[idx].header then return idx end
|
||||||
|
idx = (idx - 1 + dir + n) % n + 1
|
||||||
|
end
|
||||||
|
return start
|
||||||
|
end
|
||||||
|
|
||||||
if Input.up() then
|
if Input.up() then
|
||||||
Audio.sfx_beep()
|
Audio.sfx_beep()
|
||||||
selected_item = selected_item - 1
|
local prev = (selected_item - 2 + n) % n + 1
|
||||||
if selected_item < 1 then
|
selected_item = find_selectable(prev, -1)
|
||||||
selected_item = #items
|
|
||||||
end
|
|
||||||
elseif Input.down() then
|
elseif Input.down() then
|
||||||
Audio.sfx_beep()
|
Audio.sfx_beep()
|
||||||
selected_item = selected_item + 1
|
local next_i = selected_item % n + 1
|
||||||
if selected_item > #items then
|
selected_item = find_selectable(next_i, 1)
|
||||||
selected_item = 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if x ~= nil and y ~= nil then
|
if x ~= nil and y ~= nil then
|
||||||
@@ -63,15 +89,23 @@ function UI.update_menu(items, selected_item, x, y, centered)
|
|||||||
if centered then
|
if centered then
|
||||||
local max_w = 0
|
local max_w = 0
|
||||||
for _, item in ipairs(items) do
|
for _, item in ipairs(items) do
|
||||||
local w = print(item.label, 0, -10, 0, false, 1, false)
|
if not item.header then
|
||||||
if w > max_w then max_w = w end
|
local w = print(item.label, 0, -10, 0, false, 1, false)
|
||||||
|
if w > max_w then max_w = w end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
menu_x = (Config.screen.width - max_w) / 2
|
menu_x = (Config.screen.width - max_w) / 2
|
||||||
end
|
end
|
||||||
for i, _ in ipairs(items) do
|
local current_y = y
|
||||||
if Mouse.zone({ x = menu_x - 8, y = y + (i-1) * 10, w = Config.screen.width, h = 10 }) then
|
for i = scroll_offset + 1, math.min(n, scroll_offset + visible_count) do
|
||||||
return i, true
|
local item = items[i]
|
||||||
|
local step = item.header and 8 or 10
|
||||||
|
if not item.header then
|
||||||
|
if Mouse.zone({ x = menu_x - 8, y = current_y, w = Config.screen.width, h = 10 }) then
|
||||||
|
return i, true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
current_y = current_y + step
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ local _anim = 0
|
|||||||
local _menu_max_w = 0
|
local _menu_max_w = 0
|
||||||
local ANIM_SPEED = 2.5
|
local ANIM_SPEED = 2.5
|
||||||
local HEADER_H = 28
|
local HEADER_H = 28
|
||||||
|
MenuWindow._scroll_offset = 0
|
||||||
|
|
||||||
--- Calculates the animated x position of the menu block.
|
--- Calculates the animated x position of the menu block.
|
||||||
--- @within MenuWindow
|
--- @within MenuWindow
|
||||||
@@ -45,6 +46,17 @@ function MenuWindow.draw_norman()
|
|||||||
spr(305, nx + 32, ny + 64, Config.colors.transparent, 4)
|
spr(305, nx + 32, ny + 64, Config.colors.transparent, 4)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Adjusts _scroll_offset so the selected item is within the visible window.
|
||||||
|
--- @within MenuWindow
|
||||||
|
function MenuWindow.ensure_visible()
|
||||||
|
local sel = Context.current_menu_item
|
||||||
|
if sel <= MenuWindow._scroll_offset then
|
||||||
|
MenuWindow._scroll_offset = sel - 1
|
||||||
|
elseif sel > MenuWindow._scroll_offset + 5 then
|
||||||
|
MenuWindow._scroll_offset = sel - 5
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- Draws the menu window.
|
--- Draws the menu window.
|
||||||
--- @within MenuWindow
|
--- @within MenuWindow
|
||||||
function MenuWindow.draw()
|
function MenuWindow.draw()
|
||||||
@@ -56,9 +68,19 @@ function MenuWindow.draw()
|
|||||||
MenuWindow.draw_norman()
|
MenuWindow.draw_norman()
|
||||||
end
|
end
|
||||||
|
|
||||||
local menu_h = #_menu_items * 10
|
local menu_x = MenuWindow.calc_menu_x()
|
||||||
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2)
|
local arrow_cx = math.floor(menu_x + _menu_max_w / 2)
|
||||||
UI.draw_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false)
|
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 50) / 2)
|
||||||
|
|
||||||
|
if MenuWindow._scroll_offset > 0 then
|
||||||
|
Print.text_center("^", arrow_cx, y - 8, Config.colors.light_blue)
|
||||||
|
end
|
||||||
|
|
||||||
|
UI.draw_menu(_menu_items, Context.current_menu_item, menu_x, y, false, MenuWindow._scroll_offset, 5)
|
||||||
|
|
||||||
|
if MenuWindow._scroll_offset + 5 < #_menu_items then
|
||||||
|
Print.text_center("v", arrow_cx, y + 52, Config.colors.light_blue)
|
||||||
|
end
|
||||||
|
|
||||||
local ttg_text = "TTG"
|
local ttg_text = "TTG"
|
||||||
local ttg_w = print(ttg_text, 0, -10, 0, false, 1, false)
|
local ttg_w = print(ttg_text, 0, -10, 0, false, 1, false)
|
||||||
@@ -72,8 +94,8 @@ function MenuWindow.update()
|
|||||||
_anim = math.min(1, _anim + ANIM_SPEED * Context.delta_time)
|
_anim = math.min(1, _anim + ANIM_SPEED * Context.delta_time)
|
||||||
end
|
end
|
||||||
|
|
||||||
local menu_h = #_menu_items * 10
|
local menu_x = MenuWindow.calc_menu_x()
|
||||||
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2)
|
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 50) / 2)
|
||||||
|
|
||||||
if _click_timer > 0 then
|
if _click_timer > 0 then
|
||||||
_click_timer = _click_timer - Context.delta_time
|
_click_timer = _click_timer - Context.delta_time
|
||||||
@@ -87,8 +109,9 @@ function MenuWindow.update()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local new_item, mouse_confirmed = UI.update_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false)
|
local new_item, mouse_confirmed = UI.update_menu(_menu_items, Context.current_menu_item, menu_x, y, false, MenuWindow._scroll_offset, 5)
|
||||||
Context.current_menu_item = new_item
|
Context.current_menu_item = new_item
|
||||||
|
MenuWindow.ensure_visible()
|
||||||
|
|
||||||
if mouse_confirmed then
|
if mouse_confirmed then
|
||||||
Audio.sfx_select()
|
Audio.sfx_select()
|
||||||
@@ -186,6 +209,12 @@ function MenuWindow.ascend_debug()
|
|||||||
GameWindow.set_state("ascend_debug")
|
GameWindow.set_state("ascend_debug")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Triggers the Level Up flash animation for testing.
|
||||||
|
--- @within MenuWindow
|
||||||
|
function MenuWindow.level_up_flash()
|
||||||
|
Ascension.start_flash()
|
||||||
|
end
|
||||||
|
|
||||||
--- Refreshes the list of menu items based on current game state.
|
--- Refreshes the list of menu items based on current game state.
|
||||||
--- @within MenuWindow
|
--- @within MenuWindow
|
||||||
function MenuWindow.refresh_menu_items()
|
function MenuWindow.refresh_menu_items()
|
||||||
@@ -200,6 +229,8 @@ function MenuWindow.refresh_menu_items()
|
|||||||
table.insert(_menu_items, {label = "Credits", decision = MenuWindow.credits})
|
table.insert(_menu_items, {label = "Credits", decision = MenuWindow.credits})
|
||||||
|
|
||||||
if Context.test_mode then
|
if Context.test_mode then
|
||||||
|
table.insert(_menu_items, {label = "Debug Menu", header = true})
|
||||||
|
table.insert(_menu_items, {label = "Level Up Flash", decision = MenuWindow.level_up_flash})
|
||||||
table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test})
|
table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test})
|
||||||
table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued})
|
table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued})
|
||||||
table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test})
|
table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test})
|
||||||
@@ -212,11 +243,14 @@ function MenuWindow.refresh_menu_items()
|
|||||||
|
|
||||||
_menu_max_w = 0
|
_menu_max_w = 0
|
||||||
for _, item in ipairs(_menu_items) do
|
for _, item in ipairs(_menu_items) do
|
||||||
local w = print(item.label, 0, -10, 0, false, 1, false)
|
if not item.header then
|
||||||
if w > _menu_max_w then _menu_max_w = w end
|
local w = print(item.label, 0, -10, 0, false, 1, false)
|
||||||
|
if w > _menu_max_w then _menu_max_w = w end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Context.current_menu_item = 1
|
Context.current_menu_item = 1
|
||||||
|
MenuWindow._scroll_offset = 0
|
||||||
_click_timer = 0
|
_click_timer = 0
|
||||||
_anim = 0
|
_anim = 0
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user