diff --git a/graphics/core.lua b/graphics/core.lua
index 1ef315f..58b6b8c 100644
--- a/graphics/core.lua
+++ b/graphics/core.lua
@@ -2,101 +2,12 @@
-- Graphics Core Types, Checks, and Constructors
--
+local events = require("graphics.events")
+local flasher = require("graphics.flasher")
+
local core = {}
--- Core Events
-
-local events = {}
-
----@enum click_button
-events.click_button = {
- VIRTUAL = 0,
- LEFT_BUTTON = 1,
- RIGHT_BUTTON = 2,
- MID_BUTTON = 3
-}
-
----@enum click_type
-events.click_type = {
- TAP = 0,
- DOWN = 1,
- DRAG = 2,
- UP = 3
-}
-
----@class mouse_interaction
----@field monitor string
----@field button click_button
----@field type click_type
----@field x integer
----@field y integer
-
--- create a new monitor touch mouse interaction event
----@nodiscard
----@param monitor string
----@param x integer
----@param y integer
----@return mouse_interaction
-function events.touch(monitor, x, y)
- return {
- monitor = monitor,
- button = events.click_button.LEFT_BUTTON,
- type = events.click_type.TAP,
- x = x,
- y = y
- }
-end
-
--- create a new mouse click mouse interaction event
----@nodiscard
----@param button click_button
----@param x integer
----@param y integer
----@return mouse_interaction
-function events.click(button, x, y)
- return {
- monitor = "terminal",
- button = button,
- type = events.click_type.UP,
- x = x,
- y = y
- }
-end
-
--- create a new transposed mouse interaction event using the event's monitor/button fields
----@nodiscard
----@param event mouse_interaction
----@param new_x integer
----@param new_y integer
----@return mouse_interaction
-function events.mouse_transposed(event, new_x, new_y)
- return {
- monitor = event.monitor,
- button = event.button,
- type = event.type,
- x = new_x,
- y = new_y
- }
-end
-
--- create a new generic mouse interaction event
----@nodiscard
----@param monitor string
----@param button click_button
----@param type click_type
----@param x integer
----@param y integer
----@return mouse_interaction
-function events.mouse_generic(monitor, button, type, x, y)
- return {
- monitor = monitor,
- button = button,
- type = type,
- x = x,
- y = y
- }
-end
-
+core.flasher = flasher
core.events = events
-- Core Types
diff --git a/graphics/element.lua b/graphics/element.lua
index 13baec3..2f8dabd 100644
--- a/graphics/element.lua
+++ b/graphics/element.lua
@@ -59,10 +59,10 @@ function element.new(args)
id = -1,
elem_type = debug.getinfo(2).name,
define_completed = false,
- p_window = nil, ---@type table
- position = { x = 1, y = 1 },
+ p_window = nil, ---@type table
+ position = { x = 1, y = 1 }, ---@type coordinate_2d
child_offset = { x = 0, y = 0 },
- bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1},
+ bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds
next_y = 1,
children = {},
mt = {}
@@ -77,11 +77,22 @@ function element.new(args)
frame = core.gframe(1, 1, 1, 1)
}
+ local name_brief = "graphics.element{" .. self.elem_type .. "}: "
+
-- element as string
function self.mt.__tostring()
return "graphics.element{" .. self.elem_type .. "} @ " .. tostring(self)
end
+ -- check if a coordinate is within the bounds of this element
+ ---@param x integer
+ ---@param y integer
+ local function _in_bounds(x, y)
+ local in_x = x >= self.bounds.x1 and x <= self.bounds.x2
+ local in_y = y >= self.bounds.y1 and y <= self.bounds.y2
+ return in_x and in_y
+ end
+
---@class graphics_element
local public = {}
@@ -138,10 +149,10 @@ function element.new(args)
end
-- check frame
- assert(f.x >= 1, "graphics.element{" .. self.elem_type .. "}: frame x not >= 1")
- assert(f.y >= 1, "graphics.element{" .. self.elem_type .. "}: frame y not >= 1")
- assert(f.w >= 1, "graphics.element{" .. self.elem_type .. "}: frame width not >= 1")
- assert(f.h >= 1, "graphics.element{" .. self.elem_type .. "}: frame height not >= 1")
+ assert(f.x >= 1, name_brief .. "frame x not >= 1")
+ assert(f.y >= 1, name_brief .. "frame y not >= 1")
+ assert(f.w >= 1, name_brief .. "frame width not >= 1")
+ assert(f.h >= 1, name_brief .. "frame height not >= 1")
-- create window
protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true)
@@ -252,7 +263,7 @@ function element.new(args)
end
-- check window
- assert(self.p_window, "graphics.element{" .. self.elem_type .. "}: no parent window provided")
+ assert(self.p_window, name_brief .. "no parent window provided")
-- prepare the template
if args.parent == nil then
@@ -421,17 +432,18 @@ function element.new(args)
-- handle a monitor touch or mouse click
---@param event mouse_interaction mouse interaction event
function public.handle_mouse(event)
- local in_x = event.x >= self.bounds.x1 and event.x <= self.bounds.x2
- local in_y = event.y >= self.bounds.y1 and event.y <= self.bounds.y2
+ local x_ini, y_ini, x_cur, y_cur = event.initial.x, event.initial.y, event.current.x, event.current.y
- if in_x and in_y then
- local event_T = core.events.mouse_transposed(event, (event.x - self.position.x) + 1, (event.y - self.position.y) + 1)
+ local ini_in = _in_bounds(x_ini, y_ini)
+ local cur_in = _in_bounds(x_cur, y_cur)
- -- handle the touch event, transformed into the window frame
+ if ini_in then
+ local event_T = core.events.mouse_transposed(event, self.position.x, self.position.y)
+ if not cur_in then event_T.type = core.events.CLICK_TYPE.EXITED end
+
+ -- handle the mouse event then pass to children
protected.handle_mouse(event_T)
-
- -- pass on touch event to children
- for _, val in pairs(self.children) do val.handle_mouse(event_T) end
+ for _, child in pairs(self.children) do child.handle_mouse(event_T) end
end
end
diff --git a/graphics/events.lua b/graphics/events.lua
new file mode 100644
index 0000000..1b5219c
--- /dev/null
+++ b/graphics/events.lua
@@ -0,0 +1,152 @@
+--
+-- Graphics Events and Event Handlers
+--
+
+local util = require("scada-common.util")
+
+local events = {}
+
+---@enum CLICK_BUTTON
+events.CLICK_BUTTON = {
+ GENERIC = 0,
+ LEFT_BUTTON = 1,
+ RIGHT_BUTTON = 2,
+ MID_BUTTON = 3
+}
+
+---@enum CLICK_TYPE
+events.CLICK_TYPE = {
+ TAP = 1, -- screen tap (complete click)
+ DOWN = 2, -- button down
+ UP = 3, -- button up (completed a click)
+ DRAG = 4, -- mouse dragged
+ SCROLL_DOWN = 5, -- scroll down
+ SCROLL_UP = 6, -- scroll up
+ EXITED = 7 -- cursor exited bounds of element
+}
+
+---@class mouse_interaction
+---@field monitor string
+---@field button CLICK_BUTTON
+---@field type CLICK_TYPE
+---@field initial coordinate_2d
+---@field current coordinate_2d
+
+local handler = {
+ button_down = { { 0, 0 }, { 0, 0 }, { 0, 0 } } -- left, right, middle button down tracking
+}
+
+-- create a new 2D coordinate
+---@param x integer
+---@param y integer
+---@return coordinate_2d
+local function _coord2d(x, y) return { x = x, y = y } end
+
+-- create a new monitor touch mouse interaction event
+---@nodiscard
+---@param monitor string
+---@param x integer
+---@param y integer
+---@return mouse_interaction
+local function _monitor_touch(monitor, x, y)
+ return {
+ monitor = monitor,
+ button = events.CLICK_BUTTON.GENERIC,
+ type = events.CLICK_TYPE.TAP,
+ initial = _coord2d(x, y),
+ current = _coord2d(x, y)
+ }
+end
+
+-- create a new mouse button mouse interaction event
+---@nodiscard
+---@param button CLICK_BUTTON mouse button
+---@param type CLICK_TYPE click type
+---@param x1 integer initial x
+---@param y1 integer initial y
+---@param x2 integer current x
+---@param y2 integer current y
+---@return mouse_interaction
+local function _mouse_event(button, type, x1, y1, x2, y2)
+ return {
+ monitor = "terminal",
+ button = button,
+ type = type,
+ initial = _coord2d(x1, y1),
+ current = _coord2d(x2, y2)
+ }
+end
+
+-- create a new generic mouse interaction event
+---@nodiscard
+---@param type CLICK_TYPE
+---@param x integer
+---@param y integer
+---@return mouse_interaction
+function events.mouse_generic(type, x, y)
+ return {
+ monitor = "",
+ button = events.CLICK_BUTTON.GENERIC,
+ type = type,
+ initial = _coord2d(x, y),
+ current = _coord2d(x, y)
+ }
+end
+
+-- create a new transposed mouse interaction event using the event's monitor/button fields
+---@nodiscard
+---@param event mouse_interaction
+---@param elem_pos_x integer element's x position: new x = (event x - element x) + 1
+---@param elem_pos_y integer element's y position: new y = (event y - element y) + 1
+---@return mouse_interaction
+function events.mouse_transposed(event, elem_pos_x, elem_pos_y)
+ return {
+ monitor = event.monitor,
+ button = event.button,
+ type = event.type,
+ initial = _coord2d((event.initial.x - elem_pos_x) + 1, (event.initial.y - elem_pos_y) + 1),
+ current = _coord2d((event.current.x - elem_pos_x) + 1, (event.current.y - elem_pos_y) + 1)
+ }
+end
+
+-- create a new mouse event to pass onto graphics renderer
+-- supports: mouse_click, mouse_up, mouse_drag, mouse_scroll, and monitor_touch
+---@param event_type os_event OS event to handle
+---@param opt integer|string button, scroll direction, or monitor for monitor touch
+---@param x integer x coordinate
+---@param y integer y coordinate
+---@return mouse_interaction|nil
+function events.new_mouse_event(event_type, opt, x, y)
+ if event_type == "mouse_click" then
+ ---@cast opt 1|2|3
+ handler.button_down[opt] = { x, y }
+ return _mouse_event(opt, events.CLICK_TYPE.DOWN, x, y, x, y)
+ elseif event_type == "mouse_up" then
+ ---@cast opt 1|2|3
+ local initial = handler.button_down[opt] ---@type coordinate_2d
+ return _mouse_event(opt, events.CLICK_TYPE.UP, initial.x, initial.y, x, y)
+ elseif event_type == "monitor_touch" then
+ ---@cast opt string
+ return _monitor_touch(opt, x, y)
+ elseif event_type == "mouse_drag" then
+ ---@cast opt 1|2|3
+ local initial = handler.button_down[opt] ---@type coordinate_2d
+ return _mouse_event(opt, events.CLICK_TYPE.DRAG, initial.x, initial.y, x, y)
+ elseif event_type == "mouse_scroll" then
+ ---@cast opt 1|-1
+ local scroll_direction = util.trinary(opt == 1, events.CLICK_TYPE.SCROLL_DOWN, events.CLICK_TYPE.SCROLL_UP)
+ return _mouse_event(events.CLICK_BUTTON.GENERIC, scroll_direction, x, y, x, y)
+ end
+end
+
+-- create a new key event to pass onto graphics renderer
+-- supports: char, key, and key_up
+---@param event_type os_event
+function events.new_key_event(event_type)
+ if event_type == "char" then
+ elseif event_type == "key" then
+ elseif event_type == "key_up" then
+ end
+end
+
+return events
diff --git a/graphics/graphics.lua b/graphics/graphics.lua
deleted file mode 100644
index 11b04ff..0000000
--- a/graphics/graphics.lua
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-local flasher = require("graphics.flasher")
-local core = require("graphics.core")
-
-local graphics = {}
-
-graphics.flasher = flasher
-
--- pass mouse events to graphics engine
--- supports: mouse_click, mouse_up, mouse_drag, mouse_scroll, and monitor_touch
----@param event_type os_event
-function graphics.handle_mouse(event_type)
- if event_type == "mouse_click" then
- elseif event_type == "mouse_up" or event_type == "monitor_touch" then
- elseif event_type == "mouse_drag" then
- elseif event_type == "mouse_scroll" then
- end
-end
-
--- pass char, key, or key_up event to graphics engine
----@param event_type os_event
-function graphics.handle_key(event_type)
- if event_type == "char" then
- elseif event_type == "key" then
- elseif event_type == "key_up" then
- end
-end
-
-return graphics
diff --git a/scada-common/types.lua b/scada-common/types.lua
index 9beb1e6..8df01c1 100644
--- a/scada-common/types.lua
+++ b/scada-common/types.lua
@@ -39,6 +39,10 @@ function types.new_radiation_reading(r, u) return { radiation = r, unit = u } en
---@return radiation_reading
function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" } end
+---@class coordinate_2d
+---@field x integer
+---@field y integer
+
---@class coordinate
---@field x integer
---@field y integer