#62, #63 graphics primatives and added display boxes to renderer

This commit is contained in:
Mikayla Fischler 2022-06-06 15:42:39 -04:00
parent 285026c1fa
commit 8ea75b9501
6 changed files with 353 additions and 12 deletions

View File

@ -1,36 +1,58 @@
local log = require("scada-common.log")
local log = require("scada-common.log")
local util = require("scada-common.util")
local displaybox = require("graphics.elements.displaybox")
local structs = require("graphics.structs")
local renderer = {}
local gconf = {
-- root boxes
root = {
fgd = colors.black,
bkg = colors.lightGray
}
}
-- render engine
local engine = {
monitors = nil,
dmesg_window = nil
}
-- UI elements
local ui = {
main_box = nil,
unit_boxes = {}
}
-- reset a display to the "default", but set text scale to 0.5
local function _reset_display(monitor)
monitor.setTextScale(0.5)
monitor.setTextColor(colors.white)
monitor.setBackgroundColor(colors.black)
monitor.clear()
monitor.setCursorPos(1, 1)
end
-- link to the monitor peripherals
---@param monitors monitors_struct
function renderer.set_displays(monitors)
engine.monitors = monitors
end
-- reset all displays in use by the renderer
function renderer.reset()
-- reset primary monitor
engine.monitors.primary.setTextScale(0.5)
engine.monitors.primary.setTextColor(colors.white)
engine.monitors.primary.setBackgroundColor(colors.black)
engine.monitors.primary.clear()
engine.monitors.primary.setCursorPos(1, 1)
_reset_display(engine.monitors.primary)
-- reset unit displays
for _, monitor in pairs(engine.monitors.unit_displays) do
monitor.setTextScale(0.5)
monitor.setTextColor(colors.white)
monitor.setBackgroundColor(colors.black)
monitor.clear()
monitor.setCursorPos(1, 1)
_reset_display(monitor)
end
end
-- initialize the dmesg output window
function renderer.init_dmesg()
local disp_x, disp_y = engine.monitors.primary.getSize()
engine.dmesg_window = window.create(engine.monitors.primary, 1, 1, disp_x, disp_y)
@ -38,4 +60,22 @@ function renderer.init_dmesg()
log.direct_dmesg(engine.dmesg_window)
end
-- start the coordinator GUI
function renderer.start_ui()
local palette = structs.graphics.cpair(gconf.root.fgd, gconf.root.bkg)
ui.main_box = displaybox{window = engine.monitors.primary, fg_bg = palette}
for _, monitor in pairs(engine.monitors.unit_displays) do
table.insert(ui.unit_boxes, displaybox{window = engine.monitors.primary, fg_bg = palette})
end
end
-- close out the UI
function renderer.close_ui()
-- clear root UI elements
ui.main_box = nil
ui.unit_boxes = {}
end
return renderer

View File

@ -12,7 +12,7 @@ local config = require("coordinator.config")
local coordinator = require("coordinator.coordinator")
local renderer = require("coordinator.renderer")
local COORDINATOR_VERSION = "alpha-v0.1.4"
local COORDINATOR_VERSION = "alpha-v0.1.5"
local print = util.print
local println = util.println
@ -81,3 +81,12 @@ if modem == nil then
end
log.dmesg("wireless modem connected", "COMMS", colors.purple)
log.dmesg("starting UI...", "GRAPHICS", colors.green)
util.psleep(3)
local ui_ok = pcall(renderer.start_ui)
if not ui_ok then
renderer.close_ui()
log.dmesg("UI draw failed", "GRAPHICS", colors.green)
end

73
graphics/core.lua Normal file
View File

@ -0,0 +1,73 @@
local core = {}
local events = {}
---@class monitor_touch
---@field monitor string
---@field x integer
---@field y integer
---@param monitor string
---@param x integer
---@param y integer
---@return monitor_touch
function events.touch(monitor, x, y)
return {
monitor = monitor,
x = x,
y = y
}
end
core.events = events
local graphics = {}
---@alias TEXT_ALIGN integer
graphics.TEXT_ALIGN = {
LEFT = 1,
CENTER = 2,
RIGHT = 3
}
---@class graphics_frame
---@field x integer
---@field y integer
---@field w integer
---@field h integer
---@param x integer
---@param y integer
---@param w integer
---@param h integer
---@return graphics_frame
function graphics.gframe(x, y, w, h)
return {
x = x,
y = y,
w = w,
h = h
}
end
---@class cpair
---@field fgd color
---@field bkg color
---@field blit_fgd string
---@field blit_bkg string
---@param foreground color
---@param background color
---@return cpair
function graphics.cpair(foreground, background)
return {
fgd = foreground,
bkg = background,
blit_fgd = colors.toBlit(foreground),
blit_bkg = colors.toBlit(background)
}
end
core.graphics = graphics
return core

124
graphics/element.lua Normal file
View File

@ -0,0 +1,124 @@
local core = require("graphics.core")
local element = {}
---@class graphics_args_generic
---@field window? table
---@field parent? graphics_element
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors
-- a base graphics element, should not be created on its own
---@param args graphics_args_generic arguments
function element.new(args)
local self = {
p_window = nil, ---@type table
position = { x = 1, y = 1 },
bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1}
}
local protected = {
window = nil, ---@type table
frame = core.graphics.gframe(1, 1, 1, 1)
}
-- SETUP --
-- get the parent window
self.p_window = args.window or args.parent.window()
assert(self.p_window, "graphics.element: no parent window provided")
-- get frame coordinates/size
if args.gframe ~= nil then
protected.frame.x = args.gframe.x
protected.frame.y = args.gframe.y
protected.frame.w = args.gframe.w
protected.frame.h = args.gframe.h
else
local w, h = self.p_window.getSize()
protected.frame.x = args.x or 1
protected.frame.y = args.y or 1
protected.frame.w = args.width or w
protected.frame.h = args.height or h
end
-- create window
local f = protected.frame
protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true)
-- init display box
if args.fg_bg ~= nil then
protected.window.setBackgroundColor(args.fg_bg.bkg)
protected.window.setTextColor(args.fg_bg.fgd)
protected.window.clear()
end
-- record position
self.position.x, self.position.y = protected.window.getPosition()
-- calculate bounds
self.bounds.x1 = self.position.x
self.bounds.x2 = self.position.x + f.w - 1
self.bounds.y1 = self.position.y
self.bounds.y2 = self.position.y + f.h - 1
-- PROTECTED FUNCTIONS --
-- handle a touch event
---@param event table monitor_touch event
function protected.handle_touch(event)
end
-- handle data value changes
function protected.on_update(...)
end
---@class graphics_element
local public = {}
-- get public interface
function protected.get() return public end
-- PUBLIC FUNCTIONS --
-- get the window object
function public.window() return protected.window end
-- handle a monitor touch
---@param event monitor_touch monitor touch event
function public.handle_touch(event)
local in_x = event.x >= self.bounds.x1 and event.x <= self.bounds.x2
local in_y = event.y >= self.bounds.y1 and event.y <= self.bounds.y2
if in_x and in_y then
-- handle the touch event, transformed into the window frame
protected.handle_touch(core.events.touch(event.monitor,
(event.x - self.position.x) + 1,
(event.y - self.position.y) + 1))
end
end
-- draw the element given new data
function public.draw(...)
protected.on_update(...)
end
-- show the element
function public.show()
protected.window.setVisible(true)
end
-- hide the element
function public.hide()
protected.window.setVisible(false)
end
return protected
end
return element

View File

@ -0,0 +1,21 @@
-- Root Display Box Graphics Element
local element = require("graphics.element")
---@class displaybox_args
---@field window table
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors
-- new root display box
---@param args displaybox_args
local function displaybox(args)
-- create new graphics element base object
return element.new(args).get()
end
return displaybox

View File

@ -0,0 +1,74 @@
-- Text Box Graphics Element
local element = require("graphics.element")
---@class textbox_args
---@field text string text to show
---@field parent graphics_element
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors
-- new text box
---@param args textbox_args
local function textbox(args)
assert(args.text ~= nil, "graphics.elements.textbox: empty text box")
-- create new graphics element base object
local e = element.new(args)
-- write text
local text = args.text
local lines = { text }
local w = e.frame.w
local h = e.frame.h
-- wrap if needed
if string.len(text) > w then
local remaining = true
local s_start = 1
local s_end = w
local i = 1
lines = {}
while remaining do
local line = string.sub(text, s_start, s_end)
if line == "" then
remaining = false
else
lines[i] = line
s_start = s_end + 1
s_end = s_end + w
i = i + 1
end
end
end
-- output message
for i = 1, #lines do
local cur_x, cur_y = e.window.getCursorPos()
if i > 1 and cur_x > 1 then
if cur_y == h then
e.window.scroll(1)
e.window.setCursorPos(1, cur_y)
else
e.window.setCursorPos(1, cur_y + 1)
end
end
e.window.write(lines[i])
end
return e.get()
end
return textbox