--[[
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