theme manager module
This commit is contained in:
@@ -28,6 +28,7 @@ local LayoutEngine = req("LayoutEngine")
|
|||||||
local Renderer = req("Renderer")
|
local Renderer = req("Renderer")
|
||||||
local EventHandler = req("EventHandler")
|
local EventHandler = req("EventHandler")
|
||||||
local ScrollManager = req("ScrollManager")
|
local ScrollManager = req("ScrollManager")
|
||||||
|
local ThemeManager = req("ThemeManager")
|
||||||
|
|
||||||
-- Extract utilities
|
-- Extract utilities
|
||||||
local enums = utils.enums
|
local enums = utils.enums
|
||||||
@@ -206,22 +207,27 @@ function Element.new(props)
|
|||||||
})
|
})
|
||||||
self._eventHandler:initialize(self)
|
self._eventHandler:initialize(self)
|
||||||
|
|
||||||
-- Initialize theme state (will be managed by StateManager in immediate mode)
|
|
||||||
self._themeState = "normal"
|
|
||||||
|
|
||||||
-- Initialize state manager ID for immediate mode (use self.id which may be auto-generated)
|
-- Initialize state manager ID for immediate mode (use self.id which may be auto-generated)
|
||||||
self._stateId = self.id
|
self._stateId = self.id
|
||||||
|
|
||||||
-- Handle theme property:
|
-- Initialize ThemeManager for theme management
|
||||||
-- - theme: which theme to use (defaults to Gui.defaultTheme if not specified)
|
self._themeManager = ThemeManager.new({
|
||||||
-- - themeComponent: which component from the theme (e.g., "panel", "button", "input")
|
theme = props.theme or Gui.defaultTheme,
|
||||||
-- If themeComponent is nil, no theme is applied (manual styling)
|
themeComponent = props.themeComponent or nil,
|
||||||
self.theme = props.theme or Gui.defaultTheme
|
disabled = props.disabled or false,
|
||||||
self.themeComponent = props.themeComponent or nil
|
active = props.active or false,
|
||||||
|
disableHighlight = props.disableHighlight,
|
||||||
|
scaleCorners = props.scaleCorners,
|
||||||
|
scalingAlgorithm = props.scalingAlgorithm,
|
||||||
|
})
|
||||||
|
self._themeManager:initialize(self)
|
||||||
|
|
||||||
-- Initialize state properties
|
-- Expose theme properties for backward compatibility
|
||||||
self.disabled = props.disabled or false
|
self.theme = self._themeManager.theme
|
||||||
self.active = props.active or false
|
self.themeComponent = self._themeManager.themeComponent
|
||||||
|
self.disabled = self._themeManager.disabled
|
||||||
|
self.active = self._themeManager.active
|
||||||
|
self._themeState = self._themeManager:getState()
|
||||||
|
|
||||||
-- disableHighlight defaults to true when using themeComponent (themes handle their own visual feedback)
|
-- disableHighlight defaults to true when using themeComponent (themes handle their own visual feedback)
|
||||||
-- Can be explicitly overridden by setting props.disableHighlight
|
-- Can be explicitly overridden by setting props.disableHighlight
|
||||||
@@ -237,34 +243,14 @@ function Element.new(props)
|
|||||||
-- Explicitly set on element
|
-- Explicitly set on element
|
||||||
self.contentAutoSizingMultiplier = props.contentAutoSizingMultiplier
|
self.contentAutoSizingMultiplier = props.contentAutoSizingMultiplier
|
||||||
else
|
else
|
||||||
-- Try to source from theme
|
-- Try to source from theme via ThemeManager
|
||||||
local themeToUse = self.theme and Theme.get(self.theme) or Theme.getActive()
|
local multiplier = self._themeManager:getContentAutoSizingMultiplier()
|
||||||
if themeToUse then
|
self.contentAutoSizingMultiplier = multiplier or { 1, 1 }
|
||||||
-- First check if themeComponent has a multiplier
|
|
||||||
if self.themeComponent then
|
|
||||||
local component = themeToUse.components[self.themeComponent]
|
|
||||||
if component and component.contentAutoSizingMultiplier then
|
|
||||||
self.contentAutoSizingMultiplier = component.contentAutoSizingMultiplier
|
|
||||||
elseif themeToUse.contentAutoSizingMultiplier then
|
|
||||||
-- Fall back to theme default
|
|
||||||
self.contentAutoSizingMultiplier = themeToUse.contentAutoSizingMultiplier
|
|
||||||
else
|
|
||||||
self.contentAutoSizingMultiplier = { 1, 1 }
|
|
||||||
end
|
|
||||||
elseif themeToUse.contentAutoSizingMultiplier then
|
|
||||||
self.contentAutoSizingMultiplier = themeToUse.contentAutoSizingMultiplier
|
|
||||||
else
|
|
||||||
self.contentAutoSizingMultiplier = { 1, 1 }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.contentAutoSizingMultiplier = { 1, 1 }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Initialize 9-patch corner scaling properties
|
-- Expose 9-patch corner scaling properties for backward compatibility
|
||||||
-- These override theme component settings when specified
|
self.scaleCorners = self._themeManager.scaleCorners
|
||||||
self.scaleCorners = props.scaleCorners
|
self.scalingAlgorithm = self._themeManager.scalingAlgorithm
|
||||||
self.scalingAlgorithm = props.scalingAlgorithm
|
|
||||||
|
|
||||||
-- Initialize blur properties
|
-- Initialize blur properties
|
||||||
self.contentBlur = props.contentBlur
|
self.contentBlur = props.contentBlur
|
||||||
@@ -502,13 +488,9 @@ function Element.new(props)
|
|||||||
-- Inherit from parent if parent has fontFamily set
|
-- Inherit from parent if parent has fontFamily set
|
||||||
self.fontFamily = self.parent.fontFamily
|
self.fontFamily = self.parent.fontFamily
|
||||||
elseif props.themeComponent then
|
elseif props.themeComponent then
|
||||||
-- If using themeComponent, try to get default from theme
|
-- If using themeComponent, try to get default from theme via ThemeManager
|
||||||
local themeToUse = self.theme and Theme.get(self.theme) or Theme.getActive()
|
local defaultFont = self._themeManager:getDefaultFontFamily()
|
||||||
if themeToUse and themeToUse.fonts and themeToUse.fonts["default"] then
|
self.fontFamily = defaultFont and "default" or nil
|
||||||
self.fontFamily = "default"
|
|
||||||
else
|
|
||||||
self.fontFamily = nil
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
self.fontFamily = nil
|
self.fontFamily = nil
|
||||||
end
|
end
|
||||||
@@ -671,26 +653,23 @@ 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
|
||||||
if self.themeComponent then
|
if self._themeManager:hasThemeComponent() then
|
||||||
local themeToUse = self.theme and Theme.get(self.theme) or Theme.getActive()
|
local component = self._themeManager:getComponent()
|
||||||
if themeToUse and themeToUse.components[self.themeComponent] then
|
if component and component._ninePatchData and component._ninePatchData.contentPadding then
|
||||||
local component = themeToUse.components[self.themeComponent]
|
-- Only use 9-patch padding if no explicit padding was provided
|
||||||
if component._ninePatchData and component._ninePatchData.contentPadding then
|
if
|
||||||
-- Only use 9-patch padding if no explicit padding was provided
|
not props.padding
|
||||||
if
|
or (
|
||||||
not props.padding
|
not props.padding.top
|
||||||
or (
|
and not props.padding.right
|
||||||
not props.padding.top
|
and not props.padding.bottom
|
||||||
and not props.padding.right
|
and not props.padding.left
|
||||||
and not props.padding.bottom
|
and not props.padding.horizontal
|
||||||
and not props.padding.left
|
and not props.padding.vertical
|
||||||
and not props.padding.horizontal
|
)
|
||||||
and not props.padding.vertical
|
then
|
||||||
)
|
use9PatchPadding = true
|
||||||
then
|
ninePatchContentPadding = component._ninePatchData.contentPadding
|
||||||
use9PatchPadding = true
|
|
||||||
ninePatchContentPadding = component._ninePatchData.contentPadding
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -699,39 +678,12 @@ function Element.new(props)
|
|||||||
-- For auto-sized elements, this is content width; for explicit sizing, this is border-box width
|
-- For auto-sized elements, this is content width; for explicit sizing, this is border-box width
|
||||||
local tempPadding
|
local tempPadding
|
||||||
if use9PatchPadding then
|
if use9PatchPadding then
|
||||||
-- Scale 9-patch content padding to match the actual rendered size
|
-- Get scaled 9-patch content padding from ThemeManager
|
||||||
-- The contentPadding values are in the original image's pixel coordinates,
|
local scaledPadding = self._themeManager:getScaledContentPadding(tempWidth, tempHeight)
|
||||||
-- but we need to scale them proportionally to the element's actual size
|
if scaledPadding then
|
||||||
local themeToUse = self.theme and Theme.get(self.theme) or Theme.getActive()
|
tempPadding = scaledPadding
|
||||||
if themeToUse and themeToUse.components[self.themeComponent] then
|
|
||||||
local component = themeToUse.components[self.themeComponent]
|
|
||||||
local atlasImage = component._loadedAtlas or themeToUse.atlas
|
|
||||||
|
|
||||||
if atlasImage and type(atlasImage) ~= "string" then
|
|
||||||
local originalWidth, originalHeight = atlasImage:getDimensions()
|
|
||||||
|
|
||||||
-- Calculate the scale factor based on the element's border-box size vs original image size
|
|
||||||
-- For explicit sizing, tempWidth/tempHeight represent the border-box dimensions
|
|
||||||
local scaleX = tempWidth / originalWidth
|
|
||||||
local scaleY = tempHeight / originalHeight
|
|
||||||
|
|
||||||
tempPadding = {
|
|
||||||
left = ninePatchContentPadding.left * scaleX,
|
|
||||||
top = ninePatchContentPadding.top * scaleY,
|
|
||||||
right = ninePatchContentPadding.right * scaleX,
|
|
||||||
bottom = ninePatchContentPadding.bottom * scaleY,
|
|
||||||
}
|
|
||||||
else
|
|
||||||
-- Fallback if atlas image not available
|
|
||||||
tempPadding = {
|
|
||||||
left = ninePatchContentPadding.left,
|
|
||||||
top = ninePatchContentPadding.top,
|
|
||||||
right = ninePatchContentPadding.right,
|
|
||||||
bottom = ninePatchContentPadding.bottom,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
-- Fallback if theme not found
|
-- Fallback if scaling fails
|
||||||
tempPadding = {
|
tempPadding = {
|
||||||
left = ninePatchContentPadding.left,
|
left = ninePatchContentPadding.left,
|
||||||
top = ninePatchContentPadding.top,
|
top = ninePatchContentPadding.top,
|
||||||
@@ -924,8 +876,8 @@ function Element.new(props)
|
|||||||
if props.textColor then
|
if props.textColor then
|
||||||
self.textColor = props.textColor
|
self.textColor = props.textColor
|
||||||
else
|
else
|
||||||
-- Try to get text color from theme
|
-- Try to get text color from theme via ThemeManager
|
||||||
local themeToUse = self.theme and Theme.get(self.theme) or Theme.getActive()
|
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
|
||||||
else
|
else
|
||||||
@@ -1055,8 +1007,8 @@ function Element.new(props)
|
|||||||
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
|
-- Try to get text color from theme via ThemeManager
|
||||||
local themeToUse = self.theme and Theme.get(self.theme) or Theme.getActive()
|
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
|
||||||
else
|
else
|
||||||
@@ -1492,53 +1444,9 @@ end
|
|||||||
--- Returns the contentPadding for the current theme state, scaled to the element's size
|
--- Returns the contentPadding for the current theme state, scaled to the element's size
|
||||||
---@return table|nil -- {left, top, right, bottom} or nil if no contentPadding
|
---@return table|nil -- {left, top, right, bottom} or nil if no contentPadding
|
||||||
function Element:getScaledContentPadding()
|
function Element:getScaledContentPadding()
|
||||||
if not self.themeComponent then
|
local borderBoxWidth = self._borderBoxWidth or (self.width + self.padding.left + self.padding.right)
|
||||||
return nil
|
local borderBoxHeight = self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom)
|
||||||
end
|
return self._themeManager:getScaledContentPadding(borderBoxWidth, borderBoxHeight)
|
||||||
|
|
||||||
local themeToUse = self.theme and Theme.get(self.theme) or Theme.getActive()
|
|
||||||
if not themeToUse or not themeToUse.components[self.themeComponent] then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local component = themeToUse.components[self.themeComponent]
|
|
||||||
|
|
||||||
-- Check for state-specific override
|
|
||||||
local state = self._themeState or "normal"
|
|
||||||
if state and state ~= "normal" and component.states and component.states[state] then
|
|
||||||
component = component.states[state]
|
|
||||||
end
|
|
||||||
|
|
||||||
if not component._ninePatchData or not component._ninePatchData.contentPadding then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local contentPadding = component._ninePatchData.contentPadding
|
|
||||||
|
|
||||||
-- Scale contentPadding to match the actual rendered size
|
|
||||||
local atlasImage = component._loadedAtlas or themeToUse.atlas
|
|
||||||
if atlasImage and type(atlasImage) ~= "string" then
|
|
||||||
local originalWidth, originalHeight = atlasImage:getDimensions()
|
|
||||||
local borderBoxWidth = self._borderBoxWidth or (self.width + self.padding.left + self.padding.right)
|
|
||||||
local borderBoxHeight = self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom)
|
|
||||||
local scaleX = borderBoxWidth / originalWidth
|
|
||||||
local scaleY = borderBoxHeight / originalHeight
|
|
||||||
|
|
||||||
return {
|
|
||||||
left = contentPadding.left * scaleX,
|
|
||||||
top = contentPadding.top * scaleY,
|
|
||||||
right = contentPadding.right * scaleX,
|
|
||||||
bottom = contentPadding.bottom * scaleY,
|
|
||||||
}
|
|
||||||
else
|
|
||||||
-- Return unscaled values as fallback
|
|
||||||
return {
|
|
||||||
left = contentPadding.left,
|
|
||||||
top = contentPadding.top,
|
|
||||||
right = contentPadding.right,
|
|
||||||
bottom = contentPadding.bottom,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get or create blur instance for this element
|
--- Get or create blur instance for this element
|
||||||
@@ -2048,25 +1956,16 @@ function Element:update(dt)
|
|||||||
|
|
||||||
-- Update theme state based on interaction
|
-- Update theme state based on interaction
|
||||||
if self.themeComponent then
|
if self.themeComponent then
|
||||||
local newThemeState = "normal"
|
-- Check if any button is pressed via EventHandler
|
||||||
|
local anyPressed = self._eventHandler:isAnyButtonPressed()
|
||||||
|
|
||||||
-- Disabled state takes priority
|
-- Update theme state via ThemeManager
|
||||||
if self.disabled then
|
local newThemeState = self._themeManager:updateState(
|
||||||
newThemeState = "disabled"
|
isHovering and isActiveElement,
|
||||||
-- Active state (for inputs when focused/typing)
|
anyPressed,
|
||||||
elseif self.active then
|
self._focused,
|
||||||
newThemeState = "active"
|
self.disabled
|
||||||
-- Only show hover/pressed states if this element is active (not blocked)
|
)
|
||||||
elseif isHovering and isActiveElement then
|
|
||||||
-- Check if any button is pressed via EventHandler
|
|
||||||
local anyPressed = self._eventHandler:isAnyButtonPressed()
|
|
||||||
|
|
||||||
if anyPressed then
|
|
||||||
newThemeState = "pressed"
|
|
||||||
else
|
|
||||||
newThemeState = "hover"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 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
|
||||||
@@ -2107,255 +2006,10 @@ end
|
|||||||
---@param newViewportWidth number
|
---@param newViewportWidth number
|
||||||
---@param newViewportHeight number
|
---@param newViewportHeight number
|
||||||
function Element:recalculateUnits(newViewportWidth, newViewportHeight)
|
function Element:recalculateUnits(newViewportWidth, newViewportHeight)
|
||||||
-- Get updated scale factors
|
-- Delegate to LayoutEngine
|
||||||
local scaleX, scaleY = Gui.getScaleFactors()
|
if self._layoutEngine then
|
||||||
|
self._layoutEngine:recalculateUnits(newViewportWidth, newViewportHeight)
|
||||||
-- 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
|
|
||||||
if self.units.width.unit ~= "px" and self.units.width.unit ~= "auto" then
|
|
||||||
local parentWidth = self.parent and self.parent.width or newViewportWidth
|
|
||||||
self._borderBoxWidth = Units.resolve(self.units.width.value, self.units.width.unit, newViewportWidth, newViewportHeight, parentWidth)
|
|
||||||
elseif self.units.width.unit == "px" and self.units.width.value and Gui.baseScale then
|
|
||||||
-- Reapply base scaling to pixel widths (border-box)
|
|
||||||
self._borderBoxWidth = self.units.width.value * scaleX
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 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
|
|
||||||
if self.units.height.unit ~= "px" and self.units.height.unit ~= "auto" then
|
|
||||||
local parentHeight = self.parent and self.parent.height or newViewportHeight
|
|
||||||
self._borderBoxHeight = Units.resolve(self.units.height.value, self.units.height.unit, newViewportWidth, newViewportHeight, parentHeight)
|
|
||||||
elseif self.units.height.unit == "px" and self.units.height.value and Gui.baseScale then
|
|
||||||
-- Reapply base scaling to pixel heights (border-box)
|
|
||||||
self._borderBoxHeight = self.units.height.value * scaleY
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Recalculate position if using viewport or percentage units
|
|
||||||
if self.units.x.unit ~= "px" then
|
|
||||||
local parentWidth = self.parent and self.parent.width or newViewportWidth
|
|
||||||
local baseX = self.parent and self.parent.x or 0
|
|
||||||
local offsetX = Units.resolve(self.units.x.value, self.units.x.unit, newViewportWidth, newViewportHeight, parentWidth)
|
|
||||||
self.x = baseX + offsetX
|
|
||||||
else
|
|
||||||
-- For pixel units, update position relative to parent's new position (with base scaling)
|
|
||||||
if self.parent then
|
|
||||||
local baseX = self.parent.x
|
|
||||||
local scaledOffset = Gui.baseScale and (self.units.x.value * scaleX) or self.units.x.value
|
|
||||||
self.x = baseX + scaledOffset
|
|
||||||
elseif Gui.baseScale then
|
|
||||||
-- Top-level element with pixel position - apply base scaling
|
|
||||||
self.x = self.units.x.value * scaleX
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.units.y.unit ~= "px" then
|
|
||||||
local parentHeight = self.parent and self.parent.height or newViewportHeight
|
|
||||||
local baseY = self.parent and self.parent.y or 0
|
|
||||||
local offsetY = Units.resolve(self.units.y.value, self.units.y.unit, newViewportWidth, newViewportHeight, parentHeight)
|
|
||||||
self.y = baseY + offsetY
|
|
||||||
else
|
|
||||||
-- For pixel units, update position relative to parent's new position (with base scaling)
|
|
||||||
if self.parent then
|
|
||||||
local baseY = self.parent.y
|
|
||||||
local scaledOffset = Gui.baseScale and (self.units.y.value * scaleY) or self.units.y.value
|
|
||||||
self.y = baseY + scaledOffset
|
|
||||||
elseif Gui.baseScale then
|
|
||||||
-- Top-level element with pixel position - apply base scaling
|
|
||||||
self.y = self.units.y.value * scaleY
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Recalculate textSize if auto-scaling is enabled or using viewport/element-relative units
|
|
||||||
if self.autoScaleText and self.units.textSize.value then
|
|
||||||
local unit = self.units.textSize.unit
|
|
||||||
local value = self.units.textSize.value
|
|
||||||
|
|
||||||
if unit == "px" and Gui.baseScale then
|
|
||||||
-- With base scaling: scale pixel values relative to base resolution
|
|
||||||
self.textSize = value * scaleY
|
|
||||||
elseif unit == "px" then
|
|
||||||
-- Without base scaling but auto-scaling enabled: text doesn't scale
|
|
||||||
self.textSize = value
|
|
||||||
elseif unit == "%" or unit == "vh" then
|
|
||||||
-- Percentage and vh are relative to viewport height
|
|
||||||
self.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, newViewportHeight)
|
|
||||||
elseif unit == "vw" then
|
|
||||||
-- vw is relative to viewport width
|
|
||||||
self.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, newViewportWidth)
|
|
||||||
elseif unit == "ew" then
|
|
||||||
-- Element width relative
|
|
||||||
self.textSize = (value / 100) * self.width
|
|
||||||
elseif unit == "eh" then
|
|
||||||
-- Element height relative
|
|
||||||
self.textSize = (value / 100) * self.height
|
|
||||||
else
|
|
||||||
self.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Apply min/max constraints (with base scaling)
|
|
||||||
local minSize = self.minTextSize and (Gui.baseScale and (self.minTextSize * scaleY) or self.minTextSize)
|
|
||||||
local maxSize = self.maxTextSize and (Gui.baseScale and (self.maxTextSize * scaleY) or self.maxTextSize)
|
|
||||||
|
|
||||||
if minSize and self.textSize < minSize then
|
|
||||||
self.textSize = minSize
|
|
||||||
end
|
|
||||||
if maxSize and self.textSize > maxSize then
|
|
||||||
self.textSize = maxSize
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Protect against too-small text sizes (minimum 1px)
|
|
||||||
if self.textSize < 1 then
|
|
||||||
self.textSize = 1 -- Minimum 1px
|
|
||||||
end
|
|
||||||
elseif self.units.textSize.unit == "px" and self.units.textSize.value and Gui.baseScale then
|
|
||||||
-- No auto-scaling but base scaling is set: reapply base scaling to pixel text sizes
|
|
||||||
self.textSize = self.units.textSize.value * scaleY
|
|
||||||
|
|
||||||
-- Protect against too-small text sizes (minimum 1px)
|
|
||||||
if self.textSize < 1 then
|
|
||||||
self.textSize = 1 -- Minimum 1px
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Final protection: ensure textSize is always at least 1px (catches all edge cases)
|
|
||||||
if self.text and self.textSize and self.textSize < 1 then
|
|
||||||
self.textSize = 1 -- Minimum 1px
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Recalculate gap if using viewport or percentage units
|
|
||||||
if self.units.gap.unit ~= "px" then
|
|
||||||
local containerSize = (self.flexDirection == FlexDirection.HORIZONTAL) and (self.parent and self.parent.width or newViewportWidth)
|
|
||||||
or (self.parent and self.parent.height or newViewportHeight)
|
|
||||||
self.gap = Units.resolve(self.units.gap.value, self.units.gap.unit, newViewportWidth, newViewportHeight, containerSize)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Recalculate spacing (padding/margin) if using viewport or percentage units
|
|
||||||
-- For percentage-based padding:
|
|
||||||
-- - 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)
|
|
||||||
local parentBorderBoxWidth = self.parent and self.parent._borderBoxWidth or self._borderBoxWidth or newViewportWidth
|
|
||||||
local parentBorderBoxHeight = self.parent and self.parent._borderBoxHeight or self._borderBoxHeight or newViewportHeight
|
|
||||||
|
|
||||||
-- Handle shorthand properties first (horizontal/vertical)
|
|
||||||
local resolvedHorizontalPadding = nil
|
|
||||||
local resolvedVerticalPadding = nil
|
|
||||||
|
|
||||||
if self.units.padding.horizontal and self.units.padding.horizontal.unit ~= "px" then
|
|
||||||
resolvedHorizontalPadding =
|
|
||||||
Units.resolve(self.units.padding.horizontal.value, self.units.padding.horizontal.unit, newViewportWidth, newViewportHeight, parentBorderBoxWidth)
|
|
||||||
elseif self.units.padding.horizontal and self.units.padding.horizontal.value then
|
|
||||||
resolvedHorizontalPadding = self.units.padding.horizontal.value
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.units.padding.vertical and self.units.padding.vertical.unit ~= "px" then
|
|
||||||
resolvedVerticalPadding =
|
|
||||||
Units.resolve(self.units.padding.vertical.value, self.units.padding.vertical.unit, newViewportWidth, newViewportHeight, parentBorderBoxHeight)
|
|
||||||
elseif self.units.padding.vertical and self.units.padding.vertical.value then
|
|
||||||
resolvedVerticalPadding = self.units.padding.vertical.value
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Resolve individual padding sides (with fallback to shorthand)
|
|
||||||
for _, side in ipairs({ "top", "right", "bottom", "left" }) do
|
|
||||||
-- Check if this side was explicitly set or if we should use shorthand
|
|
||||||
local useShorthand = false
|
|
||||||
if not self.units.padding[side].explicit then
|
|
||||||
-- Not explicitly set, check if we have shorthand
|
|
||||||
if side == "left" or side == "right" then
|
|
||||||
useShorthand = resolvedHorizontalPadding ~= nil
|
|
||||||
elseif side == "top" or side == "bottom" then
|
|
||||||
useShorthand = resolvedVerticalPadding ~= nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if useShorthand then
|
|
||||||
-- Use shorthand value
|
|
||||||
if side == "left" or side == "right" then
|
|
||||||
self.padding[side] = resolvedHorizontalPadding
|
|
||||||
else
|
|
||||||
self.padding[side] = resolvedVerticalPadding
|
|
||||||
end
|
|
||||||
elseif self.units.padding[side].unit ~= "px" then
|
|
||||||
-- Recalculate non-pixel units
|
|
||||||
local parentSize = (side == "top" or side == "bottom") and parentBorderBoxHeight or parentBorderBoxWidth
|
|
||||||
self.padding[side] = Units.resolve(self.units.padding[side].value, self.units.padding[side].unit, newViewportWidth, newViewportHeight, parentSize)
|
|
||||||
end
|
|
||||||
-- If unit is "px" and not using shorthand, value stays the same
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Handle margin shorthand properties
|
|
||||||
local resolvedHorizontalMargin = nil
|
|
||||||
local resolvedVerticalMargin = nil
|
|
||||||
|
|
||||||
if self.units.margin.horizontal and self.units.margin.horizontal.unit ~= "px" then
|
|
||||||
resolvedHorizontalMargin =
|
|
||||||
Units.resolve(self.units.margin.horizontal.value, self.units.margin.horizontal.unit, newViewportWidth, newViewportHeight, parentBorderBoxWidth)
|
|
||||||
elseif self.units.margin.horizontal and self.units.margin.horizontal.value then
|
|
||||||
resolvedHorizontalMargin = self.units.margin.horizontal.value
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.units.margin.vertical and self.units.margin.vertical.unit ~= "px" then
|
|
||||||
resolvedVerticalMargin =
|
|
||||||
Units.resolve(self.units.margin.vertical.value, self.units.margin.vertical.unit, newViewportWidth, newViewportHeight, parentBorderBoxHeight)
|
|
||||||
elseif self.units.margin.vertical and self.units.margin.vertical.value then
|
|
||||||
resolvedVerticalMargin = self.units.margin.vertical.value
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Resolve individual margin sides (with fallback to shorthand)
|
|
||||||
for _, side in ipairs({ "top", "right", "bottom", "left" }) do
|
|
||||||
-- Check if this side was explicitly set or if we should use shorthand
|
|
||||||
local useShorthand = false
|
|
||||||
if not self.units.margin[side].explicit then
|
|
||||||
-- Not explicitly set, check if we have shorthand
|
|
||||||
if side == "left" or side == "right" then
|
|
||||||
useShorthand = resolvedHorizontalMargin ~= nil
|
|
||||||
elseif side == "top" or side == "bottom" then
|
|
||||||
useShorthand = resolvedVerticalMargin ~= nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if useShorthand then
|
|
||||||
-- Use shorthand value
|
|
||||||
if side == "left" or side == "right" then
|
|
||||||
self.margin[side] = resolvedHorizontalMargin
|
|
||||||
else
|
|
||||||
self.margin[side] = resolvedVerticalMargin
|
|
||||||
end
|
|
||||||
elseif self.units.margin[side].unit ~= "px" then
|
|
||||||
-- Recalculate non-pixel units
|
|
||||||
local parentSize = (side == "top" or side == "bottom") and parentBorderBoxHeight or parentBorderBoxWidth
|
|
||||||
self.margin[side] = Units.resolve(self.units.margin[side].value, self.units.margin[side].unit, newViewportWidth, newViewportHeight, parentSize)
|
|
||||||
end
|
|
||||||
-- If unit is "px" and not using shorthand, value stays the same
|
|
||||||
end
|
|
||||||
|
|
||||||
-- BORDER-BOX MODEL: Calculate content dimensions from border-box dimensions
|
|
||||||
-- For explicitly-sized elements (non-auto), _borderBoxWidth/_borderBoxHeight were set earlier
|
|
||||||
-- Now we calculate content width/height by subtracting padding
|
|
||||||
-- Only recalculate if using viewport/percentage units (where _borderBoxWidth actually changed)
|
|
||||||
if self.units.width.unit ~= "auto" and self.units.width.unit ~= "px" then
|
|
||||||
-- _borderBoxWidth was recalculated for viewport/percentage units
|
|
||||||
-- Calculate content width by subtracting padding
|
|
||||||
self.width = math.max(0, self._borderBoxWidth - self.padding.left - self.padding.right)
|
|
||||||
elseif self.units.width.unit == "auto" then
|
|
||||||
-- For auto-sized elements, width is content width (calculated in resize method)
|
|
||||||
-- Update border-box to include padding
|
|
||||||
self._borderBoxWidth = self.width + self.padding.left + self.padding.right
|
|
||||||
end
|
|
||||||
-- For pixel units, width stays as-is (may have been manually modified)
|
|
||||||
|
|
||||||
if self.units.height.unit ~= "auto" and self.units.height.unit ~= "px" then
|
|
||||||
-- _borderBoxHeight was recalculated for viewport/percentage units
|
|
||||||
-- Calculate content height by subtracting padding
|
|
||||||
self.height = math.max(0, self._borderBoxHeight - self.padding.top - self.padding.bottom)
|
|
||||||
elseif self.units.height.unit == "auto" then
|
|
||||||
-- For auto-sized elements, height is content height (calculated in resize method)
|
|
||||||
-- Update border-box to include padding
|
|
||||||
self._borderBoxHeight = self.height + self.padding.top + self.padding.bottom
|
|
||||||
end
|
|
||||||
-- For pixel units, height stays as-is (may have been manually modified)
|
|
||||||
|
|
||||||
-- Detect overflow after layout calculations
|
|
||||||
self:_detectOverflow()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Resize element and its children based on game window size change
|
--- Resize element and its children based on game window size change
|
||||||
@@ -2449,17 +2103,14 @@ function Element:calculateTextWidth()
|
|||||||
-- Resolve font path from font family (same logic as in draw)
|
-- 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.theme and Theme.get(self.theme) or Theme.getActive()
|
local themeToUse = self._themeManager:getTheme()
|
||||||
if themeToUse and themeToUse.fonts and themeToUse.fonts[self.fontFamily] then
|
if themeToUse and themeToUse.fonts and themeToUse.fonts[self.fontFamily] then
|
||||||
fontPath = themeToUse.fonts[self.fontFamily]
|
fontPath = themeToUse.fonts[self.fontFamily]
|
||||||
else
|
else
|
||||||
fontPath = self.fontFamily
|
fontPath = self.fontFamily
|
||||||
end
|
end
|
||||||
elseif self.themeComponent then
|
elseif self.themeComponent then
|
||||||
local themeToUse = self.theme and Theme.get(self.theme) or Theme.getActive()
|
fontPath = self._themeManager:getDefaultFontFamily()
|
||||||
if themeToUse and themeToUse.fonts and themeToUse.fonts.default then
|
|
||||||
fontPath = themeToUse.fonts.default
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local tempFont = FONT_CACHE.get(self.textSize, fontPath)
|
local tempFont = FONT_CACHE.get(self.textSize, fontPath)
|
||||||
@@ -2492,17 +2143,14 @@ function Element:calculateTextHeight()
|
|||||||
-- Resolve font path from font family (same logic as in draw)
|
-- 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.theme and Theme.get(self.theme) or Theme.getActive()
|
local themeToUse = self._themeManager:getTheme()
|
||||||
if themeToUse and themeToUse.fonts and themeToUse.fonts[self.fontFamily] then
|
if themeToUse and themeToUse.fonts and themeToUse.fonts[self.fontFamily] then
|
||||||
fontPath = themeToUse.fonts[self.fontFamily]
|
fontPath = themeToUse.fonts[self.fontFamily]
|
||||||
else
|
else
|
||||||
fontPath = self.fontFamily
|
fontPath = self.fontFamily
|
||||||
end
|
end
|
||||||
elseif self.themeComponent then
|
elseif self.themeComponent then
|
||||||
local themeToUse = self.theme and Theme.get(self.theme) or Theme.getActive()
|
fontPath = self._themeManager:getDefaultFontFamily()
|
||||||
if themeToUse and themeToUse.fonts and themeToUse.fonts.default then
|
|
||||||
fontPath = themeToUse.fonts.default
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
font = FONT_CACHE.get(self.textSize, fontPath)
|
font = FONT_CACHE.get(self.textSize, fontPath)
|
||||||
else
|
else
|
||||||
@@ -2658,8 +2306,6 @@ function Element:moveCursorToNextWord()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- ====================
|
-- ====================
|
||||||
-- Input Handling - Selection Management
|
-- Input Handling - Selection Management
|
||||||
-- ====================
|
-- ====================
|
||||||
@@ -3061,7 +2707,7 @@ function Element:_getFont()
|
|||||||
-- Get font path from theme or element
|
-- Get font path from theme or element
|
||||||
local fontPath = nil
|
local fontPath = nil
|
||||||
if self.fontFamily then
|
if self.fontFamily then
|
||||||
local themeToUse = self.theme and Theme.get(self.theme) or Theme.getActive()
|
local themeToUse = self._themeManager:getTheme()
|
||||||
if themeToUse and themeToUse.fonts and themeToUse.fonts[self.fontFamily] then
|
if themeToUse and themeToUse.fonts and themeToUse.fonts[self.fontFamily] then
|
||||||
fontPath = themeToUse.fonts[self.fontFamily]
|
fontPath = themeToUse.fonts[self.fontFamily]
|
||||||
else
|
else
|
||||||
@@ -3072,8 +2718,6 @@ function Element:_getFont()
|
|||||||
return FONT_CACHE.getFont(self.textSize, fontPath)
|
return FONT_CACHE.getFont(self.textSize, fontPath)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- ====================
|
-- ====================
|
||||||
-- Input Handling - Mouse Selection
|
-- Input Handling - Mouse Selection
|
||||||
-- ====================
|
-- ====================
|
||||||
@@ -3250,8 +2894,6 @@ function Element:_handleTextDrag(mouseX, mouseY)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- ====================
|
-- ====================
|
||||||
-- Input Handling - Keyboard Input
|
-- Input Handling - Keyboard Input
|
||||||
-- ====================
|
-- ====================
|
||||||
|
|||||||
262
modules/ThemeManager.lua
Normal file
262
modules/ThemeManager.lua
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
--- ThemeManager.lua
|
||||||
|
--- Manages theme application, state transitions, and property resolution for Elements
|
||||||
|
--- Extracted from Element.lua as part of element-refactor-modularization task 06
|
||||||
|
|
||||||
|
-- Setup module path for relative requires
|
||||||
|
local modulePath = (...):match("(.-)[^%.]+$")
|
||||||
|
local function req(name)
|
||||||
|
return require(modulePath .. name)
|
||||||
|
end
|
||||||
|
|
||||||
|
local Theme = req("Theme")
|
||||||
|
|
||||||
|
---@class ThemeManager
|
||||||
|
---@field theme string? -- Theme name to use
|
||||||
|
---@field themeComponent string? -- Component name from theme (e.g., "button", "panel")
|
||||||
|
---@field _themeState string -- Current theme state (normal, hover, pressed, active, disabled)
|
||||||
|
---@field disabled boolean -- If true, element is disabled
|
||||||
|
---@field active boolean -- If true, element is in active state (e.g., focused input)
|
||||||
|
---@field disableHighlight boolean -- If true, disable pressed highlight overlay
|
||||||
|
---@field scaleCorners number? -- Scale multiplier for 9-patch corners/edges
|
||||||
|
---@field scalingAlgorithm string? -- "nearest" or "bilinear" scaling for 9-patch
|
||||||
|
---@field _element table? -- Reference to parent Element
|
||||||
|
local ThemeManager = {}
|
||||||
|
ThemeManager.__index = ThemeManager
|
||||||
|
|
||||||
|
--- Create new ThemeManager instance
|
||||||
|
---@param config table Configuration options
|
||||||
|
---@return ThemeManager
|
||||||
|
function ThemeManager.new(config)
|
||||||
|
local self = setmetatable({}, ThemeManager)
|
||||||
|
|
||||||
|
-- Theme configuration
|
||||||
|
self.theme = config.theme
|
||||||
|
self.themeComponent = config.themeComponent
|
||||||
|
self.disabled = config.disabled or false
|
||||||
|
self.active = config.active or false
|
||||||
|
self.disableHighlight = config.disableHighlight
|
||||||
|
self.scaleCorners = config.scaleCorners
|
||||||
|
self.scalingAlgorithm = config.scalingAlgorithm
|
||||||
|
|
||||||
|
-- Internal state
|
||||||
|
self._themeState = "normal"
|
||||||
|
self._element = nil
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Initialize ThemeManager with parent element reference
|
||||||
|
---@param element table The parent Element
|
||||||
|
function ThemeManager:initialize(element)
|
||||||
|
self._element = element
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Update theme state based on interaction state
|
||||||
|
---@param isHovered boolean Whether element is hovered
|
||||||
|
---@param isPressed boolean Whether element is pressed
|
||||||
|
---@param isFocused boolean Whether element is focused
|
||||||
|
---@param isDisabled boolean Whether element is disabled
|
||||||
|
---@return string The new theme state
|
||||||
|
function ThemeManager:updateState(isHovered, isPressed, isFocused, isDisabled)
|
||||||
|
local newState = "normal"
|
||||||
|
|
||||||
|
-- State priority: disabled > active > pressed > hover > normal
|
||||||
|
if isDisabled or self.disabled then
|
||||||
|
newState = "disabled"
|
||||||
|
elseif self.active then
|
||||||
|
newState = "active"
|
||||||
|
elseif isPressed then
|
||||||
|
newState = "pressed"
|
||||||
|
elseif isHovered then
|
||||||
|
newState = "hover"
|
||||||
|
end
|
||||||
|
|
||||||
|
self._themeState = newState
|
||||||
|
return newState
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get current theme state
|
||||||
|
---@return string The current theme state
|
||||||
|
function ThemeManager:getState()
|
||||||
|
return self._themeState
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Set theme state directly
|
||||||
|
---@param state string The theme state to set
|
||||||
|
function ThemeManager:setState(state)
|
||||||
|
self._themeState = state
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if this ThemeManager has a theme component
|
||||||
|
---@return boolean
|
||||||
|
function ThemeManager:hasThemeComponent()
|
||||||
|
return self.themeComponent ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the theme component name
|
||||||
|
---@return string?
|
||||||
|
function ThemeManager:getThemeComponent()
|
||||||
|
return self.themeComponent
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the theme to use (element-specific or active theme)
|
||||||
|
---@return table? The theme object or nil
|
||||||
|
function ThemeManager:getTheme()
|
||||||
|
if self.theme then
|
||||||
|
return Theme.get(self.theme)
|
||||||
|
end
|
||||||
|
return Theme.getActive()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the component definition from the theme
|
||||||
|
---@return table? The component definition or nil
|
||||||
|
function ThemeManager:getComponent()
|
||||||
|
if not self.themeComponent then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local themeToUse = self:getTheme()
|
||||||
|
if not themeToUse or not themeToUse.components[self.themeComponent] then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return themeToUse.components[self.themeComponent]
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the current state's component definition (including state-specific overrides)
|
||||||
|
---@return table? The component definition for current state or nil
|
||||||
|
function ThemeManager:getStateComponent()
|
||||||
|
local component = self:getComponent()
|
||||||
|
if not component then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for state-specific override
|
||||||
|
local state = self._themeState
|
||||||
|
if state and state ~= "normal" and component.states and component.states[state] then
|
||||||
|
return component.states[state]
|
||||||
|
end
|
||||||
|
|
||||||
|
return component
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get property value from theme for current state
|
||||||
|
---@param property string The property name
|
||||||
|
---@return any? The property value or nil
|
||||||
|
function ThemeManager:getStyle(property)
|
||||||
|
local stateComponent = self:getStateComponent()
|
||||||
|
if not stateComponent then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return stateComponent[property]
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the scaled content padding for current theme state
|
||||||
|
---@param borderBoxWidth number The element's border box width
|
||||||
|
---@param borderBoxHeight number The element's border box height
|
||||||
|
---@return table? {left, top, right, bottom} or nil if no contentPadding
|
||||||
|
function ThemeManager:getScaledContentPadding(borderBoxWidth, borderBoxHeight)
|
||||||
|
if not self.themeComponent then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local themeToUse = self:getTheme()
|
||||||
|
if not themeToUse or not themeToUse.components[self.themeComponent] then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local component = themeToUse.components[self.themeComponent]
|
||||||
|
|
||||||
|
-- Check for state-specific override
|
||||||
|
local state = self._themeState or "normal"
|
||||||
|
if state and state ~= "normal" and component.states and component.states[state] then
|
||||||
|
component = component.states[state]
|
||||||
|
end
|
||||||
|
|
||||||
|
if not component._ninePatchData or not component._ninePatchData.contentPadding then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local contentPadding = component._ninePatchData.contentPadding
|
||||||
|
|
||||||
|
-- Scale contentPadding to match the actual rendered size
|
||||||
|
local atlasImage = component._loadedAtlas or themeToUse.atlas
|
||||||
|
if atlasImage and type(atlasImage) ~= "string" then
|
||||||
|
local originalWidth, originalHeight = atlasImage:getDimensions()
|
||||||
|
local scaleX = borderBoxWidth / originalWidth
|
||||||
|
local scaleY = borderBoxHeight / originalHeight
|
||||||
|
|
||||||
|
return {
|
||||||
|
left = contentPadding.left * scaleX,
|
||||||
|
top = contentPadding.top * scaleY,
|
||||||
|
right = contentPadding.right * scaleX,
|
||||||
|
bottom = contentPadding.bottom * scaleY,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get contentAutoSizingMultiplier from theme
|
||||||
|
---@return number? The multiplier or nil
|
||||||
|
function ThemeManager:getContentAutoSizingMultiplier()
|
||||||
|
if not self.themeComponent then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local themeToUse = self:getTheme()
|
||||||
|
if not themeToUse then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- First check if themeComponent has a multiplier
|
||||||
|
if self.themeComponent then
|
||||||
|
local component = themeToUse.components[self.themeComponent]
|
||||||
|
if component and component.contentAutoSizingMultiplier then
|
||||||
|
return component.contentAutoSizingMultiplier
|
||||||
|
elseif themeToUse.contentAutoSizingMultiplier then
|
||||||
|
-- Fall back to theme default
|
||||||
|
return themeToUse.contentAutoSizingMultiplier
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Fall back to theme default
|
||||||
|
if themeToUse.contentAutoSizingMultiplier then
|
||||||
|
return themeToUse.contentAutoSizingMultiplier
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get default font family from theme
|
||||||
|
---@return string? The font name or path, or nil
|
||||||
|
function ThemeManager:getDefaultFontFamily()
|
||||||
|
local themeToUse = self:getTheme()
|
||||||
|
if themeToUse and themeToUse.fonts and themeToUse.fonts["default"] then
|
||||||
|
return themeToUse.fonts["default"]
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Set theme and component
|
||||||
|
---@param themeName string? The theme name
|
||||||
|
---@param componentName string? The component name
|
||||||
|
function ThemeManager:setTheme(themeName, componentName)
|
||||||
|
self.theme = themeName
|
||||||
|
self.themeComponent = componentName
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get scale corners multiplier
|
||||||
|
---@return number?
|
||||||
|
function ThemeManager:getScaleCorners()
|
||||||
|
return self.scaleCorners
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get scaling algorithm
|
||||||
|
---@return string?
|
||||||
|
function ThemeManager:getScalingAlgorithm()
|
||||||
|
return self.scalingAlgorithm
|
||||||
|
end
|
||||||
|
|
||||||
|
return ThemeManager
|
||||||
Reference in New Issue
Block a user