change to DI
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
--[[
|
--[[
|
||||||
Color.lua - Color utility class for FlexLove
|
|
||||||
Provides color handling with RGB/RGBA support and hex string conversion
|
Provides color handling with RGB/RGBA support and hex string conversion
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
-- ====================
|
|
||||||
-- Element Object
|
|
||||||
-- ====================
|
|
||||||
|
|
||||||
-- Setup module path for relative requires
|
|
||||||
local modulePath = (...):match("(.-)[^%.]+$")
|
local modulePath = (...):match("(.-)[^%.]+$")
|
||||||
local function req(name)
|
local function req(name)
|
||||||
return require(modulePath .. name)
|
return require(modulePath .. name)
|
||||||
@@ -204,6 +199,9 @@ function Element.new(props)
|
|||||||
-- Initialize EventHandler for event processing
|
-- Initialize EventHandler for event processing
|
||||||
self._eventHandler = EventHandler.new({
|
self._eventHandler = EventHandler.new({
|
||||||
onEvent = self.onEvent,
|
onEvent = self.onEvent,
|
||||||
|
}, {
|
||||||
|
InputEvent = InputEvent,
|
||||||
|
GuiState = GuiState,
|
||||||
})
|
})
|
||||||
self._eventHandler:initialize(self)
|
self._eventHandler:initialize(self)
|
||||||
|
|
||||||
@@ -219,6 +217,8 @@ function Element.new(props)
|
|||||||
disableHighlight = props.disableHighlight,
|
disableHighlight = props.disableHighlight,
|
||||||
scaleCorners = props.scaleCorners,
|
scaleCorners = props.scaleCorners,
|
||||||
scalingAlgorithm = props.scalingAlgorithm,
|
scalingAlgorithm = props.scalingAlgorithm,
|
||||||
|
}, {
|
||||||
|
Theme = Theme,
|
||||||
})
|
})
|
||||||
self._themeManager:initialize(self)
|
self._themeManager:initialize(self)
|
||||||
|
|
||||||
@@ -320,6 +320,11 @@ function Element.new(props)
|
|||||||
onTextInput = props.onTextInput,
|
onTextInput = props.onTextInput,
|
||||||
onTextChange = props.onTextChange,
|
onTextChange = props.onTextChange,
|
||||||
onEnter = props.onEnter,
|
onEnter = props.onEnter,
|
||||||
|
}, {
|
||||||
|
GuiState = GuiState,
|
||||||
|
StateManager = StateManager,
|
||||||
|
Color = Color,
|
||||||
|
utils = utils,
|
||||||
})
|
})
|
||||||
-- Initialize will be called after self is fully constructed
|
-- Initialize will be called after self is fully constructed
|
||||||
end
|
end
|
||||||
@@ -425,9 +430,15 @@ function Element.new(props)
|
|||||||
objectFit = self.objectFit,
|
objectFit = self.objectFit,
|
||||||
objectPosition = self.objectPosition,
|
objectPosition = self.objectPosition,
|
||||||
imageOpacity = self.imageOpacity,
|
imageOpacity = self.imageOpacity,
|
||||||
contentBlur = self.contentBlur,
|
}, {
|
||||||
backdropBlur = self.backdropBlur,
|
Color = Color,
|
||||||
_themeState = self._themeState,
|
RoundedRect = RoundedRect,
|
||||||
|
NinePatch = NinePatch,
|
||||||
|
ImageRenderer = ImageRenderer,
|
||||||
|
ImageCache = ImageCache,
|
||||||
|
Theme = Theme,
|
||||||
|
Blur = Blur,
|
||||||
|
utils = utils,
|
||||||
})
|
})
|
||||||
self._renderer:initialize(self)
|
self._renderer:initialize(self)
|
||||||
|
|
||||||
@@ -1135,6 +1146,9 @@ function Element.new(props)
|
|||||||
gridColumns = self.gridColumns,
|
gridColumns = self.gridColumns,
|
||||||
columnGap = self.columnGap,
|
columnGap = self.columnGap,
|
||||||
rowGap = self.rowGap,
|
rowGap = self.rowGap,
|
||||||
|
}, {
|
||||||
|
utils = utils,
|
||||||
|
Grid = Grid,
|
||||||
})
|
})
|
||||||
-- Initialize immediately so it can be used for auto-sizing calculations
|
-- Initialize immediately so it can be used for auto-sizing calculations
|
||||||
self._layoutEngine:initialize(self)
|
self._layoutEngine:initialize(self)
|
||||||
@@ -1158,6 +1172,8 @@ function Element.new(props)
|
|||||||
hideScrollbars = props.hideScrollbars,
|
hideScrollbars = props.hideScrollbars,
|
||||||
_scrollX = props._scrollX,
|
_scrollX = props._scrollX,
|
||||||
_scrollY = props._scrollY,
|
_scrollY = props._scrollY,
|
||||||
|
}, {
|
||||||
|
utils = utils,
|
||||||
})
|
})
|
||||||
self._scrollManager:initialize(self)
|
self._scrollManager:initialize(self)
|
||||||
|
|
||||||
@@ -1960,12 +1976,7 @@ function Element:update(dt)
|
|||||||
local anyPressed = self._eventHandler:isAnyButtonPressed()
|
local anyPressed = self._eventHandler:isAnyButtonPressed()
|
||||||
|
|
||||||
-- Update theme state via ThemeManager
|
-- Update theme state via ThemeManager
|
||||||
local newThemeState = self._themeManager:updateState(
|
local newThemeState = self._themeManager:updateState(isHovering and isActiveElement, anyPressed, self._focused, self.disabled)
|
||||||
isHovering and isActiveElement,
|
|
||||||
anyPressed,
|
|
||||||
self._focused,
|
|
||||||
self.disabled
|
|
||||||
)
|
|
||||||
|
|
||||||
-- Update state (in StateManager if in immediate mode, otherwise locally)
|
-- Update state (in StateManager if in immediate mode, otherwise locally)
|
||||||
if self._stateId and Gui._immediateMode then
|
if self._stateId and Gui._immediateMode then
|
||||||
|
|||||||
@@ -3,15 +3,16 @@
|
|||||||
-- ====================
|
-- ====================
|
||||||
-- Handles all user input events (mouse, keyboard, touch) for UI elements
|
-- Handles all user input events (mouse, keyboard, touch) for UI elements
|
||||||
-- Manages event state, click detection, drag tracking, hover, and focus
|
-- Manages event state, click detection, drag tracking, hover, and focus
|
||||||
|
---
|
||||||
|
--- Dependencies (must be injected via deps parameter):
|
||||||
|
--- - InputEvent: Input event class for creating event objects
|
||||||
|
--- - GuiState: GUI state manager (unused currently, reserved for future use)
|
||||||
|
|
||||||
local modulePath = (...):match("(.-)[^%.]+$")
|
local modulePath = (...):match("(.-)[^%.]+$")
|
||||||
local function req(name)
|
local function req(name)
|
||||||
return require(modulePath .. name)
|
return require(modulePath .. name)
|
||||||
end
|
end
|
||||||
|
|
||||||
local InputEvent = req("InputEvent")
|
|
||||||
local GuiState = req("GuiState")
|
|
||||||
|
|
||||||
-- Get keyboard modifiers helper
|
-- Get keyboard modifiers helper
|
||||||
local function getModifiers()
|
local function getModifiers()
|
||||||
return {
|
return {
|
||||||
@@ -28,12 +29,22 @@ EventHandler.__index = EventHandler
|
|||||||
|
|
||||||
--- Create a new EventHandler instance
|
--- Create a new EventHandler instance
|
||||||
---@param config table Configuration options
|
---@param config table Configuration options
|
||||||
|
---@param deps table Dependencies {InputEvent, GuiState}
|
||||||
---@return EventHandler
|
---@return EventHandler
|
||||||
function EventHandler.new(config)
|
function EventHandler.new(config, deps)
|
||||||
|
-- Pure DI: Dependencies must be injected
|
||||||
|
assert(deps, "EventHandler.new: deps parameter is required")
|
||||||
|
assert(deps.InputEvent, "EventHandler.new: deps.InputEvent is required")
|
||||||
|
assert(deps.GuiState, "EventHandler.new: deps.GuiState is required")
|
||||||
|
|
||||||
config = config or {}
|
config = config or {}
|
||||||
|
|
||||||
local self = setmetatable({}, EventHandler)
|
local self = setmetatable({}, EventHandler)
|
||||||
|
|
||||||
|
-- Store dependencies
|
||||||
|
self._InputEvent = deps.InputEvent
|
||||||
|
self._GuiState = deps.GuiState
|
||||||
|
|
||||||
-- Event callback
|
-- Event callback
|
||||||
self.onEvent = config.onEvent
|
self.onEvent = config.onEvent
|
||||||
|
|
||||||
@@ -183,7 +194,7 @@ function EventHandler:_handleMousePress(mx, my, button)
|
|||||||
-- Fire press event
|
-- Fire press event
|
||||||
if self.onEvent then
|
if self.onEvent then
|
||||||
local modifiers = getModifiers()
|
local modifiers = getModifiers()
|
||||||
local pressEvent = InputEvent.new({
|
local pressEvent = self._InputEvent.new({
|
||||||
type = "press",
|
type = "press",
|
||||||
button = button,
|
button = button,
|
||||||
x = mx,
|
x = mx,
|
||||||
@@ -229,7 +240,7 @@ function EventHandler:_handleMouseDrag(mx, my, button, isHovering)
|
|||||||
local dx = mx - self._dragStartX[button]
|
local dx = mx - self._dragStartX[button]
|
||||||
local dy = my - self._dragStartY[button]
|
local dy = my - self._dragStartY[button]
|
||||||
|
|
||||||
local dragEvent = InputEvent.new({
|
local dragEvent = self._InputEvent.new({
|
||||||
type = "drag",
|
type = "drag",
|
||||||
button = button,
|
button = button,
|
||||||
x = mx,
|
x = mx,
|
||||||
@@ -289,7 +300,7 @@ function EventHandler:_handleMouseRelease(mx, my, button)
|
|||||||
|
|
||||||
-- Fire click event
|
-- Fire click event
|
||||||
if self.onEvent then
|
if self.onEvent then
|
||||||
local clickEvent = InputEvent.new({
|
local clickEvent = self._InputEvent.new({
|
||||||
type = eventType,
|
type = eventType,
|
||||||
button = button,
|
button = button,
|
||||||
x = mx,
|
x = mx,
|
||||||
@@ -331,7 +342,7 @@ function EventHandler:_handleMouseRelease(mx, my, button)
|
|||||||
|
|
||||||
-- Fire release event
|
-- Fire release event
|
||||||
if self.onEvent then
|
if self.onEvent then
|
||||||
local releaseEvent = InputEvent.new({
|
local releaseEvent = self._InputEvent.new({
|
||||||
type = "release",
|
type = "release",
|
||||||
button = button,
|
button = button,
|
||||||
x = mx,
|
x = mx,
|
||||||
@@ -363,7 +374,7 @@ function EventHandler:processTouchEvents()
|
|||||||
self._touchPressed[id] = true
|
self._touchPressed[id] = true
|
||||||
elseif self._touchPressed[id] then
|
elseif self._touchPressed[id] then
|
||||||
-- Create touch event (treat as left click)
|
-- Create touch event (treat as left click)
|
||||||
local touchEvent = InputEvent.new({
|
local touchEvent = self._InputEvent.new({
|
||||||
type = "click",
|
type = "click",
|
||||||
button = 1,
|
button = 1,
|
||||||
x = tx,
|
x = tx,
|
||||||
|
|||||||
@@ -6,26 +6,10 @@
|
|||||||
-- - Grid layout delegation
|
-- - Grid layout delegation
|
||||||
-- - Auto-sizing calculations
|
-- - Auto-sizing calculations
|
||||||
-- - CSS positioning offsets
|
-- - CSS positioning offsets
|
||||||
|
---
|
||||||
-- Setup module path for relative requires
|
--- Dependencies (must be injected via deps parameter):
|
||||||
local modulePath = (...):match("(.-)[^%.]+$")
|
--- - utils: Utility functions and enums
|
||||||
local function req(name)
|
--- - Grid: Grid layout module
|
||||||
return require(modulePath .. name)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Module dependencies
|
|
||||||
local utils = req("utils")
|
|
||||||
local Grid = req("Grid")
|
|
||||||
|
|
||||||
-- Extract enum values
|
|
||||||
local enums = utils.enums
|
|
||||||
local Positioning = enums.Positioning
|
|
||||||
local FlexDirection = enums.FlexDirection
|
|
||||||
local JustifyContent = enums.JustifyContent
|
|
||||||
local AlignContent = enums.AlignContent
|
|
||||||
local AlignItems = enums.AlignItems
|
|
||||||
local AlignSelf = enums.AlignSelf
|
|
||||||
local FlexWrap = enums.FlexWrap
|
|
||||||
|
|
||||||
---@class LayoutEngine
|
---@class LayoutEngine
|
||||||
---@field element Element Reference to the parent element
|
---@field element Element Reference to the parent element
|
||||||
@@ -58,10 +42,36 @@ LayoutEngine.__index = LayoutEngine
|
|||||||
|
|
||||||
--- Create a new LayoutEngine instance
|
--- Create a new LayoutEngine instance
|
||||||
---@param props LayoutEngineProps
|
---@param props LayoutEngineProps
|
||||||
|
---@param deps table Dependencies {utils, Grid}
|
||||||
---@return LayoutEngine
|
---@return LayoutEngine
|
||||||
function LayoutEngine.new(props)
|
function LayoutEngine.new(props, deps)
|
||||||
|
-- Pure DI: Dependencies must be injected
|
||||||
|
assert(deps, "LayoutEngine.new: deps parameter is required")
|
||||||
|
assert(deps.utils, "LayoutEngine.new: deps.utils is required")
|
||||||
|
assert(deps.Grid, "LayoutEngine.new: deps.Grid is required")
|
||||||
|
|
||||||
|
-- Extract enums from utils
|
||||||
|
local enums = deps.utils.enums
|
||||||
|
local Positioning = enums.Positioning
|
||||||
|
local FlexDirection = enums.FlexDirection
|
||||||
|
local JustifyContent = enums.JustifyContent
|
||||||
|
local AlignContent = enums.AlignContent
|
||||||
|
local AlignItems = enums.AlignItems
|
||||||
|
local AlignSelf = enums.AlignSelf
|
||||||
|
local FlexWrap = enums.FlexWrap
|
||||||
|
|
||||||
local self = setmetatable({}, LayoutEngine)
|
local self = setmetatable({}, LayoutEngine)
|
||||||
|
|
||||||
|
-- Store dependencies for instance methods
|
||||||
|
self._Grid = deps.Grid
|
||||||
|
self._Positioning = Positioning
|
||||||
|
self._FlexDirection = FlexDirection
|
||||||
|
self._JustifyContent = JustifyContent
|
||||||
|
self._AlignContent = AlignContent
|
||||||
|
self._AlignItems = AlignItems
|
||||||
|
self._AlignSelf = AlignSelf
|
||||||
|
self._FlexWrap = FlexWrap
|
||||||
|
|
||||||
-- Layout configuration
|
-- Layout configuration
|
||||||
self.positioning = props.positioning or Positioning.FLEX
|
self.positioning = props.positioning or Positioning.FLEX
|
||||||
self.flexDirection = props.flexDirection or FlexDirection.HORIZONTAL
|
self.flexDirection = props.flexDirection or FlexDirection.HORIZONTAL
|
||||||
@@ -104,9 +114,9 @@ function LayoutEngine:applyPositioningOffsets(child)
|
|||||||
|
|
||||||
-- Only apply offsets to explicitly absolute children or children in relative/absolute containers
|
-- Only apply offsets to explicitly absolute children or children in relative/absolute containers
|
||||||
-- Flex/grid children ignore positioning offsets as they participate in layout
|
-- Flex/grid children ignore positioning offsets as they participate in layout
|
||||||
local isFlexChild = child.positioning == Positioning.FLEX
|
local isFlexChild = child.positioning == self._Positioning.FLEX
|
||||||
or child.positioning == Positioning.GRID
|
or child.positioning == self._Positioning.GRID
|
||||||
or (child.positioning == Positioning.ABSOLUTE and not child._explicitlyAbsolute)
|
or (child.positioning == self._Positioning.ABSOLUTE and not child._explicitlyAbsolute)
|
||||||
|
|
||||||
if not isFlexChild then
|
if not isFlexChild then
|
||||||
-- Apply absolute positioning for explicitly absolute children
|
-- Apply absolute positioning for explicitly absolute children
|
||||||
@@ -140,7 +150,7 @@ end
|
|||||||
function LayoutEngine:layoutChildren()
|
function LayoutEngine:layoutChildren()
|
||||||
local element = self.element
|
local element = self.element
|
||||||
|
|
||||||
if self.positioning == Positioning.ABSOLUTE or self.positioning == Positioning.RELATIVE then
|
if self.positioning == self._Positioning.ABSOLUTE or self.positioning == self._Positioning.RELATIVE then
|
||||||
-- Absolute/Relative positioned containers don't layout their children according to flex rules,
|
-- Absolute/Relative positioned containers don't layout their children according to flex rules,
|
||||||
-- but they should still apply CSS positioning offsets to their children
|
-- but they should still apply CSS positioning offsets to their children
|
||||||
for _, child in ipairs(element.children) do
|
for _, child in ipairs(element.children) do
|
||||||
@@ -152,8 +162,8 @@ function LayoutEngine:layoutChildren()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Handle grid layout
|
-- Handle grid layout
|
||||||
if self.positioning == Positioning.GRID then
|
if self.positioning == self._Positioning.GRID then
|
||||||
Grid.layoutGridItems(element)
|
self._Grid.layoutGridItems(element)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -166,7 +176,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
-- Get flex children (children that participate in flex layout)
|
-- Get flex children (children that participate in flex layout)
|
||||||
local flexChildren = {}
|
local flexChildren = {}
|
||||||
for _, child in ipairs(element.children) do
|
for _, child in ipairs(element.children) do
|
||||||
local isFlexChild = not (child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute)
|
local isFlexChild = not (child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute)
|
||||||
if isFlexChild then
|
if isFlexChild then
|
||||||
table.insert(flexChildren, child)
|
table.insert(flexChildren, child)
|
||||||
end
|
end
|
||||||
@@ -184,12 +194,12 @@ function LayoutEngine:layoutChildren()
|
|||||||
|
|
||||||
for _, child in ipairs(element.children) do
|
for _, child in ipairs(element.children) do
|
||||||
-- Only consider absolutely positioned children with explicit positioning
|
-- Only consider absolutely positioned children with explicit positioning
|
||||||
if child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute then
|
if child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute then
|
||||||
-- BORDER-BOX MODEL: Use border-box dimensions for space calculations
|
-- BORDER-BOX MODEL: Use border-box dimensions for space calculations
|
||||||
local childBorderBoxWidth = child:getBorderBoxWidth()
|
local childBorderBoxWidth = child:getBorderBoxWidth()
|
||||||
local childBorderBoxHeight = child:getBorderBoxHeight()
|
local childBorderBoxHeight = child:getBorderBoxHeight()
|
||||||
|
|
||||||
if self.flexDirection == FlexDirection.HORIZONTAL then
|
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
||||||
-- Horizontal layout: main axis is X, cross axis is Y
|
-- Horizontal layout: main axis is X, cross axis is Y
|
||||||
-- Check for left positioning (reserves space at main axis start)
|
-- Check for left positioning (reserves space at main axis start)
|
||||||
if child.left then
|
if child.left then
|
||||||
@@ -241,7 +251,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
-- BORDER-BOX MODEL: element.width and element.height are already content dimensions (padding subtracted)
|
-- BORDER-BOX MODEL: element.width and element.height are already content dimensions (padding subtracted)
|
||||||
local availableMainSize = 0
|
local availableMainSize = 0
|
||||||
local availableCrossSize = 0
|
local availableCrossSize = 0
|
||||||
if self.flexDirection == FlexDirection.HORIZONTAL then
|
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
||||||
availableMainSize = element.width - reservedMainStart - reservedMainEnd
|
availableMainSize = element.width - reservedMainStart - reservedMainEnd
|
||||||
availableCrossSize = element.height - reservedCrossStart - reservedCrossEnd
|
availableCrossSize = element.height - reservedCrossStart - reservedCrossEnd
|
||||||
else
|
else
|
||||||
@@ -252,7 +262,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
-- Handle flex wrap: create lines of children
|
-- Handle flex wrap: create lines of children
|
||||||
local lines = {}
|
local lines = {}
|
||||||
|
|
||||||
if self.flexWrap == FlexWrap.NOWRAP then
|
if self.flexWrap == self._FlexWrap.NOWRAP then
|
||||||
-- All children go on one line
|
-- All children go on one line
|
||||||
lines[1] = flexChildren
|
lines[1] = flexChildren
|
||||||
else
|
else
|
||||||
@@ -265,7 +275,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
-- Include margins in size calculations
|
-- Include margins in size calculations
|
||||||
local childMainSize = 0
|
local childMainSize = 0
|
||||||
local childMainMargin = 0
|
local childMainMargin = 0
|
||||||
if self.flexDirection == FlexDirection.HORIZONTAL then
|
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
||||||
childMainSize = child:getBorderBoxWidth()
|
childMainSize = child:getBorderBoxWidth()
|
||||||
childMainMargin = child.margin.left + child.margin.right
|
childMainMargin = child.margin.left + child.margin.right
|
||||||
else
|
else
|
||||||
@@ -296,7 +306,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Handle wrap-reverse: reverse the order of lines
|
-- Handle wrap-reverse: reverse the order of lines
|
||||||
if self.flexWrap == FlexWrap.WRAP_REVERSE then
|
if self.flexWrap == self._FlexWrap.WRAP_REVERSE then
|
||||||
local reversedLines = {}
|
local reversedLines = {}
|
||||||
for i = #lines, 1, -1 do
|
for i = #lines, 1, -1 do
|
||||||
table.insert(reversedLines, lines[i])
|
table.insert(reversedLines, lines[i])
|
||||||
@@ -316,7 +326,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
-- Include margins in cross-axis size calculations
|
-- Include margins in cross-axis size calculations
|
||||||
local childCrossSize = 0
|
local childCrossSize = 0
|
||||||
local childCrossMargin = 0
|
local childCrossMargin = 0
|
||||||
if self.flexDirection == FlexDirection.HORIZONTAL then
|
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
||||||
childCrossSize = child:getBorderBoxHeight()
|
childCrossSize = child:getBorderBoxHeight()
|
||||||
childCrossMargin = child.margin.top + child.margin.bottom
|
childCrossMargin = child.margin.top + child.margin.bottom
|
||||||
else
|
else
|
||||||
@@ -336,7 +346,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
|
|
||||||
-- For single line layouts, CENTER, FLEX_END and STRETCH should use full cross size
|
-- For single line layouts, CENTER, FLEX_END and STRETCH should use full cross size
|
||||||
if #lines == 1 then
|
if #lines == 1 then
|
||||||
if self.alignItems == AlignItems.STRETCH or self.alignItems == AlignItems.CENTER or self.alignItems == AlignItems.FLEX_END then
|
if self.alignItems == self._AlignItems.STRETCH or self.alignItems == self._AlignItems.CENTER or self.alignItems == self._AlignItems.FLEX_END then
|
||||||
-- STRETCH, CENTER, and FLEX_END should use full available cross size
|
-- STRETCH, CENTER, and FLEX_END should use full available cross size
|
||||||
lineHeights[1] = availableCrossSize
|
lineHeights[1] = availableCrossSize
|
||||||
totalLinesHeight = availableCrossSize
|
totalLinesHeight = availableCrossSize
|
||||||
@@ -351,22 +361,22 @@ function LayoutEngine:layoutChildren()
|
|||||||
local freeLineSpace = availableCrossSize - totalLinesHeight
|
local freeLineSpace = availableCrossSize - totalLinesHeight
|
||||||
|
|
||||||
-- Apply AlignContent logic for both single and multiple lines
|
-- Apply AlignContent logic for both single and multiple lines
|
||||||
if self.alignContent == AlignContent.FLEX_START then
|
if self.alignContent == self._AlignContent.FLEX_START then
|
||||||
lineStartPos = 0
|
lineStartPos = 0
|
||||||
elseif self.alignContent == AlignContent.CENTER then
|
elseif self.alignContent == self._AlignContent.CENTER then
|
||||||
lineStartPos = freeLineSpace / 2
|
lineStartPos = freeLineSpace / 2
|
||||||
elseif self.alignContent == AlignContent.FLEX_END then
|
elseif self.alignContent == self._AlignContent.FLEX_END then
|
||||||
lineStartPos = freeLineSpace
|
lineStartPos = freeLineSpace
|
||||||
elseif self.alignContent == AlignContent.SPACE_BETWEEN then
|
elseif self.alignContent == self._AlignContent.SPACE_BETWEEN then
|
||||||
lineStartPos = 0
|
lineStartPos = 0
|
||||||
if #lines > 1 then
|
if #lines > 1 then
|
||||||
lineSpacing = self.gap + (freeLineSpace / (#lines - 1))
|
lineSpacing = self.gap + (freeLineSpace / (#lines - 1))
|
||||||
end
|
end
|
||||||
elseif self.alignContent == AlignContent.SPACE_AROUND then
|
elseif self.alignContent == self._AlignContent.SPACE_AROUND then
|
||||||
local spaceAroundEach = freeLineSpace / #lines
|
local spaceAroundEach = freeLineSpace / #lines
|
||||||
lineStartPos = spaceAroundEach / 2
|
lineStartPos = spaceAroundEach / 2
|
||||||
lineSpacing = self.gap + spaceAroundEach
|
lineSpacing = self.gap + spaceAroundEach
|
||||||
elseif self.alignContent == AlignContent.STRETCH then
|
elseif self.alignContent == self._AlignContent.STRETCH then
|
||||||
lineStartPos = 0
|
lineStartPos = 0
|
||||||
if #lines > 1 and freeLineSpace > 0 then
|
if #lines > 1 and freeLineSpace > 0 then
|
||||||
lineSpacing = self.gap + (freeLineSpace / #lines)
|
lineSpacing = self.gap + (freeLineSpace / #lines)
|
||||||
@@ -388,7 +398,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
-- BORDER-BOX MODEL: Use border-box dimensions for layout calculations
|
-- BORDER-BOX MODEL: Use border-box dimensions for layout calculations
|
||||||
local totalChildrenSize = 0
|
local totalChildrenSize = 0
|
||||||
for _, child in ipairs(line) do
|
for _, child in ipairs(line) do
|
||||||
if self.flexDirection == FlexDirection.HORIZONTAL then
|
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
||||||
totalChildrenSize = totalChildrenSize + child:getBorderBoxWidth() + child.margin.left + child.margin.right
|
totalChildrenSize = totalChildrenSize + child:getBorderBoxWidth() + child.margin.left + child.margin.right
|
||||||
else
|
else
|
||||||
totalChildrenSize = totalChildrenSize + child:getBorderBoxHeight() + child.margin.top + child.margin.bottom
|
totalChildrenSize = totalChildrenSize + child:getBorderBoxHeight() + child.margin.top + child.margin.bottom
|
||||||
@@ -403,22 +413,22 @@ function LayoutEngine:layoutChildren()
|
|||||||
local startPos = 0
|
local startPos = 0
|
||||||
local itemSpacing = self.gap
|
local itemSpacing = self.gap
|
||||||
|
|
||||||
if self.justifyContent == JustifyContent.FLEX_START then
|
if self.justifyContent == self._JustifyContent.FLEX_START then
|
||||||
startPos = 0
|
startPos = 0
|
||||||
elseif self.justifyContent == JustifyContent.CENTER then
|
elseif self.justifyContent == self._JustifyContent.CENTER then
|
||||||
startPos = freeSpace / 2
|
startPos = freeSpace / 2
|
||||||
elseif self.justifyContent == JustifyContent.FLEX_END then
|
elseif self.justifyContent == self._JustifyContent.FLEX_END then
|
||||||
startPos = freeSpace
|
startPos = freeSpace
|
||||||
elseif self.justifyContent == JustifyContent.SPACE_BETWEEN then
|
elseif self.justifyContent == self._JustifyContent.SPACE_BETWEEN then
|
||||||
startPos = 0
|
startPos = 0
|
||||||
if #line > 1 then
|
if #line > 1 then
|
||||||
itemSpacing = self.gap + (freeSpace / (#line - 1))
|
itemSpacing = self.gap + (freeSpace / (#line - 1))
|
||||||
end
|
end
|
||||||
elseif self.justifyContent == JustifyContent.SPACE_AROUND then
|
elseif self.justifyContent == self._JustifyContent.SPACE_AROUND then
|
||||||
local spaceAroundEach = freeSpace / #line
|
local spaceAroundEach = freeSpace / #line
|
||||||
startPos = spaceAroundEach / 2
|
startPos = spaceAroundEach / 2
|
||||||
itemSpacing = self.gap + spaceAroundEach
|
itemSpacing = self.gap + spaceAroundEach
|
||||||
elseif self.justifyContent == JustifyContent.SPACE_EVENLY then
|
elseif self.justifyContent == self._JustifyContent.SPACE_EVENLY then
|
||||||
local spaceBetween = freeSpace / (#line + 1)
|
local spaceBetween = freeSpace / (#line + 1)
|
||||||
startPos = spaceBetween
|
startPos = spaceBetween
|
||||||
itemSpacing = self.gap + spaceBetween
|
itemSpacing = self.gap + spaceBetween
|
||||||
@@ -430,11 +440,11 @@ function LayoutEngine:layoutChildren()
|
|||||||
for _, child in ipairs(line) do
|
for _, child in ipairs(line) do
|
||||||
-- Determine effective cross-axis alignment
|
-- Determine effective cross-axis alignment
|
||||||
local effectiveAlign = child.alignSelf
|
local effectiveAlign = child.alignSelf
|
||||||
if effectiveAlign == nil or effectiveAlign == AlignSelf.AUTO then
|
if effectiveAlign == nil or effectiveAlign == self._AlignSelf.AUTO then
|
||||||
effectiveAlign = self.alignItems
|
effectiveAlign = self.alignItems
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.flexDirection == FlexDirection.HORIZONTAL then
|
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
||||||
-- Horizontal layout: main axis is X, cross axis is Y
|
-- Horizontal layout: main axis is X, cross axis is Y
|
||||||
-- Position child at border box (x, y represents top-left including padding)
|
-- Position child at border box (x, y represents top-left including padding)
|
||||||
-- Add reservedMainStart and left margin to account for absolutely positioned siblings and margins
|
-- Add reservedMainStart and left margin to account for absolutely positioned siblings and margins
|
||||||
@@ -444,13 +454,13 @@ function LayoutEngine:layoutChildren()
|
|||||||
local childBorderBoxHeight = child:getBorderBoxHeight()
|
local childBorderBoxHeight = child:getBorderBoxHeight()
|
||||||
local childTotalCrossSize = childBorderBoxHeight + child.margin.top + child.margin.bottom
|
local childTotalCrossSize = childBorderBoxHeight + child.margin.top + child.margin.bottom
|
||||||
|
|
||||||
if effectiveAlign == AlignItems.FLEX_START then
|
if effectiveAlign == self._AlignItems.FLEX_START then
|
||||||
child.y = element.y + element.padding.top + reservedCrossStart + currentCrossPos + child.margin.top
|
child.y = element.y + element.padding.top + reservedCrossStart + currentCrossPos + child.margin.top
|
||||||
elseif effectiveAlign == AlignItems.CENTER then
|
elseif effectiveAlign == self._AlignItems.CENTER then
|
||||||
child.y = element.y + element.padding.top + reservedCrossStart + currentCrossPos + ((lineHeight - childTotalCrossSize) / 2) + child.margin.top
|
child.y = element.y + element.padding.top + reservedCrossStart + currentCrossPos + ((lineHeight - childTotalCrossSize) / 2) + child.margin.top
|
||||||
elseif effectiveAlign == AlignItems.FLEX_END then
|
elseif effectiveAlign == self._AlignItems.FLEX_END then
|
||||||
child.y = element.y + element.padding.top + reservedCrossStart + currentCrossPos + lineHeight - childTotalCrossSize + child.margin.top
|
child.y = element.y + element.padding.top + reservedCrossStart + currentCrossPos + lineHeight - childTotalCrossSize + child.margin.top
|
||||||
elseif effectiveAlign == AlignItems.STRETCH then
|
elseif effectiveAlign == self._AlignItems.STRETCH then
|
||||||
-- STRETCH: Only apply if height was not explicitly set
|
-- STRETCH: Only apply if height was not explicitly set
|
||||||
if child.autosizing and child.autosizing.height then
|
if child.autosizing and child.autosizing.height then
|
||||||
-- STRETCH: Set border-box height to lineHeight minus margins, content area shrinks to fit
|
-- STRETCH: Set border-box height to lineHeight minus margins, content area shrinks to fit
|
||||||
@@ -481,13 +491,13 @@ function LayoutEngine:layoutChildren()
|
|||||||
local childBorderBoxWidth = child:getBorderBoxWidth()
|
local childBorderBoxWidth = child:getBorderBoxWidth()
|
||||||
local childTotalCrossSize = childBorderBoxWidth + child.margin.left + child.margin.right
|
local childTotalCrossSize = childBorderBoxWidth + child.margin.left + child.margin.right
|
||||||
|
|
||||||
if effectiveAlign == AlignItems.FLEX_START then
|
if effectiveAlign == self._AlignItems.FLEX_START then
|
||||||
child.x = element.x + element.padding.left + reservedCrossStart + currentCrossPos + child.margin.left
|
child.x = element.x + element.padding.left + reservedCrossStart + currentCrossPos + child.margin.left
|
||||||
elseif effectiveAlign == AlignItems.CENTER then
|
elseif effectiveAlign == self._AlignItems.CENTER then
|
||||||
child.x = element.x + element.padding.left + reservedCrossStart + currentCrossPos + ((lineHeight - childTotalCrossSize) / 2) + child.margin.left
|
child.x = element.x + element.padding.left + reservedCrossStart + currentCrossPos + ((lineHeight - childTotalCrossSize) / 2) + child.margin.left
|
||||||
elseif effectiveAlign == AlignItems.FLEX_END then
|
elseif effectiveAlign == self._AlignItems.FLEX_END then
|
||||||
child.x = element.x + element.padding.left + reservedCrossStart + currentCrossPos + lineHeight - childTotalCrossSize + child.margin.left
|
child.x = element.x + element.padding.left + reservedCrossStart + currentCrossPos + lineHeight - childTotalCrossSize + child.margin.left
|
||||||
elseif effectiveAlign == AlignItems.STRETCH then
|
elseif effectiveAlign == self._AlignItems.STRETCH then
|
||||||
-- STRETCH: Only apply if width was not explicitly set
|
-- STRETCH: Only apply if width was not explicitly set
|
||||||
if child.autosizing and child.autosizing.width then
|
if child.autosizing and child.autosizing.width then
|
||||||
-- STRETCH: Set border-box width to lineHeight minus margins, content area shrinks to fit
|
-- STRETCH: Set border-box width to lineHeight minus margins, content area shrinks to fit
|
||||||
@@ -517,7 +527,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
|
|
||||||
-- Position explicitly absolute children after flex layout
|
-- Position explicitly absolute children after flex layout
|
||||||
for _, child in ipairs(element.children) do
|
for _, child in ipairs(element.children) do
|
||||||
if child.positioning == Positioning.ABSOLUTE and child._explicitlyAbsolute then
|
if child.positioning == self._Positioning.ABSOLUTE and child._explicitlyAbsolute then
|
||||||
-- Apply positioning offsets (top, right, bottom, left)
|
-- Apply positioning offsets (top, right, bottom, left)
|
||||||
self:applyPositioningOffsets(child)
|
self:applyPositioningOffsets(child)
|
||||||
|
|
||||||
@@ -547,7 +557,7 @@ function LayoutEngine:calculateAutoWidth()
|
|||||||
|
|
||||||
-- For HORIZONTAL flex: sum children widths + gaps
|
-- For HORIZONTAL flex: sum children widths + gaps
|
||||||
-- For VERTICAL flex: max of children widths
|
-- For VERTICAL flex: max of children widths
|
||||||
local isHorizontal = self.flexDirection == FlexDirection.HORIZONTAL
|
local isHorizontal = self.flexDirection == self._FlexDirection.HORIZONTAL
|
||||||
local totalWidth = contentWidth
|
local totalWidth = contentWidth
|
||||||
local maxWidth = contentWidth
|
local maxWidth = contentWidth
|
||||||
local participatingChildren = 0
|
local participatingChildren = 0
|
||||||
@@ -587,7 +597,7 @@ function LayoutEngine:calculateAutoHeight()
|
|||||||
|
|
||||||
-- For VERTICAL flex: sum children heights + gaps
|
-- For VERTICAL flex: sum children heights + gaps
|
||||||
-- For HORIZONTAL flex: max of children heights
|
-- For HORIZONTAL flex: max of children heights
|
||||||
local isVertical = self.flexDirection == FlexDirection.VERTICAL
|
local isVertical = self.flexDirection == self._FlexDirection.VERTICAL
|
||||||
local totalHeight = height
|
local totalHeight = height
|
||||||
local maxHeight = height
|
local maxHeight = height
|
||||||
local participatingChildren = 0
|
local participatingChildren = 0
|
||||||
|
|||||||
@@ -4,37 +4,53 @@
|
|||||||
--
|
--
|
||||||
-- This module is responsible for the visual presentation layer of Elements,
|
-- This module is responsible for the visual presentation layer of Elements,
|
||||||
-- delegating from Element's draw() method to keep rendering concerns separated.
|
-- delegating from Element's draw() method to keep rendering concerns separated.
|
||||||
|
---
|
||||||
-- Setup module path for relative requires
|
--- Dependencies (must be injected via deps parameter):
|
||||||
local modulePath = (...):match("(.-)[^%.]+$")
|
--- - Color: Color module for color manipulation
|
||||||
local function req(name)
|
--- - RoundedRect: Rounded rectangle drawing module
|
||||||
return require(modulePath .. name)
|
--- - NinePatch: 9-patch rendering module
|
||||||
end
|
--- - ImageRenderer: Image rendering module
|
||||||
|
--- - ImageCache: Image caching module
|
||||||
|
--- - Theme: Theme management module
|
||||||
|
--- - Blur: Blur effects module
|
||||||
|
--- - utils: Utility functions (FONT_CACHE, enums)
|
||||||
|
|
||||||
local Renderer = {}
|
local Renderer = {}
|
||||||
Renderer.__index = Renderer
|
Renderer.__index = Renderer
|
||||||
|
|
||||||
-- Dependencies
|
|
||||||
local Color = req("Color")
|
|
||||||
local RoundedRect = req("RoundedRect")
|
|
||||||
local NinePatch = req("NinePatch")
|
|
||||||
local ImageRenderer = req("ImageRenderer")
|
|
||||||
local ImageCache = req("ImageCache")
|
|
||||||
local Theme = req("Theme")
|
|
||||||
local Blur = req("Blur")
|
|
||||||
local utils = req("utils")
|
|
||||||
|
|
||||||
-- Font cache and enums (shared with Element for now - could be refactored later)
|
|
||||||
local FONT_CACHE = utils.FONT_CACHE
|
|
||||||
local enums = utils.enums
|
|
||||||
local TextAlign = enums.TextAlign
|
|
||||||
|
|
||||||
--- Create a new Renderer instance
|
--- Create a new Renderer instance
|
||||||
---@param config table Configuration table with rendering properties
|
---@param config table Configuration table with rendering properties
|
||||||
|
---@param deps table Dependencies {Color, RoundedRect, NinePatch, ImageRenderer, ImageCache, Theme, Blur, utils}
|
||||||
---@return table Renderer instance
|
---@return table Renderer instance
|
||||||
function Renderer.new(config)
|
function Renderer.new(config, deps)
|
||||||
|
-- Pure DI: Dependencies must be injected
|
||||||
|
assert(deps, "Renderer.new: deps parameter is required")
|
||||||
|
assert(deps.Color, "Renderer.new: deps.Color is required")
|
||||||
|
assert(deps.RoundedRect, "Renderer.new: deps.RoundedRect is required")
|
||||||
|
assert(deps.NinePatch, "Renderer.new: deps.NinePatch is required")
|
||||||
|
assert(deps.ImageRenderer, "Renderer.new: deps.ImageRenderer is required")
|
||||||
|
assert(deps.ImageCache, "Renderer.new: deps.ImageCache is required")
|
||||||
|
assert(deps.Theme, "Renderer.new: deps.Theme is required")
|
||||||
|
assert(deps.Blur, "Renderer.new: deps.Blur is required")
|
||||||
|
assert(deps.utils, "Renderer.new: deps.utils is required")
|
||||||
|
|
||||||
|
local Color = deps.Color
|
||||||
|
local ImageCache = deps.ImageCache
|
||||||
|
|
||||||
local self = setmetatable({}, Renderer)
|
local self = setmetatable({}, Renderer)
|
||||||
|
|
||||||
|
-- Store dependencies for instance methods
|
||||||
|
self._Color = Color
|
||||||
|
self._RoundedRect = deps.RoundedRect
|
||||||
|
self._NinePatch = deps.NinePatch
|
||||||
|
self._ImageRenderer = deps.ImageRenderer
|
||||||
|
self._ImageCache = ImageCache
|
||||||
|
self._Theme = deps.Theme
|
||||||
|
self._Blur = deps.Blur
|
||||||
|
self._utils = deps.utils
|
||||||
|
self._FONT_CACHE = deps.utils.FONT_CACHE
|
||||||
|
self._TextAlign = deps.utils.enums.TextAlign
|
||||||
|
|
||||||
-- Store reference to parent element (will be set via initialize)
|
-- Store reference to parent element (will be set via initialize)
|
||||||
self._element = nil
|
self._element = nil
|
||||||
|
|
||||||
@@ -113,7 +129,7 @@ function Renderer:getBlurInstance()
|
|||||||
|
|
||||||
-- Create or reuse blur instance
|
-- Create or reuse blur instance
|
||||||
if not self._blurInstance or self._blurInstance.quality ~= quality then
|
if not self._blurInstance or self._blurInstance.quality ~= quality then
|
||||||
self._blurInstance = Blur.new(quality)
|
self._blurInstance = self._Blur.new(quality)
|
||||||
end
|
end
|
||||||
|
|
||||||
return self._blurInstance
|
return self._blurInstance
|
||||||
@@ -132,14 +148,14 @@ end
|
|||||||
---@param height number Height
|
---@param height number Height
|
||||||
---@param drawBackgroundColor table Background color (may have animation applied)
|
---@param drawBackgroundColor table Background color (may have animation applied)
|
||||||
function Renderer:_drawBackground(x, y, width, height, drawBackgroundColor)
|
function Renderer:_drawBackground(x, y, width, height, drawBackgroundColor)
|
||||||
local backgroundWithOpacity = Color.new(
|
local backgroundWithOpacity = self._Color.new(
|
||||||
drawBackgroundColor.r,
|
drawBackgroundColor.r,
|
||||||
drawBackgroundColor.g,
|
drawBackgroundColor.g,
|
||||||
drawBackgroundColor.b,
|
drawBackgroundColor.b,
|
||||||
drawBackgroundColor.a * self.opacity
|
drawBackgroundColor.a * self.opacity
|
||||||
)
|
)
|
||||||
love.graphics.setColor(backgroundWithOpacity:toRGBA())
|
love.graphics.setColor(backgroundWithOpacity:toRGBA())
|
||||||
RoundedRect.draw("fill", x, y, width, height, self.cornerRadius)
|
self._RoundedRect.draw("fill", x, y, width, height, self.cornerRadius)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Draw image layer
|
--- Draw image layer
|
||||||
@@ -174,13 +190,13 @@ function Renderer:_drawImage(x, y, paddingLeft, paddingTop, contentWidth, conten
|
|||||||
if hasCornerRadius then
|
if hasCornerRadius then
|
||||||
-- Use stencil to clip image to rounded corners
|
-- Use stencil to clip image to rounded corners
|
||||||
love.graphics.stencil(function()
|
love.graphics.stencil(function()
|
||||||
RoundedRect.draw("fill", x, y, borderBoxWidth, borderBoxHeight, self.cornerRadius)
|
self._RoundedRect.draw("fill", x, y, borderBoxWidth, borderBoxHeight, self.cornerRadius)
|
||||||
end, "replace", 1)
|
end, "replace", 1)
|
||||||
love.graphics.setStencilTest("greater", 0)
|
love.graphics.setStencilTest("greater", 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw the image
|
-- Draw the image
|
||||||
ImageRenderer.draw(self._loadedImage, imageX, imageY, imageWidth, imageHeight, self.objectFit, self.objectPosition, finalOpacity)
|
self._ImageRenderer.draw(self._loadedImage, imageX, imageY, imageWidth, imageHeight, self.objectFit, self.objectPosition, finalOpacity)
|
||||||
|
|
||||||
-- Clear stencil if it was used
|
-- Clear stencil if it was used
|
||||||
if hasCornerRadius then
|
if hasCornerRadius then
|
||||||
@@ -204,18 +220,18 @@ function Renderer:_drawTheme(x, y, borderBoxWidth, borderBoxHeight, scaleCorners
|
|||||||
local themeToUse = nil
|
local themeToUse = nil
|
||||||
if self.theme then
|
if self.theme then
|
||||||
-- Element specifies a specific theme - load it if needed
|
-- Element specifies a specific theme - load it if needed
|
||||||
if Theme.get(self.theme) then
|
if self._Theme.get(self.theme) then
|
||||||
themeToUse = Theme.get(self.theme)
|
themeToUse = self._Theme.get(self.theme)
|
||||||
else
|
else
|
||||||
-- Try to load the theme
|
-- Try to load the theme
|
||||||
pcall(function()
|
pcall(function()
|
||||||
Theme.load(self.theme)
|
self._Theme.load(self.theme)
|
||||||
end)
|
end)
|
||||||
themeToUse = Theme.get(self.theme)
|
themeToUse = self._Theme.get(self.theme)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Use active theme
|
-- Use active theme
|
||||||
themeToUse = Theme.getActive()
|
themeToUse = self._Theme.getActive()
|
||||||
end
|
end
|
||||||
|
|
||||||
if not themeToUse then
|
if not themeToUse then
|
||||||
@@ -251,7 +267,7 @@ function Renderer:_drawTheme(x, y, borderBoxWidth, borderBoxHeight, scaleCorners
|
|||||||
|
|
||||||
if hasAllRegions then
|
if hasAllRegions then
|
||||||
-- Pass element-level overrides for scaleCorners and scalingAlgorithm
|
-- Pass element-level overrides for scaleCorners and scalingAlgorithm
|
||||||
NinePatch.draw(component, atlasToUse, x, y, borderBoxWidth, borderBoxHeight, self.opacity, scaleCorners, scalingAlgorithm)
|
self._NinePatch.draw(component, atlasToUse, x, y, borderBoxWidth, borderBoxHeight, self.opacity, scaleCorners, scalingAlgorithm)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -262,7 +278,7 @@ end
|
|||||||
---@param borderBoxWidth number Border box width
|
---@param borderBoxWidth number Border box width
|
||||||
---@param borderBoxHeight number Border box height
|
---@param borderBoxHeight number Border box height
|
||||||
function Renderer:_drawBorders(x, y, borderBoxWidth, borderBoxHeight)
|
function Renderer:_drawBorders(x, y, borderBoxWidth, borderBoxHeight)
|
||||||
local borderColorWithOpacity = Color.new(
|
local borderColorWithOpacity = self._Color.new(
|
||||||
self.borderColor.r,
|
self.borderColor.r,
|
||||||
self.borderColor.g,
|
self.borderColor.g,
|
||||||
self.borderColor.b,
|
self.borderColor.b,
|
||||||
@@ -275,7 +291,7 @@ function Renderer:_drawBorders(x, y, borderBoxWidth, borderBoxHeight)
|
|||||||
|
|
||||||
if allBorders then
|
if allBorders then
|
||||||
-- Draw complete rounded rectangle border
|
-- Draw complete rounded rectangle border
|
||||||
RoundedRect.draw("line", x, y, borderBoxWidth, borderBoxHeight, self.cornerRadius)
|
self._RoundedRect.draw("line", x, y, borderBoxWidth, borderBoxHeight, self.cornerRadius)
|
||||||
else
|
else
|
||||||
-- Draw individual borders (without rounded corners for partial borders)
|
-- Draw individual borders (without rounded corners for partial borders)
|
||||||
if self.border.top then
|
if self.border.top then
|
||||||
@@ -313,7 +329,7 @@ function Renderer:draw(backdropCanvas)
|
|||||||
if element.animation then
|
if element.animation then
|
||||||
local anim = element.animation:interpolate()
|
local anim = element.animation:interpolate()
|
||||||
if anim.opacity then
|
if anim.opacity then
|
||||||
drawBackgroundColor = Color.new(self.backgroundColor.r, self.backgroundColor.g, self.backgroundColor.b, anim.opacity)
|
drawBackgroundColor = self._Color.new(self.backgroundColor.r, self.backgroundColor.g, self.backgroundColor.b, anim.opacity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -325,7 +341,7 @@ function Renderer:draw(backdropCanvas)
|
|||||||
if self.backdropBlur and self.backdropBlur.intensity > 0 and backdropCanvas then
|
if self.backdropBlur and self.backdropBlur.intensity > 0 and backdropCanvas then
|
||||||
local blurInstance = self:getBlurInstance()
|
local blurInstance = self:getBlurInstance()
|
||||||
if blurInstance then
|
if blurInstance then
|
||||||
Blur.applyBackdrop(blurInstance, self.backdropBlur.intensity, element.x, element.y, borderBoxWidth, borderBoxHeight, backdropCanvas)
|
self._Blur.applyBackdrop(blurInstance, self.backdropBlur.intensity, element.x, element.y, borderBoxWidth, borderBoxHeight, backdropCanvas)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -366,7 +382,7 @@ function Renderer:getFont(element)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return FONT_CACHE.getFont(element.textSize, fontPath)
|
return self._FONT_CACHE.getFont(element.textSize, fontPath)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Wrap a line of text based on element's textWrap mode
|
--- Wrap a line of text based on element's textWrap mode
|
||||||
@@ -591,9 +607,9 @@ function Renderer:drawText(element)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if displayText and displayText ~= "" then
|
if displayText and displayText ~= "" then
|
||||||
local textColor = isPlaceholder and Color.new(element.textColor.r * 0.5, element.textColor.g * 0.5, element.textColor.b * 0.5, element.textColor.a * 0.5)
|
local textColor = isPlaceholder and self._Color.new(element.textColor.r * 0.5, element.textColor.g * 0.5, element.textColor.b * 0.5, element.textColor.a * 0.5)
|
||||||
or element.textColor
|
or element.textColor
|
||||||
local textColorWithOpacity = Color.new(textColor.r, textColor.g, textColor.b, textColor.a * self.opacity)
|
local textColorWithOpacity = self._Color.new(textColor.r, textColor.g, textColor.b, textColor.a * self.opacity)
|
||||||
love.graphics.setColor(textColorWithOpacity:toRGBA())
|
love.graphics.setColor(textColorWithOpacity:toRGBA())
|
||||||
|
|
||||||
local origFont = love.graphics.getFont()
|
local origFont = love.graphics.getFont()
|
||||||
@@ -602,7 +618,7 @@ function Renderer:drawText(element)
|
|||||||
local fontPath = nil
|
local fontPath = nil
|
||||||
if element.fontFamily then
|
if element.fontFamily then
|
||||||
-- Check if fontFamily is a theme font name
|
-- Check if fontFamily is a theme font name
|
||||||
local themeToUse = element.theme and Theme.get(element.theme) or Theme.getActive()
|
local themeToUse = element.theme and self._Theme.get(element.theme) or self._Theme.getActive()
|
||||||
if themeToUse and themeToUse.fonts and themeToUse.fonts[element.fontFamily] then
|
if themeToUse and themeToUse.fonts and themeToUse.fonts[element.fontFamily] then
|
||||||
fontPath = themeToUse.fonts[element.fontFamily]
|
fontPath = themeToUse.fonts[element.fontFamily]
|
||||||
else
|
else
|
||||||
@@ -611,14 +627,14 @@ function Renderer:drawText(element)
|
|||||||
end
|
end
|
||||||
elseif element.themeComponent then
|
elseif element.themeComponent then
|
||||||
-- If using themeComponent but no fontFamily specified, check for default font in theme
|
-- If using themeComponent but no fontFamily specified, check for default font in theme
|
||||||
local themeToUse = element.theme and Theme.get(element.theme) or Theme.getActive()
|
local themeToUse = element.theme and self._Theme.get(element.theme) or self._Theme.getActive()
|
||||||
if themeToUse and themeToUse.fonts and themeToUse.fonts.default then
|
if themeToUse and themeToUse.fonts and themeToUse.fonts.default then
|
||||||
fontPath = themeToUse.fonts.default
|
fontPath = themeToUse.fonts.default
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Use cached font instead of creating new one every frame
|
-- Use cached font instead of creating new one every frame
|
||||||
local font = FONT_CACHE.get(element.textSize, fontPath)
|
local font = self._FONT_CACHE.get(element.textSize, fontPath)
|
||||||
love.graphics.setFont(font)
|
love.graphics.setFont(font)
|
||||||
end
|
end
|
||||||
local font = love.graphics.getFont()
|
local font = love.graphics.getFont()
|
||||||
@@ -652,11 +668,11 @@ function Renderer:drawText(element)
|
|||||||
if element.textWrap and (element.textWrap == "word" or element.textWrap == "char" or element.textWrap == true) then
|
if element.textWrap and (element.textWrap == "word" or element.textWrap == "char" or element.textWrap == true) then
|
||||||
-- Use printf for wrapped text
|
-- Use printf for wrapped text
|
||||||
local align = "left"
|
local align = "left"
|
||||||
if element.textAlign == TextAlign.CENTER then
|
if element.textAlign == self._TextAlign.CENTER then
|
||||||
align = "center"
|
align = "center"
|
||||||
elseif element.textAlign == TextAlign.END then
|
elseif element.textAlign == self._TextAlign.END then
|
||||||
align = "right"
|
align = "right"
|
||||||
elseif element.textAlign == TextAlign.JUSTIFY then
|
elseif element.textAlign == self._TextAlign.JUSTIFY then
|
||||||
align = "justify"
|
align = "justify"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -667,16 +683,16 @@ function Renderer:drawText(element)
|
|||||||
love.graphics.printf(displayText, tx, ty, textAreaWidth, align)
|
love.graphics.printf(displayText, tx, ty, textAreaWidth, align)
|
||||||
else
|
else
|
||||||
-- Use regular print for non-wrapped text
|
-- Use regular print for non-wrapped text
|
||||||
if element.textAlign == TextAlign.START then
|
if element.textAlign == self._TextAlign.START then
|
||||||
tx = contentX
|
tx = contentX
|
||||||
ty = contentY
|
ty = contentY
|
||||||
elseif element.textAlign == TextAlign.CENTER then
|
elseif element.textAlign == self._TextAlign.CENTER then
|
||||||
tx = contentX + (textAreaWidth - textWidth) / 2
|
tx = contentX + (textAreaWidth - textWidth) / 2
|
||||||
ty = contentY + (textAreaHeight - textHeight) / 2
|
ty = contentY + (textAreaHeight - textHeight) / 2
|
||||||
elseif element.textAlign == TextAlign.END then
|
elseif element.textAlign == self._TextAlign.END then
|
||||||
tx = contentX + textAreaWidth - textWidth - 10
|
tx = contentX + textAreaWidth - textWidth - 10
|
||||||
ty = contentY + textAreaHeight - textHeight - 10
|
ty = contentY + textAreaHeight - textHeight - 10
|
||||||
elseif element.textAlign == TextAlign.JUSTIFY then
|
elseif element.textAlign == self._TextAlign.JUSTIFY then
|
||||||
--- need to figure out spreading
|
--- need to figure out spreading
|
||||||
tx = contentX
|
tx = contentX
|
||||||
ty = contentY
|
ty = contentY
|
||||||
@@ -703,7 +719,7 @@ function Renderer:drawText(element)
|
|||||||
-- Draw cursor for focused editable elements (even if text is empty)
|
-- Draw cursor for focused editable elements (even if text is empty)
|
||||||
if element._textEditor and element._textEditor:isFocused() and element._textEditor._cursorVisible then
|
if element._textEditor and element._textEditor:isFocused() and element._textEditor._cursorVisible then
|
||||||
local cursorColor = element.cursorColor or element.textColor
|
local cursorColor = element.cursorColor or element.textColor
|
||||||
local cursorWithOpacity = Color.new(cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a * self.opacity)
|
local cursorWithOpacity = self._Color.new(cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a * self.opacity)
|
||||||
love.graphics.setColor(cursorWithOpacity:toRGBA())
|
love.graphics.setColor(cursorWithOpacity:toRGBA())
|
||||||
|
|
||||||
-- Calculate cursor position using TextEditor method
|
-- Calculate cursor position using TextEditor method
|
||||||
@@ -734,8 +750,8 @@ function Renderer:drawText(element)
|
|||||||
-- Draw selection highlight for editable elements
|
-- Draw selection highlight for editable elements
|
||||||
if element._textEditor and element._textEditor:isFocused() and element._textEditor:hasSelection() and element.text and element.text ~= "" then
|
if element._textEditor and element._textEditor:isFocused() and element._textEditor:hasSelection() and element.text and element.text ~= "" then
|
||||||
local selStart, selEnd = element._textEditor:getSelection()
|
local selStart, selEnd = element._textEditor:getSelection()
|
||||||
local selectionColor = element.selectionColor or Color.new(0.3, 0.5, 0.8, 0.5)
|
local selectionColor = element.selectionColor or self._Color.new(0.3, 0.5, 0.8, 0.5)
|
||||||
local selectionWithOpacity = Color.new(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * self.opacity)
|
local selectionWithOpacity = self._Color.new(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * self.opacity)
|
||||||
|
|
||||||
-- Get selection rectangles from TextEditor
|
-- Get selection rectangles from TextEditor
|
||||||
local selectionRects = element._textEditor:_getSelectionRects(selStart, selEnd)
|
local selectionRects = element._textEditor:_getSelectionRects(selStart, selEnd)
|
||||||
@@ -774,14 +790,14 @@ function Renderer:drawText(element)
|
|||||||
if element.textSize then
|
if element.textSize then
|
||||||
local fontPath = nil
|
local fontPath = nil
|
||||||
if element.fontFamily then
|
if element.fontFamily then
|
||||||
local themeToUse = element.theme and Theme.get(element.theme) or Theme.getActive()
|
local themeToUse = element.theme and self._Theme.get(element.theme) or self._Theme.getActive()
|
||||||
if themeToUse and themeToUse.fonts and themeToUse.fonts[element.fontFamily] then
|
if themeToUse and themeToUse.fonts and themeToUse.fonts[element.fontFamily] then
|
||||||
fontPath = themeToUse.fonts[element.fontFamily]
|
fontPath = themeToUse.fonts[element.fontFamily]
|
||||||
else
|
else
|
||||||
fontPath = element.fontFamily
|
fontPath = element.fontFamily
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local font = FONT_CACHE.get(element.textSize, fontPath)
|
local font = self._FONT_CACHE.get(element.textSize, fontPath)
|
||||||
love.graphics.setFont(font)
|
love.graphics.setFont(font)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -802,7 +818,7 @@ function Renderer:drawText(element)
|
|||||||
|
|
||||||
-- Draw cursor
|
-- Draw cursor
|
||||||
local cursorColor = element.cursorColor or element.textColor
|
local cursorColor = element.cursorColor or element.textColor
|
||||||
local cursorWithOpacity = Color.new(cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a * self.opacity)
|
local cursorWithOpacity = self._Color.new(cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a * self.opacity)
|
||||||
love.graphics.setColor(cursorWithOpacity:toRGBA())
|
love.graphics.setColor(cursorWithOpacity:toRGBA())
|
||||||
love.graphics.rectangle("fill", contentX, contentY, 2, textHeight)
|
love.graphics.rectangle("fill", contentX, contentY, 2, textHeight)
|
||||||
|
|
||||||
@@ -832,10 +848,10 @@ function Renderer:drawScrollbars(element, x, y, w, h, dims)
|
|||||||
local thumbColor = element.scrollbarColor
|
local thumbColor = element.scrollbarColor
|
||||||
if element._scrollbarDragging and element._hoveredScrollbar == "vertical" then
|
if element._scrollbarDragging and element._hoveredScrollbar == "vertical" then
|
||||||
-- Active state: brighter
|
-- Active state: brighter
|
||||||
thumbColor = Color.new(math.min(1, thumbColor.r * 1.4), math.min(1, thumbColor.g * 1.4), math.min(1, thumbColor.b * 1.4), thumbColor.a)
|
thumbColor = self._Color.new(math.min(1, thumbColor.r * 1.4), math.min(1, thumbColor.g * 1.4), math.min(1, thumbColor.b * 1.4), thumbColor.a)
|
||||||
elseif element._scrollbarHoveredVertical then
|
elseif element._scrollbarHoveredVertical then
|
||||||
-- Hover state: slightly brighter
|
-- Hover state: slightly brighter
|
||||||
thumbColor = Color.new(math.min(1, thumbColor.r * 1.2), math.min(1, thumbColor.g * 1.2), math.min(1, thumbColor.b * 1.2), thumbColor.a)
|
thumbColor = self._Color.new(math.min(1, thumbColor.r * 1.2), math.min(1, thumbColor.g * 1.2), math.min(1, thumbColor.b * 1.2), thumbColor.a)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw track
|
-- Draw track
|
||||||
@@ -859,10 +875,10 @@ function Renderer:drawScrollbars(element, x, y, w, h, dims)
|
|||||||
local thumbColor = element.scrollbarColor
|
local thumbColor = element.scrollbarColor
|
||||||
if element._scrollbarDragging and element._hoveredScrollbar == "horizontal" then
|
if element._scrollbarDragging and element._hoveredScrollbar == "horizontal" then
|
||||||
-- Active state: brighter
|
-- Active state: brighter
|
||||||
thumbColor = Color.new(math.min(1, thumbColor.r * 1.4), math.min(1, thumbColor.g * 1.4), math.min(1, thumbColor.b * 1.4), thumbColor.a)
|
thumbColor = self._Color.new(math.min(1, thumbColor.r * 1.4), math.min(1, thumbColor.g * 1.4), math.min(1, thumbColor.b * 1.4), thumbColor.a)
|
||||||
elseif element._scrollbarHoveredHorizontal then
|
elseif element._scrollbarHoveredHorizontal then
|
||||||
-- Hover state: slightly brighter
|
-- Hover state: slightly brighter
|
||||||
thumbColor = Color.new(math.min(1, thumbColor.r * 1.2), math.min(1, thumbColor.g * 1.2), math.min(1, thumbColor.b * 1.2), thumbColor.a)
|
thumbColor = self._Color.new(math.min(1, thumbColor.r * 1.2), math.min(1, thumbColor.g * 1.2), math.min(1, thumbColor.b * 1.2), thumbColor.a)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Draw track
|
-- Draw track
|
||||||
@@ -885,7 +901,7 @@ end
|
|||||||
---@param borderBoxHeight number Border box height
|
---@param borderBoxHeight number Border box height
|
||||||
function Renderer:drawPressedState(x, y, borderBoxWidth, borderBoxHeight)
|
function Renderer:drawPressedState(x, y, borderBoxWidth, borderBoxHeight)
|
||||||
love.graphics.setColor(0.5, 0.5, 0.5, 0.3 * self.opacity) -- Semi-transparent gray for pressed state with opacity
|
love.graphics.setColor(0.5, 0.5, 0.5, 0.3 * self.opacity) -- Semi-transparent gray for pressed state with opacity
|
||||||
RoundedRect.draw("fill", x, y, borderBoxWidth, borderBoxHeight, self.cornerRadius)
|
self._RoundedRect.draw("fill", x, y, borderBoxWidth, borderBoxHeight, self.cornerRadius)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Cleanup renderer resources
|
--- Cleanup renderer resources
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
--- ScrollManager.lua
|
--- ScrollManager.lua
|
||||||
--- Handles scrolling, overflow detection, and scrollbar rendering/interaction for Elements
|
--- Handles scrolling, overflow detection, and scrollbar rendering/interaction for Elements
|
||||||
--- Extracted from Element.lua as part of element-refactor-modularization task 05
|
--- Extracted from Element.lua as part of element-refactor-modularization task 05
|
||||||
|
---
|
||||||
-- Setup module path for relative requires
|
--- Dependencies (must be injected via deps parameter):
|
||||||
local modulePath = (...):match("(.-)[^%.]+$")
|
--- - Color: Color module for creating color instances
|
||||||
local function req(name)
|
|
||||||
return require(modulePath .. name)
|
|
||||||
end
|
|
||||||
|
|
||||||
local Color = req("Color")
|
|
||||||
|
|
||||||
---@class ScrollManager
|
---@class ScrollManager
|
||||||
---@field overflow string -- "visible"|"hidden"|"auto"|"scroll"
|
---@field overflow string -- "visible"|"hidden"|"auto"|"scroll"
|
||||||
@@ -41,10 +36,19 @@ ScrollManager.__index = ScrollManager
|
|||||||
|
|
||||||
--- Create a new ScrollManager instance
|
--- Create a new ScrollManager instance
|
||||||
---@param config table Configuration options
|
---@param config table Configuration options
|
||||||
|
---@param deps table Dependencies {Color: Color module}
|
||||||
---@return ScrollManager
|
---@return ScrollManager
|
||||||
function ScrollManager.new(config)
|
function ScrollManager.new(config, deps)
|
||||||
|
-- Pure DI: Dependencies must be injected
|
||||||
|
assert(deps, "ScrollManager.new: deps parameter is required")
|
||||||
|
assert(deps.Color, "ScrollManager.new: deps.Color is required")
|
||||||
|
|
||||||
|
local Color = deps.Color
|
||||||
local self = setmetatable({}, ScrollManager)
|
local self = setmetatable({}, ScrollManager)
|
||||||
|
|
||||||
|
-- Store dependency for instance methods
|
||||||
|
self._Color = Color
|
||||||
|
|
||||||
-- Configuration
|
-- Configuration
|
||||||
self.overflow = config.overflow or "hidden"
|
self.overflow = config.overflow or "hidden"
|
||||||
self.overflowX = config.overflowX
|
self.overflowX = config.overflowX
|
||||||
|
|||||||
@@ -9,6 +9,12 @@
|
|||||||
-- - Focus management
|
-- - Focus management
|
||||||
-- - Keyboard input handling
|
-- - Keyboard input handling
|
||||||
-- - Text rendering (cursor, selection highlights)
|
-- - Text rendering (cursor, selection highlights)
|
||||||
|
---
|
||||||
|
--- Dependencies (must be injected via deps parameter):
|
||||||
|
--- - GuiState: GUI state manager
|
||||||
|
--- - StateManager: State persistence for immediate mode
|
||||||
|
--- - Color: Color utility class (reserved for future use)
|
||||||
|
--- - utils: Utility functions (FONT_CACHE, getModifiers)
|
||||||
|
|
||||||
-- Setup module path for relative requires
|
-- Setup module path for relative requires
|
||||||
local modulePath = (...):match("(.-)[^%.]+$")
|
local modulePath = (...):match("(.-)[^%.]+$")
|
||||||
@@ -16,16 +22,6 @@ local function req(name)
|
|||||||
return require(modulePath .. name)
|
return require(modulePath .. name)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Module dependencies
|
|
||||||
local GuiState = req("GuiState")
|
|
||||||
local StateManager = req("StateManager")
|
|
||||||
local Color = req("Color")
|
|
||||||
local utils = req("utils")
|
|
||||||
|
|
||||||
-- Extract utilities
|
|
||||||
local FONT_CACHE = utils.FONT_CACHE
|
|
||||||
local getModifiers = utils.getModifiers
|
|
||||||
|
|
||||||
-- UTF-8 support
|
-- UTF-8 support
|
||||||
local utf8 = utf8 or require("utf8")
|
local utf8 = utf8 or require("utf8")
|
||||||
|
|
||||||
@@ -51,10 +47,25 @@ TextEditor.__index = TextEditor
|
|||||||
|
|
||||||
---Create a new TextEditor instance
|
---Create a new TextEditor instance
|
||||||
---@param config TextEditorConfig
|
---@param config TextEditorConfig
|
||||||
|
---@param deps table Dependencies {GuiState, StateManager, Color, utils}
|
||||||
---@return table TextEditor instance
|
---@return table TextEditor instance
|
||||||
function TextEditor.new(config)
|
function TextEditor.new(config, deps)
|
||||||
|
-- Pure DI: Dependencies must be injected
|
||||||
|
assert(deps, "TextEditor.new: deps parameter is required")
|
||||||
|
assert(deps.GuiState, "TextEditor.new: deps.GuiState is required")
|
||||||
|
assert(deps.StateManager, "TextEditor.new: deps.StateManager is required")
|
||||||
|
assert(deps.Color, "TextEditor.new: deps.Color is required")
|
||||||
|
assert(deps.utils, "TextEditor.new: deps.utils is required")
|
||||||
|
|
||||||
local self = setmetatable({}, TextEditor)
|
local self = setmetatable({}, TextEditor)
|
||||||
|
|
||||||
|
-- Store dependencies
|
||||||
|
self._GuiState = deps.GuiState
|
||||||
|
self._StateManager = deps.StateManager
|
||||||
|
self._Color = deps.Color
|
||||||
|
self._FONT_CACHE = deps.utils.FONT_CACHE
|
||||||
|
self._getModifiers = deps.utils.getModifiers
|
||||||
|
|
||||||
-- Store configuration
|
-- Store configuration
|
||||||
self.editable = config.editable or false
|
self.editable = config.editable or false
|
||||||
self.multiline = config.multiline or false
|
self.multiline = config.multiline or false
|
||||||
@@ -117,12 +128,12 @@ function TextEditor:initialize(element)
|
|||||||
self._element = element
|
self._element = element
|
||||||
|
|
||||||
-- Restore state from StateManager if in immediate mode
|
-- Restore state from StateManager if in immediate mode
|
||||||
if element._stateId and GuiState._immediateMode then
|
if element._stateId and self._GuiState._immediateMode then
|
||||||
local state = StateManager.getState(element._stateId)
|
local state = self._StateManager.getState(element._stateId)
|
||||||
if state then
|
if state then
|
||||||
if state._focused then
|
if state._focused then
|
||||||
self._focused = true
|
self._focused = true
|
||||||
GuiState._focusedElement = element
|
self._GuiState._focusedElement = element
|
||||||
end
|
end
|
||||||
if state._textBuffer and state._textBuffer ~= "" then
|
if state._textBuffer and state._textBuffer ~= "" then
|
||||||
self._textBuffer = state._textBuffer
|
self._textBuffer = state._textBuffer
|
||||||
@@ -912,16 +923,16 @@ end
|
|||||||
|
|
||||||
---Focus this element for keyboard input
|
---Focus this element for keyboard input
|
||||||
function TextEditor:focus()
|
function TextEditor:focus()
|
||||||
if GuiState._focusedElement and GuiState._focusedElement ~= self._element then
|
if self._GuiState._focusedElement and self._GuiState._focusedElement ~= self._element then
|
||||||
-- Blur the previously focused element's text editor if it has one
|
-- Blur the previously focused element's text editor if it has one
|
||||||
if GuiState._focusedElement._textEditor then
|
if self._GuiState._focusedElement._textEditor then
|
||||||
GuiState._focusedElement._textEditor:blur()
|
self._GuiState._focusedElement._textEditor:blur()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self._focused = true
|
self._focused = true
|
||||||
if self._element then
|
if self._element then
|
||||||
GuiState._focusedElement = self._element
|
self._GuiState._focusedElement = self._element
|
||||||
end
|
end
|
||||||
|
|
||||||
self:_resetCursorBlink()
|
self:_resetCursorBlink()
|
||||||
@@ -943,8 +954,8 @@ end
|
|||||||
function TextEditor:blur()
|
function TextEditor:blur()
|
||||||
self._focused = false
|
self._focused = false
|
||||||
|
|
||||||
if self._element and GuiState._focusedElement == self._element then
|
if self._element and self._GuiState._focusedElement == self._element then
|
||||||
GuiState._focusedElement = nil
|
self._GuiState._focusedElement = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.onBlur and self._element then
|
if self.onBlur and self._element then
|
||||||
@@ -1006,7 +1017,7 @@ function TextEditor:handleKeyPress(key, scancode, isrepeat)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local modifiers = getModifiers()
|
local modifiers = self._getModifiers()
|
||||||
local ctrl = modifiers.ctrl or modifiers.super
|
local ctrl = modifiers.ctrl or modifiers.super
|
||||||
|
|
||||||
-- Handle cursor movement with selection
|
-- Handle cursor movement with selection
|
||||||
@@ -1538,11 +1549,11 @@ end
|
|||||||
|
|
||||||
---Save state to StateManager (for immediate mode)
|
---Save state to StateManager (for immediate mode)
|
||||||
function TextEditor:_saveState()
|
function TextEditor:_saveState()
|
||||||
if not self._element or not self._element._stateId or not GuiState._immediateMode then
|
if not self._element or not self._element._stateId or not self._GuiState._immediateMode then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
StateManager.updateState(self._element._stateId, {
|
self._StateManager.updateState(self._element._stateId, {
|
||||||
_focused = self._focused,
|
_focused = self._focused,
|
||||||
_textBuffer = self._textBuffer,
|
_textBuffer = self._textBuffer,
|
||||||
_cursorPosition = self._cursorPosition,
|
_cursorPosition = self._cursorPosition,
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
--- ThemeManager.lua
|
--- ThemeManager.lua
|
||||||
--- Manages theme application, state transitions, and property resolution for Elements
|
--- Manages theme application, state transitions, and property resolution for Elements
|
||||||
--- Extracted from Element.lua as part of element-refactor-modularization task 06
|
--- Extracted from Element.lua as part of element-refactor-modularization task 06
|
||||||
|
---
|
||||||
-- Setup module path for relative requires
|
--- Dependencies (must be injected via deps parameter):
|
||||||
local modulePath = (...):match("(.-)[^%.]+$")
|
--- - Theme: Theme module for loading and accessing themes
|
||||||
local function req(name)
|
|
||||||
return require(modulePath .. name)
|
|
||||||
end
|
|
||||||
|
|
||||||
local Theme = req("Theme")
|
|
||||||
|
|
||||||
---@class ThemeManager
|
---@class ThemeManager
|
||||||
---@field theme string? -- Theme name to use
|
---@field theme string? -- Theme name to use
|
||||||
@@ -25,10 +20,19 @@ ThemeManager.__index = ThemeManager
|
|||||||
|
|
||||||
--- Create new ThemeManager instance
|
--- Create new ThemeManager instance
|
||||||
---@param config table Configuration options
|
---@param config table Configuration options
|
||||||
|
---@param deps table Dependencies {Theme: Theme module}
|
||||||
---@return ThemeManager
|
---@return ThemeManager
|
||||||
function ThemeManager.new(config)
|
function ThemeManager.new(config, deps)
|
||||||
|
-- Pure DI: Dependencies must be injected
|
||||||
|
assert(deps, "ThemeManager.new: deps parameter is required")
|
||||||
|
assert(deps.Theme, "ThemeManager.new: deps.Theme is required")
|
||||||
|
|
||||||
|
local Theme = deps.Theme
|
||||||
local self = setmetatable({}, ThemeManager)
|
local self = setmetatable({}, ThemeManager)
|
||||||
|
|
||||||
|
-- Store dependency for instance methods
|
||||||
|
self._Theme = Theme
|
||||||
|
|
||||||
-- Theme configuration
|
-- Theme configuration
|
||||||
self.theme = config.theme
|
self.theme = config.theme
|
||||||
self.themeComponent = config.themeComponent
|
self.themeComponent = config.themeComponent
|
||||||
@@ -103,9 +107,9 @@ end
|
|||||||
---@return table? The theme object or nil
|
---@return table? The theme object or nil
|
||||||
function ThemeManager:getTheme()
|
function ThemeManager:getTheme()
|
||||||
if self.theme then
|
if self.theme then
|
||||||
return Theme.get(self.theme)
|
return self._Theme.get(self.theme)
|
||||||
end
|
end
|
||||||
return Theme.getActive()
|
return self._Theme.getActive()
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get the component definition from the theme
|
--- Get the component definition from the theme
|
||||||
|
|||||||
Reference in New Issue
Block a user