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

Appkit: /debug_scrollbox.lua — code sample

Code

--[[

`DebugScrollbox` class

<ul>
    <li>Renders multiline debug text to a box on the screen</li>
    <li>Disabled in Release builds</li>
</ul>

Used by `WorldWrapper` and the Appkit's Debug / Print To Screen flow node.

]]--

require 'core/appkit/lua/class'

Appkit.DebugScrollbox = Appkit.class(Appkit.DebugScrollbox)
local DebugScrollbox = Appkit.DebugScrollbox

local Application = stingray.Application
local World = stingray.World
local Color = stingray.Color
local Gui = stingray.Gui
local Vector2 = stingray.Vector2
local Vector3 = stingray.Vector3

local is_dev_build = Application.build() == "dev" or Application.build() == "debug"
local font = 'core/performance_hud/debug'
local font_material = 'core/performance_hud/debug'
local default_text_color = {255, 255, 255, 255} -- Argb
local line_count_max = 10
local text_length_max = 128
local draw_lifetime = 5

-- Positioning and sizing: these are the baseline values for a y screen resolution
-- of 720 pixels and are scaled to device.
local scale_res_y_baseline = 720 -- Font will be font_size_default when screen resolution y is this value, and scales with the y resolution as it increases/decreases
local box_top_offset_x_default = 60
local box_top_offset_y_default = 65
local font_size_default = 16
local line_spacing_default = 8
local initial_box_text_length = 50 -- Default visual box to a small size, only expand if larger text encountered
local border_buffer_default = 5

function DebugScrollbox:init(world)
    local is_enabled = is_dev_build and line_count_max > 0
    self.is_enabled = is_enabled
    if is_enabled == false then return end

    self.world = world
    self.gui = stingray.World.create_screen_gui(world, "immediate")

    -- Preallocates a list and will treat it like a ring buffer
    self.line_index = 0
    local lines = {}
    for i = 1, line_count_max do
        lines[i] = {
                text = "",
                time_added = -draw_lifetime,
                color = {default_text_color[1], default_text_color[2], default_text_color[3], default_text_color[4]}
            }
    end
    self.lines = lines
    self.is_visible = false
end

-- Optional color must be nil or table with a, r, g, b values, e.g. {255, 255, 255, 255}
function DebugScrollbox:add_line(text, optional_color)
    if self.is_enabled == false then return end

    local color = {default_text_color[1], default_text_color[2], default_text_color[3], default_text_color[4]}
    if optional_color then color = {optional_color[1], optional_color[2], optional_color[3], optional_color[4]} end

    local line_index = self.line_index + 1
    local lines = self.lines
    if line_index > #lines then line_index = 1 end
    self.line_index = line_index
    local line = lines[line_index]

    if string.len(text) > text_length_max then text = string.sub(text, 1, text_length_max) end
    line.text = text
    line.time_added = World.time(self.world)
    line.color = color
    self.is_visible = true
end

local function fade_value(current_time, time_added)
    local x = (current_time - time_added) / draw_lifetime
    return 1 - (-x) ^ 20
end

function DebugScrollbox:update(dt)
    if self.is_enabled == false then return end
    if self.is_visible == false then return end

    -- Box positioning
    local w, h = Application.back_buffer_size()
    local scale_factor = (h / scale_res_y_baseline)
    local start_x = math.floor(box_top_offset_x_default * scale_factor)
    local start_y = h - math.floor(box_top_offset_y_default * scale_factor)
    local draw_pos = Vector2(start_x, start_y)

    local gui = self.gui

    -- Draw text
    local line_spacing = math.floor(line_spacing_default * scale_factor)
    local font_size = math.floor(scale_factor * font_size_default)
    local lines_drawn = 0
    local text
    local text_height = 0
    local longest_text_len = 0
    local text_color = {0, 0, 0} -- Worker
    local current_time = World.time(self.world)
    local start_index = self.line_index
    local end_index = start_index + 1
    local lines = self.lines
    if end_index > #lines then end_index = 1 end
    local i = start_index
    repeat
        local line = lines[i]
        local time_added = line.time_added
        if (current_time - line.time_added) < draw_lifetime then
            text = line.text
            text_color = line.color
            local fade = fade_value(current_time, time_added)
            local color = Color(text_color[1] * fade, text_color[2], text_color[3], text_color[4])
            local len = string.len(text)
            if len > longest_text_len then longest_text_len = len end
            Gui.text(gui, text, font, font_size, font_material, draw_pos, color)
            if text_height == 0 then
                local min, max = stingray.Gui.text_extents(gui, text, font, font_size)
                text_height = max.y
            end
            lines_drawn = lines_drawn + 1
        else
            break
        end
        draw_pos.y = draw_pos.y - text_height - line_spacing
        i = i - 1
        if i == 0 then i = #lines end
    until i == end_index

    -- Draw background
    if lines_drawn > 0 then
        local border_buffer = math.floor(border_buffer_default * scale_factor)
        local back_color = Color(25, 40, 30, 230)
        local text_len = initial_box_text_length
        if longest_text_len > text_len then text_len = longest_text_len end
        local draw_extents = Vector2(
            (text_len * font_size / 2) + (border_buffer * 2), -- Using font_size is an approximation for max font width
            -(((text_height + line_spacing) * lines_drawn) + (border_buffer * 2)) + line_spacing
        )
        Gui.rect(
            gui,
            Vector3(
                start_x - border_buffer,
                start_y + border_buffer + text_height,
                -10
            ),
            draw_extents,
            back_color
        )
    end

    self.is_visible = lines_drawn > 0
end

return DebugScrollbox