theme loading fixed, need to fix application
This commit is contained in:
172
FlexLove.lua
172
FlexLove.lua
@@ -83,6 +83,55 @@ Theme.__index = Theme
|
||||
local themes = {}
|
||||
local activeTheme = nil
|
||||
|
||||
--- Auto-detect the base path where FlexLove is located
|
||||
---@return string modulePath, string filesystemPath
|
||||
local function getFlexLoveBasePath()
|
||||
-- Get debug info to find where this file is loaded from
|
||||
local info = debug.getinfo(1, "S")
|
||||
if info and info.source then
|
||||
local source = info.source
|
||||
-- Remove leading @ if present
|
||||
if source:sub(1, 1) == "@" then
|
||||
source = source:sub(2)
|
||||
end
|
||||
|
||||
-- Extract the directory path (remove FlexLove.lua)
|
||||
local filesystemPath = source:match("(.*/)")
|
||||
if filesystemPath then
|
||||
-- Store the original filesystem path for loading assets
|
||||
local fsPath = filesystemPath
|
||||
-- Remove leading ./ if present
|
||||
fsPath = fsPath:gsub("^%./", "")
|
||||
-- Remove trailing /
|
||||
fsPath = fsPath:gsub("/$", "")
|
||||
|
||||
-- Convert filesystem path to Lua module path
|
||||
local modulePath = fsPath:gsub("/", ".")
|
||||
|
||||
return modulePath, fsPath
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: try common paths
|
||||
return "libs", "libs"
|
||||
end
|
||||
|
||||
-- Store the base paths when module loads
|
||||
local FLEXLOVE_BASE_PATH, FLEXLOVE_FILESYSTEM_PATH = getFlexLoveBasePath()
|
||||
|
||||
--- Helper function to resolve image paths relative to FlexLove
|
||||
---@param imagePath string
|
||||
---@return string
|
||||
local function resolveImagePath(imagePath)
|
||||
-- If path is already absolute or starts with known LÖVE paths, use as-is
|
||||
if imagePath:match("^/") or imagePath:match("^[A-Z]:") then
|
||||
return imagePath
|
||||
end
|
||||
|
||||
-- Otherwise, make it relative to FlexLove's location
|
||||
return FLEXLOVE_FILESYSTEM_PATH .. "/" .. imagePath
|
||||
end
|
||||
|
||||
--- Create a new theme instance
|
||||
---@param definition ThemeDefinition
|
||||
---@return Theme
|
||||
@@ -93,7 +142,8 @@ function Theme.new(definition)
|
||||
-- Load global atlas if it's a string path
|
||||
if definition.atlas then
|
||||
if type(definition.atlas) == "string" then
|
||||
self.atlas = love.graphics.newImage(definition.atlas)
|
||||
local resolvedPath = resolveImagePath(definition.atlas)
|
||||
self.atlas = love.graphics.newImage(resolvedPath)
|
||||
else
|
||||
self.atlas = definition.atlas
|
||||
end
|
||||
@@ -106,7 +156,8 @@ function Theme.new(definition)
|
||||
for componentName, component in pairs(self.components) do
|
||||
if component.atlas then
|
||||
if type(component.atlas) == "string" then
|
||||
component._loadedAtlas = love.graphics.newImage(component.atlas)
|
||||
local resolvedPath = resolveImagePath(component.atlas)
|
||||
component._loadedAtlas = love.graphics.newImage(resolvedPath)
|
||||
else
|
||||
component._loadedAtlas = component.atlas
|
||||
end
|
||||
@@ -117,7 +168,8 @@ function Theme.new(definition)
|
||||
for stateName, stateComponent in pairs(component.states) do
|
||||
if stateComponent.atlas then
|
||||
if type(stateComponent.atlas) == "string" then
|
||||
stateComponent._loadedAtlas = love.graphics.newImage(stateComponent.atlas)
|
||||
local resolvedPath = resolveImagePath(stateComponent.atlas)
|
||||
stateComponent._loadedAtlas = love.graphics.newImage(resolvedPath)
|
||||
else
|
||||
stateComponent._loadedAtlas = stateComponent.atlas
|
||||
end
|
||||
@@ -130,35 +182,37 @@ function Theme.new(definition)
|
||||
end
|
||||
|
||||
--- Load a theme from a Lua file
|
||||
---@param path string -- Path to theme definition file
|
||||
---@param path string -- Path to theme definition file (e.g., "space" or "mytheme")
|
||||
---@return Theme
|
||||
function Theme.load(path)
|
||||
-- Check if it's a built-in theme
|
||||
local builtInPath = "themes/" .. path .. ".lua"
|
||||
local definition
|
||||
|
||||
-- Try to load as built-in first
|
||||
-- Build the theme module path relative to FlexLove
|
||||
local themePath = FLEXLOVE_BASE_PATH .. ".themes." .. path
|
||||
|
||||
local success, result = pcall(function()
|
||||
return love.filesystem.load(builtInPath)()
|
||||
return require(themePath)
|
||||
end)
|
||||
|
||||
if success then
|
||||
definition = result
|
||||
else
|
||||
-- Try to load as custom path
|
||||
-- Fallback: try as direct path
|
||||
success, result = pcall(function()
|
||||
return love.filesystem.load(path)()
|
||||
return require(path)
|
||||
end)
|
||||
|
||||
if success then
|
||||
definition = result
|
||||
else
|
||||
error("Failed to load theme from: " .. path .. "\nError: " .. tostring(result))
|
||||
error("Failed to load theme '" .. path .. "'\nTried: " .. themePath .. "\nError: " .. tostring(result))
|
||||
end
|
||||
end
|
||||
|
||||
local theme = Theme.new(definition)
|
||||
-- Register theme by both its display name and load path
|
||||
themes[theme.name] = theme
|
||||
themes[path] = theme
|
||||
|
||||
return theme
|
||||
end
|
||||
@@ -665,15 +719,12 @@ end
|
||||
---@field topElements table<integer, Element>
|
||||
---@field baseScale {width:number, height:number}?
|
||||
---@field scaleFactors {x:number, y:number}
|
||||
---@field init fun(config: {baseScale: {width:number, height:number}}): nil
|
||||
---@field resize fun(): nil
|
||||
---@field draw fun(): nil
|
||||
---@field update fun(dt:number): nil
|
||||
---@field destroy fun(): nil
|
||||
---@field defaultTheme string? -- Default theme name to use for elements
|
||||
local Gui = {
|
||||
topElements = {},
|
||||
baseScale = nil,
|
||||
scaleFactors = { x = 1.0, y = 1.0 },
|
||||
defaultTheme = nil,
|
||||
}
|
||||
|
||||
--- Initialize FlexLove with configuration
|
||||
@@ -693,14 +744,24 @@ function Gui.init(config)
|
||||
|
||||
-- Load and set theme if specified
|
||||
if config.theme then
|
||||
if type(config.theme) == "string" then
|
||||
-- Load theme by name
|
||||
Theme.load(config.theme)
|
||||
Theme.setActive(config.theme)
|
||||
elseif type(config.theme) == "table" then
|
||||
-- Load theme from definition
|
||||
local theme = Theme.new(config.theme)
|
||||
Theme.setActive(theme)
|
||||
local success, err = pcall(function()
|
||||
if type(config.theme) == "string" then
|
||||
-- Load theme by name
|
||||
Theme.load(config.theme)
|
||||
Theme.setActive(config.theme)
|
||||
Gui.defaultTheme = config.theme
|
||||
print("[FlexLove] Theme loaded: " .. config.theme)
|
||||
elseif type(config.theme) == "table" then
|
||||
-- Load theme from definition
|
||||
local theme = Theme.new(config.theme)
|
||||
Theme.setActive(theme)
|
||||
Gui.defaultTheme = theme.name
|
||||
print("[FlexLove] Theme loaded: " .. theme.name)
|
||||
end
|
||||
end)
|
||||
|
||||
if not success then
|
||||
print("[FlexLove] Failed to load theme: " .. tostring(err))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1074,7 +1135,8 @@ Element.__index = Element
|
||||
---@field gridColumns number? -- Number of columns in the grid (default: 1)
|
||||
---@field columnGap number|string? -- Gap between grid columns
|
||||
---@field rowGap number|string? -- Gap between grid rows
|
||||
---@field theme string|{component:string, state:string?}? -- Theme component to use for rendering
|
||||
---@field theme string? -- Theme name to use (e.g., "space", "dark"). Defaults to theme from Gui.init()
|
||||
---@field themeComponent string? -- Theme component to use (e.g., "panel", "button", "input"). If nil, no theme is applied
|
||||
---@field disabled boolean? -- Whether the element is disabled (default: false)
|
||||
---@field active boolean? -- Whether the element is active/focused (for inputs, default: false)
|
||||
local ElementProps = {}
|
||||
@@ -1095,9 +1157,15 @@ function Element.new(props)
|
||||
self._touchPressed = {}
|
||||
|
||||
-- Initialize theme
|
||||
self.theme = props.theme
|
||||
self._themeState = "normal"
|
||||
|
||||
-- Handle theme property:
|
||||
-- - theme: which theme to use (defaults to Gui.defaultTheme if not specified)
|
||||
-- - themeComponent: which component from the theme (e.g., "panel", "button", "input")
|
||||
-- If themeComponent is nil, no theme is applied (manual styling)
|
||||
self.theme = props.theme or Gui.defaultTheme
|
||||
self.themeComponent = props.themeComponent or nil
|
||||
|
||||
-- Initialize state properties
|
||||
self.disabled = props.disabled or false
|
||||
self.active = props.active or false
|
||||
@@ -2067,25 +2135,39 @@ function Element:draw()
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if element has a theme
|
||||
-- Check if element has a theme component
|
||||
local hasTheme = false
|
||||
if self.theme then
|
||||
local componentName, state
|
||||
|
||||
if type(self.theme) == "string" then
|
||||
componentName = self.theme
|
||||
state = self._themeState
|
||||
if self.themeComponent then
|
||||
-- Get the theme to use
|
||||
local themeToUse = nil
|
||||
if self.theme then
|
||||
-- Element specifies a specific theme - load it if needed
|
||||
if themes[self.theme] then
|
||||
themeToUse = themes[self.theme]
|
||||
else
|
||||
-- Try to load the theme
|
||||
pcall(function()
|
||||
Theme.load(self.theme)
|
||||
end)
|
||||
themeToUse = themes[self.theme]
|
||||
end
|
||||
else
|
||||
componentName = self.theme.component
|
||||
state = self.theme.state or self._themeState
|
||||
-- Use active theme
|
||||
themeToUse = Theme.getActive()
|
||||
end
|
||||
|
||||
local component = Theme.getComponent(componentName, state)
|
||||
if component then
|
||||
local activeTheme = Theme.getActive()
|
||||
if activeTheme then
|
||||
if themeToUse then
|
||||
-- Get the component from the theme
|
||||
local component = themeToUse.components[self.themeComponent]
|
||||
if component then
|
||||
-- Check for state-specific override
|
||||
local state = self._themeState
|
||||
if state and component.states and component.states[state] then
|
||||
component = component.states[state]
|
||||
end
|
||||
|
||||
-- Use component-specific atlas if available, otherwise use theme atlas
|
||||
local atlasToUse = component._loadedAtlas or activeTheme.atlas
|
||||
local atlasToUse = component._loadedAtlas or themeToUse.atlas
|
||||
|
||||
if atlasToUse then
|
||||
NineSlice.draw(
|
||||
@@ -2098,8 +2180,14 @@ function Element:draw()
|
||||
self.opacity
|
||||
)
|
||||
hasTheme = true
|
||||
else
|
||||
print("[FlexLove] No atlas for component: " .. self.themeComponent)
|
||||
end
|
||||
else
|
||||
print("[FlexLove] Component not found: " .. self.themeComponent .. " in theme: " .. themeToUse.name)
|
||||
end
|
||||
else
|
||||
print("[FlexLove] No theme available for themeComponent: " .. self.themeComponent)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2249,7 +2337,7 @@ function Element:update(dt)
|
||||
end
|
||||
|
||||
-- Handle click detection for element with enhanced event system
|
||||
if self.callback or self.theme then
|
||||
if self.callback or self.themeComponent then
|
||||
local mx, my = love.mouse.getPosition()
|
||||
-- Clickable area is the border box (x, y already includes padding)
|
||||
local bx = self.x
|
||||
@@ -2259,7 +2347,7 @@ function Element:update(dt)
|
||||
local isHovering = mx >= bx and mx <= bx + bw and my >= by and my <= by + bh
|
||||
|
||||
-- Update theme state based on interaction
|
||||
if self.theme then
|
||||
if self.themeComponent then
|
||||
-- Disabled state takes priority
|
||||
if self.disabled then
|
||||
self._themeState = "disabled"
|
||||
|
||||
@@ -10,7 +10,7 @@ print("=== Base Scaling Demo ===\n")
|
||||
|
||||
-- Initialize with base scale (call this in love.load())
|
||||
Gui.init({
|
||||
baseScale = { width = 800, height = 600 }
|
||||
baseScale = { width = 800, height = 600 },
|
||||
})
|
||||
|
||||
print("Designing UI for 800x600 base resolution\n")
|
||||
@@ -32,22 +32,36 @@ local button = Gui.new({
|
||||
})
|
||||
|
||||
print("At 800x600 (base resolution):")
|
||||
print(string.format(" Button: x=%d, y=%d, width=%d, height=%d, textSize=%d",
|
||||
button.x, button.y, button.width, button.height, button.textSize))
|
||||
print(string.format(" Padding: left=%d, top=%d (NOT scaled)",
|
||||
button.padding.left, button.padding.top))
|
||||
print(
|
||||
string.format(
|
||||
" Button: x=%d, y=%d, width=%d, height=%d, textSize=%d",
|
||||
button.x,
|
||||
button.y,
|
||||
button.width,
|
||||
button.height,
|
||||
button.textSize
|
||||
)
|
||||
)
|
||||
print(string.format(" Padding: left=%d, top=%d (NOT scaled)", button.padding.left, button.padding.top))
|
||||
|
||||
-- Simulate window resize to 1600x1200 (2x scale)
|
||||
print("\nResizing window to 1600x1200...")
|
||||
love.window.setMode(1600, 1200)
|
||||
Gui.resize() -- This updates all elements
|
||||
Gui.resize() -- This updates all elements
|
||||
|
||||
local sx, sy = Gui.getScaleFactors()
|
||||
print(string.format("Scale factors: x=%.1f, y=%.1f", sx, sy))
|
||||
print(string.format(" Button: x=%d, y=%d, width=%d, height=%d, textSize=%d",
|
||||
button.x, button.y, button.width, button.height, button.textSize))
|
||||
print(string.format(" Padding: left=%d, top=%d (NOT scaled)",
|
||||
button.padding.left, button.padding.top))
|
||||
print(
|
||||
string.format(
|
||||
" Button: x=%d, y=%d, width=%d, height=%d, textSize=%d",
|
||||
button.x,
|
||||
button.y,
|
||||
button.width,
|
||||
button.height,
|
||||
button.textSize
|
||||
)
|
||||
)
|
||||
print(string.format(" Padding: left=%d, top=%d (NOT scaled)", button.padding.left, button.padding.top))
|
||||
|
||||
-- Simulate window resize to 400x300 (0.5x scale)
|
||||
print("\nResizing window to 400x300...")
|
||||
@@ -56,16 +70,23 @@ Gui.resize()
|
||||
|
||||
sx, sy = Gui.getScaleFactors()
|
||||
print(string.format("Scale factors: x=%.1f, y=%.1f", sx, sy))
|
||||
print(string.format(" Button: x=%d, y=%d, width=%d, height=%d, textSize=%d",
|
||||
button.x, button.y, button.width, button.height, button.textSize))
|
||||
print(string.format(" Padding: left=%d, top=%d (NOT scaled)",
|
||||
button.padding.left, button.padding.top))
|
||||
print(
|
||||
string.format(
|
||||
" Button: x=%d, y=%d, width=%d, height=%d, textSize=%d",
|
||||
button.x,
|
||||
button.y,
|
||||
button.width,
|
||||
button.height,
|
||||
button.textSize
|
||||
)
|
||||
)
|
||||
print(string.format(" Padding: left=%d, top=%d (NOT scaled)", button.padding.left, button.padding.top))
|
||||
|
||||
print("\n=== Usage ===")
|
||||
print("In your main.lua:")
|
||||
print([[
|
||||
function love.load()
|
||||
local FlexLove = require("game.libs.FlexLove")
|
||||
local FlexLove = require("libs.FlexLove")
|
||||
local Gui = FlexLove.GUI
|
||||
|
||||
-- Initialize with your design resolution
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
-- Example: Setting theme in Gui.init()
|
||||
-- NOTE: This should be called in love.load() after LÖVE graphics is initialized
|
||||
local FlexLove = require("FlexLove")
|
||||
local Gui = FlexLove.GUI
|
||||
local Color = FlexLove.Color
|
||||
local Theme = FlexLove.Theme
|
||||
|
||||
-- In love.load():
|
||||
-- Initialize GUI with theme
|
||||
Gui.init({
|
||||
baseScale = { width = 1920, height = 1080 },
|
||||
theme = "space" -- Load and activate the space theme
|
||||
})
|
||||
|
||||
-- Alternative: Load theme manually if Gui.init() is called before love.load()
|
||||
-- Theme.load("space")
|
||||
-- Theme.setActive("space")
|
||||
|
||||
-- Now all elements can use the theme
|
||||
local panel = Gui.new({
|
||||
x = 100,
|
||||
y = 100,
|
||||
width = 400,
|
||||
height = 300,
|
||||
theme = "panel",
|
||||
themeComponent = "panel",
|
||||
padding = { top = 20, right = 20, bottom = 20, left = 20 },
|
||||
})
|
||||
|
||||
@@ -28,7 +35,7 @@ local button1 = Gui.new({
|
||||
text = "Normal Button",
|
||||
textAlign = "center",
|
||||
textColor = Color.new(1, 1, 1, 1),
|
||||
theme = "button",
|
||||
themeComponent = "button",
|
||||
callback = function(element, event)
|
||||
if event.type == "click" then
|
||||
print("Button clicked!")
|
||||
@@ -45,7 +52,7 @@ local button2 = Gui.new({
|
||||
text = "Disabled",
|
||||
textAlign = "center",
|
||||
textColor = Color.new(0.6, 0.6, 0.6, 1),
|
||||
theme = "button",
|
||||
themeComponent = "button",
|
||||
disabled = true, -- Shows disabled state
|
||||
callback = function(element, event)
|
||||
print("This won't fire!")
|
||||
@@ -60,7 +67,7 @@ local input1 = Gui.new({
|
||||
height = 40,
|
||||
text = "Type here...",
|
||||
textColor = Color.new(1, 1, 1, 1),
|
||||
theme = "input",
|
||||
themeComponent = "input",
|
||||
})
|
||||
|
||||
local input2 = Gui.new({
|
||||
@@ -71,7 +78,7 @@ local input2 = Gui.new({
|
||||
height = 40,
|
||||
text = "Active input",
|
||||
textColor = Color.new(1, 1, 1, 1),
|
||||
theme = "input",
|
||||
themeComponent = "input",
|
||||
active = true, -- Shows active/focused state
|
||||
})
|
||||
|
||||
@@ -83,7 +90,7 @@ local input3 = Gui.new({
|
||||
height = 40,
|
||||
text = "Disabled input",
|
||||
textColor = Color.new(0.6, 0.6, 0.6, 1),
|
||||
theme = "input",
|
||||
themeComponent = "input",
|
||||
disabled = true, -- Shows disabled state
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
-- Space Theme
|
||||
-- All images are 256x256 with perfectly centered 9-slice regions
|
||||
|
||||
local Color = require("FlexLove").Color
|
||||
-- Define Color inline to avoid circular dependency
|
||||
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 {
|
||||
name = "Space Theme",
|
||||
|
||||
Reference in New Issue
Block a user