-- 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 ---@field hidden? boolean true to hide on initial draw -- 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.complete() end return tabbar