Appkit: /debug_menu.lua — code sample - Stingray Lua API Reference

Appkit: /debug_menu.lua — code sample

Code

--[[

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