test alignment
This commit is contained in:
284
FlexLove.lua
284
FlexLove.lua
@@ -183,7 +183,7 @@ local fadeIn = FlexLove.Animation.fade(1.0, 0, 1)
|
|||||||
fadeIn:apply(element)
|
fadeIn:apply(element)
|
||||||
|
|
||||||
-- Scale animation
|
-- Scale animation
|
||||||
local scaleUp = FlexLove.Animation.scale(0.5,
|
local scaleUp = FlexLove.Animation.scale(0.5,
|
||||||
{ width = 100, height = 50 },
|
{ width = 100, height = 50 },
|
||||||
{ width = 200, height = 100 }
|
{ width = 200, height = 100 }
|
||||||
)
|
)
|
||||||
@@ -287,18 +287,30 @@ end
|
|||||||
function Color.fromHex(hexWithTag)
|
function Color.fromHex(hexWithTag)
|
||||||
local hex = hexWithTag:gsub("#", "")
|
local hex = hexWithTag:gsub("#", "")
|
||||||
if #hex == 6 then
|
if #hex == 6 then
|
||||||
local r = tonumber("0x" .. hex:sub(1, 2)) or 0
|
local r = tonumber("0x" .. hex:sub(1, 2))
|
||||||
local g = tonumber("0x" .. hex:sub(3, 4)) or 0
|
local g = tonumber("0x" .. hex:sub(3, 4))
|
||||||
local b = tonumber("0x" .. hex:sub(5, 6)) or 0
|
local b = tonumber("0x" .. hex:sub(5, 6))
|
||||||
|
if not r or not g or not b then
|
||||||
|
error(
|
||||||
|
formatError("Color", string.format("Invalid hex string format: '%s'. Contains invalid hex digits", hexWithTag))
|
||||||
|
)
|
||||||
|
end
|
||||||
return Color.new(r, g, b, 1)
|
return Color.new(r, g, b, 1)
|
||||||
elseif #hex == 8 then
|
elseif #hex == 8 then
|
||||||
local r = tonumber("0x" .. hex:sub(1, 2)) or 0
|
local r = tonumber("0x" .. hex:sub(1, 2))
|
||||||
local g = tonumber("0x" .. hex:sub(3, 4)) or 0
|
local g = tonumber("0x" .. hex:sub(3, 4))
|
||||||
local b = tonumber("0x" .. hex:sub(5, 6)) or 0
|
local b = tonumber("0x" .. hex:sub(5, 6))
|
||||||
local a = tonumber("0x" .. hex:sub(7, 8)) / 255
|
local a = tonumber("0x" .. hex:sub(7, 8))
|
||||||
return Color.new(r, g, b, a)
|
if not r or not g or not b or not a then
|
||||||
|
error(
|
||||||
|
formatError("Color", string.format("Invalid hex string format: '%s'. Contains invalid hex digits", hexWithTag))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return Color.new(r, g, b, a / 255)
|
||||||
else
|
else
|
||||||
error(formatError("Color", string.format("Invalid hex string format: '%s'. Expected #RRGGBB or #RRGGBBAA", hexWithTag)))
|
error(
|
||||||
|
formatError("Color", string.format("Invalid hex string format: '%s'. Expected #RRGGBB or #RRGGBBAA", hexWithTag))
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -399,7 +411,7 @@ local function safeLoadImage(imagePath)
|
|||||||
local success, result = pcall(function()
|
local success, result = pcall(function()
|
||||||
return love.graphics.newImage(imagePath)
|
return love.graphics.newImage(imagePath)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if success then
|
if success then
|
||||||
return result, nil
|
return result, nil
|
||||||
else
|
else
|
||||||
@@ -419,27 +431,27 @@ local function validateThemeDefinition(definition)
|
|||||||
if not definition then
|
if not definition then
|
||||||
return false, "Theme definition is nil"
|
return false, "Theme definition is nil"
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(definition) ~= "table" then
|
if type(definition) ~= "table" then
|
||||||
return false, "Theme definition must be a table"
|
return false, "Theme definition must be a table"
|
||||||
end
|
end
|
||||||
|
|
||||||
if not definition.name or type(definition.name) ~= "string" then
|
if not definition.name or type(definition.name) ~= "string" then
|
||||||
return false, "Theme must have a 'name' field (string)"
|
return false, "Theme must have a 'name' field (string)"
|
||||||
end
|
end
|
||||||
|
|
||||||
if definition.components and type(definition.components) ~= "table" then
|
if definition.components and type(definition.components) ~= "table" then
|
||||||
return false, "Theme 'components' must be a table"
|
return false, "Theme 'components' must be a table"
|
||||||
end
|
end
|
||||||
|
|
||||||
if definition.colors and type(definition.colors) ~= "table" then
|
if definition.colors and type(definition.colors) ~= "table" then
|
||||||
return false, "Theme 'colors' must be a table"
|
return false, "Theme 'colors' must be a table"
|
||||||
end
|
end
|
||||||
|
|
||||||
if definition.fonts and type(definition.fonts) ~= "table" then
|
if definition.fonts and type(definition.fonts) ~= "table" then
|
||||||
return false, "Theme 'fonts' must be a table"
|
return false, "Theme 'fonts' must be a table"
|
||||||
end
|
end
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -449,7 +461,7 @@ function Theme.new(definition)
|
|||||||
if not valid then
|
if not valid then
|
||||||
error("[FlexLove] Invalid theme definition: " .. tostring(err))
|
error("[FlexLove] Invalid theme definition: " .. tostring(err))
|
||||||
end
|
end
|
||||||
|
|
||||||
local self = setmetatable({}, Theme)
|
local self = setmetatable({}, Theme)
|
||||||
self.name = definition.name
|
self.name = definition.name
|
||||||
|
|
||||||
@@ -595,7 +607,7 @@ function Theme.getFont(fontName)
|
|||||||
if not activeTheme then
|
if not activeTheme then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
return activeTheme.fonts and activeTheme.fonts[fontName]
|
return activeTheme.fonts and activeTheme.fonts[fontName]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -606,7 +618,7 @@ function Theme.getColor(colorName)
|
|||||||
if not activeTheme then
|
if not activeTheme then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
return activeTheme.colors and activeTheme.colors[colorName]
|
return activeTheme.colors and activeTheme.colors[colorName]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -626,6 +638,43 @@ function Theme.getRegisteredThemes()
|
|||||||
return themeNames
|
return themeNames
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Get all available color names from the active theme
|
||||||
|
---@return table<string>|nil -- Array of color names, or nil if no theme active
|
||||||
|
function Theme.getColorNames()
|
||||||
|
if not activeTheme or not activeTheme.colors then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local colorNames = {}
|
||||||
|
for name, _ in pairs(activeTheme.colors) do
|
||||||
|
table.insert(colorNames, name)
|
||||||
|
end
|
||||||
|
return colorNames
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get all colors from the active theme
|
||||||
|
---@return table<string, Color>|nil -- Table of all colors, or nil if no theme active
|
||||||
|
function Theme.getAllColors()
|
||||||
|
if not activeTheme then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return activeTheme.colors
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get a color with a fallback if not found
|
||||||
|
---@param colorName string -- Name of the color to retrieve
|
||||||
|
---@param fallback Color|nil -- Fallback color if not found (default: white)
|
||||||
|
---@return Color -- The color or fallback
|
||||||
|
function Theme.getColorOrDefault(colorName, fallback)
|
||||||
|
local color = Theme.getColor(colorName)
|
||||||
|
if color then
|
||||||
|
return color
|
||||||
|
end
|
||||||
|
|
||||||
|
return fallback or Color.new(1, 1, 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
-- ====================
|
-- ====================
|
||||||
-- Rounded Rectangle Helper
|
-- Rounded Rectangle Helper
|
||||||
-- ====================
|
-- ====================
|
||||||
@@ -1021,7 +1070,7 @@ function Units.getViewport()
|
|||||||
if Gui and Gui._cachedViewport and Gui._cachedViewport.width > 0 then
|
if Gui and Gui._cachedViewport and Gui._cachedViewport.width > 0 then
|
||||||
return Gui._cachedViewport.width, Gui._cachedViewport.height
|
return Gui._cachedViewport.width, Gui._cachedViewport.height
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Query viewport dimensions normally
|
-- Query viewport dimensions normally
|
||||||
if love.graphics and love.graphics.getDimensions then
|
if love.graphics and love.graphics.getDimensions then
|
||||||
return love.graphics.getDimensions()
|
return love.graphics.getDimensions()
|
||||||
@@ -1105,7 +1154,7 @@ function Units.isValid(unitStr)
|
|||||||
if type(unitStr) ~= "string" then
|
if type(unitStr) ~= "string" then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local value, unit = Units.parse(unitStr)
|
local value, unit = Units.parse(unitStr)
|
||||||
local validUnits = { px = true, ["%"] = true, vw = true, vh = true, ew = true, eh = true }
|
local validUnits = { px = true, ["%"] = true, vw = true, vh = true, ew = true, eh = true }
|
||||||
return validUnits[unit] == true
|
return validUnits[unit] == true
|
||||||
@@ -1269,7 +1318,7 @@ local Gui = {
|
|||||||
baseScale = nil,
|
baseScale = nil,
|
||||||
scaleFactors = { x = 1.0, y = 1.0 },
|
scaleFactors = { x = 1.0, y = 1.0 },
|
||||||
defaultTheme = nil,
|
defaultTheme = nil,
|
||||||
_cachedViewport = { width = 0, height = 0 }, -- Cached viewport dimensions
|
_cachedViewport = { width = 0, height = 0 }, -- Cached viewport dimensions
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Initialize FlexLove with configuration
|
--- Initialize FlexLove with configuration
|
||||||
@@ -1348,7 +1397,7 @@ end
|
|||||||
---@return Element? -- Returns the topmost element or nil
|
---@return Element? -- Returns the topmost element or nil
|
||||||
function Gui.getElementAtPosition(x, y)
|
function Gui.getElementAtPosition(x, y)
|
||||||
local candidates = {}
|
local candidates = {}
|
||||||
|
|
||||||
-- Recursively collect all elements that contain the point
|
-- Recursively collect all elements that contain the point
|
||||||
local function collectHits(element)
|
local function collectHits(element)
|
||||||
-- Check if point is within element bounds
|
-- Check if point is within element bounds
|
||||||
@@ -1356,30 +1405,30 @@ function Gui.getElementAtPosition(x, y)
|
|||||||
local by = element.y
|
local by = element.y
|
||||||
local bw = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right)
|
local bw = element._borderBoxWidth or (element.width + element.padding.left + element.padding.right)
|
||||||
local bh = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom)
|
local bh = element._borderBoxHeight or (element.height + element.padding.top + element.padding.bottom)
|
||||||
|
|
||||||
if x >= bx and x <= bx + bw and y >= by and y <= by + bh then
|
if x >= bx and x <= bx + bw and y >= by and y <= by + bh then
|
||||||
-- Only consider elements with callbacks (interactive elements)
|
-- Only consider elements with callbacks (interactive elements)
|
||||||
if element.callback and not element.disabled then
|
if element.callback and not element.disabled then
|
||||||
table.insert(candidates, element)
|
table.insert(candidates, element)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check children
|
-- Check children
|
||||||
for _, child in ipairs(element.children) do
|
for _, child in ipairs(element.children) do
|
||||||
collectHits(child)
|
collectHits(child)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Collect hits from all top-level elements
|
-- Collect hits from all top-level elements
|
||||||
for _, element in ipairs(Gui.topElements) do
|
for _, element in ipairs(Gui.topElements) do
|
||||||
collectHits(element)
|
collectHits(element)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Sort by z-index (highest first)
|
-- Sort by z-index (highest first)
|
||||||
table.sort(candidates, function(a, b)
|
table.sort(candidates, function(a, b)
|
||||||
return a.z > b.z
|
return a.z > b.z
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Return the topmost element (highest z-index)
|
-- Return the topmost element (highest z-index)
|
||||||
return candidates[1]
|
return candidates[1]
|
||||||
end
|
end
|
||||||
@@ -1388,15 +1437,15 @@ function Gui.update(dt)
|
|||||||
-- Reset event handling flags for new frame
|
-- Reset event handling flags for new frame
|
||||||
local mx, my = love.mouse.getPosition()
|
local mx, my = love.mouse.getPosition()
|
||||||
local topElement = Gui.getElementAtPosition(mx, my)
|
local topElement = Gui.getElementAtPosition(mx, my)
|
||||||
|
|
||||||
-- Mark which element should handle events this frame
|
-- Mark which element should handle events this frame
|
||||||
Gui._activeEventElement = topElement
|
Gui._activeEventElement = topElement
|
||||||
|
|
||||||
-- Update all elements
|
-- Update all elements
|
||||||
for _, win in ipairs(Gui.topElements) do
|
for _, win in ipairs(Gui.topElements) do
|
||||||
win:update(dt)
|
win:update(dt)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clear active element for next frame
|
-- Clear active element for next frame
|
||||||
Gui._activeEventElement = nil
|
Gui._activeEventElement = nil
|
||||||
end
|
end
|
||||||
@@ -1474,15 +1523,23 @@ end
|
|||||||
---@field transition table?
|
---@field transition table?
|
||||||
--- Easing functions for animations
|
--- Easing functions for animations
|
||||||
local Easing = {
|
local Easing = {
|
||||||
linear = function(t) return t end,
|
linear = function(t)
|
||||||
|
return t
|
||||||
easeInQuad = function(t) return t * t end,
|
end,
|
||||||
easeOutQuad = function(t) return t * (2 - t) end,
|
|
||||||
|
easeInQuad = function(t)
|
||||||
|
return t * t
|
||||||
|
end,
|
||||||
|
easeOutQuad = function(t)
|
||||||
|
return t * (2 - t)
|
||||||
|
end,
|
||||||
easeInOutQuad = function(t)
|
easeInOutQuad = function(t)
|
||||||
return t < 0.5 and 2 * t * t or -1 + (4 - 2 * t) * t
|
return t < 0.5 and 2 * t * t or -1 + (4 - 2 * t) * t
|
||||||
end,
|
end,
|
||||||
|
|
||||||
easeInCubic = function(t) return t * t * t end,
|
easeInCubic = function(t)
|
||||||
|
return t * t * t
|
||||||
|
end,
|
||||||
easeOutCubic = function(t)
|
easeOutCubic = function(t)
|
||||||
local t1 = t - 1
|
local t1 = t - 1
|
||||||
return t1 * t1 * t1 + 1
|
return t1 * t1 * t1 + 1
|
||||||
@@ -1490,13 +1547,15 @@ local Easing = {
|
|||||||
easeInOutCubic = function(t)
|
easeInOutCubic = function(t)
|
||||||
return t < 0.5 and 4 * t * t * t or (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
|
return t < 0.5 and 4 * t * t * t or (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
|
||||||
end,
|
end,
|
||||||
|
|
||||||
easeInQuart = function(t) return t * t * t * t end,
|
easeInQuart = function(t)
|
||||||
|
return t * t * t * t
|
||||||
|
end,
|
||||||
easeOutQuart = function(t)
|
easeOutQuart = function(t)
|
||||||
local t1 = t - 1
|
local t1 = t - 1
|
||||||
return 1 - t1 * t1 * t1 * t1
|
return 1 - t1 * t1 * t1 * t1
|
||||||
end,
|
end,
|
||||||
|
|
||||||
easeInExpo = function(t)
|
easeInExpo = function(t)
|
||||||
return t == 0 and 0 or math.pow(2, 10 * (t - 1))
|
return t == 0 and 0 or math.pow(2, 10 * (t - 1))
|
||||||
end,
|
end,
|
||||||
@@ -1536,15 +1595,15 @@ function Animation.new(props)
|
|||||||
self.transform = props.transform
|
self.transform = props.transform
|
||||||
self.transition = props.transition
|
self.transition = props.transition
|
||||||
self.elapsed = 0
|
self.elapsed = 0
|
||||||
|
|
||||||
-- Set easing function (default to linear)
|
-- Set easing function (default to linear)
|
||||||
local easingName = props.easing or "linear"
|
local easingName = props.easing or "linear"
|
||||||
self.easing = Easing[easingName] or Easing.linear
|
self.easing = Easing[easingName] or Easing.linear
|
||||||
|
|
||||||
-- Pre-allocate result table to avoid GC pressure
|
-- Pre-allocate result table to avoid GC pressure
|
||||||
self._cachedResult = {}
|
self._cachedResult = {}
|
||||||
self._resultDirty = true
|
self._resultDirty = true
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1552,7 +1611,7 @@ end
|
|||||||
---@return boolean
|
---@return boolean
|
||||||
function Animation:update(dt)
|
function Animation:update(dt)
|
||||||
self.elapsed = self.elapsed + dt
|
self.elapsed = self.elapsed + dt
|
||||||
self._resultDirty = true -- Mark cached result as dirty
|
self._resultDirty = true -- Mark cached result as dirty
|
||||||
if self.elapsed >= self.duration then
|
if self.elapsed >= self.duration then
|
||||||
return true -- finished
|
return true -- finished
|
||||||
else
|
else
|
||||||
@@ -1566,10 +1625,10 @@ function Animation:interpolate()
|
|||||||
if not self._resultDirty then
|
if not self._resultDirty then
|
||||||
return self._cachedResult
|
return self._cachedResult
|
||||||
end
|
end
|
||||||
|
|
||||||
local t = math.min(self.elapsed / self.duration, 1)
|
local t = math.min(self.elapsed / self.duration, 1)
|
||||||
t = self.easing(t) -- Apply easing function
|
t = self.easing(t) -- Apply easing function
|
||||||
local result = self._cachedResult -- Reuse existing table
|
local result = self._cachedResult -- Reuse existing table
|
||||||
|
|
||||||
-- Clear previous values
|
-- Clear previous values
|
||||||
result.width = nil
|
result.width = nil
|
||||||
@@ -1597,7 +1656,7 @@ function Animation:interpolate()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self._resultDirty = false -- Mark as clean
|
self._resultDirty = false -- Mark as clean
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1643,8 +1702,8 @@ function Animation.scale(duration, fromScale, toScale)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local FONT_CACHE = {}
|
local FONT_CACHE = {}
|
||||||
local FONT_CACHE_MAX_SIZE = 50 -- Limit cache size to prevent unbounded growth
|
local FONT_CACHE_MAX_SIZE = 50 -- Limit cache size to prevent unbounded growth
|
||||||
local FONT_CACHE_ORDER = {} -- Track access order for LRU eviction
|
local FONT_CACHE_ORDER = {} -- Track access order for LRU eviction
|
||||||
|
|
||||||
--- Create or get a font from cache
|
--- Create or get a font from cache
|
||||||
---@param size number
|
---@param size number
|
||||||
@@ -1671,10 +1730,10 @@ function FONT_CACHE.get(size, fontPath)
|
|||||||
-- Load default font
|
-- Load default font
|
||||||
FONT_CACHE[cacheKey] = love.graphics.newFont(size)
|
FONT_CACHE[cacheKey] = love.graphics.newFont(size)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Add to access order for LRU tracking
|
-- Add to access order for LRU tracking
|
||||||
table.insert(FONT_CACHE_ORDER, cacheKey)
|
table.insert(FONT_CACHE_ORDER, cacheKey)
|
||||||
|
|
||||||
-- Evict oldest entry if cache is full (LRU eviction)
|
-- Evict oldest entry if cache is full (LRU eviction)
|
||||||
if #FONT_CACHE_ORDER > FONT_CACHE_MAX_SIZE then
|
if #FONT_CACHE_ORDER > FONT_CACHE_MAX_SIZE then
|
||||||
local oldestKey = table.remove(FONT_CACHE_ORDER, 1)
|
local oldestKey = table.remove(FONT_CACHE_ORDER, 1)
|
||||||
@@ -2159,7 +2218,11 @@ function Element.new(props)
|
|||||||
-- First, resolve padding using temporary dimensions
|
-- First, resolve padding using temporary dimensions
|
||||||
-- 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 = Units.resolveSpacing(props.padding, self.width, self.height)
|
local tempPadding = Units.resolveSpacing(props.padding, self.width, self.height)
|
||||||
self.margin = Units.resolveSpacing(props.margin, self.width, self.height)
|
|
||||||
|
-- Margin percentages are relative to parent's dimensions (CSS spec)
|
||||||
|
local parentWidth = self.parent and self.parent.width or viewportWidth
|
||||||
|
local parentHeight = self.parent and self.parent.height or viewportHeight
|
||||||
|
self.margin = Units.resolveSpacing(props.margin, parentWidth, parentHeight)
|
||||||
|
|
||||||
-- For auto-sized elements, add padding to get border-box dimensions
|
-- For auto-sized elements, add padding to get border-box dimensions
|
||||||
if self.autosizing.width then
|
if self.autosizing.width then
|
||||||
@@ -2878,16 +2941,13 @@ function Element:layoutChildren()
|
|||||||
elseif self.justifyContent == JustifyContent.SPACE_BETWEEN then
|
elseif self.justifyContent == JustifyContent.SPACE_BETWEEN then
|
||||||
startPos = 0
|
startPos = 0
|
||||||
if #line > 1 then
|
if #line > 1 then
|
||||||
-- Gap already accounted for in freeSpace calculation
|
|
||||||
itemSpacing = self.gap + (freeSpace / (#line - 1))
|
itemSpacing = self.gap + (freeSpace / (#line - 1))
|
||||||
end
|
end
|
||||||
elseif self.justifyContent == JustifyContent.SPACE_AROUND then
|
elseif self.justifyContent == JustifyContent.SPACE_AROUND then
|
||||||
-- Gap already accounted for in freeSpace calculation
|
|
||||||
local spaceAroundEach = freeSpace / #line
|
local spaceAroundEach = freeSpace / #line
|
||||||
startPos = spaceAroundEach / 2
|
startPos = spaceAroundEach / 2
|
||||||
itemSpacing = self.gap + spaceAroundEach
|
itemSpacing = self.gap + spaceAroundEach
|
||||||
elseif self.justifyContent == JustifyContent.SPACE_EVENLY then
|
elseif self.justifyContent == JustifyContent.SPACE_EVENLY then
|
||||||
-- Gap already accounted for in freeSpace calculation
|
|
||||||
local spaceBetween = freeSpace / (#line + 1)
|
local spaceBetween = freeSpace / (#line + 1)
|
||||||
startPos = spaceBetween
|
startPos = spaceBetween
|
||||||
itemSpacing = self.gap + spaceBetween
|
itemSpacing = self.gap + spaceBetween
|
||||||
@@ -2923,9 +2983,12 @@ function Element:layoutChildren()
|
|||||||
elseif effectiveAlign == AlignItems.FLEX_END then
|
elseif effectiveAlign == AlignItems.FLEX_END then
|
||||||
child.y = self.y + self.padding.top + reservedCrossStart + currentCrossPos + lineHeight - childBorderBoxHeight
|
child.y = self.y + self.padding.top + reservedCrossStart + currentCrossPos + lineHeight - childBorderBoxHeight
|
||||||
elseif effectiveAlign == AlignItems.STRETCH then
|
elseif effectiveAlign == AlignItems.STRETCH then
|
||||||
-- STRETCH: Set border-box height to lineHeight, content area shrinks to fit
|
-- STRETCH: Only apply if height was not explicitly set
|
||||||
child._borderBoxHeight = lineHeight
|
if child.autosizing and child.autosizing.height then
|
||||||
child.height = math.max(0, lineHeight - child.padding.top - child.padding.bottom)
|
-- STRETCH: Set border-box height to lineHeight, content area shrinks to fit
|
||||||
|
child._borderBoxHeight = lineHeight
|
||||||
|
child.height = math.max(0, lineHeight - child.padding.top - child.padding.bottom)
|
||||||
|
end
|
||||||
child.y = self.y + self.padding.top + reservedCrossStart + currentCrossPos
|
child.y = self.y + self.padding.top + reservedCrossStart + currentCrossPos
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2964,9 +3027,12 @@ function Element:layoutChildren()
|
|||||||
elseif effectiveAlign == AlignItems.FLEX_END then
|
elseif effectiveAlign == AlignItems.FLEX_END then
|
||||||
child.x = self.x + self.padding.left + reservedCrossStart + currentCrossPos + lineHeight - childBorderBoxWidth
|
child.x = self.x + self.padding.left + reservedCrossStart + currentCrossPos + lineHeight - childBorderBoxWidth
|
||||||
elseif effectiveAlign == AlignItems.STRETCH then
|
elseif effectiveAlign == AlignItems.STRETCH then
|
||||||
-- STRETCH: Set border-box width to lineHeight, content area shrinks to fit
|
-- STRETCH: Only apply if width was not explicitly set
|
||||||
child._borderBoxWidth = lineHeight
|
if child.autosizing and child.autosizing.width then
|
||||||
child.width = math.max(0, lineHeight - child.padding.left - child.padding.right)
|
-- STRETCH: Set border-box width to lineHeight, content area shrinks to fit
|
||||||
|
child._borderBoxWidth = lineHeight
|
||||||
|
child.width = math.max(0, lineHeight - child.padding.left - child.padding.right)
|
||||||
|
end
|
||||||
child.x = self.x + self.padding.left + reservedCrossStart + currentCrossPos
|
child.x = self.x + self.padding.left + reservedCrossStart + currentCrossPos
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -3023,7 +3089,7 @@ function Element:destroy()
|
|||||||
|
|
||||||
-- Clear animation reference
|
-- Clear animation reference
|
||||||
self.animation = nil
|
self.animation = nil
|
||||||
|
|
||||||
-- Clear callback to prevent closure leaks
|
-- Clear callback to prevent closure leaks
|
||||||
self.callback = nil
|
self.callback = nil
|
||||||
end
|
end
|
||||||
@@ -3034,7 +3100,7 @@ function Element:draw()
|
|||||||
if self.opacity <= 0 then
|
if self.opacity <= 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle opacity during animation
|
-- Handle opacity during animation
|
||||||
local drawBackgroundColor = self.backgroundColor
|
local drawBackgroundColor = self.backgroundColor
|
||||||
if self.animation then
|
if self.animation then
|
||||||
@@ -3048,7 +3114,7 @@ function Element:draw()
|
|||||||
-- Cache border box dimensions for this draw call (optimization)
|
-- Cache border box dimensions for this draw call (optimization)
|
||||||
local borderBoxWidth = self._borderBoxWidth or (self.width + self.padding.left + self.padding.right)
|
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 borderBoxHeight = self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom)
|
||||||
|
|
||||||
-- LAYER 1: Draw backgroundColor first (behind everything)
|
-- LAYER 1: Draw backgroundColor first (behind everything)
|
||||||
-- Apply opacity to all drawing operations
|
-- Apply opacity to all drawing operations
|
||||||
-- (x, y) represents border box, so draw background from (x, y)
|
-- (x, y) represents border box, so draw background from (x, y)
|
||||||
@@ -3056,14 +3122,7 @@ function Element:draw()
|
|||||||
local backgroundWithOpacity =
|
local backgroundWithOpacity =
|
||||||
Color.new(drawBackgroundColor.r, drawBackgroundColor.g, drawBackgroundColor.b, drawBackgroundColor.a * self.opacity)
|
Color.new(drawBackgroundColor.r, drawBackgroundColor.g, drawBackgroundColor.b, drawBackgroundColor.a * self.opacity)
|
||||||
love.graphics.setColor(backgroundWithOpacity:toRGBA())
|
love.graphics.setColor(backgroundWithOpacity:toRGBA())
|
||||||
RoundedRect.draw(
|
RoundedRect.draw("fill", self.x, self.y, borderBoxWidth, borderBoxHeight, self.cornerRadius)
|
||||||
"fill",
|
|
||||||
self.x,
|
|
||||||
self.y,
|
|
||||||
borderBoxWidth,
|
|
||||||
borderBoxHeight,
|
|
||||||
self.cornerRadius
|
|
||||||
)
|
|
||||||
|
|
||||||
-- LAYER 2: Draw theme on top of backgroundColor (if theme exists)
|
-- LAYER 2: Draw theme on top of backgroundColor (if theme exists)
|
||||||
if self.themeComponent then
|
if self.themeComponent then
|
||||||
@@ -3100,11 +3159,15 @@ function Element:draw()
|
|||||||
|
|
||||||
if atlasToUse and component.regions then
|
if atlasToUse and component.regions then
|
||||||
-- Validate component has required structure
|
-- Validate component has required structure
|
||||||
local hasAllRegions = component.regions.topLeft and component.regions.topCenter and
|
local hasAllRegions = component.regions.topLeft
|
||||||
component.regions.topRight and component.regions.middleLeft and
|
and component.regions.topCenter
|
||||||
component.regions.middleCenter and component.regions.middleRight and
|
and component.regions.topRight
|
||||||
component.regions.bottomLeft and component.regions.bottomCenter and
|
and component.regions.middleLeft
|
||||||
component.regions.bottomRight
|
and component.regions.middleCenter
|
||||||
|
and component.regions.middleRight
|
||||||
|
and component.regions.bottomLeft
|
||||||
|
and component.regions.bottomCenter
|
||||||
|
and component.regions.bottomRight
|
||||||
if hasAllRegions then
|
if hasAllRegions then
|
||||||
NineSlice.draw(component, atlasToUse, self.x, self.y, self.width, self.height, self.padding, self.opacity)
|
NineSlice.draw(component, atlasToUse, self.x, self.y, self.width, self.height, self.padding, self.opacity)
|
||||||
else
|
else
|
||||||
@@ -3659,7 +3722,7 @@ function Element:calculateTextWidth()
|
|||||||
fontPath = themeToUse.fonts.default
|
fontPath = themeToUse.fonts.default
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
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)
|
||||||
return width
|
return width
|
||||||
@@ -3692,7 +3755,7 @@ function Element:calculateTextHeight()
|
|||||||
fontPath = themeToUse.fonts.default
|
fontPath = themeToUse.fonts.default
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local tempFont = FONT_CACHE.get(self.textSize, fontPath)
|
local tempFont = FONT_CACHE.get(self.textSize, fontPath)
|
||||||
local height = tempFont:getHeight()
|
local height = tempFont:getHeight()
|
||||||
return height
|
return height
|
||||||
@@ -3710,19 +3773,34 @@ function Element:calculateAutoWidth()
|
|||||||
return contentWidth
|
return contentWidth
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- For HORIZONTAL flex: sum children widths + gaps
|
||||||
|
-- For VERTICAL flex: max of children widths
|
||||||
|
local isHorizontal = self.flexDirection == "horizontal"
|
||||||
local totalWidth = contentWidth
|
local totalWidth = contentWidth
|
||||||
|
local maxWidth = contentWidth
|
||||||
local participatingChildren = 0
|
local participatingChildren = 0
|
||||||
|
|
||||||
for _, child in ipairs(self.children) do
|
for _, child in ipairs(self.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
|
||||||
local childBorderBoxWidth = child:getBorderBoxWidth()
|
local childBorderBoxWidth = child:getBorderBoxWidth()
|
||||||
totalWidth = totalWidth + childBorderBoxWidth
|
if isHorizontal then
|
||||||
|
totalWidth = totalWidth + childBorderBoxWidth
|
||||||
|
else
|
||||||
|
maxWidth = math.max(maxWidth, childBorderBoxWidth)
|
||||||
|
end
|
||||||
participatingChildren = participatingChildren + 1
|
participatingChildren = participatingChildren + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return totalWidth + (self.gap * participatingChildren)
|
if isHorizontal then
|
||||||
|
-- Add gaps between children (n-1 gaps for n children)
|
||||||
|
local gapCount = math.max(0, participatingChildren - 1)
|
||||||
|
return totalWidth + (self.gap * gapCount)
|
||||||
|
else
|
||||||
|
return maxWidth
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Calculate auto height based on children
|
--- Calculate auto height based on children
|
||||||
@@ -3732,19 +3810,34 @@ function Element:calculateAutoHeight()
|
|||||||
return height
|
return height
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- For VERTICAL flex: sum children heights + gaps
|
||||||
|
-- For HORIZONTAL flex: max of children heights
|
||||||
|
local isVertical = self.flexDirection == "vertical"
|
||||||
local totalHeight = height
|
local totalHeight = height
|
||||||
|
local maxHeight = height
|
||||||
local participatingChildren = 0
|
local participatingChildren = 0
|
||||||
|
|
||||||
for _, child in ipairs(self.children) do
|
for _, child in ipairs(self.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
|
||||||
local childBorderBoxHeight = child:getBorderBoxHeight()
|
local childBorderBoxHeight = child:getBorderBoxHeight()
|
||||||
totalHeight = totalHeight + childBorderBoxHeight
|
if isVertical then
|
||||||
|
totalHeight = totalHeight + childBorderBoxHeight
|
||||||
|
else
|
||||||
|
maxHeight = math.max(maxHeight, childBorderBoxHeight)
|
||||||
|
end
|
||||||
participatingChildren = participatingChildren + 1
|
participatingChildren = participatingChildren + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return totalHeight + (self.gap * participatingChildren)
|
if isVertical then
|
||||||
|
-- Add gaps between children (n-1 gaps for n children)
|
||||||
|
local gapCount = math.max(0, participatingChildren - 1)
|
||||||
|
return totalHeight + (self.gap * gapCount)
|
||||||
|
else
|
||||||
|
return maxHeight
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param newText string
|
---@param newText string
|
||||||
@@ -3769,4 +3862,23 @@ Gui.new = Element.new
|
|||||||
Gui.Element = Element
|
Gui.Element = Element
|
||||||
Gui.Animation = Animation
|
Gui.Animation = Animation
|
||||||
Gui.Theme = Theme
|
Gui.Theme = Theme
|
||||||
return { GUI = Gui, Gui = Gui, Element = Element, Color = Color, Theme = Theme, Animation = Animation, enums = enums }
|
|
||||||
|
-- Export individual enums for convenience
|
||||||
|
return {
|
||||||
|
GUI = Gui,
|
||||||
|
Gui = Gui,
|
||||||
|
Element = Element,
|
||||||
|
Color = Color,
|
||||||
|
Theme = Theme,
|
||||||
|
Animation = Animation,
|
||||||
|
enums = enums,
|
||||||
|
-- Export individual enums at top level
|
||||||
|
Positioning = Positioning,
|
||||||
|
FlexDirection = FlexDirection,
|
||||||
|
JustifyContent = JustifyContent,
|
||||||
|
AlignItems = AlignItems,
|
||||||
|
AlignSelf = AlignSelf,
|
||||||
|
AlignContent = AlignContent,
|
||||||
|
FlexWrap = FlexWrap,
|
||||||
|
TextAlign = TextAlign,
|
||||||
|
}
|
||||||
|
|||||||
170
examples/ThemeColorAccessDemo.lua
Normal file
170
examples/ThemeColorAccessDemo.lua
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
-- Theme Color Access Demo
|
||||||
|
-- Demonstrates various ways to access and use theme colors
|
||||||
|
|
||||||
|
package.path = package.path .. ";./?.lua;../?.lua"
|
||||||
|
|
||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
local Theme = FlexLove.Theme
|
||||||
|
local Gui = FlexLove.Gui
|
||||||
|
local Color = FlexLove.Color
|
||||||
|
|
||||||
|
-- Initialize love stubs for testing
|
||||||
|
love = {
|
||||||
|
graphics = {
|
||||||
|
newFont = function(size) return { getHeight = function() return size end } end,
|
||||||
|
getFont = function() return { getHeight = function() return 12 end } end,
|
||||||
|
getWidth = function() return 1920 end,
|
||||||
|
getHeight = function() return 1080 end,
|
||||||
|
newImage = function() return {} end,
|
||||||
|
newQuad = function() return {} end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
print("=== Theme Color Access Demo ===\n")
|
||||||
|
|
||||||
|
-- Load and activate the space theme
|
||||||
|
Theme.load("space")
|
||||||
|
Theme.setActive("space")
|
||||||
|
|
||||||
|
print("1. Basic Color Access")
|
||||||
|
print("---------------------")
|
||||||
|
|
||||||
|
-- Method 1: Using Theme.getColor() (Recommended)
|
||||||
|
local primaryColor = Theme.getColor("primary")
|
||||||
|
local secondaryColor = Theme.getColor("secondary")
|
||||||
|
local textColor = Theme.getColor("text")
|
||||||
|
local textDarkColor = Theme.getColor("textDark")
|
||||||
|
|
||||||
|
print(string.format("Primary: r=%.2f, g=%.2f, b=%.2f", primaryColor.r, primaryColor.g, primaryColor.b))
|
||||||
|
print(string.format("Secondary: r=%.2f, g=%.2f, b=%.2f", secondaryColor.r, secondaryColor.g, secondaryColor.b))
|
||||||
|
print(string.format("Text: r=%.2f, g=%.2f, b=%.2f", textColor.r, textColor.g, textColor.b))
|
||||||
|
print(string.format("Text Dark: r=%.2f, g=%.2f, b=%.2f", textDarkColor.r, textDarkColor.g, textDarkColor.b))
|
||||||
|
|
||||||
|
print("\n2. Get All Available Colors")
|
||||||
|
print("----------------------------")
|
||||||
|
|
||||||
|
-- Method 2: Get all color names
|
||||||
|
local colorNames = Theme.getColorNames()
|
||||||
|
if colorNames then
|
||||||
|
print("Available colors in theme:")
|
||||||
|
for _, name in ipairs(colorNames) do
|
||||||
|
print(" - " .. name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("\n3. Get All Colors at Once")
|
||||||
|
print("-------------------------")
|
||||||
|
|
||||||
|
-- Method 3: Get all colors as a table
|
||||||
|
local allColors = Theme.getAllColors()
|
||||||
|
if allColors then
|
||||||
|
print("All colors:")
|
||||||
|
for name, color in pairs(allColors) do
|
||||||
|
print(string.format(" %s: r=%.2f, g=%.2f, b=%.2f, a=%.2f", name, color.r, color.g, color.b, color.a))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("\n4. Safe Color Access with Fallback")
|
||||||
|
print("-----------------------------------")
|
||||||
|
|
||||||
|
-- Method 4: Get color with fallback
|
||||||
|
local accentColor = Theme.getColorOrDefault("accent", Color.new(1, 0, 0, 1)) -- Falls back to red
|
||||||
|
local primaryColor2 = Theme.getColorOrDefault("primary", Color.new(1, 0, 0, 1)) -- Uses theme color
|
||||||
|
|
||||||
|
print(string.format("Accent (fallback): r=%.2f, g=%.2f, b=%.2f", accentColor.r, accentColor.g, accentColor.b))
|
||||||
|
print(string.format("Primary (theme): r=%.2f, g=%.2f, b=%.2f", primaryColor2.r, primaryColor2.g, primaryColor2.b))
|
||||||
|
|
||||||
|
print("\n5. Using Colors in GUI Elements")
|
||||||
|
print("--------------------------------")
|
||||||
|
|
||||||
|
-- Create a container with theme colors
|
||||||
|
local container = Gui.new({
|
||||||
|
width = 400,
|
||||||
|
height = 300,
|
||||||
|
backgroundColor = Theme.getColor("secondary"),
|
||||||
|
positioning = FlexLove.enums.Positioning.FLEX,
|
||||||
|
flexDirection = FlexLove.enums.FlexDirection.VERTICAL,
|
||||||
|
gap = 10,
|
||||||
|
padding = { top = 20, right = 20, bottom = 20, left = 20 },
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Create a button with primary color
|
||||||
|
local button = Gui.new({
|
||||||
|
parent = container,
|
||||||
|
width = 360,
|
||||||
|
height = 50,
|
||||||
|
backgroundColor = Theme.getColor("primary"),
|
||||||
|
textColor = Theme.getColor("text"),
|
||||||
|
text = "Click Me!",
|
||||||
|
textSize = 18,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Create a text label with dark text
|
||||||
|
local label = Gui.new({
|
||||||
|
parent = container,
|
||||||
|
width = 360,
|
||||||
|
height = 30,
|
||||||
|
backgroundColor = Theme.getColorOrDefault("background", Color.new(0.2, 0.2, 0.2, 1)),
|
||||||
|
textColor = Theme.getColor("textDark"),
|
||||||
|
text = "This is a label with dark text",
|
||||||
|
textSize = 14,
|
||||||
|
})
|
||||||
|
|
||||||
|
print("Created GUI elements with theme colors:")
|
||||||
|
print(string.format(" Container: %d children", #container.children))
|
||||||
|
print(string.format(" Button background: r=%.2f, g=%.2f, b=%.2f", button.backgroundColor.r, button.backgroundColor.g, button.backgroundColor.b))
|
||||||
|
print(string.format(" Label text color: r=%.2f, g=%.2f, b=%.2f", label.textColor.r, label.textColor.g, label.textColor.b))
|
||||||
|
|
||||||
|
print("\n6. Creating Color Variations")
|
||||||
|
print("-----------------------------")
|
||||||
|
|
||||||
|
-- Create variations of theme colors
|
||||||
|
local primaryDark = Color.new(
|
||||||
|
primaryColor.r * 0.7,
|
||||||
|
primaryColor.g * 0.7,
|
||||||
|
primaryColor.b * 0.7,
|
||||||
|
primaryColor.a
|
||||||
|
)
|
||||||
|
|
||||||
|
local primaryLight = Color.new(
|
||||||
|
math.min(1, primaryColor.r * 1.3),
|
||||||
|
math.min(1, primaryColor.g * 1.3),
|
||||||
|
math.min(1, primaryColor.b * 1.3),
|
||||||
|
primaryColor.a
|
||||||
|
)
|
||||||
|
|
||||||
|
local primaryTransparent = Color.new(
|
||||||
|
primaryColor.r,
|
||||||
|
primaryColor.g,
|
||||||
|
primaryColor.b,
|
||||||
|
0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
print(string.format("Primary (original): r=%.2f, g=%.2f, b=%.2f, a=%.2f", primaryColor.r, primaryColor.g, primaryColor.b, primaryColor.a))
|
||||||
|
print(string.format("Primary (dark): r=%.2f, g=%.2f, b=%.2f, a=%.2f", primaryDark.r, primaryDark.g, primaryDark.b, primaryDark.a))
|
||||||
|
print(string.format("Primary (light): r=%.2f, g=%.2f, b=%.2f, a=%.2f", primaryLight.r, primaryLight.g, primaryLight.b, primaryLight.a))
|
||||||
|
print(string.format("Primary (50%% alpha): r=%.2f, g=%.2f, b=%.2f, a=%.2f", primaryTransparent.r, primaryTransparent.g, primaryTransparent.b, primaryTransparent.a))
|
||||||
|
|
||||||
|
print("\n7. Quick Reference")
|
||||||
|
print("------------------")
|
||||||
|
print([[
|
||||||
|
// Basic usage:
|
||||||
|
local color = Theme.getColor("primary")
|
||||||
|
|
||||||
|
// With fallback:
|
||||||
|
local color = Theme.getColorOrDefault("accent", Color.new(1, 0, 0, 1))
|
||||||
|
|
||||||
|
// Get all colors:
|
||||||
|
local colors = Theme.getAllColors()
|
||||||
|
|
||||||
|
// Get color names:
|
||||||
|
local names = Theme.getColorNames()
|
||||||
|
|
||||||
|
// Use in elements:
|
||||||
|
local button = Gui.new({
|
||||||
|
backgroundColor = Theme.getColor("primary"),
|
||||||
|
textColor = Theme.getColor("text"),
|
||||||
|
})
|
||||||
|
]])
|
||||||
|
|
||||||
|
print("\n=== Demo Complete ===")
|
||||||
181
examples/ThemeColorAccessSimple.lua
Normal file
181
examples/ThemeColorAccessSimple.lua
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
-- Simple Theme Color Access Demo
|
||||||
|
-- Shows how to access theme colors without creating GUI elements
|
||||||
|
|
||||||
|
package.path = package.path .. ";./?.lua;../?.lua"
|
||||||
|
|
||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
local Theme = FlexLove.Theme
|
||||||
|
local Color = FlexLove.Color
|
||||||
|
|
||||||
|
-- Initialize minimal love stubs
|
||||||
|
love = {
|
||||||
|
graphics = {
|
||||||
|
newFont = function(size) return { getHeight = function() return size end } end,
|
||||||
|
newImage = function() return {} end,
|
||||||
|
newQuad = function() return {} end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
print("=== Theme Color Access - Simple Demo ===\n")
|
||||||
|
|
||||||
|
-- Load and activate the space theme
|
||||||
|
Theme.load("space")
|
||||||
|
Theme.setActive("space")
|
||||||
|
|
||||||
|
print("✓ Theme 'space' loaded and activated\n")
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- METHOD 1: Basic Color Access (Recommended)
|
||||||
|
-- ============================================
|
||||||
|
print("METHOD 1: Theme.getColor(colorName)")
|
||||||
|
print("------------------------------------")
|
||||||
|
|
||||||
|
local primaryColor = Theme.getColor("primary")
|
||||||
|
local secondaryColor = Theme.getColor("secondary")
|
||||||
|
local textColor = Theme.getColor("text")
|
||||||
|
local textDarkColor = Theme.getColor("textDark")
|
||||||
|
|
||||||
|
print(string.format("primary = Color(r=%.2f, g=%.2f, b=%.2f, a=%.2f)",
|
||||||
|
primaryColor.r, primaryColor.g, primaryColor.b, primaryColor.a))
|
||||||
|
print(string.format("secondary = Color(r=%.2f, g=%.2f, b=%.2f, a=%.2f)",
|
||||||
|
secondaryColor.r, secondaryColor.g, secondaryColor.b, secondaryColor.a))
|
||||||
|
print(string.format("text = Color(r=%.2f, g=%.2f, b=%.2f, a=%.2f)",
|
||||||
|
textColor.r, textColor.g, textColor.b, textColor.a))
|
||||||
|
print(string.format("textDark = Color(r=%.2f, g=%.2f, b=%.2f, a=%.2f)",
|
||||||
|
textDarkColor.r, textDarkColor.g, textDarkColor.b, textDarkColor.a))
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- METHOD 2: Get All Color Names
|
||||||
|
-- ============================================
|
||||||
|
print("\nMETHOD 2: Theme.getColorNames()")
|
||||||
|
print("--------------------------------")
|
||||||
|
|
||||||
|
local colorNames = Theme.getColorNames()
|
||||||
|
print("Available colors:")
|
||||||
|
for i, name in ipairs(colorNames) do
|
||||||
|
print(string.format(" %d. %s", i, name))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- METHOD 3: Get All Colors at Once
|
||||||
|
-- ============================================
|
||||||
|
print("\nMETHOD 3: Theme.getAllColors()")
|
||||||
|
print("-------------------------------")
|
||||||
|
|
||||||
|
local allColors = Theme.getAllColors()
|
||||||
|
print("All colors with values:")
|
||||||
|
for name, color in pairs(allColors) do
|
||||||
|
print(string.format(" %-10s = (%.2f, %.2f, %.2f, %.2f)",
|
||||||
|
name, color.r, color.g, color.b, color.a))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- METHOD 4: Safe Access with Fallback
|
||||||
|
-- ============================================
|
||||||
|
print("\nMETHOD 4: Theme.getColorOrDefault(colorName, fallback)")
|
||||||
|
print("-------------------------------------------------------")
|
||||||
|
|
||||||
|
-- Try to get a color that exists
|
||||||
|
local existingColor = Theme.getColorOrDefault("primary", Color.new(1, 0, 0, 1))
|
||||||
|
print(string.format("Existing color 'primary': (%.2f, %.2f, %.2f) ✓",
|
||||||
|
existingColor.r, existingColor.g, existingColor.b))
|
||||||
|
|
||||||
|
-- Try to get a color that doesn't exist (will use fallback)
|
||||||
|
local missingColor = Theme.getColorOrDefault("accent", Color.new(1, 0, 0, 1))
|
||||||
|
print(string.format("Missing color 'accent' (fallback): (%.2f, %.2f, %.2f) ✓",
|
||||||
|
missingColor.r, missingColor.g, missingColor.b))
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- PRACTICAL EXAMPLES
|
||||||
|
-- ============================================
|
||||||
|
print("\n=== Practical Usage Examples ===\n")
|
||||||
|
|
||||||
|
print("Example 1: Using colors in element creation")
|
||||||
|
print("--------------------------------------------")
|
||||||
|
print([[
|
||||||
|
local button = Gui.new({
|
||||||
|
width = 200,
|
||||||
|
height = 50,
|
||||||
|
backgroundColor = Theme.getColor("primary"),
|
||||||
|
textColor = Theme.getColor("text"),
|
||||||
|
text = "Click Me!"
|
||||||
|
})
|
||||||
|
]])
|
||||||
|
|
||||||
|
print("\nExample 2: Creating color variations")
|
||||||
|
print("-------------------------------------")
|
||||||
|
print([[
|
||||||
|
local primary = Theme.getColor("primary")
|
||||||
|
|
||||||
|
-- Darker version (70% brightness)
|
||||||
|
local primaryDark = Color.new(
|
||||||
|
primary.r * 0.7,
|
||||||
|
primary.g * 0.7,
|
||||||
|
primary.b * 0.7,
|
||||||
|
primary.a
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Lighter version (130% brightness)
|
||||||
|
local primaryLight = Color.new(
|
||||||
|
math.min(1, primary.r * 1.3),
|
||||||
|
math.min(1, primary.g * 1.3),
|
||||||
|
math.min(1, primary.b * 1.3),
|
||||||
|
primary.a
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Semi-transparent version
|
||||||
|
local primaryTransparent = Color.new(
|
||||||
|
primary.r,
|
||||||
|
primary.g,
|
||||||
|
primary.b,
|
||||||
|
0.5 -- 50% opacity
|
||||||
|
)
|
||||||
|
]])
|
||||||
|
|
||||||
|
print("\nExample 3: Safe color access")
|
||||||
|
print("-----------------------------")
|
||||||
|
print([[
|
||||||
|
-- With fallback to white if color doesn't exist
|
||||||
|
local bgColor = Theme.getColorOrDefault("background", Color.new(1, 1, 1, 1))
|
||||||
|
|
||||||
|
-- With fallback to theme's secondary color
|
||||||
|
local borderColor = Theme.getColorOrDefault(
|
||||||
|
"border",
|
||||||
|
Theme.getColor("secondary")
|
||||||
|
)
|
||||||
|
]])
|
||||||
|
|
||||||
|
print("\nExample 4: Dynamic color selection")
|
||||||
|
print("-----------------------------------")
|
||||||
|
print([[
|
||||||
|
-- Get all available colors
|
||||||
|
local colors = Theme.getAllColors()
|
||||||
|
|
||||||
|
-- Pick a random color
|
||||||
|
local colorNames = {}
|
||||||
|
for name in pairs(colors) do
|
||||||
|
table.insert(colorNames, name)
|
||||||
|
end
|
||||||
|
local randomColorName = colorNames[math.random(#colorNames)]
|
||||||
|
local randomColor = colors[randomColorName]
|
||||||
|
]])
|
||||||
|
|
||||||
|
print("\n=== Quick Reference ===\n")
|
||||||
|
print("Theme.getColor(name) -- Get a specific color")
|
||||||
|
print("Theme.getColorOrDefault(n, fb) -- Get color with fallback")
|
||||||
|
print("Theme.getAllColors() -- Get all colors as table")
|
||||||
|
print("Theme.getColorNames() -- Get array of color names")
|
||||||
|
print("Theme.hasActive() -- Check if theme is active")
|
||||||
|
print("Theme.getActive() -- Get active theme object")
|
||||||
|
|
||||||
|
print("\n=== Available Colors in 'space' Theme ===\n")
|
||||||
|
for i, name in ipairs(colorNames) do
|
||||||
|
local color = allColors[name]
|
||||||
|
print(string.format("%-10s RGB(%.0f, %.0f, %.0f)",
|
||||||
|
name,
|
||||||
|
color.r * 255,
|
||||||
|
color.g * 255,
|
||||||
|
color.b * 255))
|
||||||
|
end
|
||||||
|
|
||||||
|
print("\n=== Demo Complete ===")
|
||||||
@@ -442,9 +442,9 @@ function TestHorizontalFlexDirection:testHorizontalLayoutAlignItemsStretch()
|
|||||||
parent:addChild(child1)
|
parent:addChild(child1)
|
||||||
parent:addChild(child2)
|
parent:addChild(child2)
|
||||||
|
|
||||||
-- With align-items stretch in horizontal layout, children should stretch to parent height
|
-- With align-items stretch, children with explicit heights should keep them (CSS flexbox behavior)
|
||||||
luaunit.assertEquals(child1.height, parent.height)
|
luaunit.assertEquals(child1.height, 30)
|
||||||
luaunit.assertEquals(child2.height, parent.height)
|
luaunit.assertEquals(child2.height, 40)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test 14: Horizontal layout with align-items center
|
-- Test 14: Horizontal layout with align-items center
|
||||||
|
|||||||
@@ -402,9 +402,9 @@ function TestVerticalFlexDirection:testVerticalLayoutAlignItemsStretch()
|
|||||||
parent:addChild(child1)
|
parent:addChild(child1)
|
||||||
parent:addChild(child2)
|
parent:addChild(child2)
|
||||||
|
|
||||||
-- Children should be stretched to fill parent width
|
-- Children with explicit widths should keep them (CSS flexbox behavior)
|
||||||
luaunit.assertEquals(child1.width, parent.width)
|
luaunit.assertEquals(child1.width, 80)
|
||||||
luaunit.assertEquals(child2.width, parent.width)
|
luaunit.assertEquals(child2.width, 60)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test 13: Vertical layout with space-between
|
-- Test 13: Vertical layout with space-between
|
||||||
|
|||||||
@@ -189,11 +189,11 @@ function TestAlignItems:testHorizontalFlexAlignItemsStretch()
|
|||||||
container:addChild(child1)
|
container:addChild(child1)
|
||||||
container:addChild(child2)
|
container:addChild(child2)
|
||||||
|
|
||||||
-- Children should be stretched to fill container height
|
-- Children with explicit heights should NOT be stretched (CSS flexbox behavior)
|
||||||
luaunit.assertEquals(child1.y, 0)
|
luaunit.assertEquals(child1.y, 0)
|
||||||
luaunit.assertEquals(child2.y, 0)
|
luaunit.assertEquals(child2.y, 0)
|
||||||
luaunit.assertEquals(child1.height, 100)
|
luaunit.assertEquals(child1.height, 30) -- Keeps explicit height
|
||||||
luaunit.assertEquals(child2.height, 100)
|
luaunit.assertEquals(child2.height, 40) -- Keeps explicit height
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test 5: Vertical Flex with AlignItems.FLEX_START
|
-- Test 5: Vertical Flex with AlignItems.FLEX_START
|
||||||
@@ -357,11 +357,11 @@ function TestAlignItems:testVerticalFlexAlignItemsStretch()
|
|||||||
container:addChild(child1)
|
container:addChild(child1)
|
||||||
container:addChild(child2)
|
container:addChild(child2)
|
||||||
|
|
||||||
-- Children should be stretched to fill container width
|
-- Children with explicit widths should NOT be stretched (CSS flexbox behavior)
|
||||||
luaunit.assertEquals(child1.x, 0)
|
luaunit.assertEquals(child1.x, 0)
|
||||||
luaunit.assertEquals(child2.x, 0)
|
luaunit.assertEquals(child2.x, 0)
|
||||||
luaunit.assertEquals(child1.width, 200)
|
luaunit.assertEquals(child1.width, 50) -- Keeps explicit width
|
||||||
luaunit.assertEquals(child2.width, 200)
|
luaunit.assertEquals(child2.width, 80) -- Keeps explicit width
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test 9: Default AlignItems value (should be STRETCH)
|
-- Test 9: Default AlignItems value (should be STRETCH)
|
||||||
@@ -386,9 +386,9 @@ function TestAlignItems:testDefaultAlignItems()
|
|||||||
|
|
||||||
container:addChild(child)
|
container:addChild(child)
|
||||||
|
|
||||||
-- Default should be STRETCH
|
-- Default should be STRETCH, but explicit heights are respected
|
||||||
luaunit.assertEquals(container.alignItems, AlignItems.STRETCH)
|
luaunit.assertEquals(container.alignItems, AlignItems.STRETCH)
|
||||||
luaunit.assertEquals(child.height, 100) -- Should be stretched
|
luaunit.assertEquals(child.height, 30) -- Keeps explicit height (CSS flexbox behavior)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test 10: AlignItems with mixed child sizes
|
-- Test 10: AlignItems with mixed child sizes
|
||||||
|
|||||||
@@ -280,12 +280,12 @@ function TestFlexWrap07_WrapWithStretchAlignItems()
|
|||||||
|
|
||||||
local positions = layoutAndGetPositions(container)
|
local positions = layoutAndGetPositions(container)
|
||||||
|
|
||||||
-- All children in first line should stretch to tallest (35)
|
-- Children with explicit heights should keep them (CSS flexbox behavior)
|
||||||
luaunit.assertEquals(positions[1].height, 35) -- child1 stretched
|
luaunit.assertEquals(positions[1].height, 20) -- child1 keeps explicit height
|
||||||
luaunit.assertEquals(positions[2].height, 35) -- child2 keeps height
|
luaunit.assertEquals(positions[2].height, 35) -- child2 keeps explicit height
|
||||||
|
|
||||||
-- Child in second line should keep its height (no other children to stretch to)
|
-- Child in second line should keep its height
|
||||||
luaunit.assertEquals(positions[3].height, 25) -- child3 original height
|
luaunit.assertEquals(positions[3].height, 25) -- child3 keeps explicit height
|
||||||
|
|
||||||
-- Verify positions
|
-- Verify positions
|
||||||
luaunit.assertEquals(positions[1].y, 0) -- First line
|
luaunit.assertEquals(positions[1].y, 0) -- First line
|
||||||
|
|||||||
@@ -186,11 +186,11 @@ function TestComprehensiveFlex:testNestedFlexContainersComplexLayout()
|
|||||||
-- Positions are absolute including parent container position
|
-- Positions are absolute including parent container position
|
||||||
luaunit.assertEquals(inner2Positions[1].x, 20) -- parent x + 0
|
luaunit.assertEquals(inner2Positions[1].x, 20) -- parent x + 0
|
||||||
luaunit.assertEquals(inner2Positions[1].y, 95) -- parent y + 0
|
luaunit.assertEquals(inner2Positions[1].y, 95) -- parent y + 0
|
||||||
luaunit.assertEquals(inner2Positions[1].height, 50) -- stretched to full container height
|
luaunit.assertEquals(inner2Positions[1].height, 25) -- explicit height, not stretched (CSS spec compliance)
|
||||||
|
|
||||||
luaunit.assertEquals(inner2Positions[2].x, 60) -- parent x + 40
|
luaunit.assertEquals(inner2Positions[2].x, 60) -- parent x + 40
|
||||||
luaunit.assertEquals(inner2Positions[2].y, 95) -- parent y + 0
|
luaunit.assertEquals(inner2Positions[2].y, 95) -- parent y + 0
|
||||||
luaunit.assertEquals(inner2Positions[2].height, 50) -- stretched to full container height
|
luaunit.assertEquals(inner2Positions[2].height, 25) -- explicit height, not stretched (CSS spec compliance)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test 3: All flex properties combined with absolute positioning
|
-- Test 3: All flex properties combined with absolute positioning
|
||||||
@@ -390,19 +390,19 @@ function TestComprehensiveFlex:testDeeplyNestedFlexContainers()
|
|||||||
-- These positions are relative to level 1 container position
|
-- These positions are relative to level 1 container position
|
||||||
luaunit.assertEquals(level2Positions[1].x, 20) -- positioned by level 1
|
luaunit.assertEquals(level2Positions[1].x, 20) -- positioned by level 1
|
||||||
luaunit.assertEquals(level2Positions[1].y, 25) -- positioned by level 1
|
luaunit.assertEquals(level2Positions[1].y, 25) -- positioned by level 1
|
||||||
luaunit.assertEquals(level2Positions[1].height, 100) -- stretched to full cross-axis height
|
luaunit.assertEquals(level2Positions[1].height, 80) -- explicit height, not stretched (CSS spec compliance)
|
||||||
|
|
||||||
luaunit.assertEquals(level2Positions[2].x, 110) -- positioned by level 1 + space-between
|
luaunit.assertEquals(level2Positions[2].x, 110) -- positioned by level 1 + space-between
|
||||||
luaunit.assertEquals(level2Positions[2].y, 25) -- positioned by level 1
|
luaunit.assertEquals(level2Positions[2].y, 25) -- positioned by level 1
|
||||||
luaunit.assertEquals(level2Positions[2].height, 100) -- stretched to full cross-axis height
|
luaunit.assertEquals(level2Positions[2].height, 80) -- explicit height, not stretched (CSS spec compliance)
|
||||||
|
|
||||||
-- Level 3a: flex-end justification, center alignment
|
-- Level 3a: flex-end justification, center alignment
|
||||||
-- Positions are absolute including parent positions
|
-- Positions are absolute including parent positions
|
||||||
luaunit.assertEquals(level3aPositions[1].x, 40) -- absolute position
|
luaunit.assertEquals(level3aPositions[1].x, 40) -- absolute position
|
||||||
luaunit.assertEquals(level3aPositions[1].y, 90) -- flex-end: positioned at bottom of stretched container
|
luaunit.assertEquals(level3aPositions[1].y, 70) -- flex-end: 25 (level2.y) + 45 (80 - 35 total children)
|
||||||
|
|
||||||
luaunit.assertEquals(level3aPositions[2].x, 42.5) -- absolute position
|
luaunit.assertEquals(level3aPositions[2].x, 42.5) -- absolute position
|
||||||
luaunit.assertEquals(level3aPositions[2].y, 110) -- second item: 90 + 20 = 110
|
luaunit.assertEquals(level3aPositions[2].y, 90) -- second item: 70 + 20 = 90
|
||||||
|
|
||||||
-- Level 3b: flex-start justification, flex-end alignment
|
-- Level 3b: flex-start justification, flex-end alignment
|
||||||
-- Positions are absolute including parent positions
|
-- Positions are absolute including parent positions
|
||||||
@@ -1656,8 +1656,8 @@ function TestComprehensiveFlex:testComplexDashboardLayout()
|
|||||||
middlePositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height }
|
middlePositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height }
|
||||||
end
|
end
|
||||||
|
|
||||||
luaunit.assertEquals(middlePositions[1].x, 300) -- chart panel (300 + 20 padding)
|
luaunit.assertEquals(middlePositions[1].x, 300) -- chart panel (280 sidebar + 20 padding)
|
||||||
luaunit.assertEquals(middlePositions[2].x, 1000) -- stats panel (300 + 680 + 20)
|
luaunit.assertEquals(middlePositions[2].x, 1020) -- stats panel (280 + 20 + 680 + 40 gap with SPACE_BETWEEN)
|
||||||
|
|
||||||
-- Test chart legend wrapping
|
-- Test chart legend wrapping
|
||||||
local chartPanel = middleContent.children[1]
|
local chartPanel = middleContent.children[1]
|
||||||
@@ -1699,7 +1699,7 @@ function TestComprehensiveFlex:testComplexDashboardLayout()
|
|||||||
end
|
end
|
||||||
|
|
||||||
luaunit.assertEquals(bottomPositions[1].x, 300) -- table panel
|
luaunit.assertEquals(bottomPositions[1].x, 300) -- table panel
|
||||||
luaunit.assertEquals(bottomPositions[2].x, 860) -- right panels (300 + 540 + 20)
|
luaunit.assertEquals(bottomPositions[2].x, 880) -- right panels (280 + 20 + 540 + 40 gap with SPACE_BETWEEN)
|
||||||
|
|
||||||
-- Test right panels layout
|
-- Test right panels layout
|
||||||
local rightPanels = bottomContent.children[2]
|
local rightPanels = bottomContent.children[2]
|
||||||
@@ -1710,8 +1710,8 @@ function TestComprehensiveFlex:testComplexDashboardLayout()
|
|||||||
rightPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height }
|
rightPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height }
|
||||||
end
|
end
|
||||||
|
|
||||||
luaunit.assertEquals(rightPositions[1].x, 860) -- alerts panel
|
luaunit.assertEquals(rightPositions[1].x, 880) -- alerts panel (same as parent due to SPACE_BETWEEN)
|
||||||
luaunit.assertEquals(rightPositions[2].x, 1120) -- progress panel (860 + 240 + 20)
|
luaunit.assertEquals(rightPositions[2].x, 1140) -- progress panel (880 + 240 + 20)
|
||||||
end
|
end
|
||||||
|
|
||||||
luaunit.LuaUnit.run()
|
luaunit.LuaUnit.run()
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ function TestAuxiliaryFunctions:testCalculateAutoWidthWithChildren()
|
|||||||
local parent = Gui.new({
|
local parent = Gui.new({
|
||||||
positioning = enums.Positioning.FLEX,
|
positioning = enums.Positioning.FLEX,
|
||||||
flexDirection = enums.FlexDirection.HORIZONTAL,
|
flexDirection = enums.FlexDirection.HORIZONTAL,
|
||||||
|
gap = 5, -- Add gap to test gap calculation
|
||||||
})
|
})
|
||||||
|
|
||||||
local child1 = Gui.new({
|
local child1 = Gui.new({
|
||||||
@@ -172,6 +173,7 @@ function TestAuxiliaryFunctions:testCalculateAutoHeightWithChildren()
|
|||||||
local parent = Gui.new({
|
local parent = Gui.new({
|
||||||
positioning = enums.Positioning.FLEX,
|
positioning = enums.Positioning.FLEX,
|
||||||
flexDirection = enums.FlexDirection.VERTICAL,
|
flexDirection = enums.FlexDirection.VERTICAL,
|
||||||
|
gap = 5, -- Add gap to test gap calculation
|
||||||
})
|
})
|
||||||
|
|
||||||
local child1 = Gui.new({
|
local child1 = Gui.new({
|
||||||
@@ -491,18 +493,21 @@ function TestAuxiliaryFunctions:testAnimationInterpolationAtBoundaries()
|
|||||||
|
|
||||||
-- At start (elapsed = 0)
|
-- At start (elapsed = 0)
|
||||||
scaleAnim.elapsed = 0
|
scaleAnim.elapsed = 0
|
||||||
|
scaleAnim._resultDirty = true -- Mark dirty after changing elapsed
|
||||||
local result = scaleAnim:interpolate()
|
local result = scaleAnim:interpolate()
|
||||||
luaunit.assertEquals(result.width, 100)
|
luaunit.assertEquals(result.width, 100)
|
||||||
luaunit.assertEquals(result.height, 50)
|
luaunit.assertEquals(result.height, 50)
|
||||||
|
|
||||||
-- At end (elapsed = duration)
|
-- At end (elapsed = duration)
|
||||||
scaleAnim.elapsed = 1.0
|
scaleAnim.elapsed = 1.0
|
||||||
|
scaleAnim._resultDirty = true -- Mark dirty after changing elapsed
|
||||||
result = scaleAnim:interpolate()
|
result = scaleAnim:interpolate()
|
||||||
luaunit.assertEquals(result.width, 200)
|
luaunit.assertEquals(result.width, 200)
|
||||||
luaunit.assertEquals(result.height, 100)
|
luaunit.assertEquals(result.height, 100)
|
||||||
|
|
||||||
-- Beyond end (elapsed > duration) - should clamp to end values
|
-- Beyond end (elapsed > duration) - should clamp to end values
|
||||||
scaleAnim.elapsed = 1.5
|
scaleAnim.elapsed = 1.5
|
||||||
|
scaleAnim._resultDirty = true -- Mark dirty after changing elapsed
|
||||||
result = scaleAnim:interpolate()
|
result = scaleAnim:interpolate()
|
||||||
luaunit.assertEquals(result.width, 200)
|
luaunit.assertEquals(result.width, 200)
|
||||||
luaunit.assertEquals(result.height, 100)
|
luaunit.assertEquals(result.height, 100)
|
||||||
@@ -594,11 +599,11 @@ function TestAuxiliaryFunctions:testComplexColorManagementSystem()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Test color variations (opacity, brightness adjustments)
|
-- Test color variations (opacity, brightness adjustments)
|
||||||
|
local opacities = { 0.1, 0.25, 0.5, 0.75, 0.9 }
|
||||||
for color_name, color_set in pairs(theme_colors) do
|
for color_name, color_set in pairs(theme_colors) do
|
||||||
color_variations[color_name] = {}
|
color_variations[color_name] = {}
|
||||||
|
|
||||||
-- Create opacity variations
|
-- Create opacity variations
|
||||||
local opacities = { 0.1, 0.25, 0.5, 0.75, 0.9 }
|
|
||||||
for _, opacity in ipairs(opacities) do
|
for _, opacity in ipairs(opacities) do
|
||||||
local variant_color = Color.new(color_set.manual.r, color_set.manual.g, color_set.manual.b, opacity)
|
local variant_color = Color.new(color_set.manual.r, color_set.manual.g, color_set.manual.b, opacity)
|
||||||
color_variations[color_name]["alpha_" .. tostring(opacity)] = variant_color
|
color_variations[color_name]["alpha_" .. tostring(opacity)] = variant_color
|
||||||
@@ -678,7 +683,13 @@ function TestAuxiliaryFunctions:testComplexColorManagementSystem()
|
|||||||
ui_container:layoutChildren()
|
ui_container:layoutChildren()
|
||||||
|
|
||||||
luaunit.assertEquals(#ui_container.children, 5, "Should have 5 themed components")
|
luaunit.assertEquals(#ui_container.children, 5, "Should have 5 themed components")
|
||||||
luaunit.assertEquals(#theme_colors, 5, "Should have 5 base theme colors")
|
|
||||||
|
-- Count theme_colors (it's a table with string keys, not an array)
|
||||||
|
local theme_color_count = 0
|
||||||
|
for _ in pairs(theme_colors) do
|
||||||
|
theme_color_count = theme_color_count + 1
|
||||||
|
end
|
||||||
|
luaunit.assertEquals(theme_color_count, 5, "Should have 5 base theme colors")
|
||||||
|
|
||||||
local total_variations = 0
|
local total_variations = 0
|
||||||
for _, variations in pairs(color_variations) do
|
for _, variations in pairs(color_variations) do
|
||||||
@@ -902,6 +913,7 @@ function TestAuxiliaryFunctions:testAdvancedTextAndAutoSizingSystem()
|
|||||||
table.insert(main_container.children, nested_container)
|
table.insert(main_container.children, nested_container)
|
||||||
|
|
||||||
-- Create nested structure with auto-sizing children
|
-- Create nested structure with auto-sizing children
|
||||||
|
local prev_container = nested_container
|
||||||
for level = 1, 3 do
|
for level = 1, 3 do
|
||||||
local level_container = Gui.new({
|
local level_container = Gui.new({
|
||||||
width = 750 - (level * 50),
|
width = 750 - (level * 50),
|
||||||
@@ -911,12 +923,9 @@ function TestAuxiliaryFunctions:testAdvancedTextAndAutoSizingSystem()
|
|||||||
justifyContent = enums.JustifyContent.SPACE_AROUND,
|
justifyContent = enums.JustifyContent.SPACE_AROUND,
|
||||||
gap = 5,
|
gap = 5,
|
||||||
})
|
})
|
||||||
level_container.parent = level == 1 and nested_container
|
level_container.parent = prev_container
|
||||||
or main_container.children[#main_container.children].children[level - 1]
|
table.insert(prev_container.children, level_container)
|
||||||
table.insert(
|
prev_container = level_container
|
||||||
(level == 1 and nested_container or main_container.children[#main_container.children].children[level - 1]).children,
|
|
||||||
level_container
|
|
||||||
)
|
|
||||||
|
|
||||||
for item = 1, 4 do
|
for item = 1, 4 do
|
||||||
local item_text = string.format("L%d-Item%d: %s", level, item, string.rep("Text ", level))
|
local item_text = string.format("L%d-Item%d: %s", level, item, string.rep("Text ", level))
|
||||||
@@ -943,7 +952,13 @@ function TestAuxiliaryFunctions:testAdvancedTextAndAutoSizingSystem()
|
|||||||
#text_scenarios + 1,
|
#text_scenarios + 1,
|
||||||
"Should have scenario containers plus nested container"
|
"Should have scenario containers plus nested container"
|
||||||
)
|
)
|
||||||
luaunit.assertTrue(#content_manager.text_metrics >= #text_scenarios, "Should have metrics for all scenarios")
|
|
||||||
|
-- Count text_metrics (it's a table with string keys, not an array)
|
||||||
|
local metrics_count = 0
|
||||||
|
for _ in pairs(content_manager.text_metrics) do
|
||||||
|
metrics_count = metrics_count + 1
|
||||||
|
end
|
||||||
|
luaunit.assertTrue(metrics_count >= #text_scenarios, "Should have metrics for all scenarios")
|
||||||
|
|
||||||
print(
|
print(
|
||||||
string.format(
|
string.format(
|
||||||
@@ -1607,8 +1622,10 @@ function TestAuxiliaryFunctions:testAdvancedGUIManagementAndCleanup()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Test opacity management across hierarchy
|
-- Test opacity management across hierarchy
|
||||||
for i, element_pair in pairs(managed_elements) do
|
for element_id, element_pair in pairs(managed_elements) do
|
||||||
if i % 2 == 0 then
|
-- Extract number from "element_N" key
|
||||||
|
local num = tonumber(element_id:match("%d+"))
|
||||||
|
if num and num % 2 == 0 then
|
||||||
element_pair:updateOpacity(0.5)
|
element_pair:updateOpacity(0.5)
|
||||||
luaunit.assertEquals(element_pair.opacity, 0.5, "Even elements should have 0.5 opacity")
|
luaunit.assertEquals(element_pair.opacity, 0.5, "Even elements should have 0.5 opacity")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ end
|
|||||||
|
|
||||||
function TestUnitsSystem:tearDown()
|
function TestUnitsSystem:tearDown()
|
||||||
Gui.destroy()
|
Gui.destroy()
|
||||||
|
-- Restore original viewport size
|
||||||
|
love.graphics.getDimensions = function()
|
||||||
|
return 800, 600
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ============================================
|
-- ============================================
|
||||||
@@ -245,7 +249,7 @@ function TestUnitsSystem:testMarginUnits()
|
|||||||
})
|
})
|
||||||
|
|
||||||
luaunit.assertEquals(container.margin.top, 8) -- 8px
|
luaunit.assertEquals(container.margin.top, 8) -- 8px
|
||||||
luaunit.assertEquals(container.margin.right, 12) -- 3% of 400
|
luaunit.assertEquals(container.margin.right, 36) -- 3% of viewport width (1200) - CSS spec: % margins relative to containing block
|
||||||
luaunit.assertEquals(container.margin.bottom, 8) -- 1% of 800
|
luaunit.assertEquals(container.margin.bottom, 8) -- 1% of 800
|
||||||
luaunit.assertEquals(container.margin.left, 24) -- 2% of 1200
|
luaunit.assertEquals(container.margin.left, 24) -- 2% of 1200
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ function TestEventSystem:setUp()
|
|||||||
-- Initialize GUI before each test
|
-- Initialize GUI before each test
|
||||||
Gui.init({ baseScale = { width = 1920, height = 1080 } })
|
Gui.init({ baseScale = { width = 1920, height = 1080 } })
|
||||||
love.window.setMode(1920, 1080)
|
love.window.setMode(1920, 1080)
|
||||||
|
Gui.resize(1920, 1080) -- Recalculate scale factors after setMode
|
||||||
end
|
end
|
||||||
|
|
||||||
function TestEventSystem:tearDown()
|
function TestEventSystem:tearDown()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ TestNegativeMargin = {}
|
|||||||
|
|
||||||
function TestNegativeMargin:setUp()
|
function TestNegativeMargin:setUp()
|
||||||
FlexLove.Gui.destroy()
|
FlexLove.Gui.destroy()
|
||||||
FlexLove.Gui.init({ baseScale = { width = 1920, height = 1080 } })
|
-- Don't call init to use 1:1 scaling (like other tests)
|
||||||
end
|
end
|
||||||
|
|
||||||
function TestNegativeMargin:tearDown()
|
function TestNegativeMargin:tearDown()
|
||||||
|
|||||||
@@ -88,12 +88,59 @@ end
|
|||||||
|
|
||||||
-- Mock mouse functions
|
-- Mock mouse functions
|
||||||
love_helper.mouse = {}
|
love_helper.mouse = {}
|
||||||
|
|
||||||
|
-- Mock mouse state
|
||||||
|
local mockMouseX = 0
|
||||||
|
local mockMouseY = 0
|
||||||
|
local mockMouseButtons = {} -- Table to track button states
|
||||||
|
|
||||||
function love_helper.mouse.getPosition()
|
function love_helper.mouse.getPosition()
|
||||||
return 0, 0 -- Default position
|
return mockMouseX, mockMouseY
|
||||||
|
end
|
||||||
|
|
||||||
|
function love_helper.mouse.setPosition(x, y)
|
||||||
|
mockMouseX = x
|
||||||
|
mockMouseY = y
|
||||||
end
|
end
|
||||||
|
|
||||||
function love_helper.mouse.isDown(button)
|
function love_helper.mouse.isDown(button)
|
||||||
return false -- Default not pressed
|
return mockMouseButtons[button] or false
|
||||||
|
end
|
||||||
|
|
||||||
|
function love_helper.mouse.setDown(button, isDown)
|
||||||
|
mockMouseButtons[button] = isDown
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Mock timer functions
|
||||||
|
love_helper.timer = {}
|
||||||
|
|
||||||
|
-- Mock time state
|
||||||
|
local mockTime = 0
|
||||||
|
|
||||||
|
function love_helper.timer.getTime()
|
||||||
|
return mockTime
|
||||||
|
end
|
||||||
|
|
||||||
|
function love_helper.timer.setTime(time)
|
||||||
|
mockTime = time
|
||||||
|
end
|
||||||
|
|
||||||
|
function love_helper.timer.step(dt)
|
||||||
|
mockTime = mockTime + dt
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Mock keyboard functions
|
||||||
|
love_helper.keyboard = {}
|
||||||
|
|
||||||
|
-- Mock keyboard state
|
||||||
|
local mockKeyboardKeys = {} -- Table to track key states
|
||||||
|
|
||||||
|
function love_helper.keyboard.isDown(key)
|
||||||
|
return mockKeyboardKeys[key] or false
|
||||||
|
end
|
||||||
|
|
||||||
|
function love_helper.keyboard.setDown(key, isDown)
|
||||||
|
mockKeyboardKeys[key] = isDown
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Mock touch functions
|
-- Mock touch functions
|
||||||
|
|||||||
Reference in New Issue
Block a user