continued refactor

This commit is contained in:
Michael Freno
2025-11-19 16:49:34 -05:00
parent 21a4a29cf1
commit 57eb52e70d
6 changed files with 139 additions and 106 deletions

View File

@@ -10,6 +10,7 @@ local Units = req("Units")
local Context = req("Context")
---@type StateManager
local StateManager = req("StateManager")
---@type Performance
local Performance = req("Performance")
local ImageRenderer = req("ImageRenderer")
local ImageScaler = req("ImageScaler")
@@ -20,8 +21,10 @@ local Grid = req("Grid")
local InputEvent = req("InputEvent")
local GestureRecognizer = req("GestureRecognizer")
local TextEditor = req("TextEditor")
---@type LayoutEngine
local LayoutEngine = req("LayoutEngine")
local Renderer = req("Renderer")
---@type EventHandler
local EventHandler = req("EventHandler")
local ScrollManager = req("ScrollManager")
---@type ErrorHandler
@@ -129,6 +132,8 @@ function flexlove.init(config)
utils.init({ ErrorHandler = flexlove._ErrorHandler })
Animation.init({ ErrorHandler = flexlove._ErrorHandler, Color = Color })
Theme.init({ ErrorHandler = flexlove._ErrorHandler, Color = Color, utils = utils })
LayoutEngine.init({ ErrorHandler = flexlove._ErrorHandler, Performance = flexlove._Performance })
EventHandler.init({ ErrorHandler = flexlove._ErrorHandler, Performance = flexlove._Performance, InputEvent = InputEvent, utils = utils })
flexlove._defaultDependencies = {
Context = Context,

View File

@@ -1369,7 +1369,8 @@ function Element.new(props)
self._layoutEngine.rowGap = self.rowGap
end
self.transform = props.transform or {}
-- transform is already set at line 424 (props.transform or nil)
-- Don't overwrite it here
self.transition = props.transition or {}
if props.overflow or props.overflowX or props.overflowY then

View File

@@ -17,24 +17,27 @@
---@field _element Element?
---@field _scrollbarPressHandled boolean
---@field _InputEvent table
---@field _Context table
---@field _utils table
---@field _Performance Performance? Performance module dependency
---@field _ErrorHandler ErrorHandler
local EventHandler = {}
EventHandler.__index = EventHandler
--- Create a new EventHandler instance
--- Initialize module with shared dependencies
---@param deps table Dependencies {Performance, ErrorHandler, InputEvent, Context, utils}
function EventHandler.init(deps)
EventHandler._Performance = deps.Performance
EventHandler._ErrorHandler = deps.ErrorHandler
EventHandler._InputEvent = deps.InputEvent
EventHandler._utils = deps.utils
end
---@param config table Configuration options
---@param deps table Dependencies {InputEvent, Context, utils}
---@return EventHandler
function EventHandler.new(config, deps)
function EventHandler.new(config)
config = config or {}
local self = setmetatable({}, EventHandler)
self._InputEvent = deps.InputEvent
self._Context = deps.Context
self._utils = deps.utils
self.onEvent = config.onEvent
self.onEventDeferred = config.onEventDeferred
@@ -119,14 +122,14 @@ end
---@param isActiveElement boolean Whether this is the top element at mouse position
function EventHandler:processMouseEvents(mx, my, isHovering, isActiveElement)
-- Start performance timing
local Performance = package.loaded["modules.Performance"] or package.loaded["libs.modules.Performance"]
if Performance and Performance.isEnabled() then
Performance.startTimer("event_mouse")
-- Performance accessed via EventHandler._Performance
if EventHandler._Performance and EventHandler._Performance.enabled then
EventHandler._Performance:startTimer("event_mouse")
end
if not self._element then
if Performance and Performance.isEnabled() then
Performance.stopTimer("event_mouse")
if EventHandler._Performance and EventHandler._Performance.enabled then
EventHandler._Performance:stopTimer("event_mouse")
end
return
end
@@ -166,8 +169,8 @@ function EventHandler:processMouseEvents(mx, my, isHovering, isActiveElement)
end
end
end
if Performance and Performance.isEnabled() then
Performance.stopTimer("event_mouse")
if EventHandler._Performance and EventHandler._Performance.enabled then
EventHandler._Performance:stopTimer("event_mouse")
end
return
end
@@ -221,8 +224,8 @@ function EventHandler:processMouseEvents(mx, my, isHovering, isActiveElement)
end
-- Stop performance timing
if Performance and Performance.isEnabled() then
Performance.stopTimer("event_mouse")
if EventHandler._Performance and EventHandler._Performance.enabled then
EventHandler._Performance:stopTimer("event_mouse")
end
end
@@ -248,8 +251,8 @@ function EventHandler:_handleMousePress(mx, my, button)
end
-- Fire press event
local modifiers = self._utils.getModifiers()
local pressEvent = self._InputEvent.new({
local modifiers = EventHandler._utils.getModifiers()
local pressEvent = EventHandler._InputEvent.new({
type = "press",
button = button,
x = mx,
@@ -292,11 +295,11 @@ function EventHandler:_handleMouseDrag(mx, my, button, isHovering)
if lastX ~= mx or lastY ~= my then
-- Mouse has moved - fire drag event only if still hovering
if isHovering then
local modifiers = self._utils.getModifiers()
local modifiers = EventHandler._utils.getModifiers()
local dx = mx - self._dragStartX[button]
local dy = my - self._dragStartY[button]
local dragEvent = self._InputEvent.new({
local dragEvent = EventHandler._InputEvent.new({
type = "drag",
button = button,
x = mx,
@@ -332,7 +335,7 @@ function EventHandler:_handleMouseRelease(mx, my, button)
local element = self._element
local currentTime = love.timer.getTime()
local modifiers = self._utils.getModifiers()
local modifiers = EventHandler._utils.getModifiers()
-- Determine click count (double-click detection)
local clickCount = 1
@@ -357,7 +360,7 @@ function EventHandler:_handleMouseRelease(mx, my, button)
end
-- Fire click event
local clickEvent = self._InputEvent.new({
local clickEvent = EventHandler._InputEvent.new({
type = eventType,
button = button,
x = mx,
@@ -397,7 +400,7 @@ function EventHandler:_handleMouseRelease(mx, my, button)
end
-- Fire release event
local releaseEvent = self._InputEvent.new({
local releaseEvent = EventHandler._InputEvent.new({
type = "release",
button = button,
x = mx,
@@ -411,14 +414,14 @@ end
--- Process touch events in the update cycle
function EventHandler:processTouchEvents()
-- Start performance timing
local Performance = package.loaded["modules.Performance"] or package.loaded["libs.modules.Performance"]
if Performance and Performance.isEnabled() then
Performance.startTimer("event_touch")
-- Performance accessed via EventHandler._Performance
if EventHandler._Performance and EventHandler._Performance.enabled then
EventHandler._Performance:startTimer("event_touch")
end
if not self._element then
if Performance and Performance.isEnabled() then
Performance.stopTimer("event_touch")
if EventHandler._Performance and EventHandler._Performance.enabled then
EventHandler._Performance:stopTimer("event_touch")
end
return
end
@@ -429,8 +432,8 @@ function EventHandler:processTouchEvents()
local canProcessEvents = (self.onEvent or element.editable) and not element.disabled
if not canProcessEvents then
if Performance and Performance.isEnabled() then
Performance.stopTimer("event_touch")
if EventHandler._Performance and EventHandler._Performance.enabled then
EventHandler._Performance:stopTimer("event_touch")
end
return
end
@@ -491,8 +494,8 @@ function EventHandler:processTouchEvents()
end
-- Stop performance timing
if Performance and Performance.isEnabled() then
Performance.stopTimer("event_touch")
if EventHandler._Performance and EventHandler._Performance.enabled then
EventHandler._Performance:stopTimer("event_touch")
end
end
@@ -525,7 +528,7 @@ function EventHandler:_handleTouchBegan(touchId, x, y, pressure)
self._touchHistory[touchId] = { { x = x, y = y, timestamp = love.timer.getTime() } }
-- Create and fire touch press event
local touchEvent = self._InputEvent.fromTouch(touchId, x, y, "began", pressure)
local touchEvent = EventHandler._InputEvent.fromTouch(touchId, x, y, "began", pressure)
touchEvent.type = "touchpress"
touchEvent.dx = 0
touchEvent.dy = 0
@@ -575,7 +578,7 @@ function EventHandler:_handleTouchMoved(touchId, x, y, pressure)
self._touchHistory[touchId] = history
-- Create and fire touch move event
local touchEvent = self._InputEvent.fromTouch(touchId, x, y, "moved", pressure)
local touchEvent = EventHandler._InputEvent.fromTouch(touchId, x, y, "moved", pressure)
touchEvent.type = "touchmove"
touchEvent.dx = dx
touchEvent.dy = dy
@@ -606,7 +609,7 @@ function EventHandler:_handleTouchEnded(touchId, x, y, pressure)
local dy = y - startPos.y
-- Create and fire touch release event
local touchEvent = self._InputEvent.fromTouch(touchId, x, y, "ended", pressure)
local touchEvent = EventHandler._InputEvent.fromTouch(touchId, x, y, "ended", pressure)
touchEvent.type = "touchrelease"
touchEvent.dx = dx
touchEvent.dy = dy
@@ -677,11 +680,11 @@ function EventHandler:_invokeCallback(element, event)
self.onEvent(element, event)
end)
else
-- Fallback: execute immediately if FlexLove not available
self.onEvent(element, event)
EventHandler._ErrorHandler:error("EventHandler", "SYS_003", "FlexLove.deferCallback not available", {
eventType = event.type,
}, "Ensure FlexLove module is properly loaded")
end
else
-- Execute immediately
self.onEvent(element, event)
end
end

View File

@@ -23,9 +23,18 @@
---@field _FlexWrap table
---@field _layoutCount number Track layout recalculations per frame
---@field _lastFrameCount number Last frame number for resetting counters
---@field _ErrorHandler ErrorHandler? ErrorHandler module dependency
---@field _Performance Performance? Performance module dependency
local LayoutEngine = {}
LayoutEngine.__index = LayoutEngine
--- Initialize module with shared dependencies
---@param deps table Dependencies {ErrorHandler, Performance}
function LayoutEngine.init(deps)
LayoutEngine._ErrorHandler = deps.ErrorHandler
LayoutEngine._Performance = deps.Performance
end
---@class LayoutEngineProps
---@field positioning Positioning? Layout positioning mode (default: RELATIVE)
---@field flexDirection FlexDirection? Direction of flex layout (default: HORIZONTAL)
@@ -153,10 +162,9 @@ function LayoutEngine:layoutChildren()
end
-- Start performance timing
local Performance = package.loaded["modules.Performance"] or package.loaded["libs.modules.Performance"]
if Performance and Performance.isEnabled() then
if LayoutEngine._Performance and LayoutEngine._Performance.enabled then
local elementId = self.element.id or "unnamed"
Performance.startTimer("layout_" .. elementId)
LayoutEngine._Performance:startTimer("layout_" .. elementId)
end
-- Track layout recalculations for performance warnings
@@ -177,8 +185,8 @@ function LayoutEngine:layoutChildren()
end
-- Stop performance timing
if Performance and Performance.isEnabled() then
Performance.stopTimer("layout_" .. (self.element.id or "unnamed"))
if LayoutEngine._Performance and LayoutEngine._Performance.enabled then
LayoutEngine._Performance:stopTimer("layout_" .. (self.element.id or "unnamed"))
end
return
end
@@ -188,8 +196,8 @@ function LayoutEngine:layoutChildren()
self._Grid.layoutGridItems(self.element)
-- Stop performance timing
if Performance and Performance.isEnabled() then
Performance.stopTimer("layout_" .. (self.element.id or "unnamed"))
if LayoutEngine._Performance and LayoutEngine._Performance.enabled then
LayoutEngine._Performance:stopTimer("layout_" .. (self.element.id or "unnamed"))
end
return
end
@@ -198,8 +206,8 @@ function LayoutEngine:layoutChildren()
if childCount == 0 then
-- Stop performance timing
if Performance and Performance.isEnabled() then
Performance.stopTimer("layout_" .. (self.element.id or "unnamed"))
if LayoutEngine._Performance and LayoutEngine._Performance.enabled then
LayoutEngine._Performance:stopTimer("layout_" .. (self.element.id or "unnamed"))
end
return
end
@@ -212,18 +220,20 @@ function LayoutEngine:layoutChildren()
table.insert(flexChildren, child)
-- Warn if child uses percentage sizing but parent has autosizing
if self._ErrorHandler then
if child.units and child.units.width then
if child.units.width.unit == "%" and self.element.autosizing and self.element.autosizing.width then
self._ErrorHandler.warn("LayoutEngine",
string.format("Child '%s' uses percentage width but parent has auto-sizing enabled. This may cause unexpected results", child.id or "unnamed"))
LayoutEngine._ErrorHandler:warn("LayoutEngine", "LAY_004", "Invalid sizing combination", {
child = child.id or "unnamed",
issue = "percentage width with parent auto-sizing",
}, "Use fixed or viewport units instead of percentage when parent has auto-sizing enabled")
end
end
if child.units and child.units.height then
if child.units.height.unit == "%" and self.element.autosizing and self.element.autosizing.height then
self._ErrorHandler.warn("LayoutEngine",
string.format("Child '%s' uses percentage height but parent has auto-sizing enabled. This may cause unexpected results", child.id or "unnamed"))
end
LayoutEngine._ErrorHandler:warn("LayoutEngine", "LAY_004", "Invalid sizing combination", {
child = child.id or "unnamed",
issue = "percentage height with parent auto-sizing",
}, "Use fixed or viewport units instead of percentage when parent has auto-sizing enabled")
end
end
end
@@ -601,8 +611,8 @@ function LayoutEngine:layoutChildren()
end
-- Stop performance timing
if Performance and Performance.isEnabled() then
Performance.stopTimer("layout_" .. (self.element.id or "unnamed"))
if LayoutEngine._Performance and LayoutEngine._Performance.enabled then
LayoutEngine._Performance:stopTimer("layout_" .. (self.element.id or "unnamed"))
end
end
@@ -977,9 +987,7 @@ end
--- Track layout recalculations and warn about excessive layouts
function LayoutEngine:_trackLayoutRecalculation()
-- Get Performance module if available
local Performance = package.loaded["modules.Performance"] or package.loaded["libs.modules.Performance"]
if not Performance or not Performance.areWarningsEnabled() then
if not LayoutEngine._Performance or not LayoutEngine._Performance.warningsEnabled then
return
end
@@ -998,7 +1006,7 @@ function LayoutEngine:_trackLayoutRecalculation()
-- Warn if layout is recalculated excessively this frame
if self._layoutCount >= 10 then
local elementId = self.element and self.element.id or "unnamed"
Performance.logWarning(
LayoutEngine._Performance:logWarning(
string.format("excessive_layout_%s", elementId),
"LayoutEngine",
string.format("Layout recalculated %d times this frame for element '%s'", self._layoutCount, elementId),

View File

@@ -29,13 +29,15 @@
---@field _FONT_CACHE table
---@field _TextAlign table
---@field _ErrorHandler ErrorHandler
---@field _Performance Performance? Performance module dependency
local Renderer = {}
Renderer.__index = Renderer
--- Initialize module with shared dependencies
---@param deps table Dependencies {ErrorHandler}
---@param deps table Dependencies {ErrorHandler, Performance}
function Renderer.init(deps)
Renderer._ErrorHandler = deps.ErrorHandler
Renderer._Performance = deps.Performance
end
--- Create a new Renderer instance
@@ -138,9 +140,23 @@ function Renderer:getBlurInstance()
quality = self.backdropBlur.quality
end
-- Map string quality to numeric quality (1-10)
local numericQuality = 5 -- default medium
if type(quality) == "string" then
if quality == "low" then
numericQuality = 3
elseif quality == "medium" then
numericQuality = 5
elseif quality == "high" then
numericQuality = 8
end
elseif type(quality) == "number" then
numericQuality = quality
end
-- Create or reuse blur instance
if not self._blurInstance or self._blurInstance.quality ~= quality then
self._blurInstance = self._Blur.new(quality)
if not self._blurInstance or self._blurInstance.quality ~= numericQuality then
self._blurInstance = self._Blur.new({ quality = numericQuality })
end
return self._blurInstance
@@ -344,18 +360,17 @@ end
---@param backdropCanvas table|nil Backdrop canvas for backdrop blur
function Renderer:draw(backdropCanvas)
-- Start performance timing
local Performance = package.loaded["modules.Performance"] or package.loaded["libs.modules.Performance"]
local elementId
if Performance and Performance.isEnabled() and self._element then
if Renderer._Performance and Renderer._Performance.enabled and self._element then
elementId = self._element.id or "unnamed"
Performance.startTimer("render_" .. elementId)
Performance.incrementCounter("draw_calls", 1)
Renderer._Performance:startTimer("render_" .. elementId)
Renderer._Performance:incrementCounter("draw_calls", 1)
end
-- Early exit if element is invisible (optimization)
if self.opacity <= 0 then
if Performance and Performance.isEnabled() and elementId then
Performance.stopTimer("render_" .. elementId)
if Renderer._Performance and Renderer._Performance.enabled and elementId then
Renderer._Performance:stopTimer("render_" .. elementId)
end
return
end
@@ -415,8 +430,8 @@ function Renderer:draw(backdropCanvas)
end
-- Stop performance timing
if Performance and Performance.isEnabled() and elementId then
Performance.stopTimer("render_" .. elementId)
if Renderer._Performance and Renderer._Performance.enabled and elementId then
Renderer._Performance:stopTimer("render_" .. elementId)
end
end

View File

@@ -1,3 +1,4 @@
---@class ScrollManager
---@field overflow string -- "visible"|"hidden"|"auto"|"scroll"
---@field overflowX string? -- X-axis specific overflow (overrides overflow)
@@ -39,12 +40,17 @@
---@field _lastTouchY number -- Last touch Y position
---@field _Color table
---@field _utils table
---@field _ErrorHandler table?
---@field _ErrorHandler table? ErrorHandler module dependency
local ScrollManager = {}
ScrollManager.__index = ScrollManager
-- Lazy-loaded ErrorHandler
local ErrorHandler
--- Initialize module with shared dependencies
---@param deps table Dependencies {ErrorHandler}
function ScrollManager.init(deps)
if type(deps) == "table" then
ScrollManager._ErrorHandler = deps.ErrorHandler
end
end
--- Create a new ScrollManager instance
---@param config table Configuration options
@@ -126,12 +132,10 @@ end
--- Detect if content overflows container bounds
function ScrollManager:detectOverflow()
if not self._element then
if not ErrorHandler then
ErrorHandler = require("modules.ErrorHandler")
end
ErrorHandler.error("ScrollManager", "SYS_002", "Method called before initialization", {
ScrollManager._ErrorHandler:warn("ScrollManager", "SYS_002", "Method called before initialization", {
method = "detectOverflow"
}, "Call scrollManager:initialize(element) before using scroll methods")
return
end
local element = self._element
@@ -258,12 +262,13 @@ end
---@return table -- {vertical: {visible, trackHeight, thumbHeight, thumbY}, horizontal: {visible, trackWidth, thumbWidth, thumbX}}
function ScrollManager:calculateScrollbarDimensions()
if not self._element then
if not ErrorHandler then
ErrorHandler = require("modules.ErrorHandler")
end
ErrorHandler.error("ScrollManager", "SYS_002", "Method called before initialization", {
ScrollManager._ErrorHandler:warn("ScrollManager", "SYS_002", "Method called before initialization", {
method = "calculateScrollbarDimensions"
}, "Call scrollManager:initialize(element) before using scroll methods")
return {
vertical = { visible = false, trackHeight = 0, thumbHeight = 0, thumbY = 0 },
horizontal = { visible = false, trackWidth = 0, thumbWidth = 0, thumbX = 0 },
}
end
local element = self._element
@@ -356,12 +361,10 @@ end
---@return table|nil -- {component: "vertical"|"horizontal", region: "thumb"|"track"}
function ScrollManager:getScrollbarAtPosition(mouseX, mouseY)
if not self._element then
if not ErrorHandler then
ErrorHandler = require("modules.ErrorHandler")
end
ErrorHandler.error("ScrollManager", "SYS_002", "Method called before initialization", {
ScrollManager._ErrorHandler:warn("ScrollManager", "SYS_002", "Method called before initialization", {
method = "getScrollbarAtPosition"
}, "Call scrollManager:initialize(element) before using scroll methods")
return nil
end
local element = self._element
@@ -430,12 +433,10 @@ end
---@return boolean -- True if event was consumed
function ScrollManager:handleMousePress(mouseX, mouseY, button)
if not self._element then
if not ErrorHandler then
ErrorHandler = require("modules.ErrorHandler")
end
ErrorHandler.error("ScrollManager", "SYS_002", "Method called before initialization", {
ScrollManager._ErrorHandler:warn("ScrollManager", "SYS_002", "Method called before initialization", {
method = "handleMousePress"
}, "Call scrollManager:initialize(element) before using scroll methods")
return false
end
if button ~= 1 then