fixing sizing calcs

This commit is contained in:
Michael Freno
2025-09-16 14:59:56 -04:00
parent 4617bc3d58
commit bdd68799ab

View File

@@ -144,6 +144,47 @@ local Positioning, FlexDirection, JustifyContent, AlignContent, AlignItems, Text
enums.AlignSelf, enums.AlignSelf,
enums.JustifySelf enums.JustifySelf
--- Top level GUI manager
---@class Gui
---@field topElements table<integer, Element>
---@field resize fun(): nil
---@field draw fun(): nil
---@field update fun(dt:number): nil
---@field destroy fun(): nil
local Gui = { topElements = {} }
function Gui.resize()
local newWidth, newHeight = love.window.getMode()
for _, win in ipairs(Gui.topElements) do
win:resize(newWidth, newHeight)
end
end
function Gui.draw()
-- Sort elements by z-index before drawing
table.sort(Gui.topElements, function(a, b)
return a.z < b.z
end)
for _, win in ipairs(Gui.topElements) do
win:draw()
end
end
function Gui.update(dt)
for _, win in ipairs(Gui.topElements) do
win:update(dt)
end
end
--- Destroy all elements and their children
function Gui.destroy()
for _, win in ipairs(Gui.topElements) do
win:destroy()
end
Gui.topElements = {}
end
-- Simple GUI library for LOVE2D -- Simple GUI library for LOVE2D
-- Provides element and button creation, drawing, and click handling. -- Provides element and button creation, drawing, and click handling.
@@ -302,7 +343,7 @@ end
-- Element Object -- Element Object
-- ==================== -- ====================
---@class Element ---@class Element
---@field autosizing boolean -- Whether the element should automatically size to fit its children ---@field autosizing {width:boolean, height:boolean} -- Whether the element should automatically size to fit its children
---@field x number -- X coordinate of the element ---@field x number -- X coordinate of the element
---@field y number -- Y coordinate of the element ---@field y number -- Y coordinate of the element
---@field z number -- Z-index for layering (default: 0) ---@field z number -- Z-index for layering (default: 0)
@@ -318,7 +359,7 @@ end
---@field textColor Color -- Color of the text content ---@field textColor Color -- Color of the text content
---@field textAlign TextAlign -- Alignment of the text content ---@field textAlign TextAlign -- Alignment of the text content
---@field gap number -- Space between children elements (default: 10) ---@field gap number -- Space between children elements (default: 10)
---@field padding {top?:number, right?:number, bottom?:number, left?:number} -- Padding around children (default: {top=0, right=0, bottom=0, left=0}) ---@field padding {top?:number, right?:number, bottom?:number, left?:number}? -- Padding around children (default: {top=0, right=0, bottom=0, left=0})
---@field margin {top?:number, right?:number, bottom?:number, left?:number} -- Margin around children (default: {top=0, right=0, bottom=0, left=0}) ---@field margin {top?:number, right?:number, bottom?:number, left?:number} -- Margin around children (default: {top=0, right=0, bottom=0, left=0})
---@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)
@@ -370,18 +411,22 @@ function Element.new(props)
local self = setmetatable({}, Element) local self = setmetatable({}, Element)
self.x = props.x or 0 self.x = props.x or 0
self.y = props.y or 0 self.y = props.y or 0
if props.w == nil or props.h == nil then self.autosizing = { width = false, height = false }
self.autosizing = true
if props.w then
self.width = props.w
else else
self.autosizing = false self.autosizing.width = true
self.width = self:calculateAutoWidth()
end end
self.width = props.w or 0
self.height = props.h or 0 if props.h then
self.parent = props.parent self.height = props.h
if props.parent then else
props.parent:addChild(self) self.autosizing.height = true
self.height = self:calculateAutoHeight()
end end
self.children = {}
self.border = props.border self.border = props.border
and { and {
top = props.border.top or false, top = props.border.top or false,
@@ -408,8 +453,32 @@ function Element.new(props)
end end
self.gap = props.gap or 10 self.gap = props.gap or 10
self.padding = props.padding or { top = 0, right = 0, bottom = 0, left = 0 } self.padding = props.padding
self.margin = props.margin or { top = 0, right = 0, bottom = 0, left = 0 } and {
top = props.padding.top or 0,
right = props.padding.right or 0,
bottom = props.padding.bottom or 0,
left = props.padding.left or 0,
}
or {
top = 0,
right = 0,
bottom = 0,
left = 0,
}
self.margin = props.margin
and {
top = props.margin.top or 0,
right = props.margin.right or 0,
bottom = props.margin.bottom or 0,
left = props.margin.left or 0,
}
or {
top = 0,
right = 0,
bottom = 0,
left = 0,
}
self.text = props.text self.text = props.text
self.textColor = props.textColor self.textColor = props.textColor
@@ -432,6 +501,12 @@ function Element.new(props)
end end
end end
self.parent = props.parent
self.children = {}
if props.parent then
props.parent:addChild(self)
end
if self.positioning == Positioning.FLEX then if self.positioning == Positioning.FLEX then
self.positioning = props.positioning self.positioning = props.positioning
self.justifyContent = props.justifyContent or JustifyContent.FLEX_START self.justifyContent = props.justifyContent or JustifyContent.FLEX_START
@@ -473,6 +548,12 @@ end
function Element:addChild(child) function Element:addChild(child)
child.parent = self child.parent = self
table.insert(self.children, child) table.insert(self.children, child)
if self.autosizing.height then
self.height = self:calculateAutoHeight()
end
if self.autosizing.width then
self.width = self:calculateAutoWidth()
end
self:layoutChildren() self:layoutChildren()
end end
@@ -480,8 +561,6 @@ function Element:layoutChildren()
if self.positioning == Positioning.ABSOLUTE then if self.positioning == Positioning.ABSOLUTE then
return return
end end
self:calculateAutoWidth()
self:calculateAutoHeight()
local totalSize = 0 local totalSize = 0
local childCount = #self.children local childCount = #self.children
@@ -796,35 +875,6 @@ function Element:resize(newGameWidth, newGameHeight)
self.prevGameSize.height = newGameHeight self.prevGameSize.height = newGameHeight
end end
--- Calculate auto width based on children content size
function Element:calculateAutoWidth()
if self.autosizing == false then
return
end
if not self.children or #self.children == 0 then
self.width = 0
end
local maxWidth = 0
for _, child in ipairs(self.children) do
-- Calculate content width based on child's actual content, not existing dimensions
local childX = child.x or 0
local paddingAdjustment = (child.padding.left or 0) + (child.padding.right or 0)
local totalWidth = childX + child.width + paddingAdjustment
if totalWidth > maxWidth then
maxWidth = totalWidth
end
end
self.width = maxWidth
+ (self.padding.left or 0)
+ (self.padding.right or 0)
+ (self.margin.left or 0)
+ (self.margin.right or 0)
end
--- Calculate text width for button --- Calculate text width for button
---@return number ---@return number
function Element:calculateTextWidth() function Element:calculateTextWidth()
@@ -857,69 +907,41 @@ function Element:calculateTextHeight()
return height return height
end end
--- Calculate auto width based on children content size
function Element:calculateAutoWidth()
local width = self:calculateTextWidth()
if not self.children or #self.children == 0 then
return width
end
local totalWidth = width
for _, child in ipairs(self.children) do
local paddingAdjustment = (child.padding.left or 0) + (child.padding.right or 0)
local childWidth = child.width or child:calculateAutoWidth()
local childOffset = childWidth + paddingAdjustment
totalWidth = totalWidth + childOffset
end
return totalWidth + (self.gap * #self.children)
end
--- Calculate auto height based on children --- Calculate auto height based on children
function Element:calculateAutoHeight() function Element:calculateAutoHeight()
if self.autosizing == false then local height = self:calculateTextHeight()
return
end
if not self.children or #self.children == 0 then if not self.children or #self.children == 0 then
self.height = 0 return height
end end
local maxHeight = 0 local totalHeight = height
for _, child in ipairs(self.children) do for _, child in ipairs(self.children) do
-- Calculate content height based on child's actual content, not existing dimensions
local contentHeight = 0
if child.text then
contentHeight = child:calculateTextHeight()
elseif child.height and not child.autosizing then
contentHeight = child.height
else
contentHeight = 0
end
local childY = child.y or 0
local paddingAdjustment = (child.padding.top or 0) + (child.padding.bottom or 0) local paddingAdjustment = (child.padding.top or 0) + (child.padding.bottom or 0)
local totalHeight = childY + contentHeight + paddingAdjustment local childOffset = child.height + paddingAdjustment
if totalHeight > maxHeight then totalHeight = totalHeight + childOffset
maxHeight = totalHeight
end
end end
-- Add element's own padding and margin to the final height return totalHeight + (self.gap * #self.children)
self.height = maxHeight
+ (self.padding.top or 0)
+ (self.padding.bottom or 0)
+ (self.margin.top or 0)
+ (self.margin.bottom or 0)
end
--- Update element size to fit children automatically
function Element:updateAutoSize()
-- Store current dimensions for comparison
local oldWidth, oldHeight = self.width, self.height
if self.width == 0 then
self.width = self:calculateAutoWidth() or 0
end
if self.height == 0 then
self.height = self:calculateAutoHeight() or 0
end
-- Only re-layout children if dimensions changed
if oldWidth ~= self.width or oldHeight ~= self.height then
self:layoutChildren()
end
end
--- Set the visibility of this element and its children
---@param visible boolean -- Whether to show or hide the element
function Element:setVisible(visible)
self.visible = visible
for _, child in ipairs(self.children) do
if child.setVisible then
child:setVisible(visible)
end
end
end end
--- Get the absolute position of this element relative to screen --- Get the absolute position of this element relative to screen
@@ -947,88 +969,16 @@ function Element:getAbsoluteBounds()
} }
end end
--- Set the size of this element and all its children proportionally ---@param newText string
---@param width number -- New width ---@param autoresize boolean? --default: false
---@param height number -- New height function Element:updateText(newText, autoresize)
function Element:setSize(width, height) self.text = newText or self.text
local oldWidth = self.width if autoresize then
local oldHeight = self.height Logger:error("need to implement resize in updateText")
if oldWidth > 0 and oldHeight > 0 then
local ratioW = width / oldWidth
local ratioH = height / oldHeight
self.width = width
self.height = height
-- Resize children proportionally
for _, child in ipairs(self.children) do
if child.resize then
child:resize(ratioW, ratioH)
end
end
else
self.width = width
self.height = height
end end
end end
--- Center this element within its parent or screen Gui.new = Element.new
---@param parent Element? -- Parent element to center within (optional)
function Element:center(parent)
local parentWidth, parentHeight = love.window.getMode()
if parent then
parentWidth = parent.width
parentHeight = parent.height
end
self.x = (parentWidth - self.width) / 2
self.y = (parentHeight - self.height) / 2
end
--- Top level GUI manager
---@class Gui
---@field topElements table<integer, Element>
---@field resize fun(): nil
---@field draw fun(): nil
---@field update fun(dt:number): nil
---@field destroy fun(): nil
local Gui = { topElements = {} }
---@param props ElementProps
function Gui.new(props)
return Element.new(props)
end
function Gui.resize()
local newWidth, newHeight = love.window.getMode()
for _, win in ipairs(Gui.topElements) do
win:resize(newWidth, newHeight)
end
end
function Gui.draw()
-- Sort elements by z-index before drawing
table.sort(Gui.topElements, function(a, b)
return a.z < b.z
end)
for _, win in ipairs(Gui.topElements) do
win:draw()
end
end
function Gui.update(dt)
for _, win in ipairs(Gui.topElements) do
win:update(dt)
end
end
--- Destroy all elements and their children
function Gui.destroy()
for _, win in ipairs(Gui.topElements) do
win:destroy()
end
Gui.topElements = {}
end
Gui.Element = Element Gui.Element = Element
Gui.Animation = Animation Gui.Animation = Animation
return { GUI = Gui, Color = Color, enums = enums } return { GUI = Gui, Color = Color, enums = enums }