From f6ef2dc82e46191c912f4881f57df18fe5fc1c5d Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Mon, 13 Oct 2025 11:31:24 -0400 Subject: [PATCH] fonts --- FlexLove.lua | 74 +++++++++++-- examples/FontFamilyDemo.lua | 214 ++++++++++++++++++++++++++++++++++++ themes/space.lua | 8 +- 3 files changed, 288 insertions(+), 8 deletions(-) create mode 100644 examples/FontFamilyDemo.lua diff --git a/FlexLove.lua b/FlexLove.lua index 49f1a67..46121a3 100644 --- a/FlexLove.lua +++ b/FlexLove.lua @@ -65,17 +65,23 @@ end ---@field states table? ---@field _loadedAtlas love.Image? -- Internal: cached loaded atlas image +---@class FontFamily +---@field path string -- Path to the font file (relative to FlexLove or absolute) +---@field _loadedFont love.Font? -- Internal: cached loaded font + ---@class ThemeDefinition ---@field name string ---@field atlas string|love.Image? -- Optional: global atlas (can be overridden per component) ---@field components table ---@field colors table? +---@field fonts table? -- Optional: font family definitions (name -> path) ---@class Theme ---@field name string ---@field atlas love.Image? -- Optional: global atlas ---@field components table ---@field colors table +---@field fonts table -- Font family definitions local Theme = {} Theme.__index = Theme @@ -151,6 +157,7 @@ function Theme.new(definition) self.components = definition.components or {} self.colors = definition.colors or {} + self.fonts = definition.fonts or {} -- Load component-specific atlases for componentName, component in pairs(self.components) do @@ -1174,20 +1181,40 @@ local FONT_CACHE = {} --- Create or get a font from cache ---@param size number +---@param fontPath string? -- Optional: path to font file ---@return love.Font -function FONT_CACHE.get(size) - if not FONT_CACHE[size] then - FONT_CACHE[size] = love.graphics.newFont(size) +function FONT_CACHE.get(size, fontPath) + -- Create cache key from size and font path + local cacheKey = fontPath and (fontPath .. "_" .. tostring(size)) or tostring(size) + + if not FONT_CACHE[cacheKey] then + if fontPath then + -- Load custom font + local resolvedPath = resolveImagePath(fontPath) + -- Note: love.graphics.newFont signature is (path, size) for custom fonts + local success, font = pcall(love.graphics.newFont, resolvedPath, size) + if success then + FONT_CACHE[cacheKey] = font + else + -- Fallback to default font if custom font fails to load + print("[FlexLove] Failed to load font: " .. fontPath .. " - using default font") + FONT_CACHE[cacheKey] = love.graphics.newFont(size) + end + else + -- Load default font + FONT_CACHE[cacheKey] = love.graphics.newFont(size) + end end - return FONT_CACHE[size] + return FONT_CACHE[cacheKey] end --- Get font for text size (cached) ---@param textSize number? +---@param fontPath string? -- Optional: path to font file ---@return love.Font -function FONT_CACHE.getFont(textSize) +function FONT_CACHE.getFont(textSize, fontPath) if textSize then - return FONT_CACHE.get(textSize) + return FONT_CACHE.get(textSize, fontPath) else return love.graphics.getFont() end @@ -1256,6 +1283,7 @@ end ---@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 textSize number? -- Resolved font size for text content in pixels +---@field fontFamily string? -- Font family name from theme or path to font file ---@field autoScaleText boolean -- Whether text should auto-scale with window size (default: true) ---@field transform TransformProps -- Transform properties for animations and styling ---@field transition TransitionProps -- Transition settings for animations @@ -1303,6 +1331,7 @@ Element.__index = Element ---@field textAlign TextAlign? -- Alignment of the text content (default: START) ---@field textColor Color? -- Color of the text content (default: black) ---@field textSize number|string? -- Font size: number (px), string with units ("2vh", "10%"), or preset ("xxs"|"xs"|"sm"|"md"|"lg"|"xl"|"xxl"|"3xl"|"4xl") (default: "md") +---@field fontFamily string? -- Font family name from theme or path to font file (default: theme default or system default) ---@field autoScaleText boolean? -- Whether text should auto-scale with window size (default: true) ---@field positioning Positioning? -- Layout positioning mode (default: ABSOLUTE) ---@field flexDirection FlexDirection? -- Direction of flex layout (default: HORIZONTAL) @@ -1458,6 +1487,18 @@ function Element.new(props) self.autoScaleText = props.autoScaleText end + -- Handle fontFamily (can be font name from theme or direct path to font file) + self.fontFamily = props.fontFamily + + -- If using themeComponent but no fontFamily specified, apply default font + if props.themeComponent and not props.fontFamily then + local themeToUse = self.theme and themes[self.theme] or Theme.getActive() + if themeToUse and themeToUse.fonts then + -- Use default font from theme if available + self.fontFamily = "default" + end + end + -- Handle textSize BEFORE width/height calculation (needed for auto-sizing) if props.textSize then if type(props.textSize) == "string" then @@ -2543,8 +2584,27 @@ function Element:draw() local origFont = love.graphics.getFont() if self.textSize then + -- Resolve font path from font family + local fontPath = nil + if self.fontFamily then + -- Check if fontFamily is a theme font name + local themeToUse = self.theme and themes[self.theme] or Theme.getActive() + if themeToUse and themeToUse.fonts and themeToUse.fonts[self.fontFamily] then + fontPath = themeToUse.fonts[self.fontFamily] + else + -- Treat as direct path to font file + fontPath = self.fontFamily + end + elseif self.themeComponent then + -- If using themeComponent but no fontFamily specified, check for default font in theme + local themeToUse = self.theme and themes[self.theme] or Theme.getActive() + if themeToUse and themeToUse.fonts and themeToUse.fonts.default then + fontPath = themeToUse.fonts.default + end + end + -- Use cached font instead of creating new one every frame - local font = FONT_CACHE.get(self.textSize) + local font = FONT_CACHE.get(self.textSize, fontPath) love.graphics.setFont(font) end local font = love.graphics.getFont() diff --git a/examples/FontFamilyDemo.lua b/examples/FontFamilyDemo.lua new file mode 100644 index 0000000..20dc3b9 --- /dev/null +++ b/examples/FontFamilyDemo.lua @@ -0,0 +1,214 @@ +-- Font Family Demo +-- Demonstrates how to use custom fonts with FlexLove theme system + +local FlexLove = require("libs.FlexLove") +local Gui = FlexLove.GUI +local Color = FlexLove.Color +local Theme = FlexLove.Theme + +-- Initialize FlexLove with base scaling and theme +Gui.init({ + baseScale = { width = 1920, height = 1080 }, + theme = "space", +}) + +-- Create a simple theme with custom fonts +local customTheme = Theme.new({ + name = "Custom Font Theme", + + -- Define font families + -- Note: These paths are examples - replace with your actual font files + fonts = { + -- You can reference fonts by name in your elements + -- default = "path/to/your/font.ttf", + -- heading = "path/to/your/heading-font.ttf", + -- mono = "path/to/your/monospace-font.ttf", + }, + + colors = { + background = Color.new(0.1, 0.1, 0.15, 1), + text = Color.new(0.9, 0.9, 0.95, 1), + }, +}) + +-- Set the custom theme as active +-- Theme.setActive(customTheme) + +-- Create main container +local container = Gui.new({ + x = 100, + y = 100, + width = 1720, + height = 880, + backgroundColor = Color.new(0.1, 0.1, 0.15, 1), + cornerRadius = 10, + positioning = "flex", + flexDirection = "vertical", + padding = { top = 40, horizontal = 40, bottom = 40 }, + gap = 30, +}) + +-- Title +Gui.new({ + parent = container, + text = "Font Family Demo", + textSize = "3xl", + textColor = Color.new(1, 1, 1, 1), + textAlign = "center", + -- fontFamily = "heading", -- Uncomment to use custom heading font from theme +}) + +-- Description +Gui.new({ + parent = container, + text = "FlexLove supports custom font families through the theme system", + textSize = "md", + textColor = Color.new(0.8, 0.8, 0.9, 1), + textAlign = "center", + -- fontFamily = "default", -- Uncomment to use custom default font from theme +}) + +-- Example 1: Default System Font +local example1 = Gui.new({ + parent = container, + width = "100%", + backgroundColor = Color.new(0.15, 0.15, 0.2, 1), + cornerRadius = 8, + padding = { top = 20, horizontal = 20, bottom = 20 }, + positioning = "flex", + flexDirection = "vertical", + gap = 10, +}) + +Gui.new({ + parent = example1, + text = "1. Default System Font", + textSize = "lg", + textColor = Color.new(0.3, 0.7, 1, 1), +}) + +Gui.new({ + parent = example1, + text = "This text uses the default system font (no fontFamily specified)", + textSize = "md", + textColor = Color.new(0.9, 0.9, 0.95, 1), +}) + +-- Example 2: Font from Theme +local example2 = Gui.new({ + parent = container, + width = "100%", + backgroundColor = Color.new(0.15, 0.15, 0.2, 1), + cornerRadius = 8, + padding = { top = 20, horizontal = 20, bottom = 20 }, + positioning = "flex", + flexDirection = "vertical", + gap = 10, +}) + +Gui.new({ + parent = example2, + text = "2. Font from Theme", + textSize = "lg", + textColor = Color.new(0.3, 0.7, 1, 1), +}) + +Gui.new({ + parent = example2, + text = "Use fontFamily='default' to reference fonts defined in your theme", + textSize = "md", + textColor = Color.new(0.9, 0.9, 0.95, 1), + -- fontFamily = "default", -- Uncomment when you have fonts defined in theme +}) + +-- Example 3: Direct Font Path +local example3 = Gui.new({ + parent = container, + width = "100%", + backgroundColor = Color.new(0.15, 0.15, 0.2, 1), + cornerRadius = 8, + padding = { top = 20, horizontal = 20, bottom = 20 }, + positioning = "flex", + flexDirection = "vertical", + gap = 10, +}) + +Gui.new({ + parent = example3, + text = "3. Direct Font Path", + textSize = "lg", + textColor = Color.new(0.3, 0.7, 1, 1), +}) + +Gui.new({ + parent = example3, + text = "You can also specify a direct path: fontFamily='path/to/font.ttf'", + textSize = "md", + textColor = Color.new(0.9, 0.9, 0.95, 1), + -- fontFamily = "path/to/your/font.ttf", -- Uncomment with actual font path +}) + +-- Example 4: Different Sizes with Same Font +local example4 = Gui.new({ + parent = container, + width = "100%", + backgroundColor = Color.new(0.15, 0.15, 0.2, 1), + cornerRadius = 8, + padding = { top = 20, horizontal = 20, bottom = 20 }, + positioning = "flex", + flexDirection = "vertical", + gap = 10, +}) + +Gui.new({ + parent = example4, + text = "4. Multiple Sizes", + textSize = "lg", + textColor = Color.new(0.3, 0.7, 1, 1), +}) + +local sizeContainer = Gui.new({ + parent = example4, + positioning = "flex", + flexDirection = "vertical", + gap = 5, +}) + +local sizes = { "xs", "sm", "md", "lg", "xl", "xxl" } +for _, size in ipairs(sizes) do + Gui.new({ + parent = sizeContainer, + text = "Text size: " .. size, + textSize = size, + textColor = Color.new(0.9, 0.9, 0.95, 1), + -- fontFamily = "default", -- Same font, different sizes + }) +end + +-- Instructions +Gui.new({ + parent = container, + text = "To use custom fonts: 1) Add font files to your project, 2) Define them in theme.fonts, 3) Reference by name in elements", + textSize = "sm", + textColor = Color.new(0.6, 0.6, 0.7, 1), + textAlign = "center", +}) + +-- LÖVE callbacks +function love.load() + print("Font Family Demo loaded") + print("Add your custom font files and update the theme definition to see custom fonts in action") +end + +function love.update(dt) + Gui.update(dt) +end + +function love.draw() + love.graphics.clear(0.05, 0.05, 0.08, 1) + Gui.draw() +end + +function love.resize(w, h) + Gui.resize() +end diff --git a/themes/space.lua b/themes/space.lua index 6b7906a..22b979a 100644 --- a/themes/space.lua +++ b/themes/space.lua @@ -16,7 +16,6 @@ end return { name = "Space Theme", - components = { -- Panel component panel = { @@ -184,4 +183,11 @@ return { text = Color.new(0.80, 0.90, 1.00), -- soft cool-white for general text textDark = Color.new(0.35, 0.40, 0.45), -- dimmed gray-blue for secondary text }, + + -- 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", + }, }