diff --git a/graphics/element.lua b/graphics/element.lua index e04a0df..20bc0c0 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -360,6 +360,8 @@ function element.new(args, child_offset_x, child_offset_y) ---@param result any function protected.response_callback(result) end + -- accessors and control -- + -- get value ---@nodiscard function protected.get_value() return protected.value end @@ -387,6 +389,9 @@ function element.new(args, child_offset_x, child_offset_y) -- luacheck: pop ---@diagnostic enable: unused-local, unused-vararg + -- re-draw this element + function protected.redraw() end + -- start animations function protected.start_anim() end diff --git a/graphics/elements/colormap.lua b/graphics/elements/colormap.lua index 25fd135..7e3554f 100644 --- a/graphics/elements/colormap.lua +++ b/graphics/elements/colormap.lua @@ -1,7 +1,5 @@ -- Color Map Graphics Element -local util = require("scada-common.util") - local element = require("graphics.element") ---@class colormap_args @@ -16,7 +14,7 @@ local element = require("graphics.element") ---@return graphics_element element, element_id id local function colormap(args) local bkg = "008877FFCCEE114455DD9933BBAA2266" - local spaces = util.spaces(32) + local spaces = string.rep(" ", 32) args.width = 32 args.height = 1 @@ -25,8 +23,13 @@ local function colormap(args) local e = element.new(args) -- draw color map - e.w_set_cur(1, 1) - e.w_blit(spaces, bkg, bkg) + function e.redraw() + e.w_set_cur(1, 1) + e.w_blit(spaces, bkg, bkg) + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/controls/app.lua b/graphics/elements/controls/app.lua index a0d5949..10ef6b5 100644 --- a/graphics/elements/controls/app.lua +++ b/graphics/elements/controls/app.lua @@ -24,21 +24,17 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK ---@param args app_button_args ---@return graphics_element element, element_id id local function app_button(args) - assert(type(args.text) == "string", "graphics.elements.controls.app: text is a required field") - assert(type(args.title) == "string", "graphics.elements.controls.app: title is a required field") - assert(type(args.callback) == "function", "graphics.elements.controls.app: callback is a required field") - assert(type(args.app_fg_bg) == "table", "graphics.elements.controls.app: app_fg_bg is a required field") + assert(type(args.text) == "string", "controls.app: text is a required field") + assert(type(args.title) == "string", "controls.app: title is a required field") + assert(type(args.callback) == "function", "controls.app: callback is a required field") + assert(type(args.app_fg_bg) == "table", "controls.app: app_fg_bg is a required field") args.height = 4 - args.width = 5 + args.width = 5 -- create new graphics element base object local e = element.new(args) - -- write app title, centered - e.w_set_cur(math.floor((e.frame.w - string.len(args.title)) / 2) + 1, 4) - e.w_write(args.title) - -- draw the app button local function draw() local fgd = args.app_fg_bg.fgd @@ -120,8 +116,18 @@ local function app_button(args) if val then e.handle_mouse(core.events.mouse_generic(core.events.MOUSE_CLICK.UP, 1, 1)) end end + -- element redraw + function e.redraw() + -- write app title, centered + e.w_set_cur(math.floor((e.frame.w - string.len(args.title)) / 2) + 1, 4) + e.w_write(args.title) + + -- draw button + draw() + end + -- initial draw - draw() + e.redraw() return e.complete() end diff --git a/graphics/elements/controls/checkbox.lua b/graphics/elements/controls/checkbox.lua index 663e18d..a31e636 100644 --- a/graphics/elements/controls/checkbox.lua +++ b/graphics/elements/controls/checkbox.lua @@ -18,8 +18,8 @@ local element = require("graphics.element") ---@param args checkbox_args ---@return graphics_element element, element_id id local function checkbox(args) - assert(type(args.label) == "string", "graphics.elements.controls.checkbox: label is a required field") - assert(type(args.box_fg_bg) == "table", "graphics.elements.controls.checkbox: box_fg_bg is a required field") + assert(type(args.label) == "string", "controls.checkbox: label is a required field") + assert(type(args.box_fg_bg) == "table", "controls.checkbox: box_fg_bg is a required field") args.can_focus = true args.height = 1 @@ -105,9 +105,14 @@ local function checkbox(args) e.on_enabled = draw_label e.on_disabled = draw_label + -- element redraw + function e.redraw() + draw() + draw_label() + end + -- initial draw - draw() - draw_label() + e.redraw() return e.complete() end diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index 92e4ac5..9f402cf 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -21,21 +21,16 @@ local element = require("graphics.element") ---@param args hazard_button_args ---@return graphics_element element, element_id id local function hazard_button(args) - assert(type(args.text) == "string", "graphics.elements.controls.hazard_button: text is a required field") - assert(type(args.accent) == "number", "graphics.elements.controls.hazard_button: accent is a required field") - assert(type(args.callback) == "function", "graphics.elements.controls.hazard_button: callback is a required field") + assert(type(args.text) == "string", "controls.hazard_button: text is a required field") + assert(type(args.accent) == "number", "controls.hazard_button: accent is a required field") + assert(type(args.callback) == "function", "controls.hazard_button: callback is a required field") - -- static dimensions args.height = 3 args.width = string.len(args.text) + 4 -- create new graphics element base object local e = element.new(args) - -- write the button text - e.w_set_cur(3, 2) - e.w_write(args.text) - -- draw border ---@param accent color accent color local function draw_border(accent) @@ -158,7 +153,6 @@ local function hazard_button(args) -- 1.5 second timeout tcd.dispatch(1.5, on_timeout) - -- call the touch callback args.callback() end end @@ -195,8 +189,16 @@ local function hazard_button(args) e.w_write(args.text) end - -- initial draw of border - draw_border(args.accent) + -- element redraw + function e.redraw() + -- write the button text and draw border + e.w_set_cur(3, 2) + e.w_write(args.text) + draw_border(args.accent) + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/controls/multi_button.lua b/graphics/elements/controls/multi_button.lua index f6116b0..4de45b0 100644 --- a/graphics/elements/controls/multi_button.lua +++ b/graphics/elements/controls/multi_button.lua @@ -29,13 +29,11 @@ local element = require("graphics.element") ---@param args multi_button_args ---@return graphics_element element, element_id id local function multi_button(args) - assert(type(args.options) == "table", "graphics.elements.controls.multi_button: options is a required field") - assert(#args.options > 0, "graphics.elements.controls.multi_button: at least one option is required") - assert(type(args.callback) == "function", "graphics.elements.controls.multi_button: callback is a required field") - assert(type(args.default) == "nil" or (type(args.default) == "number" and args.default > 0), - "graphics.elements.controls.multi_button: default must be nil or a number > 0") - assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), - "graphics.elements.controls.multi_button: min_width must be nil or a number > 0") + assert(type(args.options) == "table", "controls.multi_button: options is a required field") + assert(#args.options > 0, "controls.multi_button: at least one option is required") + assert(type(args.callback) == "function", "controls.multi_button: callback is a required field") + assert(type(args.default) == "nil" or (type(args.default) == "number" and args.default > 0), "controls.multi_button: default must be nil or a number > 0") + assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), "controls.multi_button: min_width must be nil or a number > 0") -- single line args.height = 1 @@ -71,7 +69,7 @@ local function multi_button(args) end -- show the button state - local function draw() + function e.redraw() for i = 1, #args.options do local opt = args.options[i] ---@type button_option @@ -115,7 +113,7 @@ local function multi_button(args) -- tap always has identical coordinates, so this always passes for taps if button_ini == button_cur and button_cur ~= nil then e.value = button_cur - draw() + e.redraw() args.callback(e.value) end end @@ -125,11 +123,11 @@ local function multi_button(args) ---@param val integer new value function e.set_value(val) e.value = val - draw() + e.redraw() end -- initial draw - draw() + e.redraw() return e.complete() end diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index cab0977..02bda8a 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -26,10 +26,9 @@ local KEY_CLICK = core.events.KEY_CLICK ---@param args push_button_args ---@return graphics_element element, element_id id local function push_button(args) - assert(type(args.text) == "string", "graphics.elements.controls.push_button: text is a required field") - assert(type(args.callback) == "function", "graphics.elements.controls.push_button: callback is a required field") - assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), - "graphics.elements.controls.push_button: min_width must be nil or a number > 0") + assert(type(args.text) == "string", "controls.push_button: text is a required field") + assert(type(args.callback) == "function", "controls.push_button: callback is a required field") + assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), "controls.push_button: min_width must be nil or a number > 0") local text_width = string.len(args.text) @@ -46,7 +45,7 @@ local function push_button(args) local v_pad = math.floor(e.frame.h / 2) + 1 -- draw the button - local function draw() + function e.redraw() e.window.clear() -- write the button text @@ -60,7 +59,7 @@ local function push_button(args) e.value = true e.w_set_fgd(args.active_fg_bg.fgd) e.w_set_bkg(args.active_fg_bg.bkg) - draw() + e.redraw() end end @@ -70,7 +69,7 @@ local function push_button(args) e.value = false e.w_set_fgd(e.fg_bg.fgd) e.w_set_bkg(e.fg_bg.bkg) - draw() + e.redraw() end end @@ -117,7 +116,7 @@ local function push_button(args) e.value = false e.w_set_fgd(e.fg_bg.fgd) e.w_set_bkg(e.fg_bg.bkg) - draw() + e.redraw() end end @@ -127,7 +126,7 @@ local function push_button(args) e.value = false e.w_set_fgd(args.dis_fg_bg.fgd) e.w_set_bkg(args.dis_fg_bg.bkg) - draw() + e.redraw() end end @@ -136,7 +135,7 @@ local function push_button(args) e.on_unfocused = show_unpressed -- initial draw - draw() + e.redraw() return e.complete() end diff --git a/graphics/elements/controls/radio_2d.lua b/graphics/elements/controls/radio_2d.lua index a3ac712..e4386f1 100644 --- a/graphics/elements/controls/radio_2d.lua +++ b/graphics/elements/controls/radio_2d.lua @@ -27,11 +27,12 @@ local element = require("graphics.element") ---@param args radio_2d_args ---@return graphics_element element, element_id id local function radio_2d_button(args) - assert(type(args.options) == "table" and #args.options > 0, "graphics.elements.controls.radio_2d: options should be a table with length >= 1") - assert(type(args.radio_colors) == "table", "graphics.elements.controls.radio_2d: radio_colors is a required field") - assert(type(args.select_color) == "number" or type(args.color_map) == "table", "graphics.elements.controls.radio_2d: select_color or color_map is required") - assert(type(args.default) == "nil" or (type(args.default) == "number" and args.default > 0), - "graphics.elements.controls.radio_2d: default must be nil or a number > 0") + assert(type(args.options) == "table" and #args.options > 0, "controls.radio_2d: options should be a table with length >= 1") + assert(util.is_int(args.rows) and util.is_int(args.columns), "controls.radio_2d: rows/columns must be integers") + assert((args.rows * args.columns) >= #args.options, "controls.radio_2d: rows x columns size insufficient for provided number of options") + assert(type(args.radio_colors) == "table", "controls.radio_2d: radio_colors is a required field") + assert(type(args.select_color) == "number" or type(args.color_map) == "table", "controls.radio_2d: select_color or color_map is required") + assert(type(args.default) == "nil" or (type(args.default) == "number" and args.default > 0), "controls.radio_2d: default must be nil or a number > 0") local array = {} local col_widths = {} @@ -74,8 +75,8 @@ local function radio_2d_button(args) -- selected option (convert nil to 1 if missing) e.value = args.default or 1 - -- show the args.options/states - local function draw() + -- draw the element + function e.redraw() local col_x = 1 local radio_color_b = util.trinary(type(args.disable_color) == "number" and not e.enabled, args.disable_color, args.radio_colors.color_b) @@ -135,7 +136,7 @@ local function radio_2d_button(args) if elem ~= nil and event.initial.x >= elem.x_1 and event.initial.x <= elem.x_2 and event.current.x >= elem.x_1 and event.current.x <= elem.x_2 then e.value = elem.id focused_opt = elem.id - draw() + e.redraw() if type(args.callback) == "function" then args.callback(e.value) end break end @@ -149,30 +150,30 @@ local function radio_2d_button(args) if event.type == core.events.KEY_CLICK.DOWN or event.type == core.events.KEY_CLICK.HELD then if event.type == core.events.KEY_CLICK.DOWN and (event.key == keys.space or event.key == keys.enter or event.key == keys.numPadEnter) then e.value = focused_opt - draw() + e.redraw() if type(args.callback) == "function" then args.callback(e.value) end elseif event.key == keys.down then if focused_opt < #args.options then focused_opt = focused_opt + 1 - draw() + e.redraw() end elseif event.key == keys.up then if focused_opt > 1 then focused_opt = focused_opt - 1 - draw() + e.redraw() end elseif event.key == keys.right then if array[focus_y + 1] and array[focus_y + 1][focus_x] then focused_opt = array[focus_y + 1][focus_x].id else focused_opt = array[1][focus_x].id end - draw() + e.redraw() elseif event.key == keys.left then if array[focus_y - 1] and array[focus_y - 1][focus_x] then focused_opt = array[focus_y - 1][focus_x].id - draw() + e.redraw() elseif array[#array][focus_x] then focused_opt = array[#array][focus_x].id - draw() + e.redraw() end end end @@ -183,20 +184,20 @@ local function radio_2d_button(args) function e.set_value(val) if val > 0 and val <= #args.options then e.value = val - draw() + e.redraw() end end -- handle focus - e.on_focused = draw - e.on_unfocused = draw + e.on_focused = e.redraw + e.on_unfocused = e.redraw -- handle enable - e.on_enabled = draw - e.on_disabled = draw + e.on_enabled = e.redraw + e.on_disabled = e.redraw -- initial draw - draw() + e.redraw() return e.complete() end diff --git a/graphics/elements/controls/radio_button.lua b/graphics/elements/controls/radio_button.lua index cff16eb..483f1f8 100644 --- a/graphics/elements/controls/radio_button.lua +++ b/graphics/elements/controls/radio_button.lua @@ -25,14 +25,14 @@ local KEY_CLICK = core.events.KEY_CLICK ---@param args radio_button_args ---@return graphics_element element, element_id id local function radio_button(args) - assert(type(args.options) == "table", "graphics.elements.controls.radio_button: options is a required field") - assert(#args.options > 0, "graphics.elements.controls.radio_button: at least one option is required") - assert(type(args.radio_colors) == "table", "graphics.elements.controls.radio_button: radio_colors is a required field") - assert(type(args.select_color) == "number", "graphics.elements.controls.radio_button: select_color is a required field") + assert(type(args.options) == "table", "controls.radio_button: options is a required field") + assert(#args.options > 0, "controls.radio_button: at least one option is required") + assert(type(args.radio_colors) == "table", "controls.radio_button: radio_colors is a required field") + assert(type(args.select_color) == "number", "controls.radio_button: select_color is a required field") assert(type(args.default) == "nil" or (type(args.default) == "number" and args.default > 0), - "graphics.elements.controls.radio_button: default must be nil or a number > 0") + "controls.radio_button: default must be nil or a number > 0") assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), - "graphics.elements.controls.radio_button: min_width must be nil or a number > 0") + "controls.radio_button: min_width must be nil or a number > 0") -- determine widths local max_width = 1 @@ -59,7 +59,7 @@ local function radio_button(args) e.value = args.default or 1 -- show the button state - local function draw() + function e.redraw() for i = 1, #args.options do local opt = args.options[i] ---@type string @@ -97,7 +97,7 @@ local function radio_button(args) if args.options[event.current.y] ~= nil then e.value = event.current.y focused_opt = e.value - draw() + e.redraw() if type(args.callback) == "function" then args.callback(e.value) end end end @@ -109,17 +109,17 @@ local function radio_button(args) if event.type == KEY_CLICK.DOWN or event.type == KEY_CLICK.HELD then if event.type == KEY_CLICK.DOWN and (event.key == keys.space or event.key == keys.enter or event.key == keys.numPadEnter) then e.value = focused_opt - draw() + e.redraw() if type(args.callback) == "function" then args.callback(e.value) end elseif event.key == keys.down then if focused_opt < #args.options then focused_opt = focused_opt + 1 - draw() + e.redraw() end elseif event.key == keys.up then if focused_opt > 1 then focused_opt = focused_opt - 1 - draw() + e.redraw() end end end @@ -130,20 +130,20 @@ local function radio_button(args) function e.set_value(val) if val > 0 and val <= #args.options then e.value = val - draw() + e.redraw() end end -- handle focus - e.on_focused = draw - e.on_unfocused = draw + e.on_focused = e.redraw + e.on_unfocused = e.redraw -- handle enable - e.on_enabled = draw - e.on_disabled = draw + e.on_enabled = e.redraw + e.on_disabled = e.redraw -- initial draw - draw() + e.redraw() return e.complete() end diff --git a/graphics/elements/controls/sidebar.lua b/graphics/elements/controls/sidebar.lua index 646c724..3ac6bb1 100644 --- a/graphics/elements/controls/sidebar.lua +++ b/graphics/elements/controls/sidebar.lua @@ -1,6 +1,7 @@ -- Sidebar Graphics Element local tcd = require("scada-common.tcd") +local util = require("scada-common.util") local core = require("graphics.core") local element = require("graphics.element") @@ -26,11 +27,10 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK ---@param args sidebar_args ---@return graphics_element element, element_id id local function sidebar(args) - assert(type(args.tabs) == "table", "graphics.elements.controls.sidebar: tabs is a required field") - assert(#args.tabs > 0, "graphics.elements.controls.sidebar: at least one tab is required") - assert(type(args.callback) == "function", "graphics.elements.controls.sidebar: callback is a required field") + assert(type(args.tabs) == "table", "controls.sidebar: tabs is a required field") + assert(#args.tabs > 0, "controls.sidebar: at least one tab is required") + assert(type(args.callback) == "function", "controls.sidebar: callback is a required field") - -- always 3 wide args.width = 3 -- create new graphics element base object @@ -41,10 +41,14 @@ local function sidebar(args) -- default to 1st tab e.value = 1 + local was_pressed = false + -- show the button state - ---@param pressed boolean if the currently selected tab should appear as actively pressed + ---@param pressed? boolean if the currently selected tab should appear as actively pressed ---@param pressed_idx? integer optional index to show as held (that is not yet selected) local function draw(pressed, pressed_idx) + pressed = util.trinary(pressed == nil, was_pressed, pressed) + was_pressed = pressed pressed_idx = pressed_idx or e.value for i = 1, #args.tabs do @@ -65,12 +69,8 @@ local function sidebar(args) e.w_write(" ") e.w_set_cur(1, y + 1) if e.value == i then - -- show as selected e.w_write(" " .. tab.char .. "\x10") - else - -- show as unselected - e.w_write(" " .. tab.char .. " ") - end + else e.w_write(" " .. tab.char .. " ") end e.w_set_cur(1, y + 2) e.w_write(" ") end @@ -113,8 +113,10 @@ local function sidebar(args) draw(false) end - -- initial draw - draw(false) + -- element redraw + e.redraw = draw + + e.redraw() return e.complete() end diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index f7d0ee5..7615ecf 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -29,8 +29,8 @@ local function spinbox(args) local wn_prec = args.whole_num_precision local fr_prec = args.fractional_precision - assert(util.is_int(wn_prec), "graphics.element.controls.spinbox_numeric: whole number precision must be an integer") - assert(util.is_int(fr_prec), "graphics.element.controls.spinbox_numeric: fractional precision must be an integer") + assert(util.is_int(wn_prec), "controls.spinbox_numeric: whole number precision must be an integer") + assert(util.is_int(fr_prec), "controls.spinbox_numeric: fractional precision must be an integer") local fmt, fmt_init ---@type string, string @@ -44,7 +44,7 @@ local function spinbox(args) local dec_point_x = args.whole_num_precision + 1 - assert(type(args.arrow_fg_bg) == "table", "graphics.element.spinbox_numeric: arrow_fg_bg is a required field") + assert(type(args.arrow_fg_bg) == "table", "controls.spinbox_numeric: arrow_fg_bg is a required field") -- determine widths args.width = wn_prec + fr_prec + util.trinary(fr_prec > 0, 1, 0) @@ -72,8 +72,6 @@ local function spinbox(args) end end - draw_arrows(args.arrow_fg_bg.fgd) - -- populate digits from current value local function set_digits() local initial_str = util.sprintf(fmt_init, e.value) @@ -125,9 +123,6 @@ local function spinbox(args) e.w_write(util.sprintf(fmt, e.value)) end - -- init with the default value - show_num() - -- handle mouse interaction ---@param event mouse_interaction mouse event function e.handle_mouse(event) @@ -138,10 +133,8 @@ local function spinbox(args) local idx = util.trinary(event.current.x > dec_point_x, event.current.x - 1, event.current.x) if digits[idx] ~= nil then if event.current.y == 1 then - -- increment digits[idx] = digits[idx] + 1 elseif event.current.y == 3 then - -- decrement digits[idx] = digits[idx] - 1 end @@ -176,18 +169,19 @@ local function spinbox(args) end -- enable this input - function e.on_enabled() - draw_arrows(args.arrow_fg_bg.fgd) - end + function e.on_enabled() draw_arrows(args.arrow_fg_bg.fgd) end -- disable this input - function e.on_disabled() - draw_arrows(args.arrow_disable or colors.lightGray) + function e.on_disabled() draw_arrows(args.arrow_disable or colors.lightGray) end + + -- element redraw + function e.redraw() + show_num() + draw_arrows(util.trinary(e.enabled, args.arrow_fg_bg.fgd, args.arrow_disable or colors.lightGray)) end - -- default to zero, init digits table - e.value = 0 - set_digits() + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index 63f6c9b..d8da813 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -21,15 +21,13 @@ local element = require("graphics.element") ---@param args switch_button_args ---@return graphics_element element, element_id id local function switch_button(args) - assert(type(args.text) == "string", "graphics.elements.controls.switch_button: text is a required field") - assert(type(args.callback) == "function", "graphics.elements.controls.switch_button: callback is a required field") - assert(type(args.active_fg_bg) == "table", "graphics.elements.controls.switch_button: active_fg_bg is a required field") - assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), - "graphics.elements.controls.switch_button: min_width must be nil or a number > 0") + assert(type(args.text) == "string", "controls.switch_button: text is a required field") + assert(type(args.callback) == "function", "controls.switch_button: callback is a required field") + assert(type(args.active_fg_bg) == "table", "controls.switch_button: active_fg_bg is a required field") + assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), "controls.switch_button: min_width must be nil or a number > 0") local text_width = string.len(args.text) - -- single line height, calculate width args.height = 1 args.min_width = args.min_width or 0 args.width = math.max(text_width, args.min_width) @@ -37,44 +35,32 @@ local function switch_button(args) -- create new graphics element base object local e = element.new(args) - -- button state (convert nil to false if missing) e.value = args.default or false local h_pad = math.floor((e.frame.w - text_width) / 2) + 1 local v_pad = math.floor(e.frame.h / 2) + 1 -- show the button state - local function draw_state() + function e.redraw() if e.value then - -- show as pressed e.w_set_fgd(args.active_fg_bg.fgd) e.w_set_bkg(args.active_fg_bg.bkg) else - -- show as unpressed e.w_set_fgd(e.fg_bg.fgd) e.w_set_bkg(e.fg_bg.bkg) end - -- clear to redraw background e.window.clear() - - -- write the button text e.w_set_cur(h_pad, v_pad) e.w_write(args.text) end - -- initial draw - draw_state() - -- handle mouse interaction ---@param event mouse_interaction mouse event function e.handle_mouse(event) if e.enabled and core.events.was_clicked(event.type) then - -- toggle state e.value = not e.value - draw_state() - - -- call the touch callback with state + e.redraw() args.callback(e.value) end end @@ -82,11 +68,13 @@ local function switch_button(args) -- set the value ---@param val boolean new value function e.set_value(val) - -- set state e.value = val - draw_state() + e.redraw() end + -- initial draw + e.redraw() + return e.complete() end diff --git a/graphics/elements/controls/tabbar.lua b/graphics/elements/controls/tabbar.lua index e1a99ca..ad2c0d7 100644 --- a/graphics/elements/controls/tabbar.lua +++ b/graphics/elements/controls/tabbar.lua @@ -27,13 +27,11 @@ local element = require("graphics.element") ---@param args tabbar_args ---@return graphics_element element, element_id id local function tabbar(args) - assert(type(args.tabs) == "table", "graphics.elements.controls.tabbar: tabs is a required field") - assert(#args.tabs > 0, "graphics.elements.controls.tabbar: at least one tab is required") - assert(type(args.callback) == "function", "graphics.elements.controls.tabbar: callback is a required field") - assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), - "graphics.elements.controls.tabbar: min_width must be nil or a number > 0") + assert(type(args.tabs) == "table", "controls.tabbar: tabs is a required field") + assert(#args.tabs > 0, "controls.tabbar: at least one tab is required") + assert(type(args.callback) == "function", "controls.tabbar: callback is a required field") + assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), "controls.tabbar: min_width must be nil or a number > 0") - -- always 1 tall args.height = 1 -- determine widths @@ -67,7 +65,7 @@ local function tabbar(args) end -- show the tab state - local function draw() + function e.redraw() for i = 1, #args.tabs do local tab = args.tabs[i] ---@type tabbar_tab @@ -109,7 +107,7 @@ local function tabbar(args) -- tap always has identical coordinates, so this always passes for taps if tab_ini == tab_cur and tab_cur ~= nil then e.value = tab_cur - draw() + e.redraw() args.callback(e.value) end end @@ -119,11 +117,11 @@ local function tabbar(args) ---@param val integer new value function e.set_value(val) e.value = val - draw() + e.redraw() end -- initial draw - draw() + e.redraw() return e.complete() end diff --git a/graphics/elements/form/number_field.lua b/graphics/elements/form/number_field.lua index 3e2d350..f444073 100644 --- a/graphics/elements/form/number_field.lua +++ b/graphics/elements/form/number_field.lua @@ -42,30 +42,6 @@ local function number_field(args) -- make an interactive field manager local ifield = core.new_ifield(e, args.max_digits, args.fg_bg, args.dis_fg_bg) - - -- draw input - local function show() - if e.enabled then - e.w_set_bkg(args.fg_bg.bkg) - e.w_set_fgd(args.fg_bg.fgd) - else - e.w_set_bkg(args.dis_fg_bg.bkg) - e.w_set_fgd(args.dis_fg_bg.fgd) - end - - -- clear and print - e.w_set_cur(1, 1) - e.w_write(string.rep(" ", e.frame.w)) - e.w_set_cur(1, 1) - e.w_write(e.value) - - -- show cursor if focused - if e.is_focused() and e.enabled then - e.w_set_fgd(colors.lightGray) - e.w_write("_") - end - end - -- handle mouse interaction ---@param event mouse_interaction mouse event function e.handle_mouse(event) @@ -117,9 +93,7 @@ local function number_field(args) -- set the value (must be a number) ---@param val number number to show function e.set_value(val) - if tonumber(val) then - ifield.set_value("" .. tonumber(val)) - end + if tonumber(val) then ifield.set_value("" .. tonumber(val)) end end -- set minimum input value @@ -140,9 +114,6 @@ local function number_field(args) end end - -- handle focused - e.on_focused = show - -- handle unfocused function e.on_unfocused() local val = tonumber(e.value) @@ -162,12 +133,14 @@ local function number_field(args) ifield.show() end - -- handle enable + -- handle focus (not unfocus), enable, and redraw with show() + e.on_focused = ifield.show e.on_enabled = ifield.show e.on_disabled = ifield.show + e.redraw = ifield.show -- initial draw - ifield.show() + e.redraw() return e.complete() end diff --git a/graphics/elements/form/text_field.lua b/graphics/elements/form/text_field.lua index 3211676..5fc1062 100644 --- a/graphics/elements/form/text_field.lua +++ b/graphics/elements/form/text_field.lua @@ -88,16 +88,15 @@ local function text_field(args) ifield.set_value(text) end - -- handle focus + -- handle focus, enable, and redraw with show() e.on_focused = ifield.show e.on_unfocused = ifield.show - - -- handle enable e.on_enabled = ifield.show e.on_disabled = ifield.show + e.redraw = ifield.show -- initial draw - ifield.show() + e.redraw() local elem, id = e.complete() return elem, id, ifield.censor diff --git a/graphics/elements/indicators/alight.lua b/graphics/elements/indicators/alight.lua index 659b216..8ed5337 100644 --- a/graphics/elements/indicators/alight.lua +++ b/graphics/elements/indicators/alight.lua @@ -25,13 +25,13 @@ local flasher = require("graphics.flasher") ---@param args alarm_indicator_light ---@return graphics_element element, element_id id local function alarm_indicator_light(args) - assert(type(args.label) == "string", "graphics.elements.indicators.alight: label is a required field") - assert(type(args.c1) == "number", "graphics.elements.indicators.alight: c1 is a required field") - assert(type(args.c2) == "number", "graphics.elements.indicators.alight: c2 is a required field") - assert(type(args.c3) == "number", "graphics.elements.indicators.alight: c3 is a required field") + assert(type(args.label) == "string", "indicators.alight: label is a required field") + assert(type(args.c1) == "number", "indicators.alight: c1 is a required field") + assert(type(args.c2) == "number", "indicators.alight: c2 is a required field") + assert(type(args.c3) == "number", "indicators.alight: c3 is a required field") if args.flash then - assert(util.is_int(args.period), "graphics.elements.indicators.alight: period is a required field if flash is enabled") + assert(util.is_int(args.period), "indicators.alight: period is a required field if flash is enabled") end -- single line @@ -51,6 +51,8 @@ local function alarm_indicator_light(args) -- create new graphics element base object local e = element.new(args) + e.value = 1 + -- called by flasher when enabled local function flash_callback() e.w_set_cur(1, 1) @@ -105,9 +107,14 @@ local function alarm_indicator_light(args) ---@param val integer indicator state function e.set_value(val) e.on_update(val) end - -- write label and initial indicator light - e.on_update(1) - e.w_write(args.label) + -- draw label and indicator light + function e.redraw() + e.on_update(e.value) + e.w_write(args.label) + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/indicators/coremap.lua b/graphics/elements/indicators/coremap.lua index 4140c5f..12319f6 100644 --- a/graphics/elements/indicators/coremap.lua +++ b/graphics/elements/indicators/coremap.lua @@ -18,8 +18,8 @@ local element = require("graphics.element") ---@param args core_map_args ---@return graphics_element element, element_id id local function core_map(args) - assert(util.is_int(args.reactor_l), "graphics.elements.indicators.coremap: reactor_l is a required field") - assert(util.is_int(args.reactor_w), "graphics.elements.indicators.coremap: reactor_w is a required field") + assert(util.is_int(args.reactor_l), "indicators.coremap: reactor_l is a required field") + assert(util.is_int(args.reactor_w), "indicators.coremap: reactor_w is a required field") -- require max dimensions args.width = 18 @@ -31,6 +31,8 @@ local function core_map(args) -- create new graphics element base object local e = element.new(args) + e.value = 0 + local alternator = true local core_l = args.reactor_l - 2 @@ -157,11 +159,14 @@ local function core_map(args) e.on_update(e.value) end - -- initial (one-time except for resize()) frame draw - draw_frame() + -- redraw both frame and core + function e.redraw() + draw_frame() + draw_core(e.value) + end -- initial draw - e.on_update(0) + e.redraw() return e.complete() end diff --git a/graphics/elements/indicators/data.lua b/graphics/elements/indicators/data.lua index 9d7f435..6097047 100644 --- a/graphics/elements/indicators/data.lua +++ b/graphics/elements/indicators/data.lua @@ -24,25 +24,17 @@ local element = require("graphics.element") ---@param args data_indicator_args ---@return graphics_element element, element_id id 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") + assert(type(args.label) == "string", "indicators.data: label is a required field") + assert(type(args.format) == "string", "indicators.data: format is a required field") + assert(args.value ~= nil, "indicators.data: value is a required field") + assert(util.is_int(args.width), "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.w_set_fgd(args.lu_colors.color_a) - end - - -- write label - e.w_set_cur(1, 1) - e.w_write(args.label) + e.value = args.value local value_color = e.fg_bg.fgd local label_len = string.len(args.label) @@ -93,8 +85,17 @@ local function data(args) e.on_update(e.value) end - -- initial value draw - e.on_update(args.value) + -- element redraw + function e.redraw() + if args.lu_colors ~= nil then e.w_set_fgd(args.lu_colors.color_a) end + e.w_set_cur(1, 1) + e.w_write(args.label) + + e.on_update(e.value) + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/indicators/hbar.lua b/graphics/elements/indicators/hbar.lua index 32ab3a0..ef68dc6 100644 --- a/graphics/elements/indicators/hbar.lua +++ b/graphics/elements/indicators/hbar.lua @@ -28,10 +28,12 @@ local function hbar(args) -- create new graphics element base object local e = element.new(args) + e.value = 0.0 + -- 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.indicators.hbar: too small for bar") + assert(bar_width > 0, "indicators.hbar: too small for bar") -- determine bar colors local bar_bkg = e.fg_bg.blit_bkg @@ -105,20 +107,21 @@ local function hbar(args) function e.recolor(bar_fg_bg) bar_bkg = bar_fg_bg.blit_bkg bar_fgd = bar_fg_bg.blit_fgd - - -- re-draw - last_num_bars = 0 - if type(e.value) == "number" then - e.on_update(e.value) - end + e.redraw() end -- set the percentage value ---@param val number 0.0 to 1.0 function e.set_value(val) e.on_update(val) end - -- initialize to 0 - e.on_update(0) + -- element redraw + function e.redraw() + last_num_bars = -1 + e.on_update(e.value) + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/indicators/icon.lua b/graphics/elements/indicators/icon.lua index a3561de..70d7d2c 100644 --- a/graphics/elements/indicators/icon.lua +++ b/graphics/elements/indicators/icon.lua @@ -23,18 +23,17 @@ local element = require("graphics.element") ---@param args icon_indicator_args ---@return graphics_element element, element_id id 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") + assert(type(args.label) == "string", "indicators.icon: label is a required field") + assert(type(args.states) == "table", "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) + e.value = args.value or 1 + -- state blit strings local state_blit_cmds = {} for i = 1, #args.states do @@ -47,10 +46,6 @@ local function icon(args) }) end - -- write label and initial indicator light - e.w_set_cur(5, 1) - e.w_write(args.label) - -- on state change ---@param new_state integer indicator state function e.on_update(new_state) @@ -64,8 +59,16 @@ local function icon(args) ---@param val integer indicator state function e.set_value(val) e.on_update(val) end - -- initial icon draw - e.on_update(args.value or 1) + -- element redraw + function e.redraw() + e.w_set_cur(5, 1) + e.w_write(args.label) + + e.on_update(e.value) + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/indicators/led.lua b/graphics/elements/indicators/led.lua index 53297ec..72e1e93 100644 --- a/graphics/elements/indicators/led.lua +++ b/graphics/elements/indicators/led.lua @@ -23,25 +23,23 @@ local flasher = require("graphics.flasher") ---@param args indicator_led_args ---@return graphics_element element, element_id id local function indicator_led(args) - assert(type(args.label) == "string", "graphics.elements.indicators.led: label is a required field") - assert(type(args.colors) == "table", "graphics.elements.indicators.led: colors is a required field") + assert(type(args.label) == "string", "indicators.led: label is a required field") + assert(type(args.colors) == "table", "indicators.led: colors is a required field") if args.flash then - assert(util.is_int(args.period), "graphics.elements.indicators.led: period is a required field if flash is enabled") + assert(util.is_int(args.period), "indicators.led: period is a required field if flash is enabled") end - -- single line args.height = 1 - - -- determine width args.width = math.max(args.min_label_width or 0, string.len(args.label)) + 2 - -- flasher state local flash_on = true -- create new graphics element base object local e = element.new(args) + e.value = false + -- called by flasher when enabled local function flash_callback() e.w_set_cur(1, 1) @@ -88,13 +86,18 @@ local function indicator_led(args) ---@param val boolean indicator state function e.set_value(val) e.on_update(val) end - -- write label and initial indicator light - e.on_update(false) - if string.len(args.label) > 0 then - e.w_set_cur(3, 1) - e.w_write(args.label) + -- draw label and indicator light + function e.redraw() + e.on_update(e.value) + if string.len(args.label) > 0 then + e.w_set_cur(3, 1) + e.w_write(args.label) + end end + -- initial draw + e.redraw() + return e.complete() end diff --git a/graphics/elements/indicators/ledpair.lua b/graphics/elements/indicators/ledpair.lua index fed141f..3ee99fb 100644 --- a/graphics/elements/indicators/ledpair.lua +++ b/graphics/elements/indicators/ledpair.lua @@ -25,25 +25,20 @@ local flasher = require("graphics.flasher") ---@param args indicator_led_pair_args ---@return graphics_element element, element_id id local function indicator_led_pair(args) - assert(type(args.label) == "string", "graphics.elements.indicators.ledpair: label is a required field") - assert(type(args.off) == "number", "graphics.elements.indicators.ledpair: off is a required field") - assert(type(args.c1) == "number", "graphics.elements.indicators.ledpair: c1 is a required field") - assert(type(args.c2) == "number", "graphics.elements.indicators.ledpair: c2 is a required field") + assert(type(args.label) == "string", "indicators.ledpair: label is a required field") + assert(type(args.off) == "number", "indicators.ledpair: off is a required field") + assert(type(args.c1) == "number", "indicators.ledpair: c1 is a required field") + assert(type(args.c2) == "number", "indicators.ledpair: c2 is a required field") if args.flash then - assert(util.is_int(args.period), "graphics.elements.indicators.ledpair: period is a required field if flash is enabled") + assert(util.is_int(args.period), "indicators.ledpair: period is a required field if flash is enabled") end - -- single line args.height = 1 - - -- determine width args.width = math.max(args.min_label_width or 0, string.len(args.label)) + 2 - -- flasher state local flash_on = true - -- blit translations local co = colors.toBlit(args.off) local c1 = colors.toBlit(args.c1) local c2 = colors.toBlit(args.c2) @@ -51,7 +46,6 @@ local function indicator_led_pair(args) -- create new graphics element base object local e = element.new(args) - -- init value for initial check in on_update e.value = 1 -- called by flasher when enabled @@ -86,7 +80,6 @@ local function indicator_led_pair(args) elseif new_state <= 1 then flash_on = false flasher.stop(flash_callback) - e.w_blit("\x8c", co, e.fg_bg.blit_bkg) end elseif new_state == 2 then @@ -102,13 +95,18 @@ local function indicator_led_pair(args) ---@param val integer indicator state function e.set_value(val) e.on_update(val) end - -- write label and initial indicator light - e.on_update(1) - if string.len(args.label) > 0 then - e.w_set_cur(3, 1) - e.w_write(args.label) + -- draw label and indicator light + function e.redraw() + e.on_update(e.value) + if string.len(args.label) > 0 then + e.w_set_cur(3, 1) + e.w_write(args.label) + end end + -- initial draw + e.redraw() + return e.complete() end diff --git a/graphics/elements/indicators/ledrgb.lua b/graphics/elements/indicators/ledrgb.lua index 79064b4..f2320c6 100644 --- a/graphics/elements/indicators/ledrgb.lua +++ b/graphics/elements/indicators/ledrgb.lua @@ -18,19 +18,15 @@ local element = require("graphics.element") ---@param args indicator_led_rgb_args ---@return graphics_element element, element_id id local function indicator_led_rgb(args) - assert(type(args.label) == "string", "graphics.elements.indicators.ledrgb: label is a required field") - assert(type(args.colors) == "table", "graphics.elements.indicators.ledrgb: colors is a required field") + assert(type(args.label) == "string", "indicators.ledrgb: label is a required field") + assert(type(args.colors) == "table", "indicators.ledrgb: colors is a required field") - -- single line args.height = 1 - - -- determine width args.width = math.max(args.min_label_width or 0, string.len(args.label)) + 2 -- create new graphics element base object local e = element.new(args) - -- init value for initial check in on_update e.value = 1 -- on state change @@ -47,13 +43,18 @@ local function indicator_led_rgb(args) ---@param val integer indicator state function e.set_value(val) e.on_update(val) end - -- write label and initial indicator light - e.on_update(1) - if string.len(args.label) > 0 then - e.w_set_cur(3, 1) - e.w_write(args.label) + -- draw label and indicator light + function e.redraw() + e.on_update(e.value) + if string.len(args.label) > 0 then + e.w_set_cur(3, 1) + e.w_write(args.label) + end end + -- initial draw + e.redraw() + return e.complete() end diff --git a/graphics/elements/indicators/light.lua b/graphics/elements/indicators/light.lua index cea5916..742d6c1 100644 --- a/graphics/elements/indicators/light.lua +++ b/graphics/elements/indicators/light.lua @@ -23,25 +23,23 @@ local flasher = require("graphics.flasher") ---@param args indicator_light_args ---@return graphics_element element, element_id id 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") + assert(type(args.label) == "string", "indicators.light: label is a required field") + assert(type(args.colors) == "table", "indicators.light: colors is a required field") if args.flash then - assert(util.is_int(args.period), "graphics.elements.indicators.light: period is a required field if flash is enabled") + assert(util.is_int(args.period), "indicators.light: period is a required field if flash is enabled") end - -- single line args.height = 1 - - -- determine width args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 - -- flasher state local flash_on = true -- create new graphics element base object local e = element.new(args) + e.value = false + -- called by flasher when enabled local function flash_callback() e.w_set_cur(1, 1) @@ -88,10 +86,15 @@ local function indicator_light(args) ---@param val boolean indicator state function e.set_value(val) e.on_update(val) end - -- write label and initial indicator light - e.on_update(false) - e.w_set_cur(3, 1) - e.w_write(args.label) + -- draw label and indicator light + function e.redraw() + e.on_update(false) + e.w_set_cur(3, 1) + e.w_write(args.label) + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/indicators/power.lua b/graphics/elements/indicators/power.lua index e694a57..eaa1f68 100644 --- a/graphics/elements/indicators/power.lua +++ b/graphics/elements/indicators/power.lua @@ -9,7 +9,7 @@ local element = require("graphics.element") ---@field format string power format override (lua string format) ---@field rate boolean? whether to append /t to the end (power per tick) ---@field lu_colors? cpair label foreground color (a), unit foreground color (b) ----@field value any default value +---@field value number default value ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -23,26 +23,17 @@ local element = require("graphics.element") ---@param args power_indicator_args ---@return graphics_element element, element_id id local function power(args) - assert(args.value ~= nil, "graphics.elements.indicators.power: value is a required field") - assert(util.is_int(args.width), "graphics.elements.indicators.power: width is a required field") + assert(type(args.value) == "number", "indicators.power: value is a required number field") + assert(util.is_int(args.width), "indicators.power: 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.w_set_fgd(args.lu_colors.color_a) - end + e.value = args.value - -- write label - e.w_set_cur(1, 1) - e.w_write(args.label) - - local data_start = string.len(args.label) + 2 - if string.len(args.label) == 0 then data_start = 1 end + local data_start = 0 -- on state change ---@param value any new value @@ -77,8 +68,20 @@ local function power(args) ---@param val any new value function e.set_value(val) e.on_update(val) end - -- initial value draw - e.on_update(args.value) + -- element redraw + function e.redraw() + if args.lu_colors ~= nil then e.w_set_fgd(args.lu_colors.color_a) end + e.w_set_cur(1, 1) + e.w_write(args.label) + + data_start = string.len(args.label) + 2 + if string.len(args.label) == 0 then data_start = 1 end + + e.on_update(e.value) + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/indicators/rad.lua b/graphics/elements/indicators/rad.lua index c110f22..2ae9b6f 100644 --- a/graphics/elements/indicators/rad.lua +++ b/graphics/elements/indicators/rad.lua @@ -10,7 +10,7 @@ local element = require("graphics.element") ---@field format string data format (lua string format) ---@field commas? boolean whether to use commas if a number is given (default to false) ---@field lu_colors? cpair label foreground color (a), unit foreground color (b) ----@field value any default value +---@field value number default value ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -24,24 +24,17 @@ local element = require("graphics.element") ---@param args rad_indicator_args ---@return graphics_element element, element_id id local function rad(args) - assert(type(args.label) == "string", "graphics.elements.indicators.rad: label is a required field") - assert(type(args.format) == "string", "graphics.elements.indicators.rad: format is a required field") - assert(util.is_int(args.width), "graphics.elements.indicators.rad: width is a required field") + assert(type(args.value) ~= "number", "indicators.rad: value is a required number field") + assert(type(args.label) == "string", "indicators.rad: label is a required field") + assert(type(args.format) == "string", "indicators.rad: format is a required field") + assert(util.is_int(args.width), "indicators.rad: 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.w_set_fgd(args.lu_colors.color_a) - end - - -- write label - e.w_set_cur(1, 1) - e.w_write(args.label) + e.value = types.new_zero_radiation_reading() local label_len = string.len(args.label) local data_start = 1 @@ -82,8 +75,17 @@ local function rad(args) ---@param val any new value function e.set_value(val) e.on_update(val) end - -- initial value draw - e.on_update(types.new_zero_radiation_reading()) + -- element redraw + function e.redraw() + if args.lu_colors ~= nil then e.w_set_fgd(args.lu_colors.color_a) end + e.w_set_cur(1, 1) + e.w_write(args.label) + + e.on_update(e.value) + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/indicators/state.lua b/graphics/elements/indicators/state.lua index dfd6e0b..2096c6b 100644 --- a/graphics/elements/indicators/state.lua +++ b/graphics/elements/indicators/state.lua @@ -25,16 +25,14 @@ local element = require("graphics.element") ---@param args state_indicator_args ---@return graphics_element element, element_id id local function state_indicator(args) - assert(type(args.states) == "table", "graphics.elements.indicators.state: states is a required field") + assert(type(args.states) == "table", "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") + assert(args.height % 2 == 1, "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 @@ -42,7 +40,6 @@ local function state_indicator(args) 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 @@ -59,13 +56,20 @@ local function state_indicator(args) -- create new graphics element base object local e = element.new(args) + e.value = args.value or 1 + + -- element redraw + function e.redraw() + local blit_cmd = state_blit_cmds[e.value] + e.w_set_cur(1, 1) + e.w_blit(blit_cmd.text, blit_cmd.fgd, blit_cmd.bkg) + end + -- on state change ---@param new_state integer indicator state function e.on_update(new_state) - local blit_cmd = state_blit_cmds[new_state] e.value = new_state - e.w_set_cur(1, 1) - e.w_blit(blit_cmd.text, blit_cmd.fgd, blit_cmd.bkg) + e.redraw() end -- set indicator state @@ -73,7 +77,7 @@ local function state_indicator(args) function e.set_value(val) e.on_update(val) end -- initial draw - e.on_update(args.value or 1) + e.redraw() return e.complete() end diff --git a/graphics/elements/indicators/trilight.lua b/graphics/elements/indicators/trilight.lua index 9319cea..f90b21b 100644 --- a/graphics/elements/indicators/trilight.lua +++ b/graphics/elements/indicators/trilight.lua @@ -25,35 +25,29 @@ local flasher = require("graphics.flasher") ---@param args tristate_indicator_light_args ---@return graphics_element element, element_id id local function tristate_indicator_light(args) - assert(type(args.label) == "string", "graphics.elements.indicators.trilight: label is a required field") - assert(type(args.c1) == "number", "graphics.elements.indicators.trilight: c1 is a required field") - assert(type(args.c2) == "number", "graphics.elements.indicators.trilight: c2 is a required field") - assert(type(args.c3) == "number", "graphics.elements.indicators.trilight: c3 is a required field") + assert(type(args.label) == "string", "indicators.trilight: label is a required field") + assert(type(args.c1) == "number", "indicators.trilight: c1 is a required field") + assert(type(args.c2) == "number", "indicators.trilight: c2 is a required field") + assert(type(args.c3) == "number", "indicators.trilight: c3 is a required field") if args.flash then - assert(util.is_int(args.period), "graphics.elements.indicators.trilight: period is a required field if flash is enabled") + assert(util.is_int(args.period), "indicators.trilight: period is a required field if flash is enabled") end - -- single line args.height = 1 - - -- determine width args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 - -- flasher state - local flash_on = true - - -- blit translations - local c1 = colors.toBlit(args.c1) - local c2 = colors.toBlit(args.c2) - local c3 = colors.toBlit(args.c3) - -- create new graphics element base object local e = element.new(args) - -- init value for initial check in on_update e.value = 1 + local flash_on = true + + local c1 = colors.toBlit(args.c1) + local c2 = colors.toBlit(args.c2) + local c3 = colors.toBlit(args.c3) + -- called by flasher when enabled local function flash_callback() e.w_set_cur(1, 1) @@ -102,9 +96,14 @@ local function tristate_indicator_light(args) ---@param val integer indicator state function e.set_value(val) e.on_update(val) end - -- write label and initial indicator light - e.on_update(1) - e.w_write(args.label) + -- draw light and label + function e.redraw() + e.on_update(1) + e.w_write(args.label) + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/indicators/vbar.lua b/graphics/elements/indicators/vbar.lua index 05aecf3..afe56fc 100644 --- a/graphics/elements/indicators/vbar.lua +++ b/graphics/elements/indicators/vbar.lua @@ -20,13 +20,13 @@ local element = require("graphics.element") ---@param args vbar_args ---@return graphics_element element, element_id id local function vbar(args) - -- properties/state - local last_num_bars = -1 - -- create new graphics element base object local e = element.new(args) - -- blit strings + e.value = 0.0 + + local last_num_bars = -1 + local fgd = string.rep(e.fg_bg.blit_fgd, e.frame.w) local bkg = string.rep(e.fg_bg.blit_bkg, e.frame.w) local spaces = util.spaces(e.frame.w) @@ -52,10 +52,7 @@ local function vbar(args) if num_bars ~= last_num_bars then last_num_bars = num_bars - -- start bottom up local y = e.frame.h - - -- start at base of vertical bar e.w_set_cur(1, y) -- fill percentage @@ -83,22 +80,26 @@ local function vbar(args) end end + -- set the percentage value + ---@param val number 0.0 to 1.0 + function e.set_value(val) e.on_update(val) end + + -- element redraw + function e.redraw() + last_num_bars = -1 + e.on_update(e.value) + end + -- change bar color ---@param fg_bg cpair new bar colors function e.recolor(fg_bg) fgd = string.rep(fg_bg.blit_fgd, e.frame.w) bkg = string.rep(fg_bg.blit_bkg, e.frame.w) - - -- re-draw - last_num_bars = 0 - if type(e.value) == "number" then - e.on_update(e.value) - end + e.redraw() end - -- set the percentage value - ---@param val number 0.0 to 1.0 - function e.set_value(val) e.on_update(val) end + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/listbox.lua b/graphics/elements/listbox.lua index 0268951..a35330a 100644 --- a/graphics/elements/listbox.lua +++ b/graphics/elements/listbox.lua @@ -274,8 +274,20 @@ local function listbox(args) end end - draw_arrows(0) - draw_bar() + -- element redraw + function e.redraw() + draw_arrows(0) + draw_bar() + + -- redraw all children + for i = 1, #list do + local item = list[i] ---@type listbox_item + item.e.redraw() + end + end + + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/multipane.lua b/graphics/elements/multipane.lua index fff8fb3..5cfd217 100644 --- a/graphics/elements/multipane.lua +++ b/graphics/elements/multipane.lua @@ -19,23 +19,30 @@ local element = require("graphics.element") ---@param args multipane_args ---@return graphics_element element, element_id id local function multipane(args) - assert(type(args.panes) == "table", "graphics.elements.multipane: panes is a required field") + assert(type(args.panes) == "table", "multipane: panes is a required field") -- create new graphics element base object local e = element.new(args) + e.value = 1 + + -- show the selected pane + function e.redraw() + for i = 1, #args.panes do args.panes[i].hide() end + args.panes[e.value].show() + end + -- select which pane is shown ---@param value integer pane to show function e.set_value(value) if (e.value ~= value) and (value > 0) and (value <= #args.panes) then e.value = value - - for i = 1, #args.panes do args.panes[i].hide() end - args.panes[value].show() + e.redraw() end end - e.set_value(1) + -- initial draw + e.redraw() return e.complete() end diff --git a/graphics/elements/pipenet.lua b/graphics/elements/pipenet.lua index 65ccfb7..ce15c75 100644 --- a/graphics/elements/pipenet.lua +++ b/graphics/elements/pipenet.lua @@ -24,12 +24,11 @@ local element = require("graphics.element") ---@param args pipenet_args ---@return graphics_element element, element_id id local function pipenet(args) - assert(type(args.pipes) == "table", "graphics.elements.indicators.pipenet: pipes is a required field") + assert(type(args.pipes) == "table", "pipenet: pipes is a required field") args.width = 0 args.height = 0 - -- determine width/height for i = 1, #args.pipes do local pipe = args.pipes[i] ---@type pipe @@ -57,8 +56,8 @@ local function pipenet(args) if any_thin then break end end - if not any_thin then - -- draw all pipes + -- draw all pipes by drawing out lines + local function vector_draw() for p = 1, #args.pipes do local pipe = args.pipes[p] ---@type pipe @@ -161,11 +160,109 @@ local function pipenet(args) end end end - else - -- build map if using thin pipes, easist way to check adjacent blocks (cannot 'cheat' like with standard width) + end + + -- draw a particular map cell + ---@param map table 2D cell map + ---@param x integer x coord + ---@param y integer y coord + local function draw_map_cell(map, x, y) + local entry = map[x][y] ---@type _pipe_map_entry already confirmed not false + local char + local invert = false + + local function check(cx, cy) + return (map[cx] ~= nil) and (map[cx][cy] ~= nil) and (map[cx][cy] ~= false) and (map[cx][cy].fg == entry.fg) + end + + if entry.thin then + if check(x - 1, y) then -- if left + if check(x, y - 1) then -- if above + if check(x + 1, y) then -- if right + if check(x, y + 1) then -- if below + char = util.trinary(entry.atr, "\x91", "\x9d") + invert = entry.atr + else -- not below + char = util.trinary(entry.atr, "\x8e", "\x8d") + end + else -- not right + if check(x, y + 1) then -- if below + char = util.trinary(entry.atr, "\x91", "\x95") + invert = entry.atr + else -- not below + char = util.trinary(entry.atr, "\x8e", "\x85") + end + end + elseif check(x, y + 1) then-- not above, if below + if check(x + 1, y) then -- if right + char = util.trinary(entry.atr, "\x93", "\x9c") + invert = entry.atr + else -- not right + char = util.trinary(entry.atr, "\x93", "\x94") + invert = entry.atr + end + else -- not above, not below + char = "\x8c" + end + elseif check(x + 1, y) then -- not left, if right + if check(x, y - 1) then -- if above + if check(x, y + 1) then -- if below + char = util.trinary(entry.atr, "\x95", "\x9d") + invert = entry.atr + else -- not below + char = util.trinary(entry.atr, "\x8a", "\x8d") + end + else -- not above + if check(x, y + 1) then -- if below + char = util.trinary(entry.atr, "\x97", "\x9c") + invert = entry.atr + else -- not below + char = "\x8c" + end + end + else -- not left, not right + char = "\x95" + invert = entry.atr + end + else + if check(x, y - 1) then -- above + -- not below and (if left or right) + if (not check(x, y + 1)) and (check(x - 1, y) or check(x + 1, y)) then + char = util.trinary(entry.atr, "\x8f", " ") + invert = not entry.atr + else -- not below w/ sides only + char = " " + invert = true + end + elseif check(x, y + 1) then -- not above, if below + -- if left or right + if (check(x - 1, y) or check(x + 1, y)) then + char = "\x83" + invert = true + else -- not left or right + char = " " + invert = true + end + else -- not above, not below + char = util.trinary(entry.atr, "\x8f", "\x83") + invert = not entry.atr + end + end + + e.w_set_cur(x, y) + + if invert then + e.w_blit(char, entry.bg, entry.fg) + else + e.w_blit(char, entry.fg, entry.bg) + end + end + + -- draw all pipes by assembling and marking up a 2D map
+ -- this is an easy way to check adjacent blocks, which is required to properly draw thin pipes + local function map_draw() local map = {} - -- allocate map for x = 1, args.width do table.insert(map, {}) for _ = 1, args.height do table.insert(map[x], false) end @@ -215,101 +312,19 @@ local function pipenet(args) -- render for x = 1, args.width do for y = 1, args.height do - local entry = map[x][y] ---@type _pipe_map_entry|false - local char - local invert = false - - if entry ~= false then - local function check(cx, cy) - return (map[cx] ~= nil) and (map[cx][cy] ~= nil) and (map[cx][cy] ~= false) and (map[cx][cy].fg == entry.fg) - end - - if entry.thin then - if check(x - 1, y) then -- if left - if check(x, y - 1) then -- if above - if check(x + 1, y) then -- if right - if check(x, y + 1) then -- if below - char = util.trinary(entry.atr, "\x91", "\x9d") - invert = entry.atr - else -- not below - char = util.trinary(entry.atr, "\x8e", "\x8d") - end - else -- not right - if check(x, y + 1) then -- if below - char = util.trinary(entry.atr, "\x91", "\x95") - invert = entry.atr - else -- not below - char = util.trinary(entry.atr, "\x8e", "\x85") - end - end - elseif check(x, y + 1) then-- not above, if below - if check(x + 1, y) then -- if right - char = util.trinary(entry.atr, "\x93", "\x9c") - invert = entry.atr - else -- not right - char = util.trinary(entry.atr, "\x93", "\x94") - invert = entry.atr - end - else -- not above, not below - char = "\x8c" - end - elseif check(x + 1, y) then -- not left, if right - if check(x, y - 1) then -- if above - if check(x, y + 1) then -- if below - char = util.trinary(entry.atr, "\x95", "\x9d") - invert = entry.atr - else -- not below - char = util.trinary(entry.atr, "\x8a", "\x8d") - end - else -- not above - if check(x, y + 1) then -- if below - char = util.trinary(entry.atr, "\x97", "\x9c") - invert = entry.atr - else -- not below - char = "\x8c" - end - end - else -- not left, not right - char = "\x95" - invert = entry.atr - end - else - if check(x, y - 1) then -- above - -- not below and (if left or right) - if (not check(x, y + 1)) and (check(x - 1, y) or check(x + 1, y)) then - char = util.trinary(entry.atr, "\x8f", " ") - invert = not entry.atr - else -- not below w/ sides only - char = " " - invert = true - end - elseif check(x, y + 1) then -- not above, if below - -- if left or right - if (check(x - 1, y) or check(x + 1, y)) then - char = "\x83" - invert = true - else -- not left or right - char = " " - invert = true - end - else -- not above, not below - char = util.trinary(entry.atr, "\x8f", "\x83") - invert = not entry.atr - end - end - - e.w_set_cur(x, y) - - if invert then - e.w_blit(char, entry.bg, entry.fg) - else - e.w_blit(char, entry.fg, entry.bg) - end - end + if map[x][y] ~= false then draw_map_cell(map, x, y) end end end end + -- element redraw + function e.redraw() + if any_thin then map_draw() else vector_draw() end + end + + -- initial draw + e.redraw() + return e.complete() end diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 40165b0..dfd07c9 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -22,7 +22,7 @@ local element = require("graphics.element") ---@param args rectangle_args ---@return graphics_element element, element_id id local function rectangle(args) - assert(args.border ~= nil or args.thin ~= true, "graphics.elements.rectangle: thin requires border to be provided") + assert(args.border ~= nil or args.thin ~= true, "rectangle: thin requires border to be provided") -- if thin, then width will always need to be 1 if args.thin == true then @@ -65,8 +65,8 @@ local function rectangle(args) local inner_width = e.frame.w - width_x2 -- check dimensions - assert(width_x2 <= e.frame.w, "graphics.elements.rectangle: border too thick for width") - assert(width_x2 <= e.frame.h, "graphics.elements.rectangle: border too thick for height") + assert(width_x2 <= e.frame.w, "rectangle: border too thick for width") + assert(width_x2 <= e.frame.h, "rectangle: border too thick for height") -- form the basic line strings and top/bottom blit strings local spaces = util.spaces(e.frame.w) @@ -126,64 +126,69 @@ local function rectangle(args) end -- draw rectangle with borders - for y = 1, e.frame.h do - e.w_set_cur(1, y) - -- top border - if y <= border_height then - -- partial pixel fill - if args.border.even and y == border_height then - if args.thin == true then - e.w_blit(p_a, p_inv_bg, p_inv_fg) - else - local _fg = util.trinary(args.even_inner == true, string.rep(e.fg_bg.blit_bkg, e.frame.w), p_inv_bg) - local _bg = util.trinary(args.even_inner == true, blit_bg_top_bot, p_inv_fg) + function e.redraw() + for y = 1, e.frame.h do + e.w_set_cur(1, y) + -- top border + if y <= border_height then + -- partial pixel fill + if args.border.even and y == border_height then + if args.thin == true then + e.w_blit(p_a, p_inv_bg, p_inv_fg) + else + local _fg = util.trinary(args.even_inner == true, string.rep(e.fg_bg.blit_bkg, e.frame.w), p_inv_bg) + local _bg = util.trinary(args.even_inner == true, blit_bg_top_bot, p_inv_fg) - if width_x2 % 3 == 1 then - e.w_blit(p_b, _fg, _bg) - elseif width_x2 % 3 == 2 then - e.w_blit(p_a, _fg, _bg) - else - -- skip line - e.w_blit(spaces, blit_fg, blit_bg_sides) - end - end - else - e.w_blit(spaces, blit_fg, blit_bg_top_bot) - end - -- bottom border - elseif y > (e.frame.h - border_width) then - -- partial pixel fill - if args.border.even and y == ((e.frame.h - border_width) + 1) then - if args.thin == true then - if args.even_inner == true then - e.w_blit(p_b, blit_bg_top_bot, string.rep(e.fg_bg.blit_bkg, e.frame.w)) - else - e.w_blit(p_b, string.rep(e.fg_bg.blit_bkg, e.frame.w), blit_bg_top_bot) + if width_x2 % 3 == 1 then + e.w_blit(p_b, _fg, _bg) + elseif width_x2 % 3 == 2 then + e.w_blit(p_a, _fg, _bg) + else + -- skip line + e.w_blit(spaces, blit_fg, blit_bg_sides) + end end else - local _fg = util.trinary(args.even_inner == true, blit_bg_top_bot, p_inv_fg) - local _bg = util.trinary(args.even_inner == true, string.rep(e.fg_bg.blit_bkg, e.frame.w), blit_bg_top_bot) - - if width_x2 % 3 == 1 then - e.w_blit(p_a, _fg, _bg) - elseif width_x2 % 3 == 2 then - e.w_blit(p_b, _fg, _bg) + e.w_blit(spaces, blit_fg, blit_bg_top_bot) + end + -- bottom border + elseif y > (e.frame.h - border_width) then + -- partial pixel fill + if args.border.even and y == ((e.frame.h - border_width) + 1) then + if args.thin == true then + if args.even_inner == true then + e.w_blit(p_b, blit_bg_top_bot, string.rep(e.fg_bg.blit_bkg, e.frame.w)) + else + e.w_blit(p_b, string.rep(e.fg_bg.blit_bkg, e.frame.w), blit_bg_top_bot) + end else - -- skip line - e.w_blit(spaces, blit_fg, blit_bg_sides) + local _fg = util.trinary(args.even_inner == true, blit_bg_top_bot, p_inv_fg) + local _bg = util.trinary(args.even_inner == true, string.rep(e.fg_bg.blit_bkg, e.frame.w), blit_bg_top_bot) + + if width_x2 % 3 == 1 then + e.w_blit(p_a, _fg, _bg) + elseif width_x2 % 3 == 2 then + e.w_blit(p_b, _fg, _bg) + else + -- skip line + e.w_blit(spaces, blit_fg, blit_bg_sides) + end end + else + e.w_blit(spaces, blit_fg, blit_bg_top_bot) end else - e.w_blit(spaces, blit_fg, blit_bg_top_bot) - end - else - if args.thin == true then - e.w_blit(p_s, blit_fg_sides, blit_bg_sides) - else - e.w_blit(p_s, blit_fg, blit_bg_sides) + if args.thin == true then + e.w_blit(p_s, blit_fg_sides, blit_bg_sides) + else + e.w_blit(p_s, blit_fg, blit_bg_sides) + end end end end + + -- initial draw of border + e.redraw() end return e.complete() diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index 0ef223d..cfd5585 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -24,19 +24,20 @@ local TEXT_ALIGN = core.TEXT_ALIGN ---@param args textbox_args ---@return graphics_element element, element_id id local function textbox(args) - assert(type(args.text) == "string", "graphics.elements.textbox: text is a required field") + assert(type(args.text) == "string", "textbox: text is a required field") -- create new graphics element base object local e = element.new(args) + e.value = args.text + local alignment = args.alignment or TEXT_ALIGN.LEFT -- draw textbox + function e.redraw() + e.window.clear() - local function display_text(text) - e.value = text - - local lines = util.strwrap(text, e.frame.w) + local lines = util.strwrap(e.value, e.frame.w) for i = 1, #lines do if i > e.frame.h then break end @@ -56,15 +57,16 @@ local function textbox(args) end end - display_text(args.text) - -- set the string value and re-draw the text ---@param val string value function e.set_value(val) - e.window.clear() - display_text(val) + e.value = val + e.redraw() end + -- initial draw + e.redraw() + return e.complete() end diff --git a/graphics/elements/tiling.lua b/graphics/elements/tiling.lua index 2025903..f40fef5 100644 --- a/graphics/elements/tiling.lua +++ b/graphics/elements/tiling.lua @@ -22,13 +22,11 @@ local element = require("graphics.element") ---@param args tiling_args ---@return graphics_element element, element_id id local function tiling(args) - assert(type(args.fill_c) == "table", "graphics.elements.tiling: fill_c is a required field") + assert(type(args.fill_c) == "table", "tiling: fill_c is a required field") -- create new graphics element base object local e = element.new(args) - -- draw tiling box - local fill_a = args.fill_c.blit_a local fill_b = args.fill_c.blit_b @@ -38,13 +36,9 @@ local function tiling(args) local start_y = 1 local inner_width = math.floor(e.frame.w / util.trinary(even, 2, 1)) local inner_height = e.frame.h - local alternator = true -- border if args.border_c ~= nil then - e.w_set_bkg(args.border_c) - e.window.clear() - start_x = 1 + util.trinary(even, 2, 1) start_y = 2 @@ -53,35 +47,48 @@ local function tiling(args) end -- check dimensions - assert(inner_width > 0, "graphics.elements.tiling: inner_width <= 0") - assert(inner_height > 0, "graphics.elements.tiling: inner_height <= 0") - assert(start_x <= inner_width, "graphics.elements.tiling: start_x > inner_width") - assert(start_y <= inner_height, "graphics.elements.tiling: start_y > inner_height") + assert(inner_width > 0, "tiling: inner_width <= 0") + assert(inner_height > 0, "tiling: inner_height <= 0") + assert(start_x <= inner_width, "tiling: start_x > inner_width") + assert(start_y <= inner_height, "tiling: start_y > inner_height") - -- create pattern - for y = start_y, inner_height + (start_y - 1) do - e.w_set_cur(start_x, y) - for _ = 1, inner_width do - if alternator then - if even then - e.w_blit(" ", "00", fill_a .. fill_a) - else - e.w_blit(" ", "0", fill_a) - end - else - if even then - e.w_blit(" ", "00", fill_b .. fill_b) - else - e.w_blit(" ", "0", fill_b) - end - end + -- draw tiling box + function e.redraw() + local alternator = true - alternator = not alternator + if args.border_c ~= nil then + e.w_set_bkg(args.border_c) + e.window.clear() end - if inner_width % 2 == 0 then alternator = not alternator end + -- draw pattern + for y = start_y, inner_height + (start_y - 1) do + e.w_set_cur(start_x, y) + for _ = 1, inner_width do + if alternator then + if even then + e.w_blit(" ", "00", fill_a .. fill_a) + else + e.w_blit(" ", "0", fill_a) + end + else + if even then + e.w_blit(" ", "00", fill_b .. fill_b) + else + e.w_blit(" ", "0", fill_b) + end + end + + alternator = not alternator + end + + if inner_width % 2 == 0 then alternator = not alternator end + end end + -- initial draw + e.redraw() + return e.complete() end