graphics library refactoring and bugfixes

This commit is contained in:
Mikayla Fischler
2022-06-16 11:19:32 -04:00
parent b628472d81
commit 971657c3d2
10 changed files with 85 additions and 49 deletions

View File

@@ -0,0 +1,69 @@
-- Data Indicator Graphics Element
local util = require("scada-common.util")
local element = require("graphics.element")
---@class data_indicator_args
---@field label string indicator label
---@field unit? string indicator unit
---@field format string data format (lua string format)
---@field lu_colors? cpair label foreground color (a), unit foreground color (b)
---@field value any default value
---@field parent graphics_element
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field width integer length
---@field fg_bg? cpair foreground/background colors
-- new data indicator
---@param args data_indicator_args
local function data(args)
assert(type(args.label) == "string", "graphics.elements.indicators.data: label is a required field")
assert(type(args.format) == "string", "graphics.elements.indicators.data: format is a required field")
assert(args.value ~= nil, "graphics.elements.indicators.data: value is a required field")
assert(util.is_int(args.width), "graphics.elements.indicators.data: width is a required field")
-- single line
args.height = 1
-- create new graphics element base object
local e = element.new(args)
-- label color
if args.lu_colors ~= nil then
e.window.setTextColor(args.lu_colors.color_a)
end
-- write label
e.window.setCursorPos(1, 1)
e.window.write(args.label)
local data_start = string.len(args.label) + 2
-- on state change
---@param value any new value
function e.on_update(value)
local data_str = util.sprintf(args.format, value)
-- write data
e.window.setCursorPos(data_start, 1)
e.window.setTextColor(e.fg_bg.fgd)
e.window.write(data_str)
-- write label
if args.unit ~= nil then
if args.lu_colors ~= nil then
e.window.setTextColor(args.lu_colors.color_b)
end
e.window.write(" " .. args.unit)
end
end
-- initial value draw
e.on_update(args.value)
return e.get()
end
return data

View File

@@ -0,0 +1,97 @@
-- Horizontal Bar Graphics Element
local util = require("scada-common.util")
local element = require("graphics.element")
---@class hbar_args
---@field show_percent? boolean whether or not to show the percent
---@field bar_fg_bg? cpair bar foreground/background colors if showing percent
---@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 horizontal bar
---@param args hbar_args
local function hbar(args)
-- properties/state
local last_num_bars = -1
-- create new graphics element base object
local e = element.new(args)
-- bar width is width - 5 characters for " 100%" if showing percent
local bar_width = util.trinary(args.show_percent, e.frame.w - 5, e.frame.w)
assert(bar_width > 0, "graphics.elements.hbar: too small for bar")
-- determine bar colors
---@fixme this doesnt work as intended
local bar_bkg = util.trinary(args.bar_fg_bg == nil, e.fg_bg.blit_bkg, args.bar_fg_bg.blit_bkg)
local bar_fgd = util.trinary(args.bar_fg_bg == nil, e.fg_bg.blit_fgd, args.bar_fg_bg.blit_fgd)
-- handle data changes
function e.on_update(fraction)
-- enforce minimum and maximum
if fraction < 0 then
fraction = 0.0
elseif fraction > 1 then
fraction = 1.0
end
-- compute number of bars
local num_bars = util.round(fraction * (bar_width * 2))
util.print(num_bars)
-- redraw bar if changed
if num_bars ~= last_num_bars then
last_num_bars = num_bars
local fgd = ""
local bkg = ""
local spaces = ""
-- fill percentage
for _ = 1, num_bars / 2 do
spaces = spaces .. " "
fgd = fgd .. bar_fgd
bkg = bkg .. bar_bkg
end
-- add fractional bar if needed
if num_bars % 2 == 1 then
spaces = spaces .. "\x95"
fgd = fgd .. bar_bkg
bkg = bkg .. bar_fgd
end
-- pad background
for _ = 1, ((bar_width * 2) - num_bars) / 2 do
spaces = spaces .. " "
fgd = fgd .. bar_bkg
bkg = bkg .. bar_bkg
end
-- draw bar
for y = 1, e.frame.h do
e.window.setCursorPos(1, y)
-- intentionally swapped fgd/bkg since we use spaces as fill, but they are the opposite
e.window.blit(spaces, bkg, fgd)
end
end
-- update percentage
if args.show_percent then
e.window.setCursorPos(bar_width + 1, math.max(1, math.ceil(e.frame.h / 2)))
e.window.write(util.sprintf("%3.0f%%", fraction * 100))
end
end
return e.get()
end
return hbar

View File

@@ -0,0 +1,66 @@
-- Icon Indicator Graphics Element
local util = require("scada-common.util")
local element = require("graphics.element")
---@class icon_sym_color
---@field color cpair
---@field symbol string
---@class icon_indicator_args
---@field label string indicator label
---@field states table state color and symbol table
---@field value? integer default state, defaults to 1
---@field min_label_width? integer label length if omitted
---@field parent graphics_element
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors
-- new icon indicator
---@param args icon_indicator_args
local function icon(args)
assert(type(args.label) == "string", "graphics.elements.indicators.icon: label is a required field")
assert(type(args.states) == "table", "graphics.elements.indicators.icon: states is a required field")
-- single line
args.height = 1
-- determine width
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 4
-- create new graphics element base object
local e = element.new(args)
-- state blit strings
local state_blit_cmds = {}
for i = 1, #args.states do
local sym_color = args.states[i] ---@type icon_sym_color
table.insert(state_blit_cmds, {
text = " " .. sym_color.symbol .. " ",
fgd = util.strrep(sym_color.color.blit_fgd, 3),
bkg = util.strrep(sym_color.color.blit_bkg, 3)
})
end
-- write label and initial indicator light
e.window.setCursorPos(5, 1)
e.window.write(args.label)
-- on state change
---@param new_state integer indicator state
function e.on_update(new_state)
local blit_cmd = state_blit_cmds[new_state]
e.window.setCursorPos(1, 1)
e.window.blit(blit_cmd.text, blit_cmd.fgd, blit_cmd.bkg)
end
-- initial icon draw
e.on_update(args.value or 1)
return e.get()
end
return icon

View File

@@ -0,0 +1,54 @@
-- Indicator Light Graphics Element
local util = require("scada-common.util")
local element = require("graphics.element")
---@class indicator_light_args
---@field label string indicator label
---@field colors cpair on/off colors (a/b respectively)
---@field min_label_width? integer label length if omitted
---@field parent graphics_element
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors
-- new indicator light
---@param args indicator_light_args
local function indicator_light(args)
assert(type(args.label) == "string", "graphics.elements.indicators.light: label is a required field")
assert(type(args.colors) == "table", "graphics.elements.indicators.light: colors is a required field")
-- single line
args.height = 1
-- determine width
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 3
-- create new graphics element base object
local e = element.new(args)
-- on/off blit strings
local on_blit = util.strrep(args.colors.blit_a, 2)
local off_blit = util.strrep(args.colors.blit_b, 2)
-- write label and initial indicator light
e.window.setCursorPos(1, 1)
e.window.blit(" ", "000", off_blit .. e.fg_bg.blit_bkg)
e.window.write(args.label)
-- on state change
---@param new_state boolean indicator state
function e.on_update(new_state)
e.window.setCursorPos(1, 1)
if new_state then
e.window.blit(" ", "00", on_blit)
else
e.window.blit(" ", "00", off_blit)
end
end
return e.get()
end
return indicator_light

View File

@@ -0,0 +1,76 @@
-- State (Text) Indicator Graphics Element
local util = require("scada-common.util")
local element = require("graphics.element")
---@class state_text_color
---@field color cpair
---@field text string
---@class state_indicator_args
---@field states table state color and text table
---@field value? integer default state, defaults to 1
---@field min_width? integer max state text length if omitted
---@field parent graphics_element
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field height? integer 1 if omitted, must be an odd number
---@field fg_bg? cpair foreground/background colors
-- new state indicator
---@param args state_indicator_args
local function state_indicator(args)
assert(type(args.states) == "table", "graphics.elements.indicators.state: states is a required field")
-- determine height
if util.is_int(args.height) then
assert(args.height % 2 == 1, "graphics.elements.indicators.state: height should be an odd number")
else
args.height = 1
end
-- initial guess at width
args.width = args.min_width or 1
-- state blit strings
local state_blit_cmds = {}
for i = 1, #args.states do
local state_def = args.states[i] ---@type state_text_color
-- re-determine width
if string.len(state_def.text) > args.width then
args.width = string.len(state_def.text)
end
local len = string.len(state_def.text)
local lpad = math.floor((args.width - len) / 2)
local rpad = args.width - lpad
local text = util.spaces(lpad) .. state_def.text .. util.spaces(rpad)
table.insert(state_blit_cmds, {
text = text,
fgd = util.strrep(state_def.color.blit_fgd, string.len(text)),
bkg = util.strrep(state_def.color.blit_bkg, string.len(text))
})
end
-- create new graphics element base object
local e = element.new(args)
-- on state change
---@param new_state integer indicator state
function e.on_update(new_state)
local blit_cmd = state_blit_cmds[new_state]
e.window.setCursorPos(1, 1)
e.window.blit(blit_cmd.text, blit_cmd.fgd, blit_cmd.bkg)
end
-- initial draw
e.on_update(args.value or 1)
return e.get()
end
return state_indicator

View File

@@ -0,0 +1,82 @@
-- Vertical Bar Graphics Element
local util = require("scada-common.util")
local element = require("graphics.element")
---@class vbar_args
---@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 vertical bar
---@param args vbar_args
local function vbar(args)
-- properties/state
local last_num_bars = -1
-- create new graphics element base object
local e = element.new(args)
-- blit strings
local fgd = util.strrep(e.fg_bg.blit_fgd, e.frame.w)
local bkg = util.strrep(e.fg_bg.blit_bkg, e.frame.w)
local spaces = util.spaces(e.frame.w)
local one_third = util.strrep("\x8f", e.frame.w)
local two_thirds = util.strrep("\x83", e.frame.w)
-- handle data changes
function e.on_update(fraction)
-- enforce minimum and maximum
if fraction < 0 then
fraction = 0.0
elseif fraction > 1 then
fraction = 1.0
end
-- compute number of bars
local num_bars = util.round((fraction * 100) / (e.frame.h * 3))
-- redraw only if number of bars has changed
if num_bars ~= last_num_bars then
last_num_bars = num_bars
-- start bottom up
local y = e.window.h
-- start at base of vertical bar
e.window.setCursorPos(1, y)
-- fill percentage
for _ = 1, num_bars / 3 do
e.window.blit(spaces, bkg, fgd)
y = y - 1
e.window.setCursorPos(1, y)
end
-- add fractional bar if needed
if num_bars % 3 == 1 then
e.window.blit(one_third, bkg, fgd)
y = y - 1
elseif num_bars % 3 == 2 then
e.window.blit(two_thirds, bkg, fgd)
y = y - 1
end
-- fill the rest blank
while y > 0 do
e.window.setCursorPos(1, y)
e.window.blit(spaces, fgd, bkg)
y = y - 1
end
end
end
return e.get()
end
return vbar