--[[ A simple debug menu. Usage: --- **Initialization** Assuming `world` variable and the activation functions are defined... local menu = DebugMenu(world, { title = "Debug Menu Example", items = { {text = "Item 1", func = activation_function1, target = self}, {text = "Item 2", func = activation_function2, target = self} } }) **Update** menu.update() ]]-- require 'core/appkit/lua/class' local Util = require 'core/appkit/lua/util' local font = 'core/performance_hud/debug' local font_material = 'core/performance_hud/debug' Appkit.DebugMenu = Appkit.class(Appkit.DebugMenu) local DebugMenu = Appkit.DebugMenu local Application = stingray.Application local Color = stingray.Color local Gui = stingray.Gui local Keyboard = stingray.Keyboard local Mouse = stingray.Mouse local Pad1 = stingray.Pad1 local Vector2 = stingray.Vector2 local Vector3 = stingray.Vector3 local World = stingray.World DebugMenu.defaults = {} local defaults = DebugMenu.defaults defaults.title_text_color = {255, 255, 255, 230} -- Argb defaults.text_color = {255, 215, 215, 255} -- Argb defaults.back_color = {25, 40, 30, 230} -- Argb defaults.select_color = {10, 220, 220, 255} -- Argb -- Positioning and sizing: these are the baseline values for a y screen resolution -- of 720 pixels and are scaled to device. defaults.scale_res_y_baseline = 720 -- Font will be font_size when screen resolution y is this value, and scales with the y resolution as it increases/decreases defaults.title_font_size = 64 defaults.top_text_offset_y = math.floor(defaults.title_font_size * 1.25) defaults.title_separator_offset_y = math.floor(defaults.title_font_size * 0.75) defaults.font_size = math.floor(defaults.title_font_size * 0.75) defaults.line_spacing = math.floor(defaults.font_size * 0.75) defaults.box_width = 640 defaults.box_height = 400 defaults.hit_buffer_x = 48 defaults.hit_buffer_y = 15 -- `config` is a table with following format (`data`, optional, is passed to the function): -- { -- title = "Debug Menu Example", -- items = { -- {text = "Item 1", func = activation_function1, data = self}, -- {text = "Item 2", func = activation_function2, data = self} -- } -- } function DebugMenu:init(world, config) self.world = world self.gui = World.create_screen_gui(world, "immediate") self.config = config if Util.use_touch() then self.selection = nil else self.selection = 1 end local w, h = Application.back_buffer_size() self.scale = (h / defaults.scale_res_y_baseline) --Util.set_simulated_touch_enabled(true) end function DebugMenu:update() self:update_selection() self:draw() if self:check_select() then self:trigger_selection() end end -------------------------------------------------------------------------------- -- Below: internal functions, but added to DebugMenu table to -- allow external override. function DebugMenu:hit_text(pos, text, text_pos, buffer) local min, max= Gui.text_extents(self.gui, text, font, defaults.font_size * self.scale) if pos.x < text_pos.x + min.x - buffer.x then return false end if pos.x > text_pos.x + max.x + buffer.x then return false end if pos.y < text_pos.y + min.y - buffer.y then return false end if pos.y > text_pos.y + max.y + buffer.y then return false end return true end function DebugMenu:get_hit_item(pos) local scale = self.scale local buf_x = defaults.hit_buffer_x * scale local buf_y = defaults.hit_buffer_y * scale local last_pos for _, item in ipairs(self.config.items) do last_pos = item.last_pos if last_pos and self:hit_text(pos, item.text, last_pos, Vector2(buf_x, buf_y)) then return _ end end return nil end function DebugMenu.is_up_pressed() if Util.use_touch() then return false end if Keyboard then local b = Keyboard.button_id("up") if b and Keyboard.pressed(b) then return true end end if Pad1 and Pad1.active() then if Pad1.pressed(Pad1.button_id(Appkit.Util.plat(nil, "d_up", nil, "up"))) then return true end end return false end function DebugMenu.is_down_pressed() if Util.use_touch() then return false end if Keyboard then local b = Keyboard.button_id("down") if b and Keyboard.pressed(b) then return true end end if Pad1 and Pad1.active() then if Pad1.pressed(Pad1.button_id(Appkit.Util.plat(nil, "d_down", nil, "down"))) then return true end end return false end function DebugMenu.mouse_screen_pos() return Mouse.axis(Mouse.axis_id("cursor"), Mouse.RAW, 3) end function DebugMenu:check_select_touch() self.selection = nil -- with touch, selection set only while touching local touch = Util.touch_interface() if touch.num_contacts() > 0 then local id = touch.contacts() local touch_pos = touch.location(id) if touch.is_touch_up(id) then local hit_index = self:get_hit_item(touch_pos) if hit_index ~= nil then self.selection = hit_index return true end end end return false end function DebugMenu:check_select() local selection = self.selection if Util.use_touch() then return self:check_select_touch() else if Mouse and Mouse.pressed(Mouse.button_id("left")) then local scale = self.scale local buf_x = defaults.hit_buffer_x * scale local buf_y = defaults.hit_buffer_y * scale local item = self.config.items[self.selection] if item and item.last_pos and self:hit_text(self.mouse_screen_pos(), item.text, item.last_pos, Vector2(buf_x, buf_y)) then return true end end if Keyboard then local b = Keyboard.button_index("enter") if b and Keyboard.pressed(b) then return true end end if Pad1 and Pad1.active() then if Pad1.pressed(Pad1.button_id(Appkit.Util.plat(nil, "a", nil, "cross"))) then return true end end end return false end function DebugMenu:trigger_selection() local item = self.config.items[self.selection] item.func(item.data) end function DebugMenu:update_selection() if Util.use_touch() then return false end if Mouse then local hit_index = self:get_hit_item(self.mouse_screen_pos()) if hit_index ~= nil then self.selection = hit_index return end end if self:is_up_pressed() then self.selection = self.selection - 1 if self.selection == 0 then self.selection = 1 end elseif self:is_down_pressed() then self.selection = self.selection + 1 if self.selection > #self.config.items then self.selection = #self.config.items end end end function DebugMenu:draw_title(config) local gui = self.gui local text = self.config.title local text_color = defaults.title_text_color local font_size = math.floor(defaults.title_font_size * config.scale) local color = Color(text_color[1], text_color[2], text_color[3], text_color[4]) local min, max = stingray.Gui.text_extents(gui, text, font, font_size) self.title_text_height = max.y Gui.text( gui, text, font, font_size, font_material, Vector2(math.floor(config.w / 2) - math.floor(max.x / 2), config.start_y - defaults.top_text_offset_y * config.scale)) end function DebugMenu:draw_items(config) local gui = self.gui local selection = self.selection local scale = config.scale local center_x = math.floor(config.w / 2) local start_y = config.start_y - (defaults.top_text_offset_y + self.title_text_height + defaults.title_separator_offset_y) * scale local draw_pos = Vector2(center_x, start_y) local text_color = defaults.text_color local color = Color(text_color[1], text_color[2], text_color[3], text_color[4]) local line_spacing = math.floor(defaults.line_spacing * scale) local font_size = math.floor(defaults.font_size * scale) local text local text_height = 0 local longest_text_len = 0 local selection_pos -- draw each menu item text for index, item in ipairs(self.config.items) do text = item.text local len = string.len(text) if len > longest_text_len then longest_text_len = len end local min, max = stingray.Gui.text_extents(gui, text, font, font_size) text_height = max.y draw_pos.x = center_x - math.floor(max.x / 2) Gui.text(gui, text, font, font_size, font_material, draw_pos, color) item.last_pos = {x=draw_pos.x, y=draw_pos.y} draw_pos.y = draw_pos.y - text_height - line_spacing if index == selection then selection_pos = Vector2(draw_pos.x, draw_pos.y) end end -- draw selected item box if selection_pos then local selection_color = Color( defaults.select_color[1], defaults.select_color[2], defaults.select_color[3], defaults.select_color[4]) Gui.rect( self.gui, Vector3( config.start_x, selection_pos.y + text_height + line_spacing/2, -10 ), Vector2(config.extents.x, text_height + line_spacing), selection_color) end end function DebugMenu:draw_background(config) local scale = config.scale local extents = config.extents local start_x = config.start_x local start_y = config.start_y local back_color = Color( defaults.back_color[1], defaults.back_color[2], defaults.back_color[3], defaults.back_color[4]) Gui.rect( self.gui, Vector3( start_x, start_y, -10 ), extents, back_color) end function DebugMenu:draw() local w, h = Application.back_buffer_size() local scale = self.scale local extents = Vector2(defaults.box_width * scale, -defaults.box_height * scale) local config = { h = h, w = w, scale = scale, extents = extents, start_x = math.floor(w/2) - extents.x/2, start_y = h - math.floor(h/2) - extents.y/2 } self:draw_title(config) self:draw_items(config) self:draw_background(config) end return DebugMenu