diff --git a/graphics/element.lua b/graphics/element.lua index 2f8dabd..edcbdcc 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -28,6 +28,7 @@ local element = {} ---|sidebar_args ---|spinbox_args ---|switch_button_args +---|tabbar_args ---|alarm_indicator_light ---|core_map_args ---|data_indicator_args diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index 3db6a3b..4dca5c4 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -141,7 +141,7 @@ local function hazard_button(args) end -- handle mouse interaction - ---@param event mouse_interaction + ---@param event mouse_interaction mouse event function e.handle_mouse(event) if e.enabled then if core.events.was_clicked(event.type) then diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index a55a4e4..64a5aa4 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -70,7 +70,7 @@ local function push_button(args) end -- handle mouse interaction - ---@param event mouse_interaction + ---@param event mouse_interaction mouse event function e.handle_mouse(event) if e.enabled then if event.type == CLICK_TYPE.TAP then diff --git a/graphics/elements/controls/radio_button.lua b/graphics/elements/controls/radio_button.lua index 3b2a593..050bf39 100644 --- a/graphics/elements/controls/radio_button.lua +++ b/graphics/elements/controls/radio_button.lua @@ -1,5 +1,6 @@ -- Radio Button Graphics Element +local core = require("graphics.core") local element = require("graphics.element") ---@class radio_button_args @@ -82,10 +83,10 @@ local function radio_button(args) -- handle mouse interaction ---@param event mouse_interaction mouse event function e.handle_mouse(event) - -- determine what was pressed - if e.enabled then - if args.options[event.y] ~= nil then - e.value = event.y + if e.enabled and core.events.was_clicked(event.type) and (event.initial.y == event.current.y) then + -- determine what was pressed + if args.options[event.current.y] ~= nil then + e.value = event.current.y draw() args.callback(e.value) end diff --git a/graphics/elements/controls/sidebar.lua b/graphics/elements/controls/sidebar.lua index 885761d..997b372 100644 --- a/graphics/elements/controls/sidebar.lua +++ b/graphics/elements/controls/sidebar.lua @@ -2,8 +2,11 @@ local tcd = require("scada-common.tcallbackdsp") +local core = require("graphics.core") local element = require("graphics.element") +local CLICK_TYPE = core.events.CLICK_TYPE + ---@class sidebar_tab ---@field char string character identifier ---@field color cpair tab colors (fg/bg) @@ -39,7 +42,10 @@ local function sidebar(args) -- show the button state ---@param pressed boolean if the currently selected tab should appear as actively pressed - local function draw(pressed) + ---@param pressed_idx? integer optional index to show as held (that is not yet selected) + local function draw(pressed, pressed_idx) + pressed_idx = pressed_idx or e.value + for i = 1, #args.tabs do local tab = args.tabs[i] ---@type sidebar_tab @@ -47,7 +53,7 @@ local function sidebar(args) e.window.setCursorPos(1, y) - if pressed and e.value == i then + if pressed and i == pressed_idx then e.window.setTextColor(e.fg_bg.fgd) e.window.setBackgroundColor(e.fg_bg.bkg) else @@ -74,16 +80,27 @@ local function sidebar(args) function e.handle_mouse(event) -- determine what was pressed if e.enabled then - local idx = math.ceil(event.y / 3) + local cur_idx = math.ceil(event.current.y / 3) + local ini_idx = math.ceil(event.initial.y / 3) - if args.tabs[idx] ~= nil then - e.value = idx - draw(true) - - -- show as unpressed in 0.25 seconds - tcd.dispatch(0.25, function () draw(false) end) - - args.callback(e.value) + if args.tabs[cur_idx] ~= nil then + if event.type == CLICK_TYPE.TAP then + e.value = cur_idx + draw(true) + -- show as unpressed in 0.25 seconds + tcd.dispatch(0.25, function () draw(false) end) + args.callback(e.value) + elseif event.type == CLICK_TYPE.DOWN then + draw(true, cur_idx) + elseif event.type == CLICK_TYPE.UP then + if cur_idx == ini_idx then + e.value = cur_idx + draw(false) + args.callback(e.value) + else draw(false) end + elseif event.type == CLICK_TYPE.EXITED then + draw(false) + end end end end diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 15e0e76..6b88c0e 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -2,6 +2,7 @@ local util = require("scada-common.util") +local core = require("graphics.core") local element = require("graphics.element") ---@class spinbox_args @@ -130,19 +131,22 @@ local function spinbox(args) ---@param event mouse_interaction mouse event function e.handle_mouse(event) -- only handle if on an increment or decrement arrow - if e.enabled and event.x ~= dec_point_x then - local idx = util.trinary(event.x > dec_point_x, event.x - 1, event.x) - if digits[idx] ~= nil then - if event.y == 1 then - -- increment - digits[idx] = digits[idx] + 1 - elseif event.y == 3 then - -- decrement - digits[idx] = digits[idx] - 1 - end + if e.enabled and core.events.was_clicked(event.type) and + (event.current.x ~= dec_point_x) and (event.current.y ~= 2) then + if event.current.x == event.initial.x and event.current.y == event.initial.y then + 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 - update_value() - show_num() + update_value() + show_num() + end end end end diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index 012872c..344f757 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -1,5 +1,6 @@ -- Button Graphics Element +local core = require("graphics.core") local element = require("graphics.element") ---@class switch_button_args @@ -63,8 +64,9 @@ local function switch_button(args) draw_state() -- handle mouse interaction - function e.handle_mouse(_) - if e.enabled then + ---@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() diff --git a/graphics/elements/controls/tabbar.lua b/graphics/elements/controls/tabbar.lua new file mode 100644 index 0000000..6249951 --- /dev/null +++ b/graphics/elements/controls/tabbar.lua @@ -0,0 +1,130 @@ +-- Tab Bar Graphics Element + +local util = require("scada-common.util") + +local core = require("graphics.core") +local element = require("graphics.element") + +---@class tabbar_tab +---@field name string tab name +---@field color cpair tab colors (fg/bg) +---@field _start_x integer starting touch x range (inclusive) +---@field _end_x integer ending touch x range (inclusive) + +---@class tabbar_args +---@field tabs table tab options +---@field callback function function to call on tab change +---@field min_width? integer text length + 2 if omitted +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width? integer parent width if omitted +---@field fg_bg? cpair foreground/background colors + +-- new tab selector +---@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") + + -- always 1 tall + args.height = 1 + + -- determine widths + local max_width = 1 + for i = 1, #args.tabs do + local opt = args.tabs[i] ---@type tabbar_tab + if string.len(opt.name) > max_width then + max_width = string.len(opt.name) + end + end + + local button_width = math.max(max_width, args.min_width or 0) + + -- create new graphics element base object + local e = element.new(args) + + assert(e.frame.w >= (button_width * #args.tabs), "graphics.elements.controls.tabbar: width insufficent to display all tabs") + + -- default to 1st tab + e.value = 1 + + -- calculate required tab dimension information + local next_x = 1 + for i = 1, #args.tabs do + local tab = args.tabs[i] ---@type tabbar_tab + + tab._start_x = next_x + tab._end_x = next_x + button_width - 1 + + next_x = next_x + button_width + end + + -- show the tab state + local function draw() + for i = 1, #args.tabs do + local tab = args.tabs[i] ---@type tabbar_tab + + e.window.setCursorPos(tab._start_x, 1) + + if e.value == i then + e.window.setTextColor(tab.color.fgd) + e.window.setBackgroundColor(tab.color.bkg) + else + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + end + + e.window.write(util.pad(tab.name, button_width)) + end + end + + -- check which tab a given x is within + ---@return integer|nil button index or nil if not within a tab + local function which_tab(x) + for i = 1, #args.tabs do + local tab = args.tabs[i] ---@type tabbar_tab + if x >= tab._start_x and x <= tab._end_x then return i end + end + + return nil + end + + -- handle mouse interaction + ---@param event mouse_interaction mouse event + function e.handle_mouse(event) + -- determine what was pressed + if e.enabled and core.events.was_clicked(event.type) then + -- a button may have been pressed, which one was it? + local tab_ini = which_tab(event.initial.x) + local tab_cur = which_tab(event.current.x) + + -- mouse up must always have started with a mouse down on the same tab to count as a click + -- 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() + args.callback(e.value) + end + end + end + + -- set the value + ---@param val integer new value + function e.set_value(val) + e.value = val + draw() + end + + -- initial draw + draw() + + return e.get() +end + +return tabbar