From 8ea75b9501a5838d19d91ea2d9a4eb8fa58f452a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 6 Jun 2022 15:42:39 -0400 Subject: [PATCH] #62, #63 graphics primatives and added display boxes to renderer --- coordinator/renderer.lua | 62 +++++++++++++--- coordinator/startup.lua | 11 ++- graphics/core.lua | 73 ++++++++++++++++++ graphics/element.lua | 124 +++++++++++++++++++++++++++++++ graphics/elements/displaybox.lua | 21 ++++++ graphics/elements/textbox.lua | 74 ++++++++++++++++++ 6 files changed, 353 insertions(+), 12 deletions(-) create mode 100644 graphics/core.lua create mode 100644 graphics/element.lua create mode 100644 graphics/elements/displaybox.lua create mode 100644 graphics/elements/textbox.lua diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 57553ca..4ed6358 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -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 diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 5775168..8b79745 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -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 diff --git a/graphics/core.lua b/graphics/core.lua new file mode 100644 index 0000000..3d23dfc --- /dev/null +++ b/graphics/core.lua @@ -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 diff --git a/graphics/element.lua b/graphics/element.lua new file mode 100644 index 0000000..7078bc9 --- /dev/null +++ b/graphics/element.lua @@ -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 diff --git a/graphics/elements/displaybox.lua b/graphics/elements/displaybox.lua new file mode 100644 index 0000000..0d7fd47 --- /dev/null +++ b/graphics/elements/displaybox.lua @@ -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 diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua new file mode 100644 index 0000000..435df30 --- /dev/null +++ b/graphics/elements/textbox.lua @@ -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