added theme state locks
This commit is contained in:
@@ -309,10 +309,16 @@ function Element.new(props)
|
|||||||
disabled = props.disabled or false,
|
disabled = props.disabled or false,
|
||||||
active = props.active or false,
|
active = props.active or false,
|
||||||
disableHighlight = props.disableHighlight,
|
disableHighlight = props.disableHighlight,
|
||||||
|
themeStateLock = props.themeStateLock or false,
|
||||||
scaleCorners = props.scaleCorners,
|
scaleCorners = props.scaleCorners,
|
||||||
scalingAlgorithm = props.scalingAlgorithm,
|
scalingAlgorithm = props.scalingAlgorithm,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
-- Validate themeStateLock after ThemeManager is created
|
||||||
|
if props.themeStateLock and props.themeComponent then
|
||||||
|
self._themeManager:validateThemeStateLock()
|
||||||
|
end
|
||||||
|
|
||||||
-- Expose theme properties for backward compatibility
|
-- Expose theme properties for backward compatibility
|
||||||
self.theme = self._themeManager.theme
|
self.theme = self._themeManager.theme
|
||||||
self.themeComponent = self._themeManager.themeComponent
|
self.themeComponent = self._themeManager.themeComponent
|
||||||
|
|||||||
@@ -187,6 +187,30 @@ local ErrorCodes = {
|
|||||||
description = "Invalid theme color",
|
description = "Invalid theme color",
|
||||||
suggestion = "Theme colors must be valid color values (hex, rgba, Color object)",
|
suggestion = "Theme colors must be valid color values (hex, rgba, Color object)",
|
||||||
},
|
},
|
||||||
|
THM_007 = {
|
||||||
|
code = "FLEXLOVE_THM_007",
|
||||||
|
category = "THM",
|
||||||
|
description = "themeStateLock has no effect without a valid theme component",
|
||||||
|
suggestion = "Ensure themeComponent is set and valid when using themeStateLock",
|
||||||
|
},
|
||||||
|
THM_008 = {
|
||||||
|
code = "FLEXLOVE_THM_008",
|
||||||
|
category = "THM",
|
||||||
|
description = "Theme component has no state variants",
|
||||||
|
suggestion = "themeStateLock has no effect on components without state variants",
|
||||||
|
},
|
||||||
|
THM_009 = {
|
||||||
|
code = "FLEXLOVE_THM_009",
|
||||||
|
category = "THM",
|
||||||
|
description = "Requested theme state does not exist",
|
||||||
|
suggestion = "Use one of the available theme states or set themeStateLock to false",
|
||||||
|
},
|
||||||
|
THM_010 = {
|
||||||
|
code = "FLEXLOVE_THM_010",
|
||||||
|
category = "THM",
|
||||||
|
description = "Invalid themeStateLock type",
|
||||||
|
suggestion = "themeStateLock must be boolean or string (state name)",
|
||||||
|
},
|
||||||
|
|
||||||
-- Event Errors (EVT_001 - EVT_099)
|
-- Event Errors (EVT_001 - EVT_099)
|
||||||
EVT_001 = {
|
EVT_001 = {
|
||||||
|
|||||||
@@ -742,6 +742,7 @@ end
|
|||||||
---@field disabled boolean
|
---@field disabled boolean
|
||||||
---@field active boolean
|
---@field active boolean
|
||||||
---@field disableHighlight boolean -- If true, disable pressed highlight overlay
|
---@field disableHighlight boolean -- If true, disable pressed highlight overlay
|
||||||
|
---@field themeStateLock boolean|string? -- Lock theme state: true/"default" = lock to base state, false = normal behavior, string = specific state
|
||||||
---@field scaleCorners number? -- Scale multiplier for 9-patch corners/edges
|
---@field scaleCorners number? -- Scale multiplier for 9-patch corners/edges
|
||||||
---@field scalingAlgorithm string? -- "nearest" or "bilinear" scaling for 9-patch
|
---@field scalingAlgorithm string? -- "nearest" or "bilinear" scaling for 9-patch
|
||||||
---@field _element Element? -- Reference to parent Element
|
---@field _element Element? -- Reference to parent Element
|
||||||
@@ -749,7 +750,7 @@ local ThemeManager = {}
|
|||||||
ThemeManager.__index = ThemeManager
|
ThemeManager.__index = ThemeManager
|
||||||
|
|
||||||
---Create a new ThemeManager instance
|
---Create a new ThemeManager instance
|
||||||
---@param config table Configuration options {theme: string?, themeComponent: string?, disabled: boolean?, active: boolean?, disableHighlight: boolean?, scaleCorners: number?, scalingAlgorithm: string?}
|
---@param config table Configuration options {theme: string?, themeComponent: string?, disabled: boolean?, active: boolean?, disableHighlight: boolean?, themeStateLock: boolean|string?, scaleCorners: number?, scalingAlgorithm: string?}
|
||||||
---@return ThemeManager manager The new ThemeManager instance
|
---@return ThemeManager manager The new ThemeManager instance
|
||||||
function ThemeManager.new(config)
|
function ThemeManager.new(config)
|
||||||
local self = setmetatable({}, ThemeManager)
|
local self = setmetatable({}, ThemeManager)
|
||||||
@@ -759,10 +760,18 @@ function ThemeManager.new(config)
|
|||||||
self.disabled = config.disabled or false
|
self.disabled = config.disabled or false
|
||||||
self.active = config.active or false
|
self.active = config.active or false
|
||||||
self.disableHighlight = config.disableHighlight
|
self.disableHighlight = config.disableHighlight
|
||||||
|
self.themeStateLock = config.themeStateLock or false
|
||||||
self.scaleCorners = config.scaleCorners
|
self.scaleCorners = config.scaleCorners
|
||||||
self.scalingAlgorithm = config.scalingAlgorithm
|
self.scalingAlgorithm = config.scalingAlgorithm
|
||||||
|
|
||||||
self._themeState = "normal"
|
-- Set initial state based on themeStateLock
|
||||||
|
if self.themeStateLock == true or self.themeStateLock == "default" then
|
||||||
|
self._themeState = "normal"
|
||||||
|
elseif type(self.themeStateLock) == "string" then
|
||||||
|
self._themeState = self.themeStateLock
|
||||||
|
else
|
||||||
|
self._themeState = "normal"
|
||||||
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -774,6 +783,31 @@ end
|
|||||||
---@param isDisabled boolean Whether element is disabled
|
---@param isDisabled boolean Whether element is disabled
|
||||||
---@return string state The new theme state ("normal", "hover", "pressed", "active", "disabled")
|
---@return string state The new theme state ("normal", "hover", "pressed", "active", "disabled")
|
||||||
function ThemeManager:updateState(isHovered, isPressed, isFocused, isDisabled)
|
function ThemeManager:updateState(isHovered, isPressed, isFocused, isDisabled)
|
||||||
|
-- If themeStateLock is set (and not false), use the locked state
|
||||||
|
if self.themeStateLock ~= false and self.themeStateLock ~= nil then
|
||||||
|
local lockedState
|
||||||
|
|
||||||
|
if self.themeStateLock == true or self.themeStateLock == "default" then
|
||||||
|
-- true or "default" means lock to "normal" (base state)
|
||||||
|
lockedState = "normal"
|
||||||
|
elseif type(self.themeStateLock) == "string" then
|
||||||
|
-- String means lock to specific state
|
||||||
|
lockedState = self.themeStateLock
|
||||||
|
|
||||||
|
-- Validate the locked state exists in the theme component (will be done during initialization)
|
||||||
|
-- For now, just use the string value
|
||||||
|
else
|
||||||
|
-- Invalid themeStateLock value, fall back to normal behavior
|
||||||
|
lockedState = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if lockedState then
|
||||||
|
self._themeState = lockedState
|
||||||
|
return lockedState
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Normal behavior: calculate state based on interaction
|
||||||
local newState = "normal"
|
local newState = "normal"
|
||||||
|
|
||||||
if isDisabled or self.disabled then
|
if isDisabled or self.disabled then
|
||||||
@@ -961,6 +995,89 @@ function ThemeManager:setTheme(themeName, componentName)
|
|||||||
self.themeComponent = componentName
|
self.themeComponent = componentName
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Validate themeStateLock and warn if invalid
|
||||||
|
---@return boolean isValid True if themeStateLock is valid or false/nil
|
||||||
|
function ThemeManager:validateThemeStateLock()
|
||||||
|
-- false or nil is always valid (no lock)
|
||||||
|
if not self.themeStateLock or self.themeStateLock == false then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- true is always valid (lock to normal)
|
||||||
|
if self.themeStateLock == true then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- String value needs validation
|
||||||
|
if type(self.themeStateLock) == "string" then
|
||||||
|
-- "default" is always valid (lock to normal/base state)
|
||||||
|
if self.themeStateLock == "default" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local component = self:getComponent()
|
||||||
|
|
||||||
|
-- If no component, warn that themeStateLock has no effect
|
||||||
|
if not component then
|
||||||
|
if self.themeComponent then
|
||||||
|
Theme._ErrorHandler:warn("Theme", "THM_007", {
|
||||||
|
themeComponent = self.themeComponent,
|
||||||
|
reason = "themeStateLock has no effect without a valid theme component",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self.themeStateLock = false
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if component has any states at all
|
||||||
|
if not component.states or type(component.states) ~= "table" or next(component.states) == nil then
|
||||||
|
Theme._ErrorHandler:warn("Theme", "THM_008", {
|
||||||
|
themeComponent = self.themeComponent,
|
||||||
|
reason = "Theme component has no state variants, themeStateLock has no effect",
|
||||||
|
})
|
||||||
|
self.themeStateLock = false
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if the specified state exists
|
||||||
|
if not component.states[self.themeStateLock] then
|
||||||
|
-- Warn and fall back to false (no lock)
|
||||||
|
Theme._ErrorHandler:warn("Theme", "THM_009", {
|
||||||
|
themeComponent = self.themeComponent,
|
||||||
|
requestedState = self.themeStateLock,
|
||||||
|
availableStates = table.concat(self:_getAvailableStates(component), ", "),
|
||||||
|
fallback = "themeStateLock disabled (using dynamic state)",
|
||||||
|
})
|
||||||
|
self.themeStateLock = false
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Invalid type for themeStateLock
|
||||||
|
Theme._ErrorHandler:warn("Theme", "THM_010", {
|
||||||
|
themeStateLockType = type(self.themeStateLock),
|
||||||
|
reason = "themeStateLock must be boolean or string",
|
||||||
|
fallback = "themeStateLock disabled",
|
||||||
|
})
|
||||||
|
self.themeStateLock = false
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get available state names for a component
|
||||||
|
---@param component ThemeComponent The component to check
|
||||||
|
---@return table stateNames Array of state names
|
||||||
|
function ThemeManager:_getAvailableStates(component)
|
||||||
|
local states = {}
|
||||||
|
if component and component.states and type(component.states) == "table" then
|
||||||
|
for stateName, _ in pairs(component.states) do
|
||||||
|
table.insert(states, stateName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return states
|
||||||
|
end
|
||||||
|
|
||||||
Theme.Manager = ThemeManager
|
Theme.Manager = ThemeManager
|
||||||
|
|
||||||
--- Check theme definitions for correctness before use to catch configuration errors early
|
--- Check theme definitions for correctness before use to catch configuration errors early
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ local AnimationProps = {}
|
|||||||
---@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)
|
||||||
---@field disableHighlight boolean? -- Whether to disable the pressed state highlight overlay (default: false, or true when using themeComponent)
|
---@field disableHighlight boolean? -- Whether to disable the pressed state highlight overlay (default: false, or true when using themeComponent)
|
||||||
|
---@field themeStateLock boolean|string? -- Lock theme state: true/"default" = lock to base state, false = normal behavior, string = specific state ("hover", "pressed", "active", "disabled") (default: false)
|
||||||
---@field contentAutoSizingMultiplier {width:number?, height:number?}? -- Multiplier for auto-sized content dimensions (default: sourced from theme or {1, 1})
|
---@field contentAutoSizingMultiplier {width:number?, height:number?}? -- Multiplier for auto-sized content dimensions (default: sourced from theme or {1, 1})
|
||||||
---@field scaleCorners number? -- Scale multiplier for 9-patch corners/edges. E.g., 2 = 2x size (overrides theme setting)
|
---@field scaleCorners number? -- Scale multiplier for 9-patch corners/edges. E.g., 2 = 2x size (overrides theme setting)
|
||||||
---@field scalingAlgorithm "nearest"|"bilinear"? -- Scaling algorithm for 9-patch corners: "nearest" (sharp/pixelated) or "bilinear" (smooth) (overrides theme setting)
|
---@field scalingAlgorithm "nearest"|"bilinear"? -- Scaling algorithm for 9-patch corners: "nearest" (sharp/pixelated) or "bilinear" (smooth) (overrides theme setting)
|
||||||
|
|||||||
Reference in New Issue
Block a user