diff --git a/graphics/element.lua b/graphics/element.lua index 4399ac1..f356c03 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -34,6 +34,7 @@ local element = {} ---|switch_button_args ---|tabbar_args ---|number_field_args +---|text_field_args ---|alarm_indicator_light ---|core_map_args ---|data_indicator_args @@ -340,6 +341,10 @@ function element.new(args, child_offset_x, child_offset_y) ---@param event key_interaction key interaction event function protected.handle_key(event) end + -- handle a paste event + ---@param text string pasted text + function protected.handle_paste(text) end + -- handle data value changes ---@vararg any value(s) function protected.on_update(...) end @@ -603,6 +608,7 @@ function element.new(args, child_offset_x, child_offset_y) if protected.enabled then protected.enabled = false protected.disable() + public.unfocus_all() end end @@ -614,7 +620,7 @@ function element.new(args, child_offset_x, child_offset_y) -- focus the element function public.focus() - if args.can_focus and not self.focused then + if args.can_focus and protected.enabled and not self.focused then self.focused = true protected.on_focused() end @@ -698,6 +704,16 @@ function element.new(args, child_offset_x, child_offset_y) end end + -- handle text paste + ---@param text string pasted text + function public.handle_paste(text) + if protected.window.isVisible() then + -- handle the paste event then pass to children + if self.focused then protected.handle_paste(text) end + for _, child in pairs(protected.children) do child.get().handle_paste(text) end + end + end + -- draw the element given new data ---@vararg any new data function public.update(...) protected.on_update(...) end diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index da555be..b8e3013 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -131,6 +131,7 @@ local function push_button(args) end end + -- handle focus change e.on_focused = show_pressed e.on_unfocused = show_unpressed diff --git a/graphics/elements/form/number_field.lua b/graphics/elements/form/number_field.lua index da0a7ae..e0e2426 100644 --- a/graphics/elements/form/number_field.lua +++ b/graphics/elements/form/number_field.lua @@ -98,15 +98,24 @@ local function number_field(args) -- set the value ---@param val number number to show - function e.set_value(val) e.value = val end + function e.set_value(val) + e.value = val + show() + end -- set minimum input value ---@param min integer minimum allowed value - function e.set_min(min) args.min = min end + function e.set_min(min) + args.min = min + show() + end -- set maximum input value ---@param max integer maximum allowed value - function e.set_max(max) args.max = max end + function e.set_max(max) + args.max = max + show() + end -- handle focused e.on_focused = show diff --git a/graphics/elements/form/text_field.lua b/graphics/elements/form/text_field.lua new file mode 100644 index 0000000..47d512b --- /dev/null +++ b/graphics/elements/form/text_field.lua @@ -0,0 +1,177 @@ +-- Text Value Entry Graphics Element + +local util = require("scada-common.util") + +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 text_field_args +---@field value? string initial value +---@field max_len? integer maximum string length +---@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 text entry field +---@param args text_field_args +---@return graphics_element element, element_id id +local function text_field(args) + args.height = 1 + args.can_focus = true + + -- create new graphics element base object + local e = element.new(args) + + -- set initial value + e.value = args.value or "" + + local max_len = 1000 -- temporary + + local frame_start = 1 + local visible_text = e.value + local cursor_pos = string.len(visible_text) + 1 + + local function frame__update_visible() + visible_text = string.sub(e.value, frame_start, frame_start + math.min(string.len(e.value), args.width) - 1) + end + + -- draw input + local function show() + frame__update_visible() + + 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) + + if e.is_focused() and e.enabled then + -- write text with cursor + if cursor_pos == (string.len(visible_text) + 1) then + -- write text with cursor at the end, no need to blit + e.w_write(visible_text) + e.w_set_fgd(colors.lightGray) + e.w_write("_") + else + local a, b = "", "" + + if cursor_pos <= string.len(visible_text) then + a = args.fg_bg.blit_bkg + b = args.fg_bg.blit_fgd + end + + local b_fgd = string.rep(args.fg_bg.blit_fgd, cursor_pos - 1) .. a .. string.rep(args.fg_bg.blit_fgd, string.len(visible_text) - cursor_pos) + local b_bkg = string.rep(args.fg_bg.blit_bkg, cursor_pos - 1) .. b .. string.rep(args.fg_bg.blit_bkg, string.len(visible_text) - cursor_pos) + + e.w_blit(visible_text, b_fgd, b_bkg) + end + else + -- write text without cursor + e.w_write(visible_text) + end + end + + local function frame__try_lshift() + if frame_start > 1 then + frame_start = frame_start - 1 + show() + end + end + + local function frame__try_rshift() + if (frame_start + args.width - 1) < string.len(e.value) then + frame_start = frame_start + 1 + show() + end + end + + -- 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 and core.events.was_clicked(event.type) then + e.req_focus() + + if event.type == MOUSE_CLICK.UP then + cursor_pos = math.min(event.current.x, string.len(visible_text) + 1) + show() + 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) < max_len then + e.value = string.sub(e.value, 1, frame_start + cursor_pos - 2) .. event.name .. string.sub(e.value, frame_start + cursor_pos - 1, string.len(e.value)) + frame__update_visible() + if cursor_pos <= string.len(visible_text) then + cursor_pos = cursor_pos + 1 + show() + else frame__try_rshift() end + elseif event.type == KEY_CLICK.DOWN or event.type == KEY_CLICK.HELD then + if (event.key == keys.backspace or event.key == keys.delete) then + -- remove charcter at cursor + e.value = string.sub(e.value, 1, frame_start + cursor_pos - 3) .. string.sub(e.value, frame_start + cursor_pos - 1, string.len(e.value)) + if cursor_pos > 1 then + cursor_pos = cursor_pos - 1 + show() + else frame__try_lshift() end + elseif event.key == keys.left then + if cursor_pos > 1 then + cursor_pos = cursor_pos - 1 + show() + else frame__try_lshift() end + elseif event.key == keys.right then + if cursor_pos <= string.len(visible_text) then + cursor_pos = cursor_pos + 1 + show() + else frame__try_rshift() end + end + end + end + + -- set the value + ---@param val string string to show + function e.set_value(val) + e.value = val + frame_start = 1 + math.max(0, string.len(val) - args.width) + frame__update_visible() + cursor_pos = string.len(visible_text) + 1 + show() + end + + function e.handle_paste(text) + e.set_value(text) + end + + -- handle focus + e.on_focused = show + e.on_unfocused = show + + -- on enable/disable + e.enable = show + e.disable = show + + -- initial draw + show() + + return e.complete() +end + +return text_field