need to remove guiding bars
This commit is contained in:
306
FlexLove.lua
306
FlexLove.lua
@@ -352,37 +352,121 @@ function ImageScaler.scaleNearest(sourceImageData, srcX, srcY, srcW, srcH, destW
|
|||||||
if not sourceImageData then
|
if not sourceImageData then
|
||||||
error(formatError("ImageScaler", "Source ImageData cannot be nil"))
|
error(formatError("ImageScaler", "Source ImageData cannot be nil"))
|
||||||
end
|
end
|
||||||
|
|
||||||
if srcW <= 0 or srcH <= 0 or destW <= 0 or destH <= 0 then
|
if srcW <= 0 or srcH <= 0 or destW <= 0 or destH <= 0 then
|
||||||
error(formatError("ImageScaler", "Dimensions must be positive"))
|
error(formatError("ImageScaler", "Dimensions must be positive"))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Create destination ImageData
|
-- Create destination ImageData
|
||||||
local destImageData = love.image.newImageData(destW, destH)
|
local destImageData = love.image.newImageData(destW, destH)
|
||||||
|
|
||||||
-- Calculate scale ratios (cached outside loops for performance)
|
-- Calculate scale ratios (cached outside loops for performance)
|
||||||
local scaleX = srcW / destW
|
local scaleX = srcW / destW
|
||||||
local scaleY = srcH / destH
|
local scaleY = srcH / destH
|
||||||
|
|
||||||
-- Nearest-neighbor sampling
|
-- Nearest-neighbor sampling
|
||||||
for destY = 0, destH - 1 do
|
for destY = 0, destH - 1 do
|
||||||
for destX = 0, destW - 1 do
|
for destX = 0, destW - 1 do
|
||||||
-- Calculate source pixel coordinates using floor (nearest-neighbor)
|
-- Calculate source pixel coordinates using floor (nearest-neighbor)
|
||||||
local srcPixelX = math.floor(destX * scaleX) + srcX
|
local srcPixelX = math.floor(destX * scaleX) + srcX
|
||||||
local srcPixelY = math.floor(destY * scaleY) + srcY
|
local srcPixelY = math.floor(destY * scaleY) + srcY
|
||||||
|
|
||||||
-- Clamp to source bounds (safety check)
|
-- Clamp to source bounds (safety check)
|
||||||
srcPixelX = math.min(srcPixelX, srcX + srcW - 1)
|
srcPixelX = math.min(srcPixelX, srcX + srcW - 1)
|
||||||
srcPixelY = math.min(srcPixelY, srcY + srcH - 1)
|
srcPixelY = math.min(srcPixelY, srcY + srcH - 1)
|
||||||
|
|
||||||
-- Sample source pixel
|
-- Sample source pixel
|
||||||
local r, g, b, a = sourceImageData:getPixel(srcPixelX, srcPixelY)
|
local r, g, b, a = sourceImageData:getPixel(srcPixelX, srcPixelY)
|
||||||
|
|
||||||
-- Write to destination
|
-- Write to destination
|
||||||
destImageData:setPixel(destX, destY, r, g, b, a)
|
destImageData:setPixel(destX, destY, r, g, b, a)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return destImageData
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Linear interpolation helper
|
||||||
|
--- Blends between two values based on interpolation factor
|
||||||
|
---@param a number -- Start value
|
||||||
|
---@param b number -- End value
|
||||||
|
---@param t number -- Interpolation factor [0, 1]
|
||||||
|
---@return number -- Interpolated value
|
||||||
|
local function lerp(a, b, t)
|
||||||
|
return a + (b - a) * t
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Scale an ImageData region using bilinear interpolation
|
||||||
|
--- Produces smooth, filtered scaling - ideal for high-quality upscaling
|
||||||
|
---@param sourceImageData love.ImageData -- Source image data
|
||||||
|
---@param srcX number -- Source region X (0-based)
|
||||||
|
---@param srcY number -- Source region Y (0-based)
|
||||||
|
---@param srcW number -- Source region width
|
||||||
|
---@param srcH number -- Source region height
|
||||||
|
---@param destW number -- Destination width
|
||||||
|
---@param destH number -- Destination height
|
||||||
|
---@return love.ImageData -- Scaled image data
|
||||||
|
function ImageScaler.scaleBilinear(sourceImageData, srcX, srcY, srcW, srcH, destW, destH)
|
||||||
|
if not sourceImageData then
|
||||||
|
error(formatError("ImageScaler", "Source ImageData cannot be nil"))
|
||||||
|
end
|
||||||
|
|
||||||
|
if srcW <= 0 or srcH <= 0 or destW <= 0 or destH <= 0 then
|
||||||
|
error(formatError("ImageScaler", "Dimensions must be positive"))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create destination ImageData
|
||||||
|
local destImageData = love.image.newImageData(destW, destH)
|
||||||
|
|
||||||
|
-- Calculate scale ratios
|
||||||
|
local scaleX = srcW / destW
|
||||||
|
local scaleY = srcH / destH
|
||||||
|
|
||||||
|
-- Bilinear interpolation
|
||||||
|
for destY = 0, destH - 1 do
|
||||||
|
for destX = 0, destW - 1 do
|
||||||
|
-- Calculate fractional source position
|
||||||
|
local srcXf = destX * scaleX
|
||||||
|
local srcYf = destY * scaleY
|
||||||
|
|
||||||
|
-- Get integer coordinates for 2x2 sampling grid
|
||||||
|
local x0 = math.floor(srcXf)
|
||||||
|
local y0 = math.floor(srcYf)
|
||||||
|
local x1 = math.min(x0 + 1, srcW - 1)
|
||||||
|
local y1 = math.min(y0 + 1, srcH - 1)
|
||||||
|
|
||||||
|
-- Get fractional parts for interpolation
|
||||||
|
local fx = srcXf - x0
|
||||||
|
local fy = srcYf - y0
|
||||||
|
|
||||||
|
-- Sample 4 neighboring pixels (with source offset)
|
||||||
|
local r00, g00, b00, a00 = sourceImageData:getPixel(srcX + x0, srcY + y0)
|
||||||
|
local r10, g10, b10, a10 = sourceImageData:getPixel(srcX + x1, srcY + y0)
|
||||||
|
local r01, g01, b01, a01 = sourceImageData:getPixel(srcX + x0, srcY + y1)
|
||||||
|
local r11, g11, b11, a11 = sourceImageData:getPixel(srcX + x1, srcY + y1)
|
||||||
|
|
||||||
|
-- Interpolate horizontally (top and bottom rows)
|
||||||
|
local rTop = lerp(r00, r10, fx)
|
||||||
|
local gTop = lerp(g00, g10, fx)
|
||||||
|
local bTop = lerp(b00, b10, fx)
|
||||||
|
local aTop = lerp(a00, a10, fx)
|
||||||
|
|
||||||
|
local rBottom = lerp(r01, r11, fx)
|
||||||
|
local gBottom = lerp(g01, g11, fx)
|
||||||
|
local bBottom = lerp(b01, b11, fx)
|
||||||
|
local aBottom = lerp(a01, a11, fx)
|
||||||
|
|
||||||
|
-- Interpolate vertically (final result)
|
||||||
|
local r = lerp(rTop, rBottom, fy)
|
||||||
|
local g = lerp(gTop, gBottom, fy)
|
||||||
|
local b = lerp(bTop, bBottom, fy)
|
||||||
|
local a = lerp(aTop, aBottom, fy)
|
||||||
|
|
||||||
|
-- Write to destination
|
||||||
|
destImageData:setPixel(destX, destY, r, g, b, a)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return destImageData
|
return destImageData
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -996,27 +1080,119 @@ function NineSlice.draw(component, atlas, x, y, width, height, opacity)
|
|||||||
return love.graphics.newQuad(region.x, region.y, region.w, region.h, atlasWidth, atlasHeight)
|
return love.graphics.newQuad(region.x, region.y, region.w, region.h, atlasWidth, atlasHeight)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- CORNERS (no scaling - 1:1 pixel perfect)
|
-- Check if corner scaling is enabled
|
||||||
love.graphics.draw(atlas, makeQuad(regions.topLeft), x, y)
|
local scaleCorners = component.scaleCorners or false
|
||||||
love.graphics.draw(atlas, makeQuad(regions.topRight), x + left + contentWidth, y)
|
local scalingAlgorithm = component.scalingAlgorithm or "bilinear"
|
||||||
love.graphics.draw(atlas, makeQuad(regions.bottomLeft), x, y + top + contentHeight)
|
|
||||||
love.graphics.draw(atlas, makeQuad(regions.bottomRight), x + left + contentWidth, y + top + contentHeight)
|
|
||||||
|
|
||||||
-- TOP/BOTTOM EDGES (stretch horizontally only)
|
if scaleCorners and Gui and Gui.scaleFactors then
|
||||||
if contentWidth > 0 then
|
-- Initialize cache if needed
|
||||||
love.graphics.draw(atlas, makeQuad(regions.topCenter), x + left, y, 0, scaleX, 1)
|
if not component._scaledRegionCache then
|
||||||
love.graphics.draw(atlas, makeQuad(regions.bottomCenter), x + left, y + top + contentHeight, 0, scaleX, 1)
|
component._scaledRegionCache = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- LEFT/RIGHT EDGES (stretch vertically only)
|
-- Get current scale factors
|
||||||
if contentHeight > 0 then
|
local scaleFactorX = Gui.scaleFactors.x or 1
|
||||||
love.graphics.draw(atlas, makeQuad(regions.middleLeft), x, y + top, 0, 1, scaleY)
|
local scaleFactorY = Gui.scaleFactors.y or 1
|
||||||
love.graphics.draw(atlas, makeQuad(regions.middleRight), x + left + contentWidth, y + top, 0, 1, scaleY)
|
local scaleFactor = math.max(scaleFactorX, scaleFactorY)
|
||||||
end
|
|
||||||
|
|
||||||
-- CENTER (stretch both dimensions)
|
-- Helper to get or create scaled region
|
||||||
if contentWidth > 0 and contentHeight > 0 then
|
local function getScaledRegion(regionName, region, targetWidth, targetHeight)
|
||||||
love.graphics.draw(atlas, makeQuad(regions.middleCenter), x + left, y + top, 0, scaleX, scaleY)
|
local cacheKey = string.format("%s_%.2f_%s", regionName, scaleFactor, scalingAlgorithm)
|
||||||
|
|
||||||
|
if component._scaledRegionCache[cacheKey] then
|
||||||
|
return component._scaledRegionCache[cacheKey]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Extract region from atlas
|
||||||
|
local atlasData = atlas:getData()
|
||||||
|
local scaledData
|
||||||
|
|
||||||
|
if scalingAlgorithm == "nearest" then
|
||||||
|
scaledData = ImageScaler.scaleNearest(atlasData, region.x, region.y, region.w, region.h, targetWidth, targetHeight)
|
||||||
|
else
|
||||||
|
scaledData = ImageScaler.scaleBilinear(atlasData, region.x, region.y, region.w, region.h, targetWidth, targetHeight)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convert to image and cache
|
||||||
|
local scaledImage = love.graphics.newImage(scaledData)
|
||||||
|
component._scaledRegionCache[cacheKey] = scaledImage
|
||||||
|
|
||||||
|
return scaledImage
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Calculate scaled dimensions for corners
|
||||||
|
local scaledLeft = math.floor(left * scaleFactor + 0.5)
|
||||||
|
local scaledRight = math.floor(right * scaleFactor + 0.5)
|
||||||
|
local scaledTop = math.floor(top * scaleFactor + 0.5)
|
||||||
|
local scaledBottom = math.floor(bottom * scaleFactor + 0.5)
|
||||||
|
|
||||||
|
-- CORNERS (scaled using algorithm)
|
||||||
|
local topLeftScaled = getScaledRegion("topLeft", regions.topLeft, scaledLeft, scaledTop)
|
||||||
|
local topRightScaled = getScaledRegion("topRight", regions.topRight, scaledRight, scaledTop)
|
||||||
|
local bottomLeftScaled = getScaledRegion("bottomLeft", regions.bottomLeft, scaledLeft, scaledBottom)
|
||||||
|
local bottomRightScaled = getScaledRegion("bottomRight", regions.bottomRight, scaledRight, scaledBottom)
|
||||||
|
|
||||||
|
love.graphics.draw(topLeftScaled, x, y)
|
||||||
|
love.graphics.draw(topRightScaled, x + scaledLeft + contentWidth, y)
|
||||||
|
love.graphics.draw(bottomLeftScaled, x, y + scaledTop + contentHeight)
|
||||||
|
love.graphics.draw(bottomRightScaled, x + scaledLeft + contentWidth, y + scaledTop + contentHeight)
|
||||||
|
|
||||||
|
-- Update content dimensions to account for scaled borders
|
||||||
|
local adjustedContentWidth = width - scaledLeft - scaledRight
|
||||||
|
local adjustedContentHeight = height - scaledTop - scaledBottom
|
||||||
|
adjustedContentWidth = math.max(0, adjustedContentWidth)
|
||||||
|
adjustedContentHeight = math.max(0, adjustedContentHeight)
|
||||||
|
|
||||||
|
-- Recalculate stretch scales
|
||||||
|
local adjustedScaleX = adjustedContentWidth / centerW
|
||||||
|
local adjustedScaleY = adjustedContentHeight / centerH
|
||||||
|
|
||||||
|
-- TOP/BOTTOM EDGES (stretch horizontally, scale vertically)
|
||||||
|
if adjustedContentWidth > 0 then
|
||||||
|
local topCenterScaled = getScaledRegion("topCenter", regions.topCenter, regions.topCenter.w, scaledTop)
|
||||||
|
local bottomCenterScaled = getScaledRegion("bottomCenter", regions.bottomCenter, regions.bottomCenter.w, scaledBottom)
|
||||||
|
|
||||||
|
love.graphics.draw(topCenterScaled, x + scaledLeft, y, 0, adjustedScaleX, 1)
|
||||||
|
love.graphics.draw(bottomCenterScaled, x + scaledLeft, y + scaledTop + adjustedContentHeight, 0, adjustedScaleX, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- LEFT/RIGHT EDGES (stretch vertically, scale horizontally)
|
||||||
|
if adjustedContentHeight > 0 then
|
||||||
|
local middleLeftScaled = getScaledRegion("middleLeft", regions.middleLeft, scaledLeft, regions.middleLeft.h)
|
||||||
|
local middleRightScaled = getScaledRegion("middleRight", regions.middleRight, scaledRight, regions.middleRight.h)
|
||||||
|
|
||||||
|
love.graphics.draw(middleLeftScaled, x, y + scaledTop, 0, 1, adjustedScaleY)
|
||||||
|
love.graphics.draw(middleRightScaled, x + scaledLeft + adjustedContentWidth, y + scaledTop, 0, 1, adjustedScaleY)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- CENTER (stretch both dimensions, no scaling)
|
||||||
|
if adjustedContentWidth > 0 and adjustedContentHeight > 0 then
|
||||||
|
love.graphics.draw(atlas, makeQuad(regions.middleCenter), x + scaledLeft, y + scaledTop, 0, adjustedScaleX, adjustedScaleY)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Original rendering logic (no scaling)
|
||||||
|
-- CORNERS (no scaling - 1:1 pixel perfect)
|
||||||
|
love.graphics.draw(atlas, makeQuad(regions.topLeft), x, y)
|
||||||
|
love.graphics.draw(atlas, makeQuad(regions.topRight), x + left + contentWidth, y)
|
||||||
|
love.graphics.draw(atlas, makeQuad(regions.bottomLeft), x, y + top + contentHeight)
|
||||||
|
love.graphics.draw(atlas, makeQuad(regions.bottomRight), x + left + contentWidth, y + top + contentHeight)
|
||||||
|
|
||||||
|
-- TOP/BOTTOM EDGES (stretch horizontally only)
|
||||||
|
if contentWidth > 0 then
|
||||||
|
love.graphics.draw(atlas, makeQuad(regions.topCenter), x + left, y, 0, scaleX, 1)
|
||||||
|
love.graphics.draw(atlas, makeQuad(regions.bottomCenter), x + left, y + top + contentHeight, 0, scaleX, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- LEFT/RIGHT EDGES (stretch vertically only)
|
||||||
|
if contentHeight > 0 then
|
||||||
|
love.graphics.draw(atlas, makeQuad(regions.middleLeft), x, y + top, 0, 1, scaleY)
|
||||||
|
love.graphics.draw(atlas, makeQuad(regions.middleRight), x + left + contentWidth, y + top, 0, 1, scaleY)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- CENTER (stretch both dimensions)
|
||||||
|
if contentWidth > 0 and contentHeight > 0 then
|
||||||
|
love.graphics.draw(atlas, makeQuad(regions.middleCenter), x + left, y + top, 0, scaleX, scaleY)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Reset color
|
-- Reset color
|
||||||
@@ -1500,6 +1676,17 @@ function Gui.resize()
|
|||||||
Gui.scaleFactors.y = newHeight / Gui.baseScale.height
|
Gui.scaleFactors.y = newHeight / Gui.baseScale.height
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Clear scaled region caches for all themes
|
||||||
|
for _, theme in pairs(themes) do
|
||||||
|
if theme.components then
|
||||||
|
for _, component in pairs(theme.components) do
|
||||||
|
if component._scaledRegionCache then
|
||||||
|
component._scaledRegionCache = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
for _, win in ipairs(Gui.topElements) do
|
for _, win in ipairs(Gui.topElements) do
|
||||||
win:resize(newWidth, newHeight)
|
win:resize(newWidth, newHeight)
|
||||||
end
|
end
|
||||||
@@ -1584,6 +1771,8 @@ function Gui.destroy()
|
|||||||
-- Reset base scale and scale factors
|
-- Reset base scale and scale factors
|
||||||
Gui.baseScale = nil
|
Gui.baseScale = nil
|
||||||
Gui.scaleFactors = { x = 1.0, y = 1.0 }
|
Gui.scaleFactors = { x = 1.0, y = 1.0 }
|
||||||
|
-- Reset cached viewport
|
||||||
|
Gui._cachedViewport = { width = 0, height = 0 }
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Simple GUI library for LOVE2D
|
-- Simple GUI library for LOVE2D
|
||||||
@@ -4030,25 +4219,28 @@ function Element:recalculateUnits(newViewportWidth, newViewportHeight)
|
|||||||
-- BORDER-BOX MODEL: Calculate content dimensions from border-box dimensions
|
-- BORDER-BOX MODEL: Calculate content dimensions from border-box dimensions
|
||||||
-- For explicitly-sized elements (non-auto), _borderBoxWidth/_borderBoxHeight were set earlier
|
-- For explicitly-sized elements (non-auto), _borderBoxWidth/_borderBoxHeight were set earlier
|
||||||
-- Now we calculate content width/height by subtracting padding
|
-- Now we calculate content width/height by subtracting padding
|
||||||
if self.units.width.unit ~= "auto" then
|
-- Only recalculate if using viewport/percentage units (where _borderBoxWidth actually changed)
|
||||||
-- _borderBoxWidth was already set during width recalculation
|
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
|
-- Calculate content width by subtracting padding
|
||||||
self.width = math.max(0, self._borderBoxWidth - self.padding.left - self.padding.right)
|
self.width = math.max(0, self._borderBoxWidth - self.padding.left - self.padding.right)
|
||||||
else
|
elseif self.units.width.unit == "auto" then
|
||||||
-- For auto-sized elements, width is content width (calculated in resize method)
|
-- For auto-sized elements, width is content width (calculated in resize method)
|
||||||
-- Update border-box to include padding
|
-- Update border-box to include padding
|
||||||
self._borderBoxWidth = self.width + self.padding.left + self.padding.right
|
self._borderBoxWidth = self.width + self.padding.left + self.padding.right
|
||||||
end
|
end
|
||||||
|
-- For pixel units, width stays as-is (may have been manually modified)
|
||||||
|
|
||||||
if self.units.height.unit ~= "auto" then
|
if self.units.height.unit ~= "auto" and self.units.height.unit ~= "px" then
|
||||||
-- _borderBoxHeight was already set during height recalculation
|
-- _borderBoxHeight was recalculated for viewport/percentage units
|
||||||
-- Calculate content height by subtracting padding
|
-- Calculate content height by subtracting padding
|
||||||
self.height = math.max(0, self._borderBoxHeight - self.padding.top - self.padding.bottom)
|
self.height = math.max(0, self._borderBoxHeight - self.padding.top - self.padding.bottom)
|
||||||
else
|
elseif self.units.height.unit == "auto" then
|
||||||
-- For auto-sized elements, height is content height (calculated in resize method)
|
-- For auto-sized elements, height is content height (calculated in resize method)
|
||||||
-- Update border-box to include padding
|
-- Update border-box to include padding
|
||||||
self._borderBoxHeight = self.height + self.padding.top + self.padding.bottom
|
self._borderBoxHeight = self.height + self.padding.top + self.padding.bottom
|
||||||
end
|
end
|
||||||
|
-- For pixel units, height stays as-is (may have been manually modified)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Resize element and its children based on game window size change
|
--- Resize element and its children based on game window size change
|
||||||
@@ -4057,6 +4249,14 @@ end
|
|||||||
function Element:resize(newGameWidth, newGameHeight)
|
function Element:resize(newGameWidth, newGameHeight)
|
||||||
self:recalculateUnits(newGameWidth, newGameHeight)
|
self:recalculateUnits(newGameWidth, newGameHeight)
|
||||||
|
|
||||||
|
-- For non-auto-sized elements with viewport/percentage units, update content dimensions from border-box
|
||||||
|
if not self.autosizing.width and self._borderBoxWidth and self.units.width.unit ~= "px" then
|
||||||
|
self.width = math.max(0, self._borderBoxWidth - self.padding.left - self.padding.right)
|
||||||
|
end
|
||||||
|
if not self.autosizing.height and self._borderBoxHeight and self.units.height.unit ~= "px" then
|
||||||
|
self.height = math.max(0, self._borderBoxHeight - self.padding.top - self.padding.bottom)
|
||||||
|
end
|
||||||
|
|
||||||
-- Update children
|
-- Update children
|
||||||
for _, child in ipairs(self.children) do
|
for _, child in ipairs(self.children) do
|
||||||
child:resize(newGameWidth, newGameHeight)
|
child:resize(newGameWidth, newGameHeight)
|
||||||
@@ -4076,6 +4276,48 @@ function Element:resize(newGameWidth, newGameHeight)
|
|||||||
self.height = contentHeight
|
self.height = contentHeight
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Re-resolve ew/eh textSize units after all dimensions are finalized
|
||||||
|
-- This ensures textSize updates based on current width/height (whether calculated or manually set)
|
||||||
|
if self.units.textSize.value then
|
||||||
|
local unit = self.units.textSize.unit
|
||||||
|
local value = self.units.textSize.value
|
||||||
|
local scaleX, scaleY = Gui.getScaleFactors()
|
||||||
|
|
||||||
|
if unit == "ew" then
|
||||||
|
-- Element width relative (use current width)
|
||||||
|
self.textSize = (value / 100) * self.width
|
||||||
|
|
||||||
|
-- Apply min/max constraints
|
||||||
|
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
|
||||||
|
if self.textSize < 1 then
|
||||||
|
self.textSize = 1
|
||||||
|
end
|
||||||
|
elseif unit == "eh" then
|
||||||
|
-- Element height relative (use current height)
|
||||||
|
self.textSize = (value / 100) * self.height
|
||||||
|
|
||||||
|
-- Apply min/max constraints
|
||||||
|
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
|
||||||
|
if self.textSize < 1 then
|
||||||
|
self.textSize = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self:layoutChildren()
|
self:layoutChildren()
|
||||||
self.prevGameSize.width = newGameWidth
|
self.prevGameSize.width = newGameWidth
|
||||||
self.prevGameSize.height = newGameHeight
|
self.prevGameSize.height = newGameHeight
|
||||||
|
|||||||
243
examples/NineSliceCornerScalingDemo.lua
Normal file
243
examples/NineSliceCornerScalingDemo.lua
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
local Gui = FlexLove.GUI
|
||||||
|
local Theme = FlexLove.Theme
|
||||||
|
local Color = FlexLove.Color
|
||||||
|
|
||||||
|
---@class CornerScalingDemo
|
||||||
|
---@field window Element
|
||||||
|
---@field currentMode string
|
||||||
|
---@field modeButtons table
|
||||||
|
local CornerScalingDemo = {}
|
||||||
|
CornerScalingDemo.__index = CornerScalingDemo
|
||||||
|
|
||||||
|
function CornerScalingDemo.init()
|
||||||
|
local self = setmetatable({}, CornerScalingDemo)
|
||||||
|
|
||||||
|
self.currentMode = "none"
|
||||||
|
self.modeButtons = {}
|
||||||
|
|
||||||
|
-- Try to load theme
|
||||||
|
local themeLoaded = pcall(function()
|
||||||
|
Theme.load("space")
|
||||||
|
Theme.setActive("space")
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Create main window
|
||||||
|
self.window = Gui.new({
|
||||||
|
x = 50,
|
||||||
|
y = 50,
|
||||||
|
width = 900,
|
||||||
|
height = 650,
|
||||||
|
backgroundColor = Color.new(0.1, 0.1, 0.15, 0.95),
|
||||||
|
border = { top = true, bottom = true, left = true, right = true },
|
||||||
|
borderColor = Color.new(0.6, 0.6, 0.7, 1),
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "vertical",
|
||||||
|
gap = 20,
|
||||||
|
padding = { top = 20, right = 20, bottom = 20, left = 20 },
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Title
|
||||||
|
Gui.new({
|
||||||
|
parent = self.window,
|
||||||
|
height = 40,
|
||||||
|
text = "NineSlice Corner Scaling Demo",
|
||||||
|
textSize = 24,
|
||||||
|
textAlign = "center",
|
||||||
|
textColor = Color.new(1, 1, 1, 1),
|
||||||
|
backgroundColor = Color.new(0.15, 0.15, 0.25, 1),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Status
|
||||||
|
Gui.new({
|
||||||
|
parent = self.window,
|
||||||
|
height = 30,
|
||||||
|
text = themeLoaded and "✓ Theme loaded - Scaling demonstration active"
|
||||||
|
or "⚠ Theme not loaded - Please ensure theme assets exist",
|
||||||
|
textSize = 14,
|
||||||
|
textAlign = "center",
|
||||||
|
textColor = themeLoaded and Color.new(0.3, 0.9, 0.3, 1) or Color.new(0.9, 0.6, 0.3, 1),
|
||||||
|
backgroundColor = Color.new(0.08, 0.08, 0.12, 0.8),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Mode selector section
|
||||||
|
local modeSection = Gui.new({
|
||||||
|
parent = self.window,
|
||||||
|
height = 80,
|
||||||
|
backgroundColor = Color.new(0.12, 0.12, 0.18, 1),
|
||||||
|
padding = { top = 15, right = 15, bottom = 15, left = 15 },
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "vertical",
|
||||||
|
gap = 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
Gui.new({
|
||||||
|
parent = modeSection,
|
||||||
|
height = 20,
|
||||||
|
text = "Select Scaling Mode:",
|
||||||
|
textSize = 14,
|
||||||
|
textColor = Color.new(0.8, 0.9, 1, 1),
|
||||||
|
backgroundColor = Color.new(0, 0, 0, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Button container
|
||||||
|
local buttonContainer = Gui.new({
|
||||||
|
parent = modeSection,
|
||||||
|
height = 40,
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "horizontal",
|
||||||
|
gap = 15,
|
||||||
|
backgroundColor = Color.new(0, 0, 0, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Helper to create mode button
|
||||||
|
local function createModeButton(mode, label)
|
||||||
|
local button = Gui.new({
|
||||||
|
parent = buttonContainer,
|
||||||
|
width = 180,
|
||||||
|
height = 40,
|
||||||
|
text = label,
|
||||||
|
textAlign = "center",
|
||||||
|
textSize = 14,
|
||||||
|
textColor = Color.new(1, 1, 1, 1),
|
||||||
|
backgroundColor = self.currentMode == mode and Color.new(0.3, 0.6, 0.9, 1) or Color.new(0.25, 0.25, 0.35, 1),
|
||||||
|
callback = function(element, event)
|
||||||
|
if event.type == "click" then
|
||||||
|
self:setMode(mode)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
self.modeButtons[mode] = button
|
||||||
|
return button
|
||||||
|
end
|
||||||
|
|
||||||
|
createModeButton("none", "No Scaling (Default)")
|
||||||
|
createModeButton("nearest", "Nearest Neighbor")
|
||||||
|
createModeButton("bilinear", "Bilinear Interpolation")
|
||||||
|
|
||||||
|
-- Comparison section
|
||||||
|
local comparisonSection = Gui.new({
|
||||||
|
parent = self.window,
|
||||||
|
height = 420,
|
||||||
|
backgroundColor = Color.new(0.08, 0.08, 0.12, 1),
|
||||||
|
padding = { top = 20, right = 20, bottom = 20, left = 20 },
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "vertical",
|
||||||
|
gap = 15,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Description
|
||||||
|
Gui.new({
|
||||||
|
parent = comparisonSection,
|
||||||
|
height = 60,
|
||||||
|
text = "The panels below demonstrate different scaling modes.\n" ..
|
||||||
|
"• No Scaling: Corners remain at original size (may appear small at high DPI)\n" ..
|
||||||
|
"• Nearest Neighbor: Sharp, pixelated scaling (ideal for pixel art)\n" ..
|
||||||
|
"• Bilinear: Smooth, filtered scaling (ideal for high-quality graphics)",
|
||||||
|
textSize = 12,
|
||||||
|
textColor = Color.new(0.7, 0.8, 0.9, 1),
|
||||||
|
backgroundColor = Color.new(0, 0, 0, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Demo panels container
|
||||||
|
local panelsContainer = Gui.new({
|
||||||
|
parent = comparisonSection,
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "horizontal",
|
||||||
|
gap = 20,
|
||||||
|
backgroundColor = Color.new(0, 0, 0, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Helper to create demo panel
|
||||||
|
local function createDemoPanel(size, label)
|
||||||
|
local container = Gui.new({
|
||||||
|
parent = panelsContainer,
|
||||||
|
width = (900 - 80 - 40) / 3, -- Divide available space
|
||||||
|
positioning = "flex",
|
||||||
|
flexDirection = "vertical",
|
||||||
|
gap = 10,
|
||||||
|
backgroundColor = Color.new(0, 0, 0, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
Gui.new({
|
||||||
|
parent = container,
|
||||||
|
height = 20,
|
||||||
|
text = label,
|
||||||
|
textSize = 12,
|
||||||
|
textAlign = "center",
|
||||||
|
textColor = Color.new(0.8, 0.9, 1, 1),
|
||||||
|
backgroundColor = Color.new(0, 0, 0, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
local panel = Gui.new({
|
||||||
|
parent = container,
|
||||||
|
width = size,
|
||||||
|
height = size,
|
||||||
|
backgroundColor = Color.new(0.2, 0.3, 0.4, 0.5),
|
||||||
|
theme = themeLoaded and "panel" or nil,
|
||||||
|
padding = { top = 15, right = 15, bottom = 15, left = 15 },
|
||||||
|
})
|
||||||
|
|
||||||
|
Gui.new({
|
||||||
|
parent = panel,
|
||||||
|
text = "Themed\nPanel",
|
||||||
|
textSize = 14,
|
||||||
|
textAlign = "center",
|
||||||
|
textColor = Color.new(1, 1, 1, 1),
|
||||||
|
backgroundColor = Color.new(0, 0, 0, 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
return panel
|
||||||
|
end
|
||||||
|
|
||||||
|
createDemoPanel(120, "Small (120x120)")
|
||||||
|
createDemoPanel(160, "Medium (160x160)")
|
||||||
|
createDemoPanel(200, "Large (200x200)")
|
||||||
|
|
||||||
|
-- Info footer
|
||||||
|
Gui.new({
|
||||||
|
parent = self.window,
|
||||||
|
height = 30,
|
||||||
|
text = "Resize the window to see how scaling adapts to different DPI settings",
|
||||||
|
textSize = 11,
|
||||||
|
textAlign = "center",
|
||||||
|
textColor = Color.new(0.5, 0.6, 0.7, 1),
|
||||||
|
backgroundColor = Color.new(0.08, 0.08, 0.12, 1),
|
||||||
|
})
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function CornerScalingDemo:setMode(mode)
|
||||||
|
self.currentMode = mode
|
||||||
|
|
||||||
|
-- Update button colors
|
||||||
|
for modeName, button in pairs(self.modeButtons) do
|
||||||
|
button.backgroundColor = modeName == mode and Color.new(0.3, 0.6, 0.9, 1) or Color.new(0.25, 0.25, 0.35, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update theme components based on mode
|
||||||
|
local activeTheme = Theme.getActive()
|
||||||
|
if activeTheme and activeTheme.components then
|
||||||
|
for componentName, component in pairs(activeTheme.components) do
|
||||||
|
if mode == "none" then
|
||||||
|
component.scaleCorners = false
|
||||||
|
elseif mode == "nearest" then
|
||||||
|
component.scaleCorners = true
|
||||||
|
component.scalingAlgorithm = "nearest"
|
||||||
|
elseif mode == "bilinear" then
|
||||||
|
component.scaleCorners = true
|
||||||
|
component.scalingAlgorithm = "bilinear"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clear cache to force re-rendering
|
||||||
|
if component._scaledRegionCache then
|
||||||
|
component._scaledRegionCache = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Scaling mode changed to: " .. mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
return CornerScalingDemo.init()
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
-- 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 ===")
|
|
||||||
@@ -15,17 +15,13 @@ function TestUnitsSystem:setUp()
|
|||||||
-- Clear any existing GUI elements and reset viewport
|
-- Clear any existing GUI elements and reset viewport
|
||||||
Gui.destroy()
|
Gui.destroy()
|
||||||
-- Set a consistent viewport size for testing
|
-- Set a consistent viewport size for testing
|
||||||
love.graphics.getDimensions = function()
|
love.window.setMode(1200, 800)
|
||||||
return 1200, 800
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function TestUnitsSystem:tearDown()
|
function TestUnitsSystem:tearDown()
|
||||||
Gui.destroy()
|
Gui.destroy()
|
||||||
-- Restore original viewport size
|
-- Restore original viewport size
|
||||||
love.graphics.getDimensions = function()
|
love.window.setMode(800, 600)
|
||||||
return 800, 600
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ============================================
|
-- ============================================
|
||||||
@@ -150,14 +146,15 @@ function TestUnitsSystem:testResizeViewportUnits()
|
|||||||
luaunit.assertEquals(container.width, 600) -- 50% of 1200
|
luaunit.assertEquals(container.width, 600) -- 50% of 1200
|
||||||
luaunit.assertEquals(container.height, 200) -- 25% of 800
|
luaunit.assertEquals(container.height, 200) -- 25% of 800
|
||||||
|
|
||||||
-- Simulate viewport resize
|
-- Simulate viewport resize using setMode
|
||||||
love.graphics.getDimensions = function()
|
love.window.setMode(1600, 1000)
|
||||||
return 1600, 1000
|
|
||||||
end
|
|
||||||
container:resize(1600, 1000)
|
container:resize(1600, 1000)
|
||||||
|
|
||||||
luaunit.assertEquals(container.width, 800) -- 50% of 1600
|
luaunit.assertEquals(container.width, 800) -- 50% of 1600
|
||||||
luaunit.assertEquals(container.height, 250) -- 25% of 1000
|
luaunit.assertEquals(container.height, 250) -- 25% of 1000
|
||||||
|
|
||||||
|
-- Restore viewport
|
||||||
|
love.window.setMode(1200, 800)
|
||||||
end
|
end
|
||||||
|
|
||||||
function TestUnitsSystem:testResizePercentageUnits()
|
function TestUnitsSystem:testResizePercentageUnits()
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ local Gui = FlexLove.GUI
|
|||||||
-- Test suite for comprehensive text scaling
|
-- Test suite for comprehensive text scaling
|
||||||
TestTextScaling = {}
|
TestTextScaling = {}
|
||||||
|
|
||||||
|
function TestTextScaling:setUp()
|
||||||
|
-- Reset viewport to default before each test
|
||||||
|
love.window.setMode(800, 600)
|
||||||
|
Gui.destroy()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestTextScaling:tearDown()
|
||||||
|
Gui.destroy()
|
||||||
|
end
|
||||||
|
|
||||||
-- Basic functionality tests
|
-- Basic functionality tests
|
||||||
function TestTextScaling.testFixedTextSize()
|
function TestTextScaling.testFixedTextSize()
|
||||||
-- Create an element with fixed textSize in pixels (auto-scaling disabled)
|
-- Create an element with fixed textSize in pixels (auto-scaling disabled)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
-- Test padding resize behavior with percentage units
|
-- Test padding resize behavior with percentage units
|
||||||
package.path = package.path .. ";?.lua"
|
package.path = package.path .. ";?.lua"
|
||||||
local luaunit = require("testing.luaunit")
|
local luaunit = require("testing.luaunit")
|
||||||
|
local loveStub = require("testing.loveStub")
|
||||||
|
_G.love = loveStub
|
||||||
local FlexLove = require("FlexLove")
|
local FlexLove = require("FlexLove")
|
||||||
|
|
||||||
TestPaddingResize = {}
|
TestPaddingResize = {}
|
||||||
|
|||||||
288
testing/__tests__/23_image_scaler_bilinear_tests.lua
Normal file
288
testing/__tests__/23_image_scaler_bilinear_tests.lua
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
package.path = package.path .. ";?.lua"
|
||||||
|
local luaunit = require("testing.luaunit")
|
||||||
|
local loveStub = require("testing.loveStub")
|
||||||
|
_G.love = loveStub
|
||||||
|
|
||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
|
||||||
|
TestImageScalerBilinear = {}
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:setUp()
|
||||||
|
-- Create a simple test image (2x2 with distinct colors)
|
||||||
|
self.testImage2x2 = love.image.newImageData(2, 2)
|
||||||
|
-- Top-left: red
|
||||||
|
self.testImage2x2:setPixel(0, 0, 1, 0, 0, 1)
|
||||||
|
-- Top-right: green
|
||||||
|
self.testImage2x2:setPixel(1, 0, 0, 1, 0, 1)
|
||||||
|
-- Bottom-left: blue
|
||||||
|
self.testImage2x2:setPixel(0, 1, 0, 0, 1, 1)
|
||||||
|
-- Bottom-right: white
|
||||||
|
self.testImage2x2:setPixel(1, 1, 1, 1, 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:test2xScaling()
|
||||||
|
-- Scale 2x2 to 4x4 (2x factor)
|
||||||
|
local scaled = FlexLove.ImageScaler.scaleBilinear(self.testImage2x2, 0, 0, 2, 2, 4, 4)
|
||||||
|
|
||||||
|
luaunit.assertEquals(scaled:getWidth(), 4)
|
||||||
|
luaunit.assertEquals(scaled:getHeight(), 4)
|
||||||
|
|
||||||
|
-- Corner pixels should match original (no interpolation at exact positions)
|
||||||
|
local r, g, b, a = scaled:getPixel(0, 0)
|
||||||
|
luaunit.assertAlmostEquals(r, 1, 0.01) -- Red
|
||||||
|
luaunit.assertAlmostEquals(g, 0, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(b, 0, 0.01)
|
||||||
|
|
||||||
|
-- Center pixel at (1,1) should be blend of all 4 corners
|
||||||
|
-- At (0.5, 0.5) in source space -> blend of all 4 colors
|
||||||
|
r, g, b, a = scaled:getPixel(1, 1)
|
||||||
|
-- Should be approximately (0.5, 0.5, 0.5) - average of red, green, blue, white
|
||||||
|
luaunit.assertTrue(r > 0.3 and r < 0.7, "Center pixel should be blended")
|
||||||
|
luaunit.assertTrue(g > 0.3 and g < 0.7, "Center pixel should be blended")
|
||||||
|
luaunit.assertTrue(b > 0.3 and b < 0.7, "Center pixel should be blended")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:testGradientSmoothing()
|
||||||
|
-- Create a simple gradient: black to white horizontally
|
||||||
|
local gradient = love.image.newImageData(2, 1)
|
||||||
|
gradient:setPixel(0, 0, 0, 0, 0, 1) -- Black
|
||||||
|
gradient:setPixel(1, 0, 1, 1, 1, 1) -- White
|
||||||
|
|
||||||
|
-- Scale to 4 pixels wide
|
||||||
|
local scaled = FlexLove.ImageScaler.scaleBilinear(gradient, 0, 0, 2, 1, 4, 1)
|
||||||
|
|
||||||
|
luaunit.assertEquals(scaled:getWidth(), 4)
|
||||||
|
luaunit.assertEquals(scaled:getHeight(), 1)
|
||||||
|
|
||||||
|
-- Check smooth gradient progression
|
||||||
|
local r0 = scaled:getPixel(0, 0)
|
||||||
|
local r1 = scaled:getPixel(1, 0)
|
||||||
|
local r2 = scaled:getPixel(2, 0)
|
||||||
|
local r3 = scaled:getPixel(3, 0)
|
||||||
|
|
||||||
|
-- Should be monotonically increasing (or equal at end due to clamping)
|
||||||
|
luaunit.assertTrue(r0 < r1, "Gradient should increase")
|
||||||
|
luaunit.assertTrue(r1 < r2, "Gradient should increase")
|
||||||
|
luaunit.assertTrue(r2 <= r3, "Gradient should increase or stay same")
|
||||||
|
|
||||||
|
-- First should be close to black, last close to white
|
||||||
|
luaunit.assertAlmostEquals(r0, 0, 0.15)
|
||||||
|
luaunit.assertAlmostEquals(r3, 1, 0.15)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:testSameSizeScaling()
|
||||||
|
-- Scale 2x2 to 2x2 (should be identical)
|
||||||
|
local scaled = FlexLove.ImageScaler.scaleBilinear(self.testImage2x2, 0, 0, 2, 2, 2, 2)
|
||||||
|
|
||||||
|
luaunit.assertEquals(scaled:getWidth(), 2)
|
||||||
|
luaunit.assertEquals(scaled:getHeight(), 2)
|
||||||
|
|
||||||
|
-- Verify all pixels match original
|
||||||
|
for y = 0, 1 do
|
||||||
|
for x = 0, 1 do
|
||||||
|
local r1, g1, b1, a1 = self.testImage2x2:getPixel(x, y)
|
||||||
|
local r2, g2, b2, a2 = scaled:getPixel(x, y)
|
||||||
|
luaunit.assertAlmostEquals(r1, r2, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(g1, g2, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(b1, b2, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(a1, a2, 0.01)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:test1x1Scaling()
|
||||||
|
-- Create 1x1 image
|
||||||
|
local img1x1 = love.image.newImageData(1, 1)
|
||||||
|
img1x1:setPixel(0, 0, 0.5, 0.5, 0.5, 1)
|
||||||
|
|
||||||
|
-- Scale to 4x4
|
||||||
|
local scaled = FlexLove.ImageScaler.scaleBilinear(img1x1, 0, 0, 1, 1, 4, 4)
|
||||||
|
|
||||||
|
luaunit.assertEquals(scaled:getWidth(), 4)
|
||||||
|
luaunit.assertEquals(scaled:getHeight(), 4)
|
||||||
|
|
||||||
|
-- All pixels should be the same color (no neighbors to interpolate with)
|
||||||
|
for y = 0, 3 do
|
||||||
|
for x = 0, 3 do
|
||||||
|
local r, g, b = scaled:getPixel(x, y)
|
||||||
|
luaunit.assertAlmostEquals(r, 0.5, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(g, 0.5, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(b, 0.5, 0.01)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:testPureColorMaintenance()
|
||||||
|
-- Create pure white image
|
||||||
|
local whiteImg = love.image.newImageData(2, 2)
|
||||||
|
for y = 0, 1 do
|
||||||
|
for x = 0, 1 do
|
||||||
|
whiteImg:setPixel(x, y, 1, 1, 1, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local scaled = FlexLove.ImageScaler.scaleBilinear(whiteImg, 0, 0, 2, 2, 4, 4)
|
||||||
|
|
||||||
|
-- All pixels should remain pure white
|
||||||
|
for y = 0, 3 do
|
||||||
|
for x = 0, 3 do
|
||||||
|
local r, g, b = scaled:getPixel(x, y)
|
||||||
|
luaunit.assertAlmostEquals(r, 1, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(g, 1, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(b, 1, 0.01)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test pure black
|
||||||
|
local blackImg = love.image.newImageData(2, 2)
|
||||||
|
for y = 0, 1 do
|
||||||
|
for x = 0, 1 do
|
||||||
|
blackImg:setPixel(x, y, 0, 0, 0, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scaled = FlexLove.ImageScaler.scaleBilinear(blackImg, 0, 0, 2, 2, 4, 4)
|
||||||
|
|
||||||
|
for y = 0, 3 do
|
||||||
|
for x = 0, 3 do
|
||||||
|
local r, g, b = scaled:getPixel(x, y)
|
||||||
|
luaunit.assertAlmostEquals(r, 0, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(g, 0, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(b, 0, 0.01)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:testAlphaInterpolation()
|
||||||
|
-- Create image with varying alpha
|
||||||
|
local img = love.image.newImageData(2, 2)
|
||||||
|
img:setPixel(0, 0, 1, 0, 0, 1.0) -- Opaque red
|
||||||
|
img:setPixel(1, 0, 1, 0, 0, 0.0) -- Transparent red
|
||||||
|
img:setPixel(0, 1, 1, 0, 0, 1.0) -- Opaque red
|
||||||
|
img:setPixel(1, 1, 1, 0, 0, 0.0) -- Transparent red
|
||||||
|
|
||||||
|
local scaled = FlexLove.ImageScaler.scaleBilinear(img, 0, 0, 2, 2, 4, 2)
|
||||||
|
|
||||||
|
-- Check that alpha is interpolated smoothly
|
||||||
|
local r, g, b, a0 = scaled:getPixel(0, 0)
|
||||||
|
luaunit.assertAlmostEquals(a0, 1.0, 0.01)
|
||||||
|
|
||||||
|
local r, g, b, a1 = scaled:getPixel(1, 0)
|
||||||
|
-- Should be between 1.0 and 0.0
|
||||||
|
luaunit.assertTrue(a1 > 0.3 and a1 < 0.7, "Alpha should be interpolated")
|
||||||
|
|
||||||
|
local r, g, b, a3 = scaled:getPixel(3, 0)
|
||||||
|
luaunit.assertAlmostEquals(a3, 0.0, 0.15)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:testSubregionScaling()
|
||||||
|
-- Create 4x4 image with different quadrants
|
||||||
|
local img4x4 = love.image.newImageData(4, 4)
|
||||||
|
|
||||||
|
-- Fill with pattern: top-left red, rest black
|
||||||
|
for y = 0, 3 do
|
||||||
|
for x = 0, 3 do
|
||||||
|
if x < 2 and y < 2 then
|
||||||
|
img4x4:setPixel(x, y, 1, 0, 0, 1) -- red
|
||||||
|
else
|
||||||
|
img4x4:setPixel(x, y, 0, 0, 0, 1) -- black
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Scale only the top-left 2x2 red quadrant to 4x4
|
||||||
|
local scaled = FlexLove.ImageScaler.scaleBilinear(img4x4, 0, 0, 2, 2, 4, 4)
|
||||||
|
|
||||||
|
luaunit.assertEquals(scaled:getWidth(), 4)
|
||||||
|
luaunit.assertEquals(scaled:getHeight(), 4)
|
||||||
|
|
||||||
|
-- All pixels should be red (from source quadrant)
|
||||||
|
for y = 0, 3 do
|
||||||
|
for x = 0, 3 do
|
||||||
|
local r, g, b = scaled:getPixel(x, y)
|
||||||
|
luaunit.assertAlmostEquals(r, 1, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(g, 0, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(b, 0, 0.01)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:testEdgePixelHandling()
|
||||||
|
-- Create 3x3 checkerboard
|
||||||
|
local checkerboard = love.image.newImageData(3, 3)
|
||||||
|
for y = 0, 2 do
|
||||||
|
for x = 0, 2 do
|
||||||
|
if (x + y) % 2 == 0 then
|
||||||
|
checkerboard:setPixel(x, y, 1, 1, 1, 1) -- white
|
||||||
|
else
|
||||||
|
checkerboard:setPixel(x, y, 0, 0, 0, 1) -- black
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Scale to 9x9
|
||||||
|
local scaled = FlexLove.ImageScaler.scaleBilinear(checkerboard, 0, 0, 3, 3, 9, 9)
|
||||||
|
|
||||||
|
luaunit.assertEquals(scaled:getWidth(), 9)
|
||||||
|
luaunit.assertEquals(scaled:getHeight(), 9)
|
||||||
|
|
||||||
|
-- Verify corners are correct (no out-of-bounds access)
|
||||||
|
local r, g, b = scaled:getPixel(0, 0)
|
||||||
|
luaunit.assertAlmostEquals(r, 1, 0.01) -- Top-left should be white
|
||||||
|
|
||||||
|
r, g, b = scaled:getPixel(8, 8)
|
||||||
|
luaunit.assertAlmostEquals(r, 1, 0.01) -- Bottom-right should be white
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:testNonUniformScaling()
|
||||||
|
-- Scale 2x2 to 6x4 (3x horizontal, 2x vertical)
|
||||||
|
local scaled = FlexLove.ImageScaler.scaleBilinear(self.testImage2x2, 0, 0, 2, 2, 6, 4)
|
||||||
|
|
||||||
|
luaunit.assertEquals(scaled:getWidth(), 6)
|
||||||
|
luaunit.assertEquals(scaled:getHeight(), 4)
|
||||||
|
|
||||||
|
-- Top-left corner should be red
|
||||||
|
local r, g, b = scaled:getPixel(0, 0)
|
||||||
|
luaunit.assertAlmostEquals(r, 1, 0.01)
|
||||||
|
luaunit.assertAlmostEquals(g, 0, 0.01)
|
||||||
|
|
||||||
|
-- Should have smooth interpolation in between
|
||||||
|
r, g, b = scaled:getPixel(2, 1)
|
||||||
|
-- Middle area should have blended colors
|
||||||
|
luaunit.assertTrue(r > 0.1, "Should have some red component")
|
||||||
|
luaunit.assertTrue(g > 0.1, "Should have some green component")
|
||||||
|
luaunit.assertTrue(b > 0.1, "Should have some blue component")
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestImageScalerBilinear:testComparison_SmootherThanNearest()
|
||||||
|
-- Create gradient
|
||||||
|
local gradient = love.image.newImageData(2, 1)
|
||||||
|
gradient:setPixel(0, 0, 0, 0, 0, 1)
|
||||||
|
gradient:setPixel(1, 0, 1, 1, 1, 1)
|
||||||
|
|
||||||
|
local bilinear = FlexLove.ImageScaler.scaleBilinear(gradient, 0, 0, 2, 1, 8, 1)
|
||||||
|
local nearest = FlexLove.ImageScaler.scaleNearest(gradient, 0, 0, 2, 1, 8, 1)
|
||||||
|
|
||||||
|
-- Count unique values (nearest should have fewer due to blocky nature)
|
||||||
|
local bilinearValues = {}
|
||||||
|
local nearestValues = {}
|
||||||
|
|
||||||
|
for x = 0, 7 do
|
||||||
|
local rb = bilinear:getPixel(x, 0)
|
||||||
|
local rn = nearest:getPixel(x, 0)
|
||||||
|
bilinearValues[string.format("%.2f", rb)] = true
|
||||||
|
nearestValues[string.format("%.2f", rn)] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local bilinearCount = 0
|
||||||
|
for _ in pairs(bilinearValues) do bilinearCount = bilinearCount + 1 end
|
||||||
|
|
||||||
|
local nearestCount = 0
|
||||||
|
for _ in pairs(nearestValues) do nearestCount = nearestCount + 1 end
|
||||||
|
|
||||||
|
-- Bilinear should have more unique values (smoother gradient)
|
||||||
|
luaunit.assertTrue(bilinearCount >= nearestCount,
|
||||||
|
"Bilinear should produce smoother gradient with more unique values")
|
||||||
|
end
|
||||||
|
|
||||||
|
luaunit.LuaUnit.run()
|
||||||
@@ -153,5 +153,53 @@ function love_helper.touch.getPosition(id)
|
|||||||
return 0, 0 -- Default touch position
|
return 0, 0 -- Default touch position
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Mock image functions
|
||||||
|
love_helper.image = {}
|
||||||
|
|
||||||
|
-- Mock ImageData object
|
||||||
|
local ImageData = {}
|
||||||
|
ImageData.__index = ImageData
|
||||||
|
|
||||||
|
function ImageData.new(width, height)
|
||||||
|
local self = setmetatable({}, ImageData)
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
-- Store pixel data as a 2D array [y][x] = {r, g, b, a}
|
||||||
|
self.pixels = {}
|
||||||
|
for y = 0, height - 1 do
|
||||||
|
self.pixels[y] = {}
|
||||||
|
for x = 0, width - 1 do
|
||||||
|
self.pixels[y][x] = {0, 0, 0, 0} -- Default to transparent black
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageData:getWidth()
|
||||||
|
return self.width
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageData:getHeight()
|
||||||
|
return self.height
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageData:setPixel(x, y, r, g, b, a)
|
||||||
|
if x >= 0 and x < self.width and y >= 0 and y < self.height then
|
||||||
|
self.pixels[y][x] = {r, g, b, a or 1}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ImageData:getPixel(x, y)
|
||||||
|
if x >= 0 and x < self.width and y >= 0 and y < self.height then
|
||||||
|
local pixel = self.pixels[y][x]
|
||||||
|
return pixel[1], pixel[2], pixel[3], pixel[4]
|
||||||
|
end
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function love_helper.image.newImageData(width, height)
|
||||||
|
return ImageData.new(width, height)
|
||||||
|
end
|
||||||
|
|
||||||
_G.love = love_helper
|
_G.love = love_helper
|
||||||
return love_helper
|
return love_helper
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
local Color = require("libs.FlexLove").Color
|
||||||
|
|
||||||
|
return {
|
||||||
|
name = "Metal Theme",
|
||||||
|
contentAutoSizingMultiplier = { width = 1.05, height = 1.1 },
|
||||||
|
components = {
|
||||||
|
framev1 = {
|
||||||
|
atlas = "themes/metal/Frame/Frame01a.9.png",
|
||||||
|
},
|
||||||
|
framev2 = {
|
||||||
|
atlas = "themes/metal/Frame/Frame01b.9.png",
|
||||||
|
},
|
||||||
|
framev3 = {
|
||||||
|
atlas = "themes/metal/Frame/Frame02a.9.png",
|
||||||
|
},
|
||||||
|
framev4 = {
|
||||||
|
atlas = "themes/metal/Frame/Frame02b.9.png",
|
||||||
|
},
|
||||||
|
framev5 = {
|
||||||
|
atlas = "themes/metal/Frame/Frame03a.9.png",
|
||||||
|
},
|
||||||
|
framev6 = {
|
||||||
|
atlas = "themes/metal/Frame/Frame03b.9.png",
|
||||||
|
},
|
||||||
|
buttonv1 = {
|
||||||
|
atlas = "themes/metal/Button/Button01a_1.9.png",
|
||||||
|
states = {
|
||||||
|
hover = {
|
||||||
|
atlas = "themes/metal/Button/Button01a_4.9.png",
|
||||||
|
},
|
||||||
|
pressed = {
|
||||||
|
atlas = "themes/metal/Button/Button01a_2.9.png",
|
||||||
|
},
|
||||||
|
disabled = {
|
||||||
|
atlas = "themes/metal/Button/Button01a_4.9.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
buttonv2 = {
|
||||||
|
atlas = "themes/metal/Button/Button02a_1.9.png",
|
||||||
|
states = {
|
||||||
|
hover = {
|
||||||
|
atlas = "themes/metal/Button/Button02a_4.9.png",
|
||||||
|
},
|
||||||
|
pressed = {
|
||||||
|
atlas = "themes/metal/Button/Button02a_2.9.png",
|
||||||
|
},
|
||||||
|
disabled = {
|
||||||
|
atlas = "themes/metal/Button/Button02a_4.9.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Optional: Theme colors
|
||||||
|
colors = {
|
||||||
|
primary = Color.new(),
|
||||||
|
secondary = Color.new(),
|
||||||
|
text = Color.new(),
|
||||||
|
textDark = Color.new(),
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Optional: Theme fonts
|
||||||
|
-- Define font families that can be referenced by name
|
||||||
|
-- Paths are relative to FlexLove location or absolute
|
||||||
|
fonts = {
|
||||||
|
default = "themes/space/VT323-Regular.ttf",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,4 @@
|
|||||||
-- Space Theme
|
local Color = require("libs.FlexLove").Color
|
||||||
|
|
||||||
local Color = {}
|
|
||||||
Color.__index = Color
|
|
||||||
|
|
||||||
function Color.new(r, g, b, a)
|
|
||||||
local self = setmetatable({}, Color)
|
|
||||||
self.r = r or 0
|
|
||||||
self.g = g or 0
|
|
||||||
self.b = b or 0
|
|
||||||
self.a = a or 1
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name = "Space Theme",
|
name = "Space Theme",
|
||||||
|
|||||||
Reference in New Issue
Block a user