diff --git a/FlexLove.lua b/FlexLove.lua index 2077454..e38486c 100644 --- a/FlexLove.lua +++ b/FlexLove.lua @@ -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, diff --git a/modules/Element.lua b/modules/Element.lua index 7534ad5..20b449e 100644 --- a/modules/Element.lua +++ b/modules/Element.lua @@ -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 diff --git a/modules/EventHandler.lua b/modules/EventHandler.lua index 82906e5..11110e0 100644 --- a/modules/EventHandler.lua +++ b/modules/EventHandler.lua @@ -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 diff --git a/modules/LayoutEngine.lua b/modules/LayoutEngine.lua index 825fb3c..407f1bf 100644 --- a/modules/LayoutEngine.lua +++ b/modules/LayoutEngine.lua @@ -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 @@ -170,15 +178,15 @@ function LayoutEngine:layoutChildren() self:applyPositioningOffsets(child) end end - + -- Detect overflow after children positioning if self.element._detectOverflow then self.element:_detectOverflow() 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 @@ -186,10 +194,10 @@ function LayoutEngine:layoutChildren() -- Handle grid layout if self.positioning == self._Positioning.GRID then 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 @@ -210,20 +218,22 @@ function LayoutEngine:layoutChildren() local isFlexChild = not (child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute) if isFlexChild then 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")) - end + if child.units and child.units.width then + if child.units.width.unit == "%" and self.element.autosizing and self.element.autosizing.width then + 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 - 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 + end + if child.units and child.units.height then + if child.units.height.unit == "%" and self.element.autosizing and self.element.autosizing.height then + 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 @@ -599,10 +609,10 @@ function LayoutEngine:layoutChildren() if self.element._detectOverflow then self.element:_detectOverflow() 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,28 +987,26 @@ 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 - + -- Get current frame count from Context local currentFrame = self._Context and self._Context._frameNumber or 0 - + -- Reset counter on new frame if currentFrame ~= self._lastFrameCount then self._lastFrameCount = currentFrame self._layoutCount = 0 end - + -- Increment layout count self._layoutCount = self._layoutCount + 1 - + -- 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), diff --git a/modules/Renderer.lua b/modules/Renderer.lua index d591e1a..fd4875e 100644 --- a/modules/Renderer.lua +++ b/modules/Renderer.lua @@ -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 diff --git a/modules/ScrollManager.lua b/modules/ScrollManager.lua index 1f66a26..6e03b34 100644 --- a/modules/ScrollManager.lua +++ b/modules/ScrollManager.lua @@ -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