cc-mek-scada/graphics/elements/form/number_field.lua

160 lines
5.2 KiB
Lua

-- Numeric Value Entry Graphics Element
local core = require("graphics.core")
local element = require("graphics.element")
local KEY_CLICK = core.events.KEY_CLICK
local MOUSE_CLICK = core.events.MOUSE_CLICK
---@class number_field_args
---@field default? number default value, defaults to 0
---@field min? number minimum, forced on unfocus
---@field max? number maximum, forced on unfocus
---@field max_digits? integer maximum number of digits, defaults to width
---@field allow_decimal? boolean true to allow decimals
---@field allow_negative? boolean true to allow negative numbers
---@field dis_fg_bg? cpair foreground/background colors when disabled
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer auto incremented 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 numeric entry field
---@param args number_field_args
---@return graphics_element element, element_id id
local function number_field(args)
args.height = 1
args.can_focus = true
-- create new graphics element base object
local e = element.new(args)
local has_decimal = false
args.max_digits = args.max_digits or e.frame.w
-- set initial value
e.value = "" .. (args.default or 0)
-- make an interactive field manager
local ifield = core.new_ifield(e, args.max_digits, args.fg_bg, args.dis_fg_bg)
-- handle mouse interaction
---@param event mouse_interaction mouse event
function e.handle_mouse(event)
-- only handle if on an increment or decrement arrow
if e.enabled then
if core.events.was_clicked(event.type) then
e.take_focus()
if event.type == MOUSE_CLICK.UP then
ifield.move_cursor(event.current.x)
end
elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then
ifield.select_all()
end
end
end
-- handle keyboard interaction
---@param event key_interaction key event
function e.handle_key(event)
if event.type == KEY_CLICK.CHAR and string.len(e.value) < args.max_digits then
if tonumber(event.name) then
if e.value == 0 then e.value = "" end
ifield.try_insert_char(event.name)
end
elseif event.type == KEY_CLICK.DOWN or event.type == KEY_CLICK.HELD then
if (event.key == keys.backspace or event.key == keys.delete) and (string.len(e.value) > 0) then
ifield.backspace()
has_decimal = string.find(e.value, "%.") ~= nil
elseif (event.key == keys.period or event.key == keys.numPadDecimal) and (not has_decimal) and args.allow_decimal then
has_decimal = true
ifield.try_insert_char(".")
elseif (event.key == keys.minus or event.key == keys.numPadSubtract) and (string.len(e.value) == 0) and args.allow_negative then
ifield.set_value("-")
elseif event.key == keys.left then
ifield.nav_left()
elseif event.key == keys.right then
ifield.nav_right()
elseif event.key == keys.a and event.ctrl then
ifield.select_all()
elseif event.key == keys.home or event.key == keys.up then
ifield.nav_start()
elseif event.key == keys["end"] or event.key == keys.down then
ifield.nav_end()
end
end
end
-- 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
end
-- set minimum input value
---@param min integer minimum allowed value
function e.set_min(min)
args.min = min
e.on_unfocused()
end
-- set maximum input value
---@param max integer maximum allowed value
function e.set_max(max)
args.max = max
e.on_unfocused()
end
-- replace text with pasted text if its a number
---@param text string string pasted
function e.handle_paste(text)
if tonumber(text) then
ifield.set_value("" .. tonumber(text))
else
ifield.set_value("0")
end
end
-- handle unfocused
function e.on_unfocused()
local val = tonumber(e.value)
local max = tonumber(args.max)
local min = tonumber(args.min)
if type(val) == "number" then
if type(args.max) == "number" and val > max then
e.value = "" .. max
ifield.nav_start()
elseif type(args.min) == "number" and val < min then
e.value = "" .. min
ifield.nav_start()
else
e.value = "" .. val
ifield.nav_end()
end
else
e.value = ""
end
ifield.show()
end
-- 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
e.redraw()
return e.complete()
end
return number_field