fix Gui references
This commit is contained in:
@@ -24,6 +24,9 @@ local enums = utils.enums
|
|||||||
---@class FlexLove
|
---@class FlexLove
|
||||||
local flexlove = Context
|
local flexlove = Context
|
||||||
|
|
||||||
|
-- Initialize Units module with Context dependency
|
||||||
|
Units.initialize(Context)
|
||||||
|
|
||||||
-- Add version and metadata
|
-- Add version and metadata
|
||||||
flexlove._VERSION = "0.1.0"
|
flexlove._VERSION = "0.1.0"
|
||||||
flexlove._DESCRIPTION = "UI Library for LÖVE Framework based on flexbox"
|
flexlove._DESCRIPTION = "UI Library for LÖVE Framework based on flexbox"
|
||||||
|
|||||||
@@ -441,7 +441,7 @@ end
|
|||||||
Support for viewport-relative units:
|
Support for viewport-relative units:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local element = Gui.new({
|
local element = FlexLove.new({
|
||||||
width = "50vw", -- 50% of viewport width
|
width = "50vw", -- 50% of viewport width
|
||||||
height = "30vh", -- 30% of viewport height
|
height = "30vh", -- 30% of viewport height
|
||||||
x = "25%", -- 25% of parent width
|
x = "25%", -- 25% of parent width
|
||||||
|
|||||||
@@ -31,43 +31,16 @@ local resolveTextSizePreset = utils.resolveTextSizePreset
|
|||||||
local getModifiers = utils.getModifiers
|
local getModifiers = utils.getModifiers
|
||||||
|
|
||||||
-- Extract enum values
|
-- Extract enum values
|
||||||
local Positioning = enums.Positioning
|
local Positioning, FlexDirection, JustifyContent, AlignContent, AlignItems, TextAlign, AlignSelf, JustifySelf, FlexWrap =
|
||||||
local FlexDirection = enums.FlexDirection
|
enums.Positioning,
|
||||||
local JustifyContent = enums.JustifyContent
|
enums.FlexDirection,
|
||||||
local AlignContent = enums.AlignContent
|
enums.JustifyContent,
|
||||||
local AlignItems = enums.AlignItems
|
enums.AlignContent,
|
||||||
local TextAlign = enums.TextAlign
|
enums.AlignItems,
|
||||||
local AlignSelf = enums.AlignSelf
|
enums.TextAlign,
|
||||||
local JustifySelf = enums.JustifySelf
|
enums.AlignSelf,
|
||||||
local FlexWrap = enums.FlexWrap
|
enums.JustifySelf,
|
||||||
|
enums.FlexWrap
|
||||||
-- Reference to Gui (via Context)
|
|
||||||
local Gui = Context
|
|
||||||
|
|
||||||
-- UTF-8 support (available in LÖVE/Lua 5.3+)
|
|
||||||
local utf8 = utf8 or require("utf8")
|
|
||||||
|
|
||||||
--[[
|
|
||||||
INTERNAL FIELD NAMING CONVENTIONS:
|
|
||||||
---------------------------------
|
|
||||||
Fields prefixed with underscore (_) are internal/private and should not be accessed directly:
|
|
||||||
|
|
||||||
- _eventHandler: Internal EventHandler instance for input event processing
|
|
||||||
- _themeState: Internal current theme state (managed automatically)
|
|
||||||
- _borderBoxWidth: Internal cached border-box width (optimization)
|
|
||||||
- _borderBoxHeight: Internal cached border-box height (optimization)
|
|
||||||
- _explicitlyAbsolute: Internal flag for positioning logic
|
|
||||||
- _originalPositioning: Internal original positioning value
|
|
||||||
- _cachedResult: Internal animation cache (Animation class)
|
|
||||||
- _resultDirty: Internal animation dirty flag (Animation class)
|
|
||||||
- _loadedAtlas: Internal cached atlas image (ThemeComponent)
|
|
||||||
- _cachedViewport: Internal viewport cache (Gui class)
|
|
||||||
|
|
||||||
Public API methods to access internal state:
|
|
||||||
- Element:getBorderBoxWidth() - Get border-box width
|
|
||||||
- Element:getBorderBoxHeight() - Get border-box height
|
|
||||||
- Element:getBounds() - Get element bounds
|
|
||||||
]]
|
|
||||||
|
|
||||||
---@class Element
|
---@class Element
|
||||||
---@field id string
|
---@field id string
|
||||||
@@ -266,7 +239,7 @@ function Element.new(props)
|
|||||||
self.onEvent = props.onEvent
|
self.onEvent = props.onEvent
|
||||||
|
|
||||||
-- Auto-generate ID in immediate mode if not provided
|
-- Auto-generate ID in immediate mode if not provided
|
||||||
if Gui._immediateMode and (not props.id or props.id == "") then
|
if Context._immediateMode and (not props.id or props.id == "") then
|
||||||
self.id = StateManager.generateID(props, props.parent)
|
self.id = StateManager.generateID(props, props.parent)
|
||||||
else
|
else
|
||||||
self.id = props.id or ""
|
self.id = props.id or ""
|
||||||
@@ -292,7 +265,7 @@ function Element.new(props)
|
|||||||
self._stateId = self.id
|
self._stateId = self.id
|
||||||
|
|
||||||
self._themeManager = Theme.Manager.new({
|
self._themeManager = Theme.Manager.new({
|
||||||
theme = props.theme or Gui.defaultTheme,
|
theme = props.theme or Context.defaultTheme,
|
||||||
themeComponent = props.themeComponent or nil,
|
themeComponent = props.themeComponent or nil,
|
||||||
disabled = props.disabled or false,
|
disabled = props.disabled or false,
|
||||||
active = props.active or false,
|
active = props.active or false,
|
||||||
@@ -466,7 +439,7 @@ function Element.new(props)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Sync self.text with restored _textBuffer for editable elements in immediate mode
|
-- Sync self.text with restored _textBuffer for editable elements in immediate mode
|
||||||
if self.editable and Gui._immediateMode and self._textBuffer then
|
if self.editable and Context._immediateMode and self._textBuffer then
|
||||||
self.text = self._textBuffer
|
self.text = self._textBuffer
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -574,10 +547,8 @@ function Element.new(props)
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Get scale factors from Gui (will be used later)
|
local scaleX, scaleY = Context.getScaleFactors()
|
||||||
local scaleX, scaleY = Gui.getScaleFactors()
|
|
||||||
|
|
||||||
-- Store original textSize units and constraints
|
|
||||||
self.minTextSize = props.minTextSize
|
self.minTextSize = props.minTextSize
|
||||||
self.maxTextSize = props.maxTextSize
|
self.maxTextSize = props.maxTextSize
|
||||||
|
|
||||||
@@ -649,7 +620,7 @@ function Element.new(props)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Pixel textSize value
|
-- Pixel textSize value
|
||||||
if self.autoScaleText and Gui.baseScale then
|
if self.autoScaleText and Context.baseScale then
|
||||||
-- With base scaling: store original pixel value and scale relative to base resolution
|
-- With base scaling: store original pixel value and scale relative to base resolution
|
||||||
self.units.textSize = { value = props.textSize, unit = "px" }
|
self.units.textSize = { value = props.textSize, unit = "px" }
|
||||||
self.textSize = props.textSize * scaleY
|
self.textSize = props.textSize * scaleY
|
||||||
@@ -661,13 +632,13 @@ function Element.new(props)
|
|||||||
self.textSize = props.textSize -- Initial size is the specified pixel value
|
self.textSize = props.textSize -- Initial size is the specified pixel value
|
||||||
else
|
else
|
||||||
-- No auto-scaling: apply base scaling if set, otherwise use raw value
|
-- No auto-scaling: apply base scaling if set, otherwise use raw value
|
||||||
self.textSize = Gui.baseScale and (props.textSize * scaleY) or props.textSize
|
self.textSize = Context.baseScale and (props.textSize * scaleY) or props.textSize
|
||||||
self.units.textSize = { value = props.textSize, unit = "px" }
|
self.units.textSize = { value = props.textSize, unit = "px" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- No textSize specified - use auto-scaling default
|
-- No textSize specified - use auto-scaling default
|
||||||
if self.autoScaleText and Gui.baseScale then
|
if self.autoScaleText and Context.baseScale then
|
||||||
-- With base scaling: use 12px as default and scale
|
-- With base scaling: use 12px as default and scale
|
||||||
self.units.textSize = { value = 12, unit = "px" }
|
self.units.textSize = { value = 12, unit = "px" }
|
||||||
self.textSize = 12 * scaleY
|
self.textSize = 12 * scaleY
|
||||||
@@ -677,7 +648,7 @@ function Element.new(props)
|
|||||||
self.textSize = (1.5 / 100) * viewportHeight
|
self.textSize = (1.5 / 100) * viewportHeight
|
||||||
else
|
else
|
||||||
-- No auto-scaling: use 12px with optional base scaling
|
-- No auto-scaling: use 12px with optional base scaling
|
||||||
self.textSize = Gui.baseScale and (12 * scaleY) or 12
|
self.textSize = Context.baseScale and (12 * scaleY) or 12
|
||||||
self.units.textSize = { value = nil, unit = "px" }
|
self.units.textSize = { value = nil, unit = "px" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -692,7 +663,7 @@ function Element.new(props)
|
|||||||
local parentWidth = self.parent and self.parent.width or viewportWidth
|
local parentWidth = self.parent and self.parent.width or viewportWidth
|
||||||
tempWidth = Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth)
|
tempWidth = Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth)
|
||||||
else
|
else
|
||||||
tempWidth = Gui.baseScale and (widthProp * scaleX) or widthProp
|
tempWidth = Context.baseScale and (widthProp * scaleX) or widthProp
|
||||||
self.units.width = { value = widthProp, unit = "px" }
|
self.units.width = { value = widthProp, unit = "px" }
|
||||||
end
|
end
|
||||||
self.width = tempWidth
|
self.width = tempWidth
|
||||||
@@ -723,7 +694,7 @@ function Element.new(props)
|
|||||||
tempHeight = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight)
|
tempHeight = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight)
|
||||||
else
|
else
|
||||||
-- Apply base scaling to pixel values
|
-- Apply base scaling to pixel values
|
||||||
tempHeight = Gui.baseScale and (heightProp * scaleY) or heightProp
|
tempHeight = Context.baseScale and (heightProp * scaleY) or heightProp
|
||||||
self.units.height = { value = heightProp, unit = "px" }
|
self.units.height = { value = heightProp, unit = "px" }
|
||||||
end
|
end
|
||||||
self.height = tempHeight
|
self.height = tempHeight
|
||||||
@@ -760,6 +731,7 @@ function Element.new(props)
|
|||||||
-- Check if we should use 9-patch content padding for auto-sizing
|
-- Check if we should use 9-patch content padding for auto-sizing
|
||||||
local use9PatchPadding = false
|
local use9PatchPadding = false
|
||||||
local ninePatchContentPadding = nil
|
local ninePatchContentPadding = nil
|
||||||
|
local tempPadding = nil
|
||||||
if self._themeManager:hasThemeComponent() then
|
if self._themeManager:hasThemeComponent() then
|
||||||
local component = self._themeManager:getComponent()
|
local component = self._themeManager:getComponent()
|
||||||
if component and component._ninePatchData and component._ninePatchData.contentPadding then
|
if component and component._ninePatchData and component._ninePatchData.contentPadding then
|
||||||
@@ -777,20 +749,10 @@ function Element.new(props)
|
|||||||
then
|
then
|
||||||
use9PatchPadding = true
|
use9PatchPadding = true
|
||||||
ninePatchContentPadding = component._ninePatchData.contentPadding
|
ninePatchContentPadding = component._ninePatchData.contentPadding
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- First, resolve padding using temporary dimensions
|
|
||||||
-- For auto-sized elements, this is content width; for explicit sizing, this is border-box width
|
|
||||||
local tempPadding
|
|
||||||
if use9PatchPadding then
|
|
||||||
-- Get scaled 9-patch content padding from ThemeManager
|
|
||||||
local scaledPadding = self._themeManager:getScaledContentPadding(tempWidth, tempHeight)
|
local scaledPadding = self._themeManager:getScaledContentPadding(tempWidth, tempHeight)
|
||||||
if scaledPadding then
|
if scaledPadding then
|
||||||
tempPadding = scaledPadding
|
tempPadding = scaledPadding
|
||||||
else
|
else
|
||||||
-- Fallback if scaling fails
|
|
||||||
tempPadding = {
|
tempPadding = {
|
||||||
left = ninePatchContentPadding.left,
|
left = ninePatchContentPadding.left,
|
||||||
top = ninePatchContentPadding.top,
|
top = ninePatchContentPadding.top,
|
||||||
@@ -798,9 +760,10 @@ function Element.new(props)
|
|||||||
bottom = ninePatchContentPadding.bottom,
|
bottom = ninePatchContentPadding.bottom,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
else
|
end
|
||||||
tempPadding = Units.resolveSpacing(props.padding, self.width, self.height)
|
tempPadding = Units.resolveSpacing(props.padding, self.width, self.height)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Margin percentages are relative to parent's dimensions (CSS spec)
|
-- Margin percentages are relative to parent's dimensions (CSS spec)
|
||||||
local parentWidth = self.parent and self.parent.width or viewportWidth
|
local parentWidth = self.parent and self.parent.width or viewportWidth
|
||||||
@@ -853,8 +816,8 @@ function Element.new(props)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Apply min/max constraints (also scaled)
|
-- Apply min/max constraints (also scaled)
|
||||||
local minSize = self.minTextSize and (Gui.baseScale and (self.minTextSize * scaleY) or self.minTextSize)
|
local minSize = self.minTextSize and (Context.baseScale and (self.minTextSize * scaleY) or self.minTextSize)
|
||||||
local maxSize = self.maxTextSize and (Gui.baseScale and (self.maxTextSize * scaleY) or self.maxTextSize)
|
local maxSize = self.maxTextSize and (Context.baseScale and (self.maxTextSize * scaleY) or self.maxTextSize)
|
||||||
|
|
||||||
if minSize and self.textSize < minSize then
|
if minSize and self.textSize < minSize then
|
||||||
self.textSize = minSize
|
self.textSize = minSize
|
||||||
@@ -943,7 +906,7 @@ function Element.new(props)
|
|||||||
|
|
||||||
------ add hereditary ------
|
------ add hereditary ------
|
||||||
if props.parent == nil then
|
if props.parent == nil then
|
||||||
table.insert(Gui.topElements, self)
|
table.insert(Context.topElements, self)
|
||||||
|
|
||||||
-- Handle x position with units
|
-- Handle x position with units
|
||||||
if props.x then
|
if props.x then
|
||||||
@@ -953,7 +916,7 @@ function Element.new(props)
|
|||||||
self.x = Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
|
self.x = Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
|
||||||
else
|
else
|
||||||
-- Apply base scaling to pixel positions
|
-- Apply base scaling to pixel positions
|
||||||
self.x = Gui.baseScale and (props.x * scaleX) or props.x
|
self.x = Context.baseScale and (props.x * scaleX) or props.x
|
||||||
self.units.x = { value = props.x, unit = "px" }
|
self.units.x = { value = props.x, unit = "px" }
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -969,7 +932,7 @@ function Element.new(props)
|
|||||||
self.y = Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
|
self.y = Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
|
||||||
else
|
else
|
||||||
-- Apply base scaling to pixel positions
|
-- Apply base scaling to pixel positions
|
||||||
self.y = Gui.baseScale and (props.y * scaleY) or props.y
|
self.y = Context.baseScale and (props.y * scaleY) or props.y
|
||||||
self.units.y = { value = props.y, unit = "px" }
|
self.units.y = { value = props.y, unit = "px" }
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -1039,7 +1002,7 @@ function Element.new(props)
|
|||||||
self.x = Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth)
|
self.x = Units.resolve(value, unit, viewportWidth, viewportHeight, parentWidth)
|
||||||
else
|
else
|
||||||
-- Apply base scaling to pixel positions
|
-- Apply base scaling to pixel positions
|
||||||
self.x = Gui.baseScale and (props.x * scaleX) or props.x
|
self.x = Context.baseScale and (props.x * scaleX) or props.x
|
||||||
self.units.x = { value = props.x, unit = "px" }
|
self.units.x = { value = props.x, unit = "px" }
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -1056,7 +1019,7 @@ function Element.new(props)
|
|||||||
self.y = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight)
|
self.y = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight)
|
||||||
else
|
else
|
||||||
-- Apply base scaling to pixel positions
|
-- Apply base scaling to pixel positions
|
||||||
self.y = Gui.baseScale and (props.y * scaleY) or props.y
|
self.y = Context.baseScale and (props.y * scaleY) or props.y
|
||||||
self.units.y = { value = props.y, unit = "px" }
|
self.units.y = { value = props.y, unit = "px" }
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -1079,7 +1042,7 @@ function Element.new(props)
|
|||||||
self.x = baseX + offsetX
|
self.x = baseX + offsetX
|
||||||
else
|
else
|
||||||
-- Apply base scaling to pixel offsets
|
-- Apply base scaling to pixel offsets
|
||||||
local scaledOffset = Gui.baseScale and (props.x * scaleX) or props.x
|
local scaledOffset = Context.baseScale and (props.x * scaleX) or props.x
|
||||||
self.x = baseX + scaledOffset
|
self.x = baseX + scaledOffset
|
||||||
self.units.x = { value = props.x, unit = "px" }
|
self.units.x = { value = props.x, unit = "px" }
|
||||||
end
|
end
|
||||||
@@ -1092,12 +1055,12 @@ function Element.new(props)
|
|||||||
if type(props.y) == "string" then
|
if type(props.y) == "string" then
|
||||||
local value, unit = Units.parse(props.y)
|
local value, unit = Units.parse(props.y)
|
||||||
self.units.y = { value = value, unit = unit }
|
self.units.y = { value = value, unit = unit }
|
||||||
local parentHeight = self.parent.height
|
parentHeight = self.parent.height
|
||||||
local offsetY = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight)
|
local offsetY = Units.resolve(value, unit, viewportWidth, viewportHeight, parentHeight)
|
||||||
self.y = baseY + offsetY
|
self.y = baseY + offsetY
|
||||||
else
|
else
|
||||||
-- Apply base scaling to pixel offsets
|
-- Apply base scaling to pixel offsets
|
||||||
local scaledOffset = Gui.baseScale and (props.y * scaleY) or props.y
|
local scaledOffset = Context.baseScale and (props.y * scaleY) or props.y
|
||||||
self.y = baseY + scaledOffset
|
self.y = baseY + scaledOffset
|
||||||
self.units.y = { value = props.y, unit = "px" }
|
self.units.y = { value = props.y, unit = "px" }
|
||||||
end
|
end
|
||||||
@@ -1109,13 +1072,11 @@ function Element.new(props)
|
|||||||
self.z = props.z or self.parent.z or 0
|
self.z = props.z or self.parent.z or 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Set textColor with priority: props > parent > theme text color > black
|
|
||||||
if props.textColor then
|
if props.textColor then
|
||||||
self.textColor = props.textColor
|
self.textColor = props.textColor
|
||||||
elseif self.parent.textColor then
|
elseif self.parent.textColor then
|
||||||
self.textColor = self.parent.textColor
|
self.textColor = self.parent.textColor
|
||||||
else
|
else
|
||||||
-- Try to get text color from theme via ThemeManager
|
|
||||||
local themeToUse = self._themeManager:getTheme()
|
local themeToUse = self._themeManager:getTheme()
|
||||||
if themeToUse and themeToUse.colors and themeToUse.colors.text then
|
if themeToUse and themeToUse.colors and themeToUse.colors.text then
|
||||||
self.textColor = themeToUse.colors.text
|
self.textColor = themeToUse.colors.text
|
||||||
@@ -1267,7 +1228,7 @@ function Element.new(props)
|
|||||||
utils = utils,
|
utils = utils,
|
||||||
Grid = Grid,
|
Grid = Grid,
|
||||||
Units = Units,
|
Units = Units,
|
||||||
Gui = Gui,
|
Context = Context,
|
||||||
})
|
})
|
||||||
-- 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)
|
||||||
@@ -1327,7 +1288,7 @@ function Element.new(props)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Register element in z-index tracking for immediate mode
|
-- Register element in z-index tracking for immediate mode
|
||||||
if Gui._immediateMode then
|
if Context._immediateMode then
|
||||||
Context.registerElement(self)
|
Context.registerElement(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1718,7 +1679,7 @@ function Element:addChild(child)
|
|||||||
|
|
||||||
-- In immediate mode, defer layout until endFrame() when all elements are created
|
-- In immediate mode, defer layout until endFrame() when all elements are created
|
||||||
-- This prevents premature overflow detection with incomplete children
|
-- This prevents premature overflow detection with incomplete children
|
||||||
if not Gui._immediateMode then
|
if not Context._immediateMode then
|
||||||
self:layoutChildren()
|
self:layoutChildren()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1738,9 +1699,9 @@ end
|
|||||||
--- Destroy element and its children
|
--- Destroy element and its children
|
||||||
function Element:destroy()
|
function Element:destroy()
|
||||||
-- Remove from global elements list
|
-- Remove from global elements list
|
||||||
for i, win in ipairs(Gui.topElements) do
|
for i, win in ipairs(Context.topElements) do
|
||||||
if win == self then
|
if win == self then
|
||||||
table.remove(Gui.topElements, i)
|
table.remove(Context.topElements, i)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1938,17 +1899,15 @@ end
|
|||||||
---@param dt number
|
---@param dt number
|
||||||
function Element:update(dt)
|
function Element:update(dt)
|
||||||
-- Restore scrollbar state from StateManager in immediate mode
|
-- Restore scrollbar state from StateManager in immediate mode
|
||||||
if self._stateId and Gui._immediateMode then
|
if self._stateId and Context._immediateMode then
|
||||||
local state = StateManager.getState(self._stateId)
|
local state = StateManager.getState(self._stateId)
|
||||||
if state then
|
if state then
|
||||||
-- Restore to Element properties (for backward compatibility)
|
|
||||||
self._scrollbarHoveredVertical = state.scrollbarHoveredVertical or false
|
self._scrollbarHoveredVertical = state.scrollbarHoveredVertical or false
|
||||||
self._scrollbarHoveredHorizontal = state.scrollbarHoveredHorizontal or false
|
self._scrollbarHoveredHorizontal = state.scrollbarHoveredHorizontal or false
|
||||||
self._scrollbarDragging = state.scrollbarDragging or false
|
self._scrollbarDragging = state.scrollbarDragging or false
|
||||||
self._hoveredScrollbar = state.hoveredScrollbar
|
self._hoveredScrollbar = state.hoveredScrollbar
|
||||||
self._scrollbarDragOffset = state.scrollbarDragOffset or 0
|
self._scrollbarDragOffset = state.scrollbarDragOffset or 0
|
||||||
|
|
||||||
-- Also restore to ScrollManager if it exists
|
|
||||||
if self._scrollManager then
|
if self._scrollManager then
|
||||||
self._scrollManager._scrollbarHoveredVertical = self._scrollbarHoveredVertical
|
self._scrollManager._scrollbarHoveredVertical = self._scrollbarHoveredVertical
|
||||||
self._scrollManager._scrollbarHoveredHorizontal = self._scrollbarHoveredHorizontal
|
self._scrollManager._scrollbarHoveredHorizontal = self._scrollbarHoveredHorizontal
|
||||||
@@ -1988,14 +1947,12 @@ function Element:update(dt)
|
|||||||
|
|
||||||
local mx, my = love.mouse.getPosition()
|
local mx, my = love.mouse.getPosition()
|
||||||
|
|
||||||
-- Update scrollbar hover state via ScrollManager
|
|
||||||
if self._scrollManager then
|
if self._scrollManager then
|
||||||
self._scrollManager:updateHoverState(mx, my)
|
self._scrollManager:updateHoverState(mx, my)
|
||||||
self:_syncScrollManagerState()
|
self:_syncScrollManagerState()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update scrollbar state in StateManager if in immediate mode
|
if self._stateId and Context._immediateMode then
|
||||||
if self._stateId and Gui._immediateMode then
|
|
||||||
StateManager.updateState(self._stateId, {
|
StateManager.updateState(self._stateId, {
|
||||||
scrollbarHoveredVertical = self._scrollbarHoveredVertical,
|
scrollbarHoveredVertical = self._scrollbarHoveredVertical,
|
||||||
scrollbarHoveredHorizontal = self._scrollbarHoveredHorizontal,
|
scrollbarHoveredHorizontal = self._scrollbarHoveredHorizontal,
|
||||||
@@ -2004,18 +1961,15 @@ function Element:update(dt)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle scrollbar dragging
|
|
||||||
if self._scrollbarDragging and love.mouse.isDown(1) then
|
if self._scrollbarDragging and love.mouse.isDown(1) then
|
||||||
self:_handleScrollbarDrag(mx, my)
|
self:_handleScrollbarDrag(mx, my)
|
||||||
elseif self._scrollbarDragging then
|
elseif self._scrollbarDragging then
|
||||||
-- Mouse button released - delegate to ScrollManager
|
|
||||||
if self._scrollManager then
|
if self._scrollManager then
|
||||||
self._scrollManager:handleMouseRelease(1)
|
self._scrollManager:handleMouseRelease(1)
|
||||||
self:_syncScrollManagerState()
|
self:_syncScrollManagerState()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update StateManager if in immediate mode
|
if self._stateId and Context._immediateMode then
|
||||||
if self._stateId and Gui._immediateMode then
|
|
||||||
StateManager.updateState(self._stateId, {
|
StateManager.updateState(self._stateId, {
|
||||||
scrollbarDragging = false,
|
scrollbarDragging = false,
|
||||||
})
|
})
|
||||||
@@ -2080,13 +2034,13 @@ function Element:update(dt)
|
|||||||
-- Check if this is the topmost element at the mouse position (z-index ordering)
|
-- Check if this is the topmost element at the mouse position (z-index ordering)
|
||||||
-- This prevents blocked elements from receiving interactions or visual feedback
|
-- This prevents blocked elements from receiving interactions or visual feedback
|
||||||
local isActiveElement
|
local isActiveElement
|
||||||
if Gui._immediateMode then
|
if Context._immediateMode then
|
||||||
-- In immediate mode, use z-index occlusion detection
|
-- In immediate mode, use z-index occlusion detection
|
||||||
local topElement = Context.getTopElementAt(mx, my)
|
local topElement = Context.getTopElementAt(mx, my)
|
||||||
isActiveElement = (topElement == self or topElement == nil)
|
isActiveElement = (topElement == self or topElement == nil)
|
||||||
else
|
else
|
||||||
-- In retained mode, use the old _activeEventElement mechanism
|
-- In retained mode, use the old _activeEventElement mechanism
|
||||||
isActiveElement = (Gui._activeEventElement == nil or Gui._activeEventElement == self)
|
isActiveElement = (Context._activeEventElement == nil or Context._activeEventElement == self)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update theme state based on interaction
|
-- Update theme state based on interaction
|
||||||
@@ -2098,7 +2052,7 @@ function Element:update(dt)
|
|||||||
local newThemeState = self._themeManager:updateState(isHovering and isActiveElement, anyPressed, self._focused, self.disabled)
|
local newThemeState = self._themeManager:updateState(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 Context._immediateMode then
|
||||||
-- Update in StateManager for immediate mode
|
-- Update in StateManager for immediate mode
|
||||||
local hover = newThemeState == "hover"
|
local hover = newThemeState == "hover"
|
||||||
local pressed = newThemeState == "pressed"
|
local pressed = newThemeState == "pressed"
|
||||||
@@ -2180,15 +2134,15 @@ function Element:resize(newGameWidth, newGameHeight)
|
|||||||
if self.units.textSize.value then
|
if self.units.textSize.value then
|
||||||
local unit = self.units.textSize.unit
|
local unit = self.units.textSize.unit
|
||||||
local value = self.units.textSize.value
|
local value = self.units.textSize.value
|
||||||
local _, scaleY = Gui.getScaleFactors()
|
local _, scaleY = Context.getScaleFactors()
|
||||||
|
|
||||||
if unit == "ew" then
|
if unit == "ew" then
|
||||||
-- Element width relative (use current width)
|
-- Element width relative (use current width)
|
||||||
self.textSize = (value / 100) * self.width
|
self.textSize = (value / 100) * self.width
|
||||||
|
|
||||||
-- Apply min/max constraints
|
-- Apply min/max constraints
|
||||||
local minSize = self.minTextSize and (Gui.baseScale and (self.minTextSize * scaleY) or self.minTextSize)
|
local minSize = self.minTextSize and (Context.baseScale and (self.minTextSize * scaleY) or self.minTextSize)
|
||||||
local maxSize = self.maxTextSize and (Gui.baseScale and (self.maxTextSize * scaleY) or self.maxTextSize)
|
local maxSize = self.maxTextSize and (Context.baseScale and (self.maxTextSize * scaleY) or self.maxTextSize)
|
||||||
if minSize and self.textSize < minSize then
|
if minSize and self.textSize < minSize then
|
||||||
self.textSize = minSize
|
self.textSize = minSize
|
||||||
end
|
end
|
||||||
@@ -2203,8 +2157,8 @@ function Element:resize(newGameWidth, newGameHeight)
|
|||||||
self.textSize = (value / 100) * self.height
|
self.textSize = (value / 100) * self.height
|
||||||
|
|
||||||
-- Apply min/max constraints
|
-- Apply min/max constraints
|
||||||
local minSize = self.minTextSize and (Gui.baseScale and (self.minTextSize * scaleY) or self.minTextSize)
|
local minSize = self.minTextSize and (Context.baseScale and (self.minTextSize * scaleY) or self.minTextSize)
|
||||||
local maxSize = self.maxTextSize and (Gui.baseScale and (self.maxTextSize * scaleY) or self.maxTextSize)
|
local maxSize = self.maxTextSize and (Context.baseScale and (self.maxTextSize * scaleY) or self.maxTextSize)
|
||||||
if minSize and self.textSize < minSize then
|
if minSize and self.textSize < minSize then
|
||||||
self.textSize = minSize
|
self.textSize = minSize
|
||||||
end
|
end
|
||||||
@@ -2230,7 +2184,6 @@ function Element:calculateTextWidth()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if self.textSize then
|
if self.textSize then
|
||||||
-- Resolve font path from font family (same logic as in draw)
|
|
||||||
local fontPath = nil
|
local fontPath = nil
|
||||||
if self.fontFamily then
|
if self.fontFamily then
|
||||||
local themeToUse = self._themeManager:getTheme()
|
local themeToUse = self._themeManager:getTheme()
|
||||||
@@ -2245,7 +2198,6 @@ function Element:calculateTextWidth()
|
|||||||
|
|
||||||
local tempFont = FONT_CACHE.get(self.textSize, fontPath)
|
local tempFont = FONT_CACHE.get(self.textSize, fontPath)
|
||||||
local width = tempFont:getWidth(self.text)
|
local width = tempFont:getWidth(self.text)
|
||||||
-- Apply contentAutoSizingMultiplier if set
|
|
||||||
if self.contentAutoSizingMultiplier and self.contentAutoSizingMultiplier.width then
|
if self.contentAutoSizingMultiplier and self.contentAutoSizingMultiplier.width then
|
||||||
width = width * self.contentAutoSizingMultiplier.width
|
width = width * self.contentAutoSizingMultiplier.width
|
||||||
end
|
end
|
||||||
@@ -2254,7 +2206,6 @@ function Element:calculateTextWidth()
|
|||||||
|
|
||||||
local font = love.graphics.getFont()
|
local font = love.graphics.getFont()
|
||||||
local width = font:getWidth(self.text)
|
local width = font:getWidth(self.text)
|
||||||
-- Apply contentAutoSizingMultiplier if set
|
|
||||||
if self.contentAutoSizingMultiplier and self.contentAutoSizingMultiplier.width then
|
if self.contentAutoSizingMultiplier and self.contentAutoSizingMultiplier.width then
|
||||||
width = width * self.contentAutoSizingMultiplier.width
|
width = width * self.contentAutoSizingMultiplier.width
|
||||||
end
|
end
|
||||||
@@ -2267,10 +2218,8 @@ function Element:calculateTextHeight()
|
|||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get the font
|
|
||||||
local font
|
local font
|
||||||
if self.textSize then
|
if self.textSize then
|
||||||
-- Resolve font path from font family (same logic as in draw)
|
|
||||||
local fontPath = nil
|
local fontPath = nil
|
||||||
if self.fontFamily then
|
if self.fontFamily then
|
||||||
local themeToUse = self._themeManager:getTheme()
|
local themeToUse = self._themeManager:getTheme()
|
||||||
@@ -2289,26 +2238,19 @@ function Element:calculateTextHeight()
|
|||||||
|
|
||||||
local height = font:getHeight()
|
local height = font:getHeight()
|
||||||
|
|
||||||
-- If text wrapping is enabled, calculate height based on wrapped lines
|
|
||||||
if self.textWrap and (self.textWrap == "word" or self.textWrap == "char" or self.textWrap == true) then
|
if self.textWrap and (self.textWrap == "word" or self.textWrap == "char" or self.textWrap == true) then
|
||||||
-- Calculate available width for wrapping
|
|
||||||
local availableWidth = self.width
|
local availableWidth = self.width
|
||||||
|
|
||||||
-- If width is not set or is 0, try to use parent's content width
|
|
||||||
if (not availableWidth or availableWidth <= 0) and self.parent then
|
if (not availableWidth or availableWidth <= 0) and self.parent then
|
||||||
-- Use parent's content width (excluding padding)
|
|
||||||
availableWidth = self.parent.width
|
availableWidth = self.parent.width
|
||||||
end
|
end
|
||||||
|
|
||||||
if availableWidth and availableWidth > 0 then
|
if availableWidth and availableWidth > 0 then
|
||||||
-- Get the wrapped text lines using getWrap (returns width and table of lines)
|
|
||||||
local wrappedWidth, wrappedLines = font:getWrap(self.text, availableWidth)
|
local wrappedWidth, wrappedLines = font:getWrap(self.text, availableWidth)
|
||||||
-- Height is line height * number of lines
|
|
||||||
height = height * #wrappedLines
|
height = height * #wrappedLines
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply contentAutoSizingMultiplier if set
|
|
||||||
if self.contentAutoSizingMultiplier and self.contentAutoSizingMultiplier.height then
|
if self.contentAutoSizingMultiplier and self.contentAutoSizingMultiplier.height then
|
||||||
height = height * self.contentAutoSizingMultiplier.height
|
height = height * self.contentAutoSizingMultiplier.height
|
||||||
end
|
end
|
||||||
@@ -2317,19 +2259,11 @@ function Element:calculateTextHeight()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Element:calculateAutoWidth()
|
function Element:calculateAutoWidth()
|
||||||
-- During construction, LayoutEngine might not be initialized yet
|
|
||||||
-- Fall back to text width calculation
|
|
||||||
if not self._layoutEngine then
|
|
||||||
return self:calculateTextWidth()
|
|
||||||
end
|
|
||||||
-- Delegate to LayoutEngine
|
|
||||||
return self._layoutEngine:calculateAutoWidth()
|
return self._layoutEngine:calculateAutoWidth()
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Calculate auto height based on children
|
--- Calculate auto height based on children
|
||||||
function Element:calculateAutoHeight()
|
function Element:calculateAutoHeight()
|
||||||
-- During construction, LayoutEngine might not be initialized yet
|
|
||||||
-- Fall back to text height calculation
|
|
||||||
if not self._layoutEngine then
|
if not self._layoutEngine then
|
||||||
return self:calculateTextHeight()
|
return self:calculateTextHeight()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
---@field rowGap number? Gap between grid rows
|
---@field rowGap number? Gap between grid rows
|
||||||
---@field _Grid table
|
---@field _Grid table
|
||||||
---@field _Units table
|
---@field _Units table
|
||||||
---@field _Gui table
|
---@field _Context table
|
||||||
---@field _Positioning table
|
---@field _Positioning table
|
||||||
---@field _FlexDirection table
|
---@field _FlexDirection table
|
||||||
---@field _JustifyContent table
|
---@field _JustifyContent table
|
||||||
@@ -39,7 +39,7 @@ 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, Units, Gui}
|
---@param deps table Dependencies {utils, Grid, Units, Context}
|
||||||
---@return LayoutEngine
|
---@return LayoutEngine
|
||||||
function LayoutEngine.new(props, deps)
|
function LayoutEngine.new(props, deps)
|
||||||
local enums = deps.utils.enums
|
local enums = deps.utils.enums
|
||||||
@@ -56,7 +56,7 @@ function LayoutEngine.new(props, deps)
|
|||||||
-- Store dependencies for instance methods
|
-- Store dependencies for instance methods
|
||||||
self._Grid = deps.Grid
|
self._Grid = deps.Grid
|
||||||
self._Units = deps.Units
|
self._Units = deps.Units
|
||||||
self._Gui = deps.Gui
|
self._Context = deps.Context
|
||||||
self._Positioning = Positioning
|
self._Positioning = Positioning
|
||||||
self._FlexDirection = FlexDirection
|
self._FlexDirection = FlexDirection
|
||||||
self._JustifyContent = JustifyContent
|
self._JustifyContent = JustifyContent
|
||||||
@@ -141,12 +141,14 @@ end
|
|||||||
|
|
||||||
--- Layout children within this element according to positioning mode
|
--- Layout children within this element according to positioning mode
|
||||||
function LayoutEngine:layoutChildren()
|
function LayoutEngine:layoutChildren()
|
||||||
local element = self.element
|
if self.element == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if self.positioning == self._Positioning.ABSOLUTE or self.positioning == self._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(self.element.children) do
|
||||||
if child.top or child.right or child.bottom or child.left then
|
if child.top or child.right or child.bottom or child.left then
|
||||||
self:applyPositioningOffsets(child)
|
self:applyPositioningOffsets(child)
|
||||||
end
|
end
|
||||||
@@ -156,11 +158,11 @@ function LayoutEngine:layoutChildren()
|
|||||||
|
|
||||||
-- Handle grid layout
|
-- Handle grid layout
|
||||||
if self.positioning == self._Positioning.GRID then
|
if self.positioning == self._Positioning.GRID then
|
||||||
self._Grid.layoutGridItems(element)
|
self._Grid.layoutGridItems(self.element)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local childCount = #element.children
|
local childCount = #self.element.children
|
||||||
|
|
||||||
if childCount == 0 then
|
if childCount == 0 then
|
||||||
return
|
return
|
||||||
@@ -168,7 +170,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(self.element.children) do
|
||||||
local isFlexChild = not (child.positioning == self._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)
|
||||||
@@ -185,7 +187,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
local reservedCrossStart = 0 -- Space reserved at the start of cross axis (top for horizontal, left for vertical)
|
local reservedCrossStart = 0 -- Space reserved at the start of cross axis (top for horizontal, left for vertical)
|
||||||
local reservedCrossEnd = 0 -- Space reserved at the end of cross axis (bottom for horizontal, right for vertical)
|
local reservedCrossEnd = 0 -- Space reserved at the end of cross axis (bottom for horizontal, right for vertical)
|
||||||
|
|
||||||
for _, child in ipairs(element.children) do
|
for _, child in ipairs(self.element.children) do
|
||||||
-- Only consider absolutely positioned children with explicit positioning
|
-- Only consider absolutely positioned children with explicit positioning
|
||||||
if child.positioning == self._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
|
||||||
@@ -245,11 +247,11 @@ function LayoutEngine:layoutChildren()
|
|||||||
local availableMainSize = 0
|
local availableMainSize = 0
|
||||||
local availableCrossSize = 0
|
local availableCrossSize = 0
|
||||||
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
if self.flexDirection == self._FlexDirection.HORIZONTAL then
|
||||||
availableMainSize = element.width - reservedMainStart - reservedMainEnd
|
availableMainSize = self.element.width - reservedMainStart - reservedMainEnd
|
||||||
availableCrossSize = element.height - reservedCrossStart - reservedCrossEnd
|
availableCrossSize = self.element.height - reservedCrossStart - reservedCrossEnd
|
||||||
else
|
else
|
||||||
availableMainSize = element.height - reservedMainStart - reservedMainEnd
|
availableMainSize = self.element.height - reservedMainStart - reservedMainEnd
|
||||||
availableCrossSize = element.width - reservedCrossStart - reservedCrossEnd
|
availableCrossSize = self.element.width - reservedCrossStart - reservedCrossEnd
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle flex wrap: create lines of children
|
-- Handle flex wrap: create lines of children
|
||||||
@@ -448,11 +450,16 @@ function LayoutEngine:layoutChildren()
|
|||||||
local childTotalCrossSize = childBorderBoxHeight + child.margin.top + child.margin.bottom
|
local childTotalCrossSize = childBorderBoxHeight + child.margin.top + child.margin.bottom
|
||||||
|
|
||||||
if effectiveAlign == self._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 = self.element.y + self.element.padding.top + reservedCrossStart + currentCrossPos + child.margin.top
|
||||||
elseif effectiveAlign == self._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 = self.element.y
|
||||||
|
+ self.element.padding.top
|
||||||
|
+ reservedCrossStart
|
||||||
|
+ currentCrossPos
|
||||||
|
+ ((lineHeight - childTotalCrossSize) / 2)
|
||||||
|
+ child.margin.top
|
||||||
elseif effectiveAlign == self._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 = self.element.y + self.element.padding.top + reservedCrossStart + currentCrossPos + lineHeight - childTotalCrossSize + child.margin.top
|
||||||
elseif effectiveAlign == self._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
|
||||||
@@ -461,7 +468,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
child._borderBoxHeight = availableHeight
|
child._borderBoxHeight = availableHeight
|
||||||
child.height = math.max(0, availableHeight - child.padding.top - child.padding.bottom)
|
child.height = math.max(0, availableHeight - child.padding.top - child.padding.bottom)
|
||||||
end
|
end
|
||||||
child.y = element.y + element.padding.top + reservedCrossStart + currentCrossPos + child.margin.top
|
child.y = self.element.y + self.element.padding.top + reservedCrossStart + currentCrossPos + child.margin.top
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply positioning offsets (top, right, bottom, left)
|
-- Apply positioning offsets (top, right, bottom, left)
|
||||||
@@ -485,11 +492,16 @@ function LayoutEngine:layoutChildren()
|
|||||||
local childTotalCrossSize = childBorderBoxWidth + child.margin.left + child.margin.right
|
local childTotalCrossSize = childBorderBoxWidth + child.margin.left + child.margin.right
|
||||||
|
|
||||||
if effectiveAlign == self._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 = self.element.x + self.element.padding.left + reservedCrossStart + currentCrossPos + child.margin.left
|
||||||
elseif effectiveAlign == self._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 = self.element.x
|
||||||
|
+ self.element.padding.left
|
||||||
|
+ reservedCrossStart
|
||||||
|
+ currentCrossPos
|
||||||
|
+ ((lineHeight - childTotalCrossSize) / 2)
|
||||||
|
+ child.margin.left
|
||||||
elseif effectiveAlign == self._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 = self.element.x + self.element.padding.left + reservedCrossStart + currentCrossPos + lineHeight - childTotalCrossSize + child.margin.left
|
||||||
elseif effectiveAlign == self._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
|
||||||
@@ -498,7 +510,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
child._borderBoxWidth = availableWidth
|
child._borderBoxWidth = availableWidth
|
||||||
child.width = math.max(0, availableWidth - child.padding.left - child.padding.right)
|
child.width = math.max(0, availableWidth - child.padding.left - child.padding.right)
|
||||||
end
|
end
|
||||||
child.x = element.x + element.padding.left + reservedCrossStart + currentCrossPos + child.margin.left
|
child.x = self.element.x + self.element.padding.left + reservedCrossStart + currentCrossPos + child.margin.left
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply positioning offsets (top, right, bottom, left)
|
-- Apply positioning offsets (top, right, bottom, left)
|
||||||
@@ -519,7 +531,7 @@ function LayoutEngine:layoutChildren()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- 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(self.element.children) do
|
||||||
if child.positioning == self._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)
|
||||||
@@ -532,19 +544,21 @@ function LayoutEngine:layoutChildren()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Detect overflow after children are laid out
|
-- Detect overflow after children are laid out
|
||||||
if element._detectOverflow then
|
if self.element._detectOverflow then
|
||||||
element:_detectOverflow()
|
self.element:_detectOverflow()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Calculate auto width based on children
|
--- Calculate auto width based on children
|
||||||
---@return number The calculated width
|
---@return number
|
||||||
function LayoutEngine:calculateAutoWidth()
|
function LayoutEngine:calculateAutoWidth()
|
||||||
local element = self.element
|
if self.element == nil then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
-- BORDER-BOX MODEL: Calculate content width, caller will add padding to get border-box
|
-- BORDER-BOX MODEL: Calculate content width, caller will add padding to get border-box
|
||||||
local contentWidth = element:calculateTextWidth()
|
local contentWidth = self.element:calculateTextWidth()
|
||||||
if not element.children or #element.children == 0 then
|
if not self.element.children or #self.element.children == 0 then
|
||||||
return contentWidth
|
return contentWidth
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -555,7 +569,7 @@ function LayoutEngine:calculateAutoWidth()
|
|||||||
local maxWidth = contentWidth
|
local maxWidth = contentWidth
|
||||||
local participatingChildren = 0
|
local participatingChildren = 0
|
||||||
|
|
||||||
for _, child in ipairs(element.children) do
|
for _, child in ipairs(self.element.children) do
|
||||||
-- Skip explicitly absolute positioned children as they don't affect parent auto-sizing
|
-- Skip explicitly absolute positioned children as they don't affect parent auto-sizing
|
||||||
if not child._explicitlyAbsolute then
|
if not child._explicitlyAbsolute then
|
||||||
-- BORDER-BOX MODEL: Use border-box width for auto-sizing calculations
|
-- BORDER-BOX MODEL: Use border-box width for auto-sizing calculations
|
||||||
@@ -578,13 +592,14 @@ function LayoutEngine:calculateAutoWidth()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Calculate auto height based on children
|
---@return number
|
||||||
---@return number The calculated height
|
|
||||||
function LayoutEngine:calculateAutoHeight()
|
function LayoutEngine:calculateAutoHeight()
|
||||||
local element = self.element
|
if self.element == nil then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
local height = element:calculateTextHeight()
|
local height = self.element:calculateTextHeight()
|
||||||
if not element.children or #element.children == 0 then
|
if not self.element.children or #self.element.children == 0 then
|
||||||
return height
|
return height
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -595,7 +610,7 @@ function LayoutEngine:calculateAutoHeight()
|
|||||||
local maxHeight = height
|
local maxHeight = height
|
||||||
local participatingChildren = 0
|
local participatingChildren = 0
|
||||||
|
|
||||||
for _, child in ipairs(element.children) do
|
for _, child in ipairs(self.element.children) do
|
||||||
-- Skip explicitly absolute positioned children as they don't affect parent auto-sizing
|
-- Skip explicitly absolute positioned children as they don't affect parent auto-sizing
|
||||||
if not child._explicitlyAbsolute then
|
if not child._explicitlyAbsolute then
|
||||||
-- BORDER-BOX MODEL: Use border-box height for auto-sizing calculations
|
-- BORDER-BOX MODEL: Use border-box height for auto-sizing calculations
|
||||||
@@ -622,162 +637,174 @@ end
|
|||||||
---@param newViewportWidth number
|
---@param newViewportWidth number
|
||||||
---@param newViewportHeight number
|
---@param newViewportHeight number
|
||||||
function LayoutEngine:recalculateUnits(newViewportWidth, newViewportHeight)
|
function LayoutEngine:recalculateUnits(newViewportWidth, newViewportHeight)
|
||||||
local element = self.element
|
if self.element == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
local Units = self._Units
|
local Units = self._Units
|
||||||
local Gui = self._Gui
|
|
||||||
|
|
||||||
-- Get updated scale factors
|
-- Get updated scale factors
|
||||||
local scaleX, scaleY = Gui.getScaleFactors()
|
local scaleX, scaleY = self._Context.getScaleFactors()
|
||||||
|
|
||||||
-- Recalculate border-box width if using viewport or percentage units (skip auto-sized)
|
-- Recalculate border-box width if using viewport or percentage units (skip auto-sized)
|
||||||
-- Store in _borderBoxWidth temporarily, will calculate content width after padding is resolved
|
-- Store in _borderBoxWidth temporarily, will calculate content width after padding is resolved
|
||||||
if element.units.width.unit ~= "px" and element.units.width.unit ~= "auto" then
|
if self.element.units.width.unit ~= "px" and self.element.units.width.unit ~= "auto" then
|
||||||
local parentWidth = element.parent and element.parent.width or newViewportWidth
|
local parentWidth = self.element.parent and self.element.parent.width or newViewportWidth
|
||||||
element._borderBoxWidth = Units.resolve(element.units.width.value, element.units.width.unit, newViewportWidth, newViewportHeight, parentWidth)
|
self.element._borderBoxWidth =
|
||||||
elseif element.units.width.unit == "px" and element.units.width.value and Gui.baseScale then
|
Units.resolve(self.element.units.width.value, self.element.units.width.unit, newViewportWidth, newViewportHeight, parentWidth)
|
||||||
|
elseif self.element.units.width.unit == "px" and self.element.units.width.value and self._Context.baseScale then
|
||||||
-- Reapply base scaling to pixel widths (border-box)
|
-- Reapply base scaling to pixel widths (border-box)
|
||||||
element._borderBoxWidth = element.units.width.value * scaleX
|
self.element._borderBoxWidth = self.element.units.width.value * scaleX
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Recalculate border-box height if using viewport or percentage units (skip auto-sized)
|
-- Recalculate border-box height if using viewport or percentage units (skip auto-sized)
|
||||||
-- Store in _borderBoxHeight temporarily, will calculate content height after padding is resolved
|
-- Store in _borderBoxHeight temporarily, will calculate content height after padding is resolved
|
||||||
if element.units.height.unit ~= "px" and element.units.height.unit ~= "auto" then
|
if self.element.units.height.unit ~= "px" and self.element.units.height.unit ~= "auto" then
|
||||||
local parentHeight = element.parent and element.parent.height or newViewportHeight
|
local parentHeight = self.element.parent and self.element.parent.height or newViewportHeight
|
||||||
element._borderBoxHeight = Units.resolve(element.units.height.value, element.units.height.unit, newViewportWidth, newViewportHeight, parentHeight)
|
self.element._borderBoxHeight =
|
||||||
elseif element.units.height.unit == "px" and element.units.height.value and Gui.baseScale then
|
Units.resolve(self.element.units.height.value, self.element.units.height.unit, newViewportWidth, newViewportHeight, parentHeight)
|
||||||
|
elseif self.element.units.height.unit == "px" and self.element.units.height.value and self._Context.baseScale then
|
||||||
-- Reapply base scaling to pixel heights (border-box)
|
-- Reapply base scaling to pixel heights (border-box)
|
||||||
element._borderBoxHeight = element.units.height.value * scaleY
|
self.element._borderBoxHeight = self.element.units.height.value * scaleY
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Recalculate position if using viewport or percentage units
|
-- Recalculate position if using viewport or percentage units
|
||||||
if element.units.x.unit ~= "px" then
|
if self.element.units.x.unit ~= "px" then
|
||||||
local parentWidth = element.parent and element.parent.width or newViewportWidth
|
local parentWidth = self.element.parent and self.element.parent.width or newViewportWidth
|
||||||
local baseX = element.parent and element.parent.x or 0
|
local baseX = self.element.parent and self.element.parent.x or 0
|
||||||
local offsetX = Units.resolve(element.units.x.value, element.units.x.unit, newViewportWidth, newViewportHeight, parentWidth)
|
local offsetX = Units.resolve(self.element.units.x.value, self.element.units.x.unit, newViewportWidth, newViewportHeight, parentWidth)
|
||||||
element.x = baseX + offsetX
|
self.element.x = baseX + offsetX
|
||||||
else
|
else
|
||||||
-- For pixel units, update position relative to parent's new position (with base scaling)
|
-- For pixel units, update position relative to parent's new position (with base scaling)
|
||||||
if element.parent then
|
if self.element.parent then
|
||||||
local baseX = element.parent.x
|
local baseX = self.element.parent.x
|
||||||
local scaledOffset = Gui.baseScale and (element.units.x.value * scaleX) or element.units.x.value
|
local scaledOffset = self._Context.baseScale and (self.element.units.x.value * scaleX) or self.element.units.x.value
|
||||||
element.x = baseX + scaledOffset
|
self.element.x = baseX + scaledOffset
|
||||||
elseif Gui.baseScale then
|
elseif self._Context.baseScale then
|
||||||
-- Top-level element with pixel position - apply base scaling
|
-- Top-level element with pixel position - apply base scaling
|
||||||
element.x = element.units.x.value * scaleX
|
self.element.x = self.element.units.x.value * scaleX
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if element.units.y.unit ~= "px" then
|
if self.element.units.y.unit ~= "px" then
|
||||||
local parentHeight = element.parent and element.parent.height or newViewportHeight
|
local parentHeight = self.element.parent and self.element.parent.height or newViewportHeight
|
||||||
local baseY = element.parent and element.parent.y or 0
|
local baseY = self.element.parent and self.element.parent.y or 0
|
||||||
local offsetY = Units.resolve(element.units.y.value, element.units.y.unit, newViewportWidth, newViewportHeight, parentHeight)
|
local offsetY = Units.resolve(self.element.units.y.value, self.element.units.y.unit, newViewportWidth, newViewportHeight, parentHeight)
|
||||||
element.y = baseY + offsetY
|
self.element.y = baseY + offsetY
|
||||||
else
|
else
|
||||||
-- For pixel units, update position relative to parent's new position (with base scaling)
|
-- For pixel units, update position relative to parent's new position (with base scaling)
|
||||||
if element.parent then
|
if self.element.parent then
|
||||||
local baseY = element.parent.y
|
local baseY = self.element.parent.y
|
||||||
local scaledOffset = Gui.baseScale and (element.units.y.value * scaleY) or element.units.y.value
|
local scaledOffset = self._Context.baseScale and (self.element.units.y.value * scaleY) or self.element.units.y.value
|
||||||
element.y = baseY + scaledOffset
|
self.element.y = baseY + scaledOffset
|
||||||
elseif Gui.baseScale then
|
elseif self._Context.baseScale then
|
||||||
-- Top-level element with pixel position - apply base scaling
|
-- Top-level element with pixel position - apply base scaling
|
||||||
element.y = element.units.y.value * scaleY
|
self.element.y = self.element.units.y.value * scaleY
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Recalculate textSize if auto-scaling is enabled or using viewport/element-relative units
|
-- Recalculate textSize if auto-scaling is enabled or using viewport/element-relative units
|
||||||
if element.autoScaleText and element.units.textSize.value then
|
if self.element.autoScaleText and self.element.units.textSize.value then
|
||||||
local unit = element.units.textSize.unit
|
local unit = self.element.units.textSize.unit
|
||||||
local value = element.units.textSize.value
|
local value = self.element.units.textSize.value
|
||||||
|
|
||||||
if unit == "px" and Gui.baseScale then
|
if unit == "px" and self._Context.baseScale then
|
||||||
-- With base scaling: scale pixel values relative to base resolution
|
-- With base scaling: scale pixel values relative to base resolution
|
||||||
element.textSize = value * scaleY
|
self.element.textSize = value * scaleY
|
||||||
elseif unit == "px" then
|
elseif unit == "px" then
|
||||||
-- Without base scaling but auto-scaling enabled: text doesn't scale
|
-- Without base scaling but auto-scaling enabled: text doesn't scale
|
||||||
element.textSize = value
|
self.element.textSize = value
|
||||||
elseif unit == "%" or unit == "vh" then
|
elseif unit == "%" or unit == "vh" then
|
||||||
-- Percentage and vh are relative to viewport height
|
-- Percentage and vh are relative to viewport height
|
||||||
element.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, newViewportHeight)
|
self.element.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, newViewportHeight)
|
||||||
elseif unit == "vw" then
|
elseif unit == "vw" then
|
||||||
-- vw is relative to viewport width
|
-- vw is relative to viewport width
|
||||||
element.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, newViewportWidth)
|
self.element.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, newViewportWidth)
|
||||||
elseif unit == "ew" then
|
elseif unit == "ew" then
|
||||||
-- Element width relative
|
-- Element width relative
|
||||||
element.textSize = (value / 100) * element.width
|
self.element.textSize = (value / 100) * self.element.width
|
||||||
elseif unit == "eh" then
|
elseif unit == "eh" then
|
||||||
-- Element height relative
|
-- Element height relative
|
||||||
element.textSize = (value / 100) * element.height
|
self.element.textSize = (value / 100) * self.element.height
|
||||||
else
|
else
|
||||||
element.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, nil)
|
self.element.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply min/max constraints (with base scaling)
|
-- Apply min/max constraints (with base scaling)
|
||||||
local minSize = element.minTextSize and (Gui.baseScale and (element.minTextSize * scaleY) or element.minTextSize)
|
local minSize = self.element.minTextSize and (self._Context.baseScale and (self.element.minTextSize * scaleY) or self.element.minTextSize)
|
||||||
local maxSize = element.maxTextSize and (Gui.baseScale and (element.maxTextSize * scaleY) or element.maxTextSize)
|
local maxSize = self.element.maxTextSize and (self._Context.baseScale and (self.element.maxTextSize * scaleY) or self.element.maxTextSize)
|
||||||
|
|
||||||
if minSize and element.textSize < minSize then
|
if minSize and self.element.textSize < minSize then
|
||||||
element.textSize = minSize
|
self.element.textSize = minSize
|
||||||
end
|
end
|
||||||
if maxSize and element.textSize > maxSize then
|
if maxSize and self.element.textSize > maxSize then
|
||||||
element.textSize = maxSize
|
self.element.textSize = maxSize
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Protect against too-small text sizes (minimum 1px)
|
-- Protect against too-small text sizes (minimum 1px)
|
||||||
if element.textSize < 1 then
|
if self.element.textSize < 1 then
|
||||||
element.textSize = 1 -- Minimum 1px
|
self.element.textSize = 1 -- Minimum 1px
|
||||||
end
|
end
|
||||||
elseif element.units.textSize.unit == "px" and element.units.textSize.value and Gui.baseScale then
|
elseif self.element.units.textSize.unit == "px" and self.element.units.textSize.value and self._Context.baseScale then
|
||||||
-- No auto-scaling but base scaling is set: reapply base scaling to pixel text sizes
|
-- No auto-scaling but base scaling is set: reapply base scaling to pixel text sizes
|
||||||
element.textSize = element.units.textSize.value * scaleY
|
self.element.textSize = self.element.units.textSize.value * scaleY
|
||||||
|
|
||||||
-- Protect against too-small text sizes (minimum 1px)
|
-- Protect against too-small text sizes (minimum 1px)
|
||||||
if element.textSize < 1 then
|
if self.element.textSize < 1 then
|
||||||
element.textSize = 1 -- Minimum 1px
|
self.element.textSize = 1 -- Minimum 1px
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Final protection: ensure textSize is always at least 1px (catches all edge cases)
|
-- Final protection: ensure textSize is always at least 1px (catches all edge cases)
|
||||||
if element.text and element.textSize and element.textSize < 1 then
|
if self.element.text and self.element.textSize and self.element.textSize < 1 then
|
||||||
element.textSize = 1 -- Minimum 1px
|
self.element.textSize = 1 -- Minimum 1px
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Recalculate gap if using viewport or percentage units
|
-- Recalculate gap if using viewport or percentage units
|
||||||
if element.units.gap.unit ~= "px" then
|
if self.element.units.gap.unit ~= "px" then
|
||||||
local containerSize = (self.flexDirection == self._FlexDirection.HORIZONTAL) and (element.parent and element.parent.width or newViewportWidth)
|
local containerSize = (self.flexDirection == self._FlexDirection.HORIZONTAL) and (self.element.parent and self.element.parent.width or newViewportWidth)
|
||||||
or (element.parent and element.parent.height or newViewportHeight)
|
or (self.element.parent and self.element.parent.height or newViewportHeight)
|
||||||
element.gap = Units.resolve(element.units.gap.value, element.units.gap.unit, newViewportWidth, newViewportHeight, containerSize)
|
self.element.gap = Units.resolve(self.element.units.gap.value, self.element.units.gap.unit, newViewportWidth, newViewportHeight, containerSize)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Recalculate spacing (padding/margin) if using viewport or percentage units
|
-- Recalculate spacing (padding/margin) if using viewport or percentage units
|
||||||
-- For percentage-based padding:
|
-- For percentage-based padding:
|
||||||
-- - If element has a parent: use parent's border-box dimensions (CSS spec for child elements)
|
-- - If element has a parent: use parent's border-box dimensions (CSS spec for child elements)
|
||||||
-- - If element has no parent: use element's own border-box dimensions (CSS spec for root elements)
|
-- - If element has no parent: use element's own border-box dimensions (CSS spec for root elements)
|
||||||
local parentBorderBoxWidth = element.parent and element.parent._borderBoxWidth or element._borderBoxWidth or newViewportWidth
|
local parentBorderBoxWidth = self.element.parent and self.element.parent._borderBoxWidth or self.element._borderBoxWidth or newViewportWidth
|
||||||
local parentBorderBoxHeight = element.parent and element.parent._borderBoxHeight or element._borderBoxHeight or newViewportHeight
|
local parentBorderBoxHeight = self.element.parent and self.element.parent._borderBoxHeight or self.element._borderBoxHeight or newViewportHeight
|
||||||
|
|
||||||
-- Handle shorthand properties first (horizontal/vertical)
|
-- Handle shorthand properties first (horizontal/vertical)
|
||||||
local resolvedHorizontalPadding = nil
|
local resolvedHorizontalPadding = nil
|
||||||
local resolvedVerticalPadding = nil
|
local resolvedVerticalPadding = nil
|
||||||
|
|
||||||
if element.units.padding.horizontal and element.units.padding.horizontal.unit ~= "px" then
|
if self.element.units.padding.horizontal and self.element.units.padding.horizontal.unit ~= "px" then
|
||||||
resolvedHorizontalPadding =
|
resolvedHorizontalPadding = Units.resolve(
|
||||||
Units.resolve(element.units.padding.horizontal.value, element.units.padding.horizontal.unit, newViewportWidth, newViewportHeight, parentBorderBoxWidth)
|
self.element.units.padding.horizontal.value,
|
||||||
elseif element.units.padding.horizontal and element.units.padding.horizontal.value then
|
self.element.units.padding.horizontal.unit,
|
||||||
resolvedHorizontalPadding = element.units.padding.horizontal.value
|
newViewportWidth,
|
||||||
|
newViewportHeight,
|
||||||
|
parentBorderBoxWidth
|
||||||
|
)
|
||||||
|
elseif self.element.units.padding.horizontal and self.element.units.padding.horizontal.value then
|
||||||
|
resolvedHorizontalPadding = self.element.units.padding.horizontal.value
|
||||||
end
|
end
|
||||||
|
|
||||||
if element.units.padding.vertical and element.units.padding.vertical.unit ~= "px" then
|
if self.element.units.padding.vertical and self.element.units.padding.vertical.unit ~= "px" then
|
||||||
resolvedVerticalPadding =
|
resolvedVerticalPadding = Units.resolve(
|
||||||
Units.resolve(element.units.padding.vertical.value, element.units.padding.vertical.unit, newViewportWidth, newViewportHeight, parentBorderBoxHeight)
|
self.element.units.padding.vertical.value,
|
||||||
elseif element.units.padding.vertical and element.units.padding.vertical.value then
|
self.element.units.padding.vertical.unit,
|
||||||
resolvedVerticalPadding = element.units.padding.vertical.value
|
newViewportWidth,
|
||||||
|
newViewportHeight,
|
||||||
|
parentBorderBoxHeight
|
||||||
|
)
|
||||||
|
elseif self.element.units.padding.vertical and self.element.units.padding.vertical.value then
|
||||||
|
resolvedVerticalPadding = self.element.units.padding.vertical.value
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Resolve individual padding sides (with fallback to shorthand)
|
-- Resolve individual padding sides (with fallback to shorthand)
|
||||||
for _, side in ipairs({ "top", "right", "bottom", "left" }) do
|
for _, side in ipairs({ "top", "right", "bottom", "left" }) do
|
||||||
-- Check if this side was explicitly set or if we should use shorthand
|
-- Check if this side was explicitly set or if we should use shorthand
|
||||||
local useShorthand = false
|
local useShorthand = false
|
||||||
if not element.units.padding[side].explicit then
|
if not self.element.units.padding[side].explicit then
|
||||||
-- Not explicitly set, check if we have shorthand
|
-- Not explicitly set, check if we have shorthand
|
||||||
if side == "left" or side == "right" then
|
if side == "left" or side == "right" then
|
||||||
useShorthand = resolvedHorizontalPadding ~= nil
|
useShorthand = resolvedHorizontalPadding ~= nil
|
||||||
@@ -789,15 +816,15 @@ function LayoutEngine:recalculateUnits(newViewportWidth, newViewportHeight)
|
|||||||
if useShorthand then
|
if useShorthand then
|
||||||
-- Use shorthand value
|
-- Use shorthand value
|
||||||
if side == "left" or side == "right" then
|
if side == "left" or side == "right" then
|
||||||
element.padding[side] = resolvedHorizontalPadding
|
self.element.padding[side] = resolvedHorizontalPadding
|
||||||
else
|
else
|
||||||
element.padding[side] = resolvedVerticalPadding
|
self.element.padding[side] = resolvedVerticalPadding
|
||||||
end
|
end
|
||||||
elseif element.units.padding[side].unit ~= "px" then
|
elseif self.element.units.padding[side].unit ~= "px" then
|
||||||
-- Recalculate non-pixel units
|
-- Recalculate non-pixel units
|
||||||
local parentSize = (side == "top" or side == "bottom") and parentBorderBoxHeight or parentBorderBoxWidth
|
local parentSize = (side == "top" or side == "bottom") and parentBorderBoxHeight or parentBorderBoxWidth
|
||||||
element.padding[side] =
|
self.element.padding[side] =
|
||||||
Units.resolve(element.units.padding[side].value, element.units.padding[side].unit, newViewportWidth, newViewportHeight, parentSize)
|
Units.resolve(self.element.units.padding[side].value, self.element.units.padding[side].unit, newViewportWidth, newViewportHeight, parentSize)
|
||||||
end
|
end
|
||||||
-- If unit is "px" and not using shorthand, value stays the same
|
-- If unit is "px" and not using shorthand, value stays the same
|
||||||
end
|
end
|
||||||
@@ -806,25 +833,35 @@ function LayoutEngine:recalculateUnits(newViewportWidth, newViewportHeight)
|
|||||||
local resolvedHorizontalMargin = nil
|
local resolvedHorizontalMargin = nil
|
||||||
local resolvedVerticalMargin = nil
|
local resolvedVerticalMargin = nil
|
||||||
|
|
||||||
if element.units.margin.horizontal and element.units.margin.horizontal.unit ~= "px" then
|
if self.element.units.margin.horizontal and self.element.units.margin.horizontal.unit ~= "px" then
|
||||||
resolvedHorizontalMargin =
|
resolvedHorizontalMargin = Units.resolve(
|
||||||
Units.resolve(element.units.margin.horizontal.value, element.units.margin.horizontal.unit, newViewportWidth, newViewportHeight, parentBorderBoxWidth)
|
self.element.units.margin.horizontal.value,
|
||||||
elseif element.units.margin.horizontal and element.units.margin.horizontal.value then
|
self.element.units.margin.horizontal.unit,
|
||||||
resolvedHorizontalMargin = element.units.margin.horizontal.value
|
newViewportWidth,
|
||||||
|
newViewportHeight,
|
||||||
|
parentBorderBoxWidth
|
||||||
|
)
|
||||||
|
elseif self.element.units.margin.horizontal and self.element.units.margin.horizontal.value then
|
||||||
|
resolvedHorizontalMargin = self.element.units.margin.horizontal.value
|
||||||
end
|
end
|
||||||
|
|
||||||
if element.units.margin.vertical and element.units.margin.vertical.unit ~= "px" then
|
if self.element.units.margin.vertical and self.element.units.margin.vertical.unit ~= "px" then
|
||||||
resolvedVerticalMargin =
|
resolvedVerticalMargin = Units.resolve(
|
||||||
Units.resolve(element.units.margin.vertical.value, element.units.margin.vertical.unit, newViewportWidth, newViewportHeight, parentBorderBoxHeight)
|
self.element.units.margin.vertical.value,
|
||||||
elseif element.units.margin.vertical and element.units.margin.vertical.value then
|
self.element.units.margin.vertical.unit,
|
||||||
resolvedVerticalMargin = element.units.margin.vertical.value
|
newViewportWidth,
|
||||||
|
newViewportHeight,
|
||||||
|
parentBorderBoxHeight
|
||||||
|
)
|
||||||
|
elseif self.element.units.margin.vertical and self.element.units.margin.vertical.value then
|
||||||
|
resolvedVerticalMargin = self.element.units.margin.vertical.value
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Resolve individual margin sides (with fallback to shorthand)
|
-- Resolve individual margin sides (with fallback to shorthand)
|
||||||
for _, side in ipairs({ "top", "right", "bottom", "left" }) do
|
for _, side in ipairs({ "top", "right", "bottom", "left" }) do
|
||||||
-- Check if this side was explicitly set or if we should use shorthand
|
-- Check if this side was explicitly set or if we should use shorthand
|
||||||
local useShorthand = false
|
local useShorthand = false
|
||||||
if not element.units.margin[side].explicit then
|
if not self.element.units.margin[side].explicit then
|
||||||
-- Not explicitly set, check if we have shorthand
|
-- Not explicitly set, check if we have shorthand
|
||||||
if side == "left" or side == "right" then
|
if side == "left" or side == "right" then
|
||||||
useShorthand = resolvedHorizontalMargin ~= nil
|
useShorthand = resolvedHorizontalMargin ~= nil
|
||||||
@@ -836,14 +873,15 @@ function LayoutEngine:recalculateUnits(newViewportWidth, newViewportHeight)
|
|||||||
if useShorthand then
|
if useShorthand then
|
||||||
-- Use shorthand value
|
-- Use shorthand value
|
||||||
if side == "left" or side == "right" then
|
if side == "left" or side == "right" then
|
||||||
element.margin[side] = resolvedHorizontalMargin
|
self.element.margin[side] = resolvedHorizontalMargin
|
||||||
else
|
else
|
||||||
element.margin[side] = resolvedVerticalMargin
|
self.element.margin[side] = resolvedVerticalMargin
|
||||||
end
|
end
|
||||||
elseif element.units.margin[side].unit ~= "px" then
|
elseif self.element.units.margin[side].unit ~= "px" then
|
||||||
-- Recalculate non-pixel units
|
-- Recalculate non-pixel units
|
||||||
local parentSize = (side == "top" or side == "bottom") and parentBorderBoxHeight or parentBorderBoxWidth
|
local parentSize = (side == "top" or side == "bottom") and parentBorderBoxHeight or parentBorderBoxWidth
|
||||||
element.margin[side] = Units.resolve(element.units.margin[side].value, element.units.margin[side].unit, newViewportWidth, newViewportHeight, parentSize)
|
self.element.margin[side] =
|
||||||
|
Units.resolve(self.element.units.margin[side].value, self.element.units.margin[side].unit, newViewportWidth, newViewportHeight, parentSize)
|
||||||
end
|
end
|
||||||
-- If unit is "px" and not using shorthand, value stays the same
|
-- If unit is "px" and not using shorthand, value stays the same
|
||||||
end
|
end
|
||||||
@@ -852,31 +890,31 @@ function LayoutEngine:recalculateUnits(newViewportWidth, newViewportHeight)
|
|||||||
-- For explicitly-sized elements (non-auto), _borderBoxWidth/_borderBoxHeight were set earlier
|
-- For explicitly-sized elements (non-auto), _borderBoxWidth/_borderBoxHeight were set earlier
|
||||||
-- Now we calculate content width/height by subtracting padding
|
-- Now we calculate content width/height by subtracting padding
|
||||||
-- Only recalculate if using viewport/percentage units (where _borderBoxWidth actually changed)
|
-- Only recalculate if using viewport/percentage units (where _borderBoxWidth actually changed)
|
||||||
if element.units.width.unit ~= "auto" and element.units.width.unit ~= "px" then
|
if self.element.units.width.unit ~= "auto" and self.element.units.width.unit ~= "px" then
|
||||||
-- _borderBoxWidth was recalculated for viewport/percentage units
|
-- _borderBoxWidth was recalculated for viewport/percentage units
|
||||||
-- Calculate content width by subtracting padding
|
-- Calculate content width by subtracting padding
|
||||||
element.width = math.max(0, element._borderBoxWidth - element.padding.left - element.padding.right)
|
self.element.width = math.max(0, self.element._borderBoxWidth - self.element.padding.left - self.element.padding.right)
|
||||||
elseif element.units.width.unit == "auto" then
|
elseif self.element.units.width.unit == "auto" then
|
||||||
-- For auto-sized elements, width is content width (calculated in resize method)
|
-- For auto-sized elements, width is content width (calculated in resize method)
|
||||||
-- Update border-box to include padding
|
-- Update border-box to include padding
|
||||||
element._borderBoxWidth = element.width + element.padding.left + element.padding.right
|
self.element._borderBoxWidth = self.element.width + self.element.padding.left + self.element.padding.right
|
||||||
end
|
end
|
||||||
-- For pixel units, width stays as-is (may have been manually modified)
|
-- For pixel units, width stays as-is (may have been manually modified)
|
||||||
|
|
||||||
if element.units.height.unit ~= "auto" and element.units.height.unit ~= "px" then
|
if self.element.units.height.unit ~= "auto" and self.element.units.height.unit ~= "px" then
|
||||||
-- _borderBoxHeight was recalculated for viewport/percentage units
|
-- _borderBoxHeight was recalculated for viewport/percentage units
|
||||||
-- Calculate content height by subtracting padding
|
-- Calculate content height by subtracting padding
|
||||||
element.height = math.max(0, element._borderBoxHeight - element.padding.top - element.padding.bottom)
|
self.element.height = math.max(0, self.element._borderBoxHeight - self.element.padding.top - self.element.padding.bottom)
|
||||||
elseif element.units.height.unit == "auto" then
|
elseif self.element.units.height.unit == "auto" then
|
||||||
-- For auto-sized elements, height is content height (calculated in resize method)
|
-- For auto-sized elements, height is content height (calculated in resize method)
|
||||||
-- Update border-box to include padding
|
-- Update border-box to include padding
|
||||||
element._borderBoxHeight = element.height + element.padding.top + element.padding.bottom
|
self.element._borderBoxHeight = self.element.height + self.element.padding.top + self.element.padding.bottom
|
||||||
end
|
end
|
||||||
-- For pixel units, height stays as-is (may have been manually modified)
|
-- For pixel units, height stays as-is (may have been manually modified)
|
||||||
|
|
||||||
-- Detect overflow after layout calculations
|
-- Detect overflow after layout calculations
|
||||||
if element._detectOverflow then
|
if self.element._detectOverflow then
|
||||||
element:_detectOverflow()
|
self.element:_detectOverflow()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
local Units = {}
|
local Units = {}
|
||||||
|
|
||||||
|
local Context = nil
|
||||||
|
|
||||||
|
--- Initialize Units module with Context dependency
|
||||||
|
---@param context table The Context module
|
||||||
|
function Units.initialize(context)
|
||||||
|
Context = context
|
||||||
|
end
|
||||||
|
|
||||||
---@param value string|number
|
---@param value string|number
|
||||||
---@return number, string -- Returns numeric value and unit type ("px", "%", "vw", "vh")
|
---@return number, string -- Returns numeric value and unit type ("px", "%", "vw", "vh")
|
||||||
function Units.parse(value)
|
function Units.parse(value)
|
||||||
@@ -66,8 +74,8 @@ end
|
|||||||
---@return number, number -- width, height
|
---@return number, number -- width, height
|
||||||
function Units.getViewport()
|
function Units.getViewport()
|
||||||
-- Return cached viewport if available (only during resize operations)
|
-- Return cached viewport if available (only during resize operations)
|
||||||
if Gui and Gui._cachedViewport and Gui._cachedViewport.width > 0 then
|
if Context and Context._cachedViewport and Context._cachedViewport.width > 0 then
|
||||||
return Gui._cachedViewport.width, Gui._cachedViewport.height
|
return Context._cachedViewport.width, Context._cachedViewport.height
|
||||||
end
|
end
|
||||||
|
|
||||||
if love.graphics and love.graphics.getDimensions then
|
if love.graphics and love.graphics.getDimensions then
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ local AnimationProps = {}
|
|||||||
---@field gridColumns number? -- Number of columns in the grid (default: 1)
|
---@field gridColumns number? -- Number of columns in the grid (default: 1)
|
||||||
---@field columnGap number|string? -- Gap between grid columns (default: 0)
|
---@field columnGap number|string? -- Gap between grid columns (default: 0)
|
||||||
---@field rowGap number|string? -- Gap between grid rows (default: 0)
|
---@field rowGap number|string? -- Gap between grid rows (default: 0)
|
||||||
---@field theme string? -- Theme name to use (e.g., "space", "metal"). Defaults to theme from Gui.init()
|
---@field theme string? -- Theme name to use (e.g., "space", "metal"). Defaults to theme from flexlove.init()
|
||||||
---@field themeComponent string? -- Theme component to use (e.g., "panel", "button", "input"). If nil, no theme is applied
|
---@field themeComponent string? -- Theme component to use (e.g., "panel", "button", "input"). If nil, no theme is applied
|
||||||
---@field disabled boolean? -- Whether the element is disabled (default: false)
|
---@field disabled boolean? -- Whether the element is disabled (default: false)
|
||||||
---@field active boolean? -- Whether the element is active/focused (for inputs, default: false)
|
---@field active boolean? -- Whether the element is active/focused (for inputs, default: false)
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ return {
|
|||||||
Elements with `themeComponent` automatically set `disableHighlight = true` to prevent the default gray pressed overlay from interfering with theme visuals. You can override this:
|
Elements with `themeComponent` automatically set `disableHighlight = true` to prevent the default gray pressed overlay from interfering with theme visuals. You can override this:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
Gui.new({
|
FlexLove.new({
|
||||||
themeComponent = "button",
|
themeComponent = "button",
|
||||||
disableHighlight = false, -- Force enable highlight overlay
|
disableHighlight = false, -- Force enable highlight overlay
|
||||||
-- ...
|
-- ...
|
||||||
|
|||||||
Reference in New Issue
Block a user