getting ready for first release
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,4 +6,5 @@ themes/metal/
|
||||
themes/space/
|
||||
.DS_STORE
|
||||
tasks
|
||||
testoutput.txt
|
||||
testoutput
|
||||
release
|
||||
|
||||
323
FlexLove.lua
323
FlexLove.lua
@@ -1,10 +1,3 @@
|
||||
--[[
|
||||
FlexLove - UI Library for LÖVE Framework 'based' on flexbox
|
||||
VERSION: 0.1.0
|
||||
LICENSE: MIT
|
||||
For full documentation, see README.md
|
||||
]]
|
||||
|
||||
local modulePath = (...):match("(.-)[^%.]+$") -- Get the module path prefix (e.g., "libs." or "")
|
||||
local function req(name)
|
||||
return require(modulePath .. "modules." .. name)
|
||||
@@ -12,60 +5,75 @@ end
|
||||
|
||||
-- internals
|
||||
local Blur = req("Blur")
|
||||
local ImageCache = req("ImageCache")
|
||||
local ImageDataReader = req("ImageDataReader")
|
||||
local ImageRenderer = req("ImageRenderer")
|
||||
local ImageScaler = req("ImageScaler")
|
||||
local NinePatchParser = req("NinePatchParser")
|
||||
local utils = req("utils")
|
||||
local Units = req("Units")
|
||||
local GuiState = req("GuiState")
|
||||
local StateManager = req("StateManager")
|
||||
|
||||
-- externals
|
||||
---@type Element
|
||||
local Element = req("Element")
|
||||
---@type Theme
|
||||
local Theme = req("Theme")
|
||||
|
||||
-- externals
|
||||
---@type Animation
|
||||
local Animation = req("Animation")
|
||||
---@type Color
|
||||
local Color = req("Color")
|
||||
---@type Element
|
||||
local Element = req("Element")
|
||||
|
||||
local enums = utils.enums
|
||||
|
||||
local Positioning, FlexDirection, JustifyContent, AlignContent, AlignItems, TextAlign, AlignSelf, JustifySelf, FlexWrap =
|
||||
enums.Positioning,
|
||||
enums.FlexDirection,
|
||||
enums.JustifyContent,
|
||||
enums.AlignContent,
|
||||
enums.AlignItems,
|
||||
enums.TextAlign,
|
||||
enums.AlignSelf,
|
||||
enums.JustifySelf,
|
||||
enums.FlexWrap
|
||||
|
||||
-- ====================
|
||||
-- Top level GUI manager
|
||||
-- FlexLove - UI Library
|
||||
-- ====================
|
||||
|
||||
---@class Gui
|
||||
local Gui = GuiState
|
||||
---@class FlexLove
|
||||
local flexlove = {
|
||||
_VERSION = "FlexLove v0.1.0",
|
||||
_DESCRIPTION = "UI Library for LÖVE Framework based on flexbox",
|
||||
_URL = "https://github.com/Station-Alpha/FlexLove",
|
||||
_LICENSE = [[
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Mike Freno
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
]],
|
||||
}
|
||||
|
||||
-- Copy GuiState properties into flexlove
|
||||
for k, v in pairs(GuiState) do
|
||||
flexlove[k] = v
|
||||
end
|
||||
|
||||
--- Initialize FlexLove with configuration
|
||||
---@param config {baseScale?: {width?:number, height?:number}, theme?: string|ThemeDefinition, immediateMode?: boolean, stateRetentionFrames?: number, maxStateEntries?: number, autoFrameManagement?: boolean}
|
||||
function Gui.init(config)
|
||||
function flexlove.init(config)
|
||||
config = config or {}
|
||||
|
||||
if config.baseScale then
|
||||
Gui.baseScale = {
|
||||
flexlove.baseScale = {
|
||||
width = config.baseScale.width or 1920,
|
||||
height = config.baseScale.height or 1080,
|
||||
}
|
||||
|
||||
local currentWidth, currentHeight = Units.getViewport()
|
||||
Gui.scaleFactors.x = currentWidth / Gui.baseScale.width
|
||||
Gui.scaleFactors.y = currentHeight / Gui.baseScale.height
|
||||
flexlove.scaleFactors.x = currentWidth / flexlove.baseScale.width
|
||||
flexlove.scaleFactors.y = currentHeight / flexlove.baseScale.height
|
||||
end
|
||||
|
||||
if config.theme then
|
||||
@@ -73,11 +81,11 @@ function Gui.init(config)
|
||||
if type(config.theme) == "string" then
|
||||
Theme.load(config.theme)
|
||||
Theme.setActive(config.theme)
|
||||
Gui.defaultTheme = config.theme
|
||||
flexlove.defaultTheme = config.theme
|
||||
elseif type(config.theme) == "table" then
|
||||
local theme = Theme.new(config.theme)
|
||||
Theme.setActive(theme)
|
||||
Gui.defaultTheme = theme.name
|
||||
flexlove.defaultTheme = theme.name
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -87,10 +95,10 @@ function Gui.init(config)
|
||||
end
|
||||
|
||||
local immediateMode = config.immediateMode or false
|
||||
Gui.setMode(immediateMode and "immediate" or "retained")
|
||||
flexlove.setMode(immediateMode and "immediate" or "retained")
|
||||
|
||||
-- Configure auto frame management (defaults to false for manual control)
|
||||
Gui._autoFrameManagement = config.autoFrameManagement or false
|
||||
flexlove._autoFrameManagement = config.autoFrameManagement or false
|
||||
|
||||
-- Configure state management
|
||||
if config.stateRetentionFrames or config.maxStateEntries then
|
||||
@@ -101,42 +109,42 @@ function Gui.init(config)
|
||||
end
|
||||
end
|
||||
|
||||
function Gui.resize()
|
||||
function flexlove.resize()
|
||||
local newWidth, newHeight = love.window.getMode()
|
||||
|
||||
if Gui.baseScale then
|
||||
Gui.scaleFactors.x = newWidth / Gui.baseScale.width
|
||||
Gui.scaleFactors.y = newHeight / Gui.baseScale.height
|
||||
if flexlove.baseScale then
|
||||
flexlove.scaleFactors.x = newWidth / flexlove.baseScale.width
|
||||
flexlove.scaleFactors.y = newHeight / flexlove.baseScale.height
|
||||
end
|
||||
|
||||
Blur.clearCache()
|
||||
|
||||
Gui._gameCanvas = nil
|
||||
Gui._backdropCanvas = nil
|
||||
Gui._canvasDimensions = { width = 0, height = 0 }
|
||||
flexlove._gameCanvas = nil
|
||||
flexlove._backdropCanvas = nil
|
||||
flexlove._canvasDimensions = { width = 0, height = 0 }
|
||||
|
||||
for _, win in ipairs(Gui.topElements) do
|
||||
for _, win in ipairs(flexlove.topElements) do
|
||||
win:resize(newWidth, newHeight)
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the rendering mode (immediate or retained)
|
||||
---@param mode "immediate"|"retained" The rendering mode to use
|
||||
function Gui.setMode(mode)
|
||||
function flexlove.setMode(mode)
|
||||
if mode == "immediate" then
|
||||
Gui._immediateMode = true
|
||||
Gui._immediateModeState = StateManager
|
||||
flexlove._immediateMode = true
|
||||
flexlove._immediateModeState = StateManager
|
||||
-- Reset frame state
|
||||
Gui._frameStarted = false
|
||||
Gui._autoBeganFrame = false
|
||||
flexlove._frameStarted = false
|
||||
flexlove._autoBeganFrame = false
|
||||
elseif mode == "retained" then
|
||||
Gui._immediateMode = false
|
||||
Gui._immediateModeState = nil
|
||||
flexlove._immediateMode = false
|
||||
flexlove._immediateModeState = nil
|
||||
-- Clear immediate mode state
|
||||
Gui._frameStarted = false
|
||||
Gui._autoBeganFrame = false
|
||||
Gui._currentFrameElements = {}
|
||||
Gui._frameNumber = 0
|
||||
flexlove._frameStarted = false
|
||||
flexlove._autoBeganFrame = false
|
||||
flexlove._currentFrameElements = {}
|
||||
flexlove._frameNumber = 0
|
||||
else
|
||||
error("[FlexLove] Invalid mode: " .. tostring(mode) .. ". Expected 'immediate' or 'retained'")
|
||||
end
|
||||
@@ -144,34 +152,34 @@ end
|
||||
|
||||
--- Get the current rendering mode
|
||||
---@return "immediate"|"retained"
|
||||
function Gui.getMode()
|
||||
return Gui._immediateMode and "immediate" or "retained"
|
||||
function flexlove.getMode()
|
||||
return flexlove._immediateMode and "immediate" or "retained"
|
||||
end
|
||||
|
||||
--- Begin a new immediate mode frame
|
||||
function Gui.beginFrame()
|
||||
if not Gui._immediateMode then
|
||||
function flexlove.beginFrame()
|
||||
if not flexlove._immediateMode then
|
||||
return
|
||||
end
|
||||
|
||||
-- Increment frame counter
|
||||
Gui._frameNumber = Gui._frameNumber + 1
|
||||
flexlove._frameNumber = flexlove._frameNumber + 1
|
||||
StateManager.incrementFrame()
|
||||
|
||||
-- Clear current frame elements
|
||||
Gui._currentFrameElements = {}
|
||||
Gui._frameStarted = true
|
||||
flexlove._currentFrameElements = {}
|
||||
flexlove._frameStarted = true
|
||||
|
||||
-- Clear top elements (they will be recreated this frame)
|
||||
Gui.topElements = {}
|
||||
flexlove.topElements = {}
|
||||
|
||||
-- Clear z-index ordered elements from previous frame
|
||||
GuiState.clearFrameElements()
|
||||
end
|
||||
|
||||
--- End the current immediate mode frame
|
||||
function Gui.endFrame()
|
||||
if not Gui._immediateMode then
|
||||
function flexlove.endFrame()
|
||||
if not flexlove._immediateMode then
|
||||
return
|
||||
end
|
||||
|
||||
@@ -180,7 +188,7 @@ function Gui.endFrame()
|
||||
|
||||
-- Layout all top-level elements now that all children have been added
|
||||
-- This ensures overflow detection happens with complete child lists
|
||||
for _, element in ipairs(Gui._currentFrameElements) do
|
||||
for _, element in ipairs(flexlove._currentFrameElements) do
|
||||
if not element.parent then
|
||||
element:layoutChildren() -- Layout with all children present
|
||||
end
|
||||
@@ -188,7 +196,7 @@ function Gui.endFrame()
|
||||
|
||||
-- Auto-update all top-level elements (triggers additional state updates)
|
||||
-- This must happen BEFORE saving state so that scroll positions and overflow are calculated
|
||||
for _, element in ipairs(Gui._currentFrameElements) do
|
||||
for _, element in ipairs(flexlove._currentFrameElements) do
|
||||
-- Only update top-level elements (those without parents in the current frame)
|
||||
-- Element:update() will recursively update children
|
||||
if not element.parent then
|
||||
@@ -197,7 +205,7 @@ function Gui.endFrame()
|
||||
end
|
||||
|
||||
-- Save state back for all elements created this frame
|
||||
for _, element in ipairs(Gui._currentFrameElements) do
|
||||
for _, element in ipairs(flexlove._currentFrameElements) do
|
||||
if element.id and element.id ~= "" then
|
||||
local state = StateManager.getState(element.id, {})
|
||||
|
||||
@@ -237,21 +245,21 @@ function Gui.endFrame()
|
||||
StateManager.forceCleanupIfNeeded()
|
||||
|
||||
-- Clear frame started flag
|
||||
Gui._frameStarted = false
|
||||
flexlove._frameStarted = false
|
||||
end
|
||||
|
||||
-- Canvas cache for game rendering
|
||||
Gui._gameCanvas = nil
|
||||
Gui._backdropCanvas = nil
|
||||
Gui._canvasDimensions = { width = 0, height = 0 }
|
||||
flexlove._gameCanvas = nil
|
||||
flexlove._backdropCanvas = nil
|
||||
flexlove._canvasDimensions = { width = 0, height = 0 }
|
||||
|
||||
---@param gameDrawFunc function|nil
|
||||
---@param postDrawFunc function|nil
|
||||
function Gui.draw(gameDrawFunc, postDrawFunc)
|
||||
function flexlove.draw(gameDrawFunc, postDrawFunc)
|
||||
-- Auto-end frame if it was auto-started in immediate mode
|
||||
if Gui._immediateMode and Gui._autoBeganFrame then
|
||||
Gui.endFrame()
|
||||
Gui._autoBeganFrame = false
|
||||
if flexlove._immediateMode and flexlove._autoBeganFrame then
|
||||
flexlove.endFrame()
|
||||
flexlove._autoBeganFrame = false
|
||||
end
|
||||
|
||||
local outerCanvas = love.graphics.getCanvas()
|
||||
@@ -260,14 +268,14 @@ function Gui.draw(gameDrawFunc, postDrawFunc)
|
||||
if type(gameDrawFunc) == "function" then
|
||||
local width, height = love.graphics.getDimensions()
|
||||
|
||||
if not Gui._gameCanvas or Gui._canvasDimensions.width ~= width or Gui._canvasDimensions.height ~= height then
|
||||
Gui._gameCanvas = love.graphics.newCanvas(width, height)
|
||||
Gui._backdropCanvas = love.graphics.newCanvas(width, height)
|
||||
Gui._canvasDimensions.width = width
|
||||
Gui._canvasDimensions.height = height
|
||||
if not flexlove._gameCanvas or flexlove._canvasDimensions.width ~= width or flexlove._canvasDimensions.height ~= height then
|
||||
flexlove._gameCanvas = love.graphics.newCanvas(width, height)
|
||||
flexlove._backdropCanvas = love.graphics.newCanvas(width, height)
|
||||
flexlove._canvasDimensions.width = width
|
||||
flexlove._canvasDimensions.height = height
|
||||
end
|
||||
|
||||
gameCanvas = Gui._gameCanvas
|
||||
gameCanvas = flexlove._gameCanvas
|
||||
|
||||
love.graphics.setCanvas(gameCanvas)
|
||||
love.graphics.clear()
|
||||
@@ -278,7 +286,7 @@ function Gui.draw(gameDrawFunc, postDrawFunc)
|
||||
love.graphics.draw(gameCanvas, 0, 0)
|
||||
end
|
||||
|
||||
table.sort(Gui.topElements, function(a, b)
|
||||
table.sort(flexlove.topElements, function(a, b)
|
||||
return a.z < b.z
|
||||
end)
|
||||
|
||||
@@ -295,7 +303,7 @@ function Gui.draw(gameDrawFunc, postDrawFunc)
|
||||
end
|
||||
|
||||
local needsBackdropCanvas = false
|
||||
for _, win in ipairs(Gui.topElements) do
|
||||
for _, win in ipairs(flexlove.topElements) do
|
||||
if hasBackdropBlur(win) then
|
||||
needsBackdropCanvas = true
|
||||
break
|
||||
@@ -303,7 +311,7 @@ function Gui.draw(gameDrawFunc, postDrawFunc)
|
||||
end
|
||||
|
||||
if needsBackdropCanvas and gameCanvas then
|
||||
local backdropCanvas = Gui._backdropCanvas
|
||||
local backdropCanvas = flexlove._backdropCanvas
|
||||
local prevColor = { love.graphics.getColor() }
|
||||
|
||||
love.graphics.setCanvas(backdropCanvas)
|
||||
@@ -314,7 +322,7 @@ function Gui.draw(gameDrawFunc, postDrawFunc)
|
||||
love.graphics.setCanvas(outerCanvas)
|
||||
love.graphics.setColor(unpack(prevColor))
|
||||
|
||||
for _, win in ipairs(Gui.topElements) do
|
||||
for _, win in ipairs(flexlove.topElements) do
|
||||
-- Check if this element tree has backdrop blur
|
||||
local needsBackdrop = hasBackdropBlur(win)
|
||||
|
||||
@@ -334,7 +342,7 @@ function Gui.draw(gameDrawFunc, postDrawFunc)
|
||||
love.graphics.setCanvas(outerCanvas)
|
||||
end
|
||||
else
|
||||
for _, win in ipairs(Gui.topElements) do
|
||||
for _, win in ipairs(flexlove.topElements) do
|
||||
win:draw(nil)
|
||||
end
|
||||
end
|
||||
@@ -365,7 +373,7 @@ end
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@return Element?
|
||||
function Gui.getElementAtPosition(x, y)
|
||||
function flexlove.getElementAtPosition(x, y)
|
||||
local candidates = {}
|
||||
local blockingElements = {}
|
||||
|
||||
@@ -420,7 +428,7 @@ function Gui.getElementAtPosition(x, y)
|
||||
end
|
||||
end
|
||||
|
||||
for _, element in ipairs(Gui.topElements) do
|
||||
for _, element in ipairs(flexlove.topElements) do
|
||||
collectHits(element)
|
||||
end
|
||||
|
||||
@@ -457,21 +465,21 @@ function Gui.getElementAtPosition(x, y)
|
||||
return blockingElements[1]
|
||||
end
|
||||
|
||||
function Gui.update(dt)
|
||||
function flexlove.update(dt)
|
||||
local mx, my = love.mouse.getPosition()
|
||||
local topElement = Gui.getElementAtPosition(mx, my)
|
||||
local topElement = flexlove.getElementAtPosition(mx, my)
|
||||
|
||||
Gui._activeEventElement = topElement
|
||||
flexlove._activeEventElement = topElement
|
||||
|
||||
for _, win in ipairs(Gui.topElements) do
|
||||
for _, win in ipairs(flexlove.topElements) do
|
||||
win:update(dt)
|
||||
end
|
||||
|
||||
Gui._activeEventElement = nil
|
||||
flexlove._activeEventElement = nil
|
||||
|
||||
-- In immediate mode, save state after update so that cursor blink timer changes persist
|
||||
if Gui._immediateMode and Gui._currentFrameElements then
|
||||
for _, element in ipairs(Gui._currentFrameElements) do
|
||||
if flexlove._immediateMode and flexlove._currentFrameElements then
|
||||
for _, element in ipairs(flexlove._currentFrameElements) do
|
||||
if element.id and element.id ~= "" and element.editable and element._focused then
|
||||
local state = StateManager.getState(element.id, {})
|
||||
|
||||
@@ -489,9 +497,9 @@ end
|
||||
|
||||
--- Forward text input to focused element
|
||||
---@param text string
|
||||
function Gui.textinput(text)
|
||||
if Gui._focusedElement then
|
||||
Gui._focusedElement:textinput(text)
|
||||
function flexlove.textinput(text)
|
||||
if flexlove._focusedElement then
|
||||
flexlove._focusedElement:textinput(text)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -499,14 +507,14 @@ end
|
||||
---@param key string
|
||||
---@param scancode string
|
||||
---@param isrepeat boolean
|
||||
function Gui.keypressed(key, scancode, isrepeat)
|
||||
if Gui._focusedElement then
|
||||
Gui._focusedElement:keypressed(key, scancode, isrepeat)
|
||||
function flexlove.keypressed(key, scancode, isrepeat)
|
||||
if flexlove._focusedElement then
|
||||
flexlove._focusedElement:keypressed(key, scancode, isrepeat)
|
||||
end
|
||||
end
|
||||
|
||||
--- Handle mouse wheel scrolling
|
||||
function Gui.wheelmoved(x, y)
|
||||
function flexlove.wheelmoved(x, y)
|
||||
local mx, my = love.mouse.getPosition()
|
||||
|
||||
local function findScrollableAtPosition(elements, mx, my)
|
||||
@@ -538,7 +546,7 @@ function Gui.wheelmoved(x, y)
|
||||
end
|
||||
|
||||
-- In immediate mode, use z-index ordered elements and respect occlusion
|
||||
if Gui._immediateMode then
|
||||
if flexlove._immediateMode then
|
||||
-- Find topmost scrollable element at mouse position using z-index ordering
|
||||
for i = #GuiState._zIndexOrderedElements, 1, -1 do
|
||||
local element = GuiState._zIndexOrderedElements[i]
|
||||
@@ -559,7 +567,7 @@ function Gui.wheelmoved(x, y)
|
||||
end
|
||||
else
|
||||
-- In retained mode, use the old tree traversal method
|
||||
local scrollableElement = findScrollableAtPosition(Gui.topElements, mx, my)
|
||||
local scrollableElement = findScrollableAtPosition(flexlove.topElements, mx, my)
|
||||
if scrollableElement then
|
||||
scrollableElement:_handleWheelScroll(x, y)
|
||||
end
|
||||
@@ -567,35 +575,35 @@ function Gui.wheelmoved(x, y)
|
||||
end
|
||||
|
||||
--- Destroy all elements and their children
|
||||
function Gui.destroy()
|
||||
for _, win in ipairs(Gui.topElements) do
|
||||
function flexlove.destroy()
|
||||
for _, win in ipairs(flexlove.topElements) do
|
||||
win:destroy()
|
||||
end
|
||||
Gui.topElements = {}
|
||||
Gui.baseScale = nil
|
||||
Gui.scaleFactors = { x = 1.0, y = 1.0 }
|
||||
Gui._cachedViewport = { width = 0, height = 0 }
|
||||
Gui._gameCanvas = nil
|
||||
Gui._backdropCanvas = nil
|
||||
Gui._canvasDimensions = { width = 0, height = 0 }
|
||||
Gui._focusedElement = nil
|
||||
flexlove.topElements = {}
|
||||
flexlove.baseScale = nil
|
||||
flexlove.scaleFactors = { x = 1.0, y = 1.0 }
|
||||
flexlove._cachedViewport = { width = 0, height = 0 }
|
||||
flexlove._gameCanvas = nil
|
||||
flexlove._backdropCanvas = nil
|
||||
flexlove._canvasDimensions = { width = 0, height = 0 }
|
||||
flexlove._focusedElement = nil
|
||||
end
|
||||
|
||||
--- Create a new element (supports both immediate and retained mode)
|
||||
---@param props ElementProps
|
||||
---@return Element
|
||||
function Gui.new(props)
|
||||
function flexlove.new(props)
|
||||
props = props or {}
|
||||
|
||||
-- If not in immediate mode, use standard Element.new
|
||||
if not Gui._immediateMode then
|
||||
if not flexlove._immediateMode then
|
||||
return Element.new(props)
|
||||
end
|
||||
|
||||
-- Auto-begin frame if not manually started (convenience feature)
|
||||
if not Gui._frameStarted then
|
||||
Gui.beginFrame()
|
||||
Gui._autoBeganFrame = true
|
||||
if not flexlove._frameStarted then
|
||||
flexlove.beginFrame()
|
||||
flexlove._autoBeganFrame = true
|
||||
end
|
||||
|
||||
-- Immediate mode: generate ID if not provided
|
||||
@@ -686,7 +694,7 @@ function Gui.new(props)
|
||||
end
|
||||
|
||||
-- Store element in current frame tracking
|
||||
table.insert(Gui._currentFrameElements, element)
|
||||
table.insert(flexlove._currentFrameElements, element)
|
||||
|
||||
-- Save state back at end of frame (we'll do this in endFrame)
|
||||
-- For now, we need to update the state when properties change
|
||||
@@ -698,8 +706,8 @@ end
|
||||
|
||||
--- Get state count (for debugging)
|
||||
---@return number
|
||||
function Gui.getStateCount()
|
||||
if not Gui._immediateMode then
|
||||
function flexlove.getStateCount()
|
||||
if not flexlove._immediateMode then
|
||||
return 0
|
||||
end
|
||||
return StateManager.getStateCount()
|
||||
@@ -707,16 +715,16 @@ end
|
||||
|
||||
--- Clear state for a specific element ID
|
||||
---@param id string
|
||||
function Gui.clearState(id)
|
||||
if not Gui._immediateMode then
|
||||
function flexlove.clearState(id)
|
||||
if not flexlove._immediateMode then
|
||||
return
|
||||
end
|
||||
StateManager.clearState(id)
|
||||
end
|
||||
|
||||
--- Clear all immediate mode states
|
||||
function Gui.clearAllStates()
|
||||
if not Gui._immediateMode then
|
||||
function flexlove.clearAllStates()
|
||||
if not flexlove._immediateMode then
|
||||
return
|
||||
end
|
||||
StateManager.clearAllStates()
|
||||
@@ -724,8 +732,8 @@ end
|
||||
|
||||
--- Get state statistics (for debugging)
|
||||
---@return table
|
||||
function Gui.getStateStats()
|
||||
if not Gui._immediateMode then
|
||||
function flexlove.getStateStats()
|
||||
if not flexlove._immediateMode then
|
||||
return { stateCount = 0, frameNumber = 0 }
|
||||
end
|
||||
return StateManager.getStats()
|
||||
@@ -734,65 +742,40 @@ end
|
||||
--- Helper function: Create a button with default styling
|
||||
---@param props table
|
||||
---@return Element
|
||||
function Gui.button(props)
|
||||
function flexlove.button(props)
|
||||
props = props or {}
|
||||
props.themeComponent = props.themeComponent or "button"
|
||||
return Gui.new(props)
|
||||
return flexlove.new(props)
|
||||
end
|
||||
|
||||
--- Helper function: Create a panel/container
|
||||
---@param props table
|
||||
---@return Element
|
||||
function Gui.panel(props)
|
||||
function flexlove.panel(props)
|
||||
props = props or {}
|
||||
return Gui.new(props)
|
||||
return flexlove.new(props)
|
||||
end
|
||||
|
||||
--- Helper function: Create a text label
|
||||
---@param props table
|
||||
---@return Element
|
||||
function Gui.text(props)
|
||||
function flexlove.text(props)
|
||||
props = props or {}
|
||||
return Gui.new(props)
|
||||
return flexlove.new(props)
|
||||
end
|
||||
|
||||
--- Helper function: Create an input field
|
||||
---@param props table
|
||||
---@return Element
|
||||
function Gui.input(props)
|
||||
function flexlove.input(props)
|
||||
props = props or {}
|
||||
props.editable = true
|
||||
return Gui.new(props)
|
||||
return flexlove.new(props)
|
||||
end
|
||||
|
||||
-- Export original Element.new for direct access if needed
|
||||
Gui.Element = Element
|
||||
Gui.Animation = Animation
|
||||
Gui.Theme = Theme
|
||||
Gui.ImageCache = ImageCache
|
||||
Gui.ImageDataReader = ImageDataReader
|
||||
Gui.ImageRenderer = ImageRenderer
|
||||
Gui.ImageScaler = ImageScaler
|
||||
Gui.NinePatchParser = NinePatchParser
|
||||
Gui.StateManager = StateManager
|
||||
-- only export what should be used externally
|
||||
flexlove.Animation = Animation
|
||||
flexlove.Color = Color
|
||||
flexlove.enums = enums
|
||||
|
||||
return {
|
||||
Gui = Gui,
|
||||
Element = Element,
|
||||
Color = Color,
|
||||
Theme = Theme,
|
||||
Positioning = Positioning,
|
||||
FlexDirection = FlexDirection,
|
||||
JustifyContent = JustifyContent,
|
||||
AlignContent = AlignContent,
|
||||
AlignItems = AlignItems,
|
||||
TextAlign = TextAlign,
|
||||
AlignSelf = AlignSelf,
|
||||
JustifySelf = JustifySelf,
|
||||
FlexWrap = FlexWrap,
|
||||
enums = enums,
|
||||
-- generally should not be used directly, exported for testing, mainly
|
||||
ImageCache = ImageCache,
|
||||
ImageRenderer = ImageRenderer,
|
||||
ImageScaler = ImageScaler,
|
||||
}
|
||||
return flexlove
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# FlexLöve
|
||||
# FlexLöve v0.1.0
|
||||
|
||||
**A comprehensive UI library providing flexbox/grid layouts, theming, animations, and event handling for LÖVE2D games.**
|
||||
|
||||
|
||||
98
modules/ErrorHandler.lua
Normal file
98
modules/ErrorHandler.lua
Normal file
@@ -0,0 +1,98 @@
|
||||
-- modules/ErrorHandler.lua
|
||||
local ErrorHandler = {}
|
||||
|
||||
--- Format an error or warning message
|
||||
---@param module string The module name (e.g., "Element", "Units", "Theme")
|
||||
---@param level string "Error" or "Warning"
|
||||
---@param message string The error/warning message
|
||||
---@return string Formatted message
|
||||
local function formatMessage(module, level, message)
|
||||
return string.format("[FlexLove - %s] %s: %s", module, level, message)
|
||||
end
|
||||
|
||||
--- Throw a critical error (stops execution)
|
||||
---@param module string The module name
|
||||
---@param message string The error message
|
||||
function ErrorHandler.error(module, message)
|
||||
error(formatMessage(module, "Error", message), 2)
|
||||
end
|
||||
|
||||
--- Print a warning (non-critical, continues execution)
|
||||
---@param module string The module name
|
||||
---@param message string The warning message
|
||||
function ErrorHandler.warn(module, message)
|
||||
print(formatMessage(module, "Warning", message))
|
||||
end
|
||||
|
||||
--- Validate that a value is not nil
|
||||
---@param module string The module name
|
||||
---@param value any The value to check
|
||||
---@param paramName string The parameter name
|
||||
---@return boolean True if valid
|
||||
function ErrorHandler.assertNotNil(module, value, paramName)
|
||||
if value == nil then
|
||||
ErrorHandler.error(module, string.format("Parameter '%s' cannot be nil", paramName))
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Validate that a value is of the expected type
|
||||
---@param module string The module name
|
||||
---@param value any The value to check
|
||||
---@param expectedType string The expected type name
|
||||
---@param paramName string The parameter name
|
||||
---@return boolean True if valid
|
||||
function ErrorHandler.assertType(module, value, expectedType, paramName)
|
||||
local actualType = type(value)
|
||||
if actualType ~= expectedType then
|
||||
ErrorHandler.error(module, string.format(
|
||||
"Parameter '%s' must be %s, got %s",
|
||||
paramName, expectedType, actualType
|
||||
))
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Validate that a number is within a range
|
||||
---@param module string The module name
|
||||
---@param value number The value to check
|
||||
---@param min number Minimum value (inclusive)
|
||||
---@param max number Maximum value (inclusive)
|
||||
---@param paramName string The parameter name
|
||||
---@return boolean True if valid
|
||||
function ErrorHandler.assertRange(module, value, min, max, paramName)
|
||||
if value < min or value > max then
|
||||
ErrorHandler.error(module, string.format(
|
||||
"Parameter '%s' must be between %s and %s, got %s",
|
||||
paramName, tostring(min), tostring(max), tostring(value)
|
||||
))
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Warn if a value is deprecated
|
||||
---@param module string The module name
|
||||
---@param oldName string The deprecated name
|
||||
---@param newName string The new name to use
|
||||
function ErrorHandler.warnDeprecated(module, oldName, newName)
|
||||
ErrorHandler.warn(module, string.format(
|
||||
"'%s' is deprecated. Use '%s' instead",
|
||||
oldName, newName
|
||||
))
|
||||
end
|
||||
|
||||
--- Warn about a common mistake
|
||||
---@param module string The module name
|
||||
---@param issue string Description of the issue
|
||||
---@param suggestion string Suggested fix
|
||||
function ErrorHandler.warnCommonMistake(module, issue, suggestion)
|
||||
ErrorHandler.warn(module, string.format(
|
||||
"%s. Suggestion: %s",
|
||||
issue, suggestion
|
||||
))
|
||||
end
|
||||
|
||||
return ErrorHandler
|
||||
@@ -4,6 +4,7 @@ local function req(name)
|
||||
end
|
||||
|
||||
local NinePatchParser = req("NinePatchParser")
|
||||
local Color = req("Color")
|
||||
|
||||
--- Standardized error message formatter
|
||||
---@param module string -- Module name (e.g., "Color", "Theme", "Units")
|
||||
@@ -699,6 +700,7 @@ function ThemeManager:setTheme(themeName, componentName)
|
||||
self.themeComponent = componentName
|
||||
end
|
||||
|
||||
-- Export both Theme and ThemeManager
|
||||
Theme.Manager = ThemeManager
|
||||
|
||||
return Theme
|
||||
|
||||
Reference in New Issue
Block a user