Michael Freno e61fb91cac oops
2025-11-13 09:25:57 -05:00
2025-11-13 09:11:44 -05:00
2025-11-11 13:17:45 -05:00
2025-11-13 09:11:44 -05:00
2025-09-15 13:50:21 -04:00
2025-11-13 09:25:57 -05:00
2025-09-12 16:41:11 -04:00
2025-11-13 09:11:44 -05:00

FlexLöve v0.1.0

A comprehensive UI library providing flexbox/grid layouts, theming, animations, and event handling for LÖVE2D games.

FlexLöve is a lightweight, flexible GUI library for Löve2D that implements a flexbox-based layout system. It provides a simple way to create and manage UI elements with automatic layout calculations, animations, theming, and responsive design. Immediate mode support is now included (retained is default).

⚠️ Development Status

This library is under active development. While many features are functional, some aspects may change or have incomplete/broken implementations.

Coming Soon

The following features are currently being actively developed:

  • Animations: Simple to use animations for UI transitions and effects
  • Generic Image Support: Enhanced image rendering capabilities and utilities

Features

  • Flexbox Layout: Modern flexbox layouts for UI elements with full flex properties
  • Grid Layout: CSS-like (but simplified) grid system for structured layouts
  • Element Management: Hierarchical element structures with automatic sizing
  • Interactive Elements: Buttons with click detection, event system, and callbacks
  • Theme System: 9-patch (NinePatch) theming with state support (normal, hover, pressed, disabled)
  • Android 9-Patch Auto-Parsing: Automatic parsing of *.9.png files with multi-region support
  • Animations: Built-in animation support for transitions and effects
  • Responsive Design: Automatic resizing with viewport units (vw, vh, %)
  • Color Handling: Utility classes for managing colors in various formats
  • Text Rendering: Flexible text display with alignment and auto-scaling
  • Corner Radius: Rounded corners with individual corner control
  • Advanced Positioning: Absolute, relative, flex, and grid positioning modes

Installation

Add the modules directory and FlexLove.lua into your project and require it:

local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color

Quick Start

local FlexLove = require("FlexLove")

-- Initialize with base scaling and theme
FlexLove.Gui.init({
  baseScale = { width = 1920, height = 1080 },
  theme = "space"
  immediateMode = true -- Optional: enable immediate mode (default: false)
})

-- Create a button with flexbox layout
local button = FlexLove.Element.new({
  width = "20vw",
  height = "10vh",
  backgroundColor = FlexLove.Color.new(0.2, 0.2, 0.8, 1),
  text = "Click Me",
  textSize = "md",
  themeComponent = "button",
  onEvent = function(element, event)
    print("Button clicked!")
  end
})

-- In your love.update and love.draw:
function love.update(dt)
  FlexLove.Gui.update(dt)
end

function love.draw()
  FlexLove.Gui.draw()
end

API Conventions

Method Patterns

  • Constructors: ClassName.new(props) → instance
  • Static Methods: ClassName.methodName(args) → result
  • Instance Methods: instance:methodName(args) → result
  • Getters: instance:getPropertyName() → value
  • Internal Fields: _fieldName (private, do not access directly)
  • Error Handling: Constructors throw errors, utility functions return nil + error string

Return Value Patterns

  • Single Success: return value
  • Success/Failure: return result, errorMessage (nil on success for error)
  • Multiple Values: return value1, value2 (documented in @return)
  • Constructors: Always return instance (never nil)

Core Concepts

Immediate Mode vs Retained Mode

FlexLöve supports both immediate mode and retained mode UI paradigms, giving you flexibility in how you structure your UI code:

Retained Mode (Default)

In retained mode, you create elements once and they persist across frames. The library manages the element hierarchy, but you must manage changes in element state by .

local someGameState = true
-- Create elements once (e.g., in love.load)
local button1 = FlexLove.Element.new({
  text = "Button 1",
  disabled = someGameState,
  onEvent = function() print("Clicked!") end
})
-- ... other things ... --
local someGameState = false  -- button1 will not change its disabled state, you need a way to update the element

local button2 = FlexLove.Element.new({
  text = "Click to activate button 1",
  onEvent = function(_, event)
    if event.type == "release" then -- only fire on mouse release
      button1.disabled = false -- this will actual update the element
    end
  end
})
-- ... other things ... --
local someGameState = false  -- button1 will not change its disabled state, you need a way to update the element

local button2 = FlexLove.Element.new({
  text = "Click to activate button 1",
  onEvent = function(_, event)
    if event.type == "release" then -- only fire on mouse release
      button1.disabled = false -- this will actual update the element
    end
  end
})

Best for:

  • Complex UIs with many persistent elements
  • Elements that maintain state over time
  • UIs that don't change frequently

Immediate Mode

In immediate mode, you recreate UI elements every frame based on your application state. This approach can be simpler for dynamic UIs that change frequently. There is of course some overhead for this, which is why it is not the default behavior.

-- Recreate UI every frame
local someGameState = true
-- Create elements once (e.g., in love.load)
local button1 = FlexLove.Element.new({
  text = "Button 1",
  disabled = someGameState,
  onEvent = function() print("Clicked!") end
})
-- ... other things ... --
local someGameState = false  -- button1 in immediate mode will have its state updated 

local button2 = FlexLove.Element.new({
  text = "Click to activate button 1",
  onEvent = function(_, event)
    if event.type == "release" then -- only fire on mouse release
      button1.disabled = false -- this will also update the element
    end
  end
})

Best for:

  • Simple UIs that change frequently
  • Procedurally generated interfaces
  • Debugging and development tools
  • UIs driven directly by application state

You should be able to mix both modes in the same application - use retained mode for your main UI and immediate mode for debug overlays or dynamic elements, though this hasn't been tested.

Element Properties

Common properties for all elements:

{
  -- Positioning & Size
  x = 0,                    -- X position (number or string with units)
  y = 0,                    -- Y position
  width = 100,              -- Width (number, string, or "auto")
  height = 100,             -- Height
  z = 0,                    -- Z-index for layering
  
  -- Visual Styling
  backgroundColor = Color.new(0, 0, 0, 0),  -- Background color
  cornerRadius = 0,         -- Uniform radius or {topLeft, topRight, bottomLeft, bottomRight}
  border = {},              -- {top, right, bottom, left} boolean flags
  borderColor = Color.new(0, 0, 0, 1),
  opacity = 1,              -- 0 to 1
  
  -- Layout
  positioning = "flex",     -- "absolute", "relative", "flex", or "grid"
  padding = {},             -- {top, right, bottom, left} or shortcuts
  margin = {},              -- {top, right, bottom, left} or shortcuts
  gap = 10,                 -- Space between children
  
  -- Flexbox Properties
  flexDirection = "horizontal",  -- "horizontal" or "vertical"
  justifyContent = "flex-start", -- Main axis alignment
  alignItems = "stretch",        -- Cross axis alignment
  flexWrap = "nowrap",           -- "nowrap" or "wrap"
  
  -- Grid Properties
  gridRows = 1,
  gridColumns = 1,
  rowGap = 10,
  columnGap = 10,
  
  -- Text
  text = "Hello",
  textColor = Color.new(1, 1, 1, 1),
  textAlign = "start",      -- "start", "center", "end"
  textSize = "md",          -- Number or preset ("xs", "sm", "md", "lg", "xl", etc.)
  
  -- Theming
  theme = "space",          -- Theme name
  themeComponent = "button", -- Component type from theme
  
  -- Interaction
  onEvent = function(element, event) end,
  disabled = false,
  disableHighlight = false, -- Disable pressed overlay (auto-true for themed elements)
  
  -- Hierarchy
  parent = nil,             -- Parent element
}

Layout Modes

Absolute Positioning

local element = Gui.new({
  positioning = "absolute",
  x = 100,
  y = 50,
  width = 200,
  height = 100
})

Flexbox Layout

local container = Gui.new({
  positioning = "flex",
  flexDirection = "horizontal",
  justifyContent = "center",
  alignItems = "center",
  gap = 10
})

Grid Layout

local grid = Gui.new({
  positioning = "grid",
  gridRows = 3,
  gridColumns = 3,
  rowGap = 10,
  columnGap = 10
})

Corner Radius

Supports uniform or individual corner radii:

-- Uniform radius
cornerRadius = 15

-- Individual corners
cornerRadius = {
  topLeft = 20,
  topRight = 10,
  bottomLeft = 10,
  bottomRight = 20
}

Theme System

To create a theme explore themes/space.lua as a reference

Load and apply themes for consistent styling:

local Theme = FlexLove.Theme

-- Load a theme
Theme.load("space")
Theme.setActive("space")

-- Use theme on elements
local button = Gui.new({
  width = 200,
  height = 60,
  text = "Themed Button",
  themeComponent = "button",  -- Uses "button" component from active theme
  backgroundColor = Color.new(0.5, 0.5, 1, 0.3)  -- Renders behind theme
})

Android 9-Patch Support

FlexLove automatically parses Android 9-patch (*.9.png) files:

-- Theme definition with auto-parsed 9-patch
{
  name = "My Theme",
  components = {
    button = {
      atlas = "themes/mytheme/button.9.png"
      -- insets automatically extracted from 9-patch borders
      -- supports multiple stretch regions for complex scaling
    },
    panel = {
      atlas = "themes/mytheme/panel.png",
      insets = { left = 20, top = 20, right = 20, bottom = 20 }
      -- manual insets still supported (overrides auto-parsing)
    }
  }
}

9-Patch Format:

  • Files ending in .9.png are automatically detected and parsed
  • Guide pixels are automatically removed - the 1px border is stripped during loading
  • Top/left borders define stretchable regions (black pixels)
  • Bottom/right borders define content padding (optional) - automatically applied to child positioning
  • Supports multiple non-contiguous stretch regions
  • Manual insets override auto-parsing when specified

Scaling Corners:

{
  button = {
    atlas = "themes/mytheme/button.9.png",
    scaleCorners = 2  -- Scale corners by 2x (number = direct multiplier)
  }
}
  • scaleCorners accepts a number (e.g., 2 = 2x size, 0.5 = half size)
  • Default: nil (no scaling, 1:1 pixel perfect)
  • Corners scale uniformly while edges stretch as defined by guides

Themes support state-based rendering:

  • normal - Default state
  • hover - Mouse over element
  • pressed - Element being clicked
  • disabled - Element is disabled
  • active - Element is active/focused

Event System

Enhanced event handling with detailed event information:

onEvent = function(element, event)
  -- event.type: "click", "press", "release", "rightclick", "middleclick"
  -- event.button: 1 (left), 2 (right), 3 (middle)
  -- event.x, event.y: Mouse position
  -- event.clickCount: Number of clicks (for double-click detection)
  -- event.modifiers: { shift, ctrl, alt, gui }
  
  if event.type == "click" and event.modifiers.shift then
    print("Shift-clicked!")
  end
end

Input Fields

FlexLöve provides text input support with single-line (and multi-line coming soon) fields:

-- Create a text input field
local input = Gui.new({
  x = 10,
  y = 10,
  width = 200,
  height = 30,
  editable = true,
  text = "Type here...",
  placeholder = "Enter text",
  textColor = Color.new(1, 1, 1, 1),
  onTextChange = function(element, newText, oldText)
    print("Text changed:", newText)
  end
})

local textArea = Gui.new({
  x = 10,
  y = 50,
  width = 300,
  height = 150,
  editable = true,
  multiline = true,
  text = "",
  placeholder = "Enter multiple lines..."
})

Important: To enable key repeat for navigation keys (arrows, backspace, delete), add this to your love.load():

function love.load()
  love.keyboard.setKeyRepeat(true)
end

Input Properties:

  • editable - Enable text input (default: false)
  • multiline - Allow multiple lines (default: false)
  • placeholder - Placeholder text when empty
  • maxLength - Maximum character count
  • passwordMode - Hide text with bullets
  • selectOnFocus - Select all text when focused

Input Callbacks:

  • onTextChange(element, newText, oldText) - Called when text changes
  • onTextInput(element, text) - Called for each character input
  • onEnter(element) - Called when Enter is pressed (single-line only)
  • onFocus(element) - Called when input gains focus
  • onBlur(element) - Called when input loses focus

Features:

  • Cursor positioning and blinking
  • Text selection (mouse and keyboard)
  • Copy/Cut/Paste (Ctrl+C/X/V)
  • Word navigation (Ctrl+Arrow keys)
  • Select all (Ctrl+A)
  • Automatic text scrolling to keep cursor visible
  • UTF-8 support

Responsive Units

Support for viewport-relative units:

local element = Gui.new({
  width = "50vw",   -- 50% of viewport width
  height = "30vh",  -- 30% of viewport height
  x = "25%",        -- 25% of parent width
  textSize = "3vh"  -- 3% of viewport height
})

Animations

Create smooth transitions:

local Animation = FlexLove.Animation

-- Fade animation
local fadeIn = FlexLove.Animation.fade(1.0, 0, 1)
fadeIn:apply(element)

-- Scale animation
local scaleUp = FlexLove.Animation.scale(0.5,
  { width = 100, height = 50 },
  { width = 200, height = 100 }
)
scaleUp:apply(element)

-- Custom animation with easing
local customAnim = FlexLove.Animation.new({
  duration = 1.0,
  start = { opacity = 0, width = 100 },
  final = { opacity = 1, width = 200 },
  easing = "easeInOutCubic"
})
customAnim:apply(element)

Creating Colors

-- From RGB values (0-1 range)
local red = FlexLove.Color.new(1, 0, 0, 1)

-- From hex string
local blue = FlexLove.Color.fromHex("#0000FF")
local semiTransparent = FlexLove.Color.fromHex("#FF000080")

API Reference

Gui (Main Module)

  • Gui.init(props) - Initialize GUI system with base scale
  • Gui.new(props) - Create a new element
  • Gui.update(dt) - Update all elements
  • Gui.draw() - Draw all elements
  • Gui.resize() - Handle window resize

Color

  • Color.new(r, g, b, a) - Create color (values 0-1)
  • Color.fromHex(hex) - Create from hex string
  • Color:toHex() - Convert to hex string
  • Color:toRGBA() - Get RGBA values

Theme

  • Theme.load(name) - Load theme by name
  • Theme.setActive(name) - Set active theme
  • Theme.getActive() - Get current active theme

Animation

  • Animation.new(props) - Create custom animation
  • Animation.fade(duration, from, to) - Fade animation
  • Animation.scale(duration, from, to) - Scale animation

Enums

TextAlign

  • START - Align to start
  • CENTER - Center align
  • END - Align to end
  • JUSTIFY - Justify text

Positioning

  • ABSOLUTE - Absolute positioning
  • RELATIVE - Relative positioning
  • FLEX - Flexbox layout
  • GRID - Grid layout

FlexDirection

  • HORIZONTAL - Horizontal flex
  • VERTICAL - Vertical flex

JustifyContent

  • FLEX_START - Align to start
  • CENTER - Center align
  • FLEX_END - Align to end
  • SPACE_AROUND - Space around items
  • SPACE_BETWEEN - Space between items
  • SPACE_EVENLY - Even spacing

AlignItems / AlignSelf

  • STRETCH - Stretch to fill
  • FLEX_START - Align to start
  • FLEX_END - Align to end
  • CENTER - Center align
  • BASELINE - Baseline align

FlexWrap

  • NOWRAP - No wrapping
  • WRAP - Wrap items

Examples

The examples/ directory contains comprehensive demos:

  • EventSystemDemo.lua - Event handling and callbacks
  • CornerRadiusDemo.lua - Rounded corners showcase
  • ThemeLayeringDemo.lua - Theme system with layering
  • DisableHighlightDemo.lua - Highlight control
  • SimpleGrid.lua - Grid layout examples
  • TextSizePresets.lua - Text sizing options
  • OnClickAnimations.lua - Animation examples
  • ZIndexDemo.lua - Layering demonstration

Testing

Run tests with:

lua testing/runAll.lua
# or a specific test:
lua testing/__tests__/<specific_test>

Compatibility

Compatibility:

  • Lua: 5.1+
  • LÖVE: 11.x (tested)
  • LuaJIT: Compatible

License

MIT License - see LICENSE file for details.

Contributing

This library is under active development. Contributions, bug reports, and feature requests are welcome!

Description
No description provided
Readme MIT 59 MiB
Languages
Lua 98.3%
Shell 1.7%