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