text scaling basics

This commit is contained in:
Michael Freno
2025-10-09 15:42:56 -04:00
parent 198114431b
commit a1530c2376
4 changed files with 243 additions and 21 deletions

View File

@@ -132,7 +132,7 @@ function Units.parse(value)
unit = "px" unit = "px"
end end
local validUnits = { px = true, ["%"] = true, vw = true, vh = true } local validUnits = { px = true, ["%"] = true, vw = true, vh = true, ew = true, eh = true }
if not validUnits[unit] then if not validUnits[unit] then
return num, "px" return num, "px"
end end
@@ -462,6 +462,7 @@ end
---@field justifySelf JustifySelf -- Alignment of the item itself along main axis (default: AUTO) ---@field justifySelf JustifySelf -- Alignment of the item itself along main axis (default: AUTO)
---@field alignSelf AlignSelf -- Alignment of the item itself along cross axis (default: AUTO) ---@field alignSelf AlignSelf -- Alignment of the item itself along cross axis (default: AUTO)
---@field textSize number? -- Font size for text content ---@field textSize number? -- Font size for text content
---@field autoScaleText boolean -- Whether text should auto-scale with window size (default: true)
---@field transform TransformProps -- Transform properties for animations and styling ---@field transform TransformProps -- Transform properties for animations and styling
---@field transition TransitionProps -- Transition settings for animations ---@field transition TransitionProps -- Transition settings for animations
---@field callback function? -- Callback function for click events ---@field callback function? -- Callback function for click events
@@ -492,7 +493,8 @@ Element.__index = Element
---@field titleColor Color? -- Color of the text content (default: black) ---@field titleColor Color? -- Color of the text content (default: black)
---@field textAlign TextAlign? -- Alignment of the text content (default: START) ---@field textAlign TextAlign? -- Alignment of the text content (default: START)
---@field textColor Color? -- Color of the text content (default: black) ---@field textColor Color? -- Color of the text content (default: black)
---@field textSize number|string? -- Font size for text content (default: nil) ---@field textSize number|string? -- Font size for text content (default: auto-scaled)
---@field autoScaleText boolean? -- Whether text should auto-scale with window size (default: true)
---@field positioning Positioning? -- Layout positioning mode (default: ABSOLUTE) ---@field positioning Positioning? -- Layout positioning mode (default: ABSOLUTE)
---@field flexDirection FlexDirection? -- Direction of flex layout (default: HORIZONTAL) ---@field flexDirection FlexDirection? -- Direction of flex layout (default: HORIZONTAL)
---@field justifyContent JustifyContent? -- Alignment of items along main axis (default: FLEX_START) ---@field justifyContent JustifyContent? -- Alignment of items along main axis (default: FLEX_START)
@@ -629,17 +631,61 @@ function Element.new(props)
self.padding = Units.resolveSpacing(props.padding, self.width, self.height) self.padding = Units.resolveSpacing(props.padding, self.width, self.height)
self.margin = Units.resolveSpacing(props.margin, self.width, self.height) self.margin = Units.resolveSpacing(props.margin, self.width, self.height)
-- Store original textSize units -- Store original textSize units and constraints
self.minTextSize = props.minTextSize
self.maxTextSize = props.maxTextSize
-- Auto-scale text by default (can be disabled with autoScaleText = false)
if props.autoScaleText == nil then
self.autoScaleText = true
else
self.autoScaleText = props.autoScaleText
end
if props.textSize then if props.textSize then
if type(props.textSize) == "string" then if type(props.textSize) == "string" then
local value, unit = Units.parse(props.textSize) local value, unit = Units.parse(props.textSize)
self.units.textSize = { value = value, unit = unit } self.units.textSize = { value = value, unit = unit }
self.textSize = Units.resolve(value, unit, viewportWidth, viewportHeight, nil)
-- Resolve textSize based on unit type
if unit == "%" or unit == "vh" then
-- Percentage and vh are relative to viewport height
self.textSize = Units.resolve(value, unit, viewportWidth, viewportHeight, viewportHeight)
elseif unit == "vw" then
-- vw is relative to viewport width
self.textSize = Units.resolve(value, unit, viewportWidth, viewportHeight, viewportWidth)
elseif unit == "ew" then
-- Element width relative (will be resolved after width is set)
self.textSize = (value / 100) * self.width
elseif unit == "eh" then
-- Element height relative (will be resolved after height is set)
self.textSize = (value / 100) * self.height
else
self.textSize = Units.resolve(value, unit, viewportWidth, viewportHeight, nil)
end
else else
self.textSize = props.textSize
self.units.textSize = { value = props.textSize, unit = "px" } self.units.textSize = { value = props.textSize, unit = "px" }
end end
else else
self.units.textSize = { value = 12, unit = "px" } -- No textSize specified - use auto-scaling default
if self.autoScaleText then
-- Default to 1.5vh (1.5% of viewport height) for auto-scaling
self.textSize = (1.5 / 100) * viewportHeight
self.units.textSize = { value = 1.5, unit = "vh" }
else
-- Fixed 12px when auto-scaling is disabled
self.textSize = 12
self.units.textSize = { value = nil, unit = "px" }
end
end
-- Apply min/max constraints
if self.minTextSize and self.textSize < self.minTextSize then
self.textSize = self.minTextSize
end
if self.maxTextSize and self.textSize > self.maxTextSize then
self.textSize = self.maxTextSize
end end
-- Store original spacing values for proper resize handling -- Store original spacing values for proper resize handling
@@ -1369,10 +1415,10 @@ function Element:draw()
love.graphics.setColor(textColorWithOpacity:toRGBA()) love.graphics.setColor(textColorWithOpacity:toRGBA())
local origFont = love.graphics.getFont() local origFont = love.graphics.getFont()
local tempFont
if self.textSize then if self.textSize then
tempFont = love.graphics.newFont(self.textSize) -- Use cached font instead of creating new one every frame
love.graphics.setFont(tempFont) local font = FONT_CACHE.get(self.textSize)
love.graphics.setFont(font)
end end
local font = love.graphics.getFont() local font = love.graphics.getFont()
local textWidth = font:getWidth(self.text) local textWidth = font:getWidth(self.text)
@@ -1520,10 +1566,34 @@ function Element:recalculateUnits(newViewportWidth, newViewportHeight)
end end
end end
-- Recalculate textSize if using viewport units -- Recalculate textSize if auto-scaling is enabled or using viewport/element-relative units
if self.units.textSize.unit ~= "px" then if self.autoScaleText and self.units.textSize.value and self.units.textSize.unit ~= "px" then
self.textSize = local unit = self.units.textSize.unit
Units.resolve(self.units.textSize.value, self.units.textSize.unit, newViewportWidth, newViewportHeight, nil) local value = self.units.textSize.value
if unit == "%" or unit == "vh" then
-- Percentage and vh are relative to viewport height
self.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, newViewportHeight)
elseif unit == "vw" then
-- vw is relative to viewport width
self.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, newViewportWidth)
elseif unit == "ew" then
-- Element width relative
self.textSize = (value / 100) * self.width
elseif unit == "eh" then
-- Element height relative
self.textSize = (value / 100) * self.height
else
self.textSize = Units.resolve(value, unit, newViewportWidth, newViewportHeight, nil)
end
-- Apply min/max constraints
if self.minTextSize and self.textSize < self.minTextSize then
self.textSize = self.minTextSize
end
if self.maxTextSize and self.textSize > self.maxTextSize then
self.textSize = self.maxTextSize
end
end end
-- Recalculate gap if using viewport or percentage units -- Recalculate gap if using viewport or percentage units

View File

@@ -0,0 +1,131 @@
-- Example demonstrating text auto-scaling feature
-- Text automatically scales proportionally with window size by default
package.path = package.path .. ";?.lua"
require("testing/loveStub")
local FlexLove = require("FlexLove")
local Gui = FlexLove.GUI
local Color = FlexLove.Color
print("=== Text Auto-Scaling Examples ===\n")
-- Example 1: Default auto-scaling (enabled by default)
print("1. Default Auto-Scaling (no textSize specified)")
print(" Text will scale proportionally with window size")
local button1 = Gui.new({
x = 10,
y = 10,
padding = { horizontal = 16, vertical = 8 },
text = "Auto-Scaled Button",
textAlign = "center",
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(1, 1, 1),
textColor = Color.new(1, 1, 1),
})
print(" Initial size (800x600): textSize = " .. button1.textSize .. "px")
button1:resize(1600, 1200)
print(" After resize (1600x1200): textSize = " .. button1.textSize .. "px")
print(" Scaling factor: " .. (button1.textSize / 9.0) .. "x\n")
-- Example 2: Disable auto-scaling for fixed text size
print("2. Auto-Scaling Disabled (autoScaleText = false)")
print(" Text remains fixed at 12px regardless of window size")
Gui.destroy()
local button2 = Gui.new({
x = 10,
y = 60,
padding = { horizontal = 16, vertical = 8 },
text = "Fixed Size Button",
textAlign = "center",
autoScaleText = false,
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(1, 1, 1),
textColor = Color.new(1, 1, 1),
})
print(" Initial size (800x600): textSize = " .. button2.textSize .. "px")
button2:resize(1600, 1200)
print(" After resize (1600x1200): textSize = " .. button2.textSize .. "px")
print(" No scaling applied\n")
-- Example 3: Custom auto-scaling with viewport units
print("3. Custom Auto-Scaling (textSize = '2vh')")
print(" Text scales at 2% of viewport height")
Gui.destroy()
local title = Gui.new({
x = 10,
y = 110,
text = "Large Title",
textSize = "2vh",
textColor = Color.new(1, 1, 1),
})
print(" Initial size (800x600): textSize = " .. title.textSize .. "px")
title:resize(1600, 1200)
print(" After resize (1600x1200): textSize = " .. title.textSize .. "px")
print(" Scaling factor: " .. (title.textSize / 12.0) .. "x\n")
-- Example 4: Fixed pixel size (still auto-scales if using viewport units)
print("4. Fixed Pixel Size (textSize = 20)")
print(" Explicit pixel values don't scale")
Gui.destroy()
local button3 = Gui.new({
x = 10,
y = 160,
padding = { horizontal = 16, vertical = 8 },
text = "20px Button",
textSize = 20,
textAlign = "center",
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(1, 1, 1),
textColor = Color.new(1, 1, 1),
})
print(" Initial size (800x600): textSize = " .. button3.textSize .. "px")
button3:resize(1600, 1200)
print(" After resize (1600x1200): textSize = " .. button3.textSize .. "px")
print(" Fixed at 20px\n")
-- Example 5: Element-relative scaling
print("5. Element-Relative Scaling (textSize = '10ew')")
print(" Text scales at 10% of element width")
Gui.destroy()
local box = Gui.new({
x = 10,
y = 210,
width = 200,
height = 100,
text = "Responsive Box",
textSize = "10ew",
textAlign = "center",
background = Color.new(0.2, 0.2, 0.2),
textColor = Color.new(1, 1, 1),
})
print(" Initial (width=200): textSize = " .. box.textSize .. "px")
box.width = 400
box:resize(800, 600)
print(" After width change (width=400): textSize = " .. box.textSize .. "px")
print(" Scales with element size\n")
-- Example 6: Combining auto-scaling with min/max constraints
print("6. Auto-Scaling with Constraints")
print(" Text scales between 10px and 24px")
Gui.destroy()
local constrained = Gui.new({
x = 10,
y = 260,
text = "Constrained Text",
textSize = "3vh",
minTextSize = 10,
maxTextSize = 24,
textColor = Color.new(1, 1, 1),
})
print(" Initial (3vh of 600): textSize = " .. constrained.textSize .. "px")
constrained:resize(1600, 1200)
print(" After resize (3vh of 1200 = 36px, clamped): textSize = " .. constrained.textSize .. "px")
print(" Clamped to maxTextSize = 24px\n")
print("=== Summary ===")
print("• Auto-scaling is ENABLED by default")
print("• Default scaling: 1.5vh (1.5% of viewport height)")
print("• Disable with: autoScaleText = false")
print("• Custom scaling: use vh, vw, %, ew, or eh units")
print("• Fixed sizes: use pixel values (e.g., textSize = 16)")
print("• Constraints: use minTextSize and maxTextSize")

View File

@@ -107,7 +107,7 @@ function TestTextScaling.testVhTextSize()
end end
function TestTextScaling.testNoTextSize() function TestTextScaling.testNoTextSize()
-- Create an element without textSize specified -- Create an element without textSize specified (auto-scaling enabled by default)
local element = Gui.new({ local element = Gui.new({
id = "testElement", id = "testElement",
width = 100, width = 100,
@@ -115,13 +115,32 @@ function TestTextScaling.testNoTextSize()
text = "Hello World", text = "Hello World",
}) })
-- Check initial state - should default to some value -- Check initial state - should auto-scale by default (1.5vh)
luaunit.assertEquals(element.units.textSize.value, nil) luaunit.assertEquals(element.autoScaleText, true)
luaunit.assertEquals(element.textSize, 12) -- Default fallback luaunit.assertEquals(element.units.textSize.value, 1.5)
luaunit.assertEquals(element.units.textSize.unit, "vh")
luaunit.assertEquals(element.textSize, 9.0) -- 1.5% of 600px
-- Resize should not affect default textSize -- Resize should scale the text
element:resize(1600, 1200) element:resize(1600, 1200)
luaunit.assertEquals(element.textSize, 12) luaunit.assertEquals(element.textSize, 18.0) -- 1.5% of 1200px
-- Test with auto-scaling disabled
local elementNoScale = Gui.new({
id = "testElementNoScale",
width = 100,
height = 50,
text = "Hello World",
autoScaleText = false,
})
luaunit.assertEquals(elementNoScale.autoScaleText, false)
luaunit.assertEquals(elementNoScale.units.textSize.value, nil)
luaunit.assertEquals(elementNoScale.textSize, 12) -- Fixed 12px
-- Resize should not affect textSize when auto-scaling is disabled
elementNoScale:resize(1600, 1200)
luaunit.assertEquals(elementNoScale.textSize, 12)
end end
-- Edge case tests -- Edge case tests

View File

@@ -17,20 +17,22 @@ function love_helper.graphics.getDimensions()
end end
function love_helper.graphics.newFont(size) function love_helper.graphics.newFont(size)
-- Ensure size is a number
local fontSize = tonumber(size) or 12
-- Return a mock font object with basic methods -- Return a mock font object with basic methods
return { return {
getWidth = function(self, text) getWidth = function(self, text)
-- Handle both colon and dot syntax -- Handle both colon and dot syntax
if type(self) == "string" then if type(self) == "string" then
-- Called with dot syntax: font.getWidth(text) -- Called with dot syntax: font.getWidth(text)
return #self * size / 2 return #self * fontSize / 2
else else
-- Called with colon syntax: font:getWidth(text) -- Called with colon syntax: font:getWidth(text)
return #text * size / 2 return #text * fontSize / 2
end end
end, end,
getHeight = function() getHeight = function()
return size return fontSize
end, end,
} }
end end