diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..aee4ce5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,165 @@ +# Changelog + +All notable changes to FlexLove will be documented in this file. + +## [Unreleased] + +### Added +- **Corner Radius Support**: Added `cornerRadius` property for rounded corners + - Supports uniform radius (single number) or individual corners (table) + - Automatically clips children to parent's rounded corners using stencil buffer + - Works with backgroundColor, borders, and themes + - Added `CornerRadiusDemo.lua` example + +- **Disable Highlight Property**: Added `disableHighlight` property to control pressed overlay + - Automatically defaults to `true` when `themeComponent` is set + - Can be explicitly overridden for custom behavior + - Added `DisableHighlightDemo.lua` example + +- **Theme Layering System**: Improved rendering order for better visual control + - Layer 1: backgroundColor (behind everything) + - Layer 2: Theme 9-slice (on top of backgroundColor) + - Layer 3: Borders (on top of theme) + - Layer 4: Text (on top of everything) + - Added `ThemeLayeringDemo.lua` example + +- **Rounded Rectangle Helper**: Added `RoundedRect` module for smooth corner rendering + - `RoundedRect.draw()` - Draw filled or outlined rounded rectangles + - `RoundedRect.getPoints()` - Generate polygon points for rounded shapes + - `RoundedRect.stencilFunction()` - Create stencil functions for clipping + +### Changed +- **Property Rename**: `background` → `backgroundColor` (breaking change) + - More explicit and consistent with CSS naming + - All examples and tests updated + +- **Theme Rendering**: Themes now render on top of backgroundColor instead of replacing it + - Allows tinting themed elements with semi-transparent backgrounds + - Borders always render on top of themes when specified + +- **9-Slice Scaling**: Improved scaling algorithm for theme images + - Properly handles elements smaller than corner sizes + - Proportional corner scaling prevents overlap + - Better handling of edge cases + +### Fixed +- Theme images now scale correctly to any element size +- Corner regions no longer overlap when element is too small +- Stencil clipping properly contains children within rounded corners + +## Previous Changes + +### Theme System +- 9-slice/9-patch theming with state support +- State-based rendering (normal, hover, pressed, disabled, active) +- Flexible atlas organization (separate images, single atlas, or hybrid) +- Theme loading and activation system + +### Layout System +- Flexbox layout with full flex properties +- Grid layout system +- Absolute and relative positioning +- Automatic sizing and responsive design + +### Event System +- Enhanced event handling with detailed event information +- Support for click, press, release, right-click, middle-click +- Modifier key detection (shift, ctrl, alt, gui) +- Double-click and multi-click detection + +### Styling +- Color utilities with hex support +- Text rendering with alignment and auto-scaling +- Text size presets (xs, sm, md, lg, xl, etc.) +- Viewport-relative units (vw, vh, %) +- Border control (individual sides) +- Opacity support + +### Animation +- Built-in animation system +- Fade and scale animations +- Custom animation support with easing +- Smooth transitions + +## Migration Guide + +### From `background` to `backgroundColor` + +If you're updating existing code, replace all instances of `background` with `backgroundColor`: + +```lua +-- Old +Gui.new({ + background = Color.new(0.2, 0.2, 0.2, 1) +}) + +-- New +Gui.new({ + backgroundColor = Color.new(0.2, 0.2, 0.2, 1) +}) +``` + +### Using Corner Radius + +```lua +-- Uniform radius +Gui.new({ + cornerRadius = 10, + -- ... +}) + +-- Individual corners +Gui.new({ + cornerRadius = { + topLeft = 20, + topRight = 10, + bottomLeft = 10, + bottomRight = 20 + }, + -- ... +}) +``` + +### Controlling Highlight Overlay + +```lua +-- Regular button (highlight enabled by default) +Gui.new({ + callback = function(element, event) end +}) + +-- Themed button (highlight disabled by default) +Gui.new({ + themeComponent = "button", + callback = function(element, event) end +}) + +-- Override default behavior +Gui.new({ + themeComponent = "button", + disableHighlight = false, -- Force enable + callback = function(element, event) end +}) +``` + +## Compatibility + +- **Lua**: 5.1+ +- **LÖVE**: 11.x (tested) +- **LuaJIT**: Compatible + +## Known Issues + +- Some type annotations show warnings in Lua language servers (cosmetic only) +- Theme system requires proper atlas setup for visual feedback +- Very small elements may have visual artifacts with complex corner radii + +## Future Plans + +- Input field components +- Scrollable containers +- Dropdown menus +- Tooltip system +- More built-in themes +- Performance optimizations +- Additional layout modes diff --git a/README.md b/README.md index 6448584..b7106ec 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,381 @@ # FlexLöve -A Löve Gui based on Flexbox +A Löve GUI library based on Flexbox with theming and animation support -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 like windows and buttons with automatic layout calculations, animations, and responsive design. +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. -# NOTE: This library is no where near ready for sane use, there are many broken, incomplete and missing features. +## ⚠️ Development Status + +This library is under active development. While many features are functional, some aspects may change or have incomplete implementations. ## Features -- **Flexbox Layout**: Implement modern flexbox layouts for UI elements -- **Window Management**: Create hierarchical window structures with automatic sizing -- **Button System**: Interactive buttons with click detection and callbacks +- **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-slice/9-patch theming with state support (normal, hover, pressed, disabled) - **Animations**: Built-in animation support for transitions and effects -- **Responsive Design**: Automatic resizing based on window dimensions +- **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 options +- **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 -To use FlexLove, simply copy the `FlexLove.lua` file into your project's `libs` directory and require it in your main application: +Copy the `FlexLove.lua` file into your project and require it: ```lua local FlexLove = require("FlexLove") +local Gui = FlexLove.GUI +local Color = FlexLove.Color ``` -## Basic Usage +## Quick Start ```lua local FlexLove = require("FlexLove") -local Gui, Color = Flexlove.GUI, Flexlove.Color --- Create a main window -local mainWindow = Gui.new({ +local Gui = FlexLove.GUI +local Color = FlexLove.Color + +function love.load() + -- Initialize GUI system + Gui.init({ + baseScale = { width = 1920, height = 1080 } + }) + + -- Create a container + local container = Gui.new({ x = 100, y = 100, - w = 400, - h = 300, - background = Color.new(0.2, 0.2, 0.2, 1), - text = "Main Window", -}) - --- Create a button inside the window -local button = Gui.new({ - parent = mainWindow, - x = 50, - y = 50, - w = 100, - h = 40, + width = 400, + height = 300, + backgroundColor = Color.new(0.2, 0.2, 0.2, 1), + cornerRadius = 10, + border = { top = true, bottom = true, left = true, right = true }, + borderColor = Color.new(0.8, 0.8, 0.8, 1), + positioning = "flex", + flexDirection = "vertical", + gap = 10, + padding = { top = 20, right = 20, bottom = 20, left = 20 } + }) + + -- Create a button + local button = Gui.new({ + parent = container, + width = 200, + height = 50, text = "Click Me", - callback = function(button) + textAlign = "center", + textColor = Color.new(1, 1, 1, 1), + backgroundColor = Color.new(0.2, 0.6, 0.9, 1), + cornerRadius = 8, + callback = function(element, event) + if event.type == "click" then print("Button clicked!") - end, -}) + end + end + }) +end --- In your love.update function function love.update(dt) - Gui.update(dt) + Gui.update(dt) end --- In your love.draw function function love.draw() - Gui.draw() + Gui.draw() end + +function love.resize(w, h) + Gui.resize() +end +``` + +## Core Concepts + +### Element Properties + +Common properties for all elements: + +```lua +{ + -- 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 + callback = 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 +```lua +local element = Gui.new({ + positioning = "absolute", + x = 100, + y = 50, + width = 200, + height = 100 +}) +``` + +#### Flexbox Layout +```lua +local container = Gui.new({ + positioning = "flex", + flexDirection = "horizontal", + justifyContent = "center", + alignItems = "center", + gap = 10 +}) +``` + +#### Grid Layout +```lua +local grid = Gui.new({ + positioning = "grid", + gridRows = 3, + gridColumns = 3, + rowGap = 10, + columnGap = 10 +}) +``` + +### Corner Radius + +Supports uniform or individual corner radii: + +```lua +-- Uniform radius +cornerRadius = 15 + +-- Individual corners +cornerRadius = { + topLeft = 20, + topRight = 10, + bottomLeft = 10, + bottomRight = 20 +} +``` + +### Theme System + +Load and apply themes for consistent styling: + +```lua +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 +}) +``` + +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: + +```lua +callback = 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 +``` + +### Responsive Units + +Support for viewport-relative units: + +```lua +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: + +```lua +local Animation = FlexLove.Animation + +-- Fade animation +element.animation = Animation.fade(1.0, 0, 1) + +-- Scale animation +element.animation = Animation.scale(0.5, 1, 1.2) + +-- Custom animation +element.animation = Animation.new({ + duration = 1.0, + from = { width = 100, height = 50 }, + to = { width = 200, height = 100 }, + easing = "easeInOut" +}) ``` ## 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 -Utility class for color handling with RGB and RGBA components. - -- `Color.new(r, g, b, a)` - Create new color instance -- `Color.fromHex(hex)` - Convert hex string to color -- `Color:toHex()` - Convert color to hex string +- `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 -### Window +### Theme -Main window class for creating UI containers. - -- `Window.new(props)` - Create a new window -- `Window:addChild(child)` - Add child elements to the window -- `Window:draw()` - Draw the window and all its children -- `Window:update(dt)` - Update window state (propagates to children) -- `Window:resize(newWidth, newHeight)` - Resize window based on game size change - -### Button - -Interactive button element. - -- `Button.new(props)` - Create a new button -- `Button:draw()` - Draw the button -- `Button:update(dt)` - Update button state (handles click detection) -- `Button:updateText(newText, autoresize)` - Update button text +- `Theme.load(name)` - Load theme by name +- `Theme.setActive(name)` - Set active theme +- `Theme.getActive()` - Get current active theme ### Animation -Animation system for UI transitions. - -- `Animation.new(props)` - Create a new animation -- `Animation:interpolate()` - Get interpolated values during animation -- `Animation:apply(element)` - Apply animation to a GUI element -- `Animation.fade(duration, fromOpacity, toOpacity)` - Create a fade animation -- `Animation.scale(duration, fromScale, toScale)` - Create a scale animation +- `Animation.new(props)` - Create custom animation +- `Animation.fade(duration, from, to)` - Fade animation +- `Animation.scale(duration, from, to)` - Scale animation ## Enums -Predefined enums for various layout and styling options: +### TextAlign +- `START` - Align to start +- `CENTER` - Center align +- `END` - Align to end +- `JUSTIFY` - Justify text -- TextAlign: START, CENTER, END, JUSTIFY -- Positioning: ABSOLUTE, FLEX -- FlexDirection: HORIZONTAL, VERTICAL -- JustifyContent: FLEX_START, CENTER, SPACE_AROUND, FLEX_END, SPACE_EVENLY, SPACE_BETWEEN -- AlignItems: STRETCH, FLEX_START, FLEX_END, CENTER, BASELINE -- AlignSelf: AUTO, STRETCH, FLEX_START, FLEX_END, CENTER, BASELINE -- AlignContent: STRETCH, FLEX_START, FLEX_END, CENTER, SPACE_BETWEEN, SPACE_AROUND +### 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 -See the `examples/` directory for complete usage 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: +```bash +cd testing +lua runAll.lua +``` ## License MIT License - see LICENSE file for details. + +## Contributing + +This library is under active development. Contributions, bug reports, and feature requests are welcome! diff --git a/themes/README.md b/themes/README.md index 6aa7676..f6620db 100644 --- a/themes/README.md +++ b/themes/README.md @@ -2,7 +2,15 @@ ## Overview -FlexLove supports a flexible 9-slice/9-patch theming system that allows you to create scalable UI components using texture atlases. +FlexLove supports a flexible 9-slice/9-patch theming system that allows you to create scalable UI components using texture atlases. Themes provide state-based visual feedback and automatically handle element sizing. + +## Key Features + +- **9-Slice Scaling**: Images scale properly to any size without distortion +- **State Management**: Automatic visual feedback for hover, pressed, disabled, and active states +- **Layered Rendering**: Themes render on top of backgroundColor, with borders on top +- **Flexible Organization**: Use separate images, single atlas, or hybrid approach +- **Automatic Highlight Disable**: Elements with themes automatically disable the pressed overlay ## Image Organization Options @@ -166,6 +174,7 @@ Legend: local FlexLove = require("FlexLove") local Theme = FlexLove.Theme local Gui = FlexLove.GUI +local Color = FlexLove.Color -- Load theme Theme.load("my_theme") @@ -176,9 +185,14 @@ local button = Gui.new({ width = 150, height = 40, text = "Click Me", - theme = "button", -- Uses button component from active theme + textAlign = "center", + textColor = Color.new(1, 1, 1, 1), + backgroundColor = Color.new(0.2, 0.4, 0.8, 0.3), -- Shows behind theme + themeComponent = "button", -- Uses button component from active theme callback = function(element, event) - print("Clicked!") + if event.type == "click" then + print("Clicked!") + end end }) @@ -186,16 +200,34 @@ local button = Gui.new({ local panel = Gui.new({ width = 300, height = 200, - theme = "panel" + backgroundColor = Color.new(0.1, 0.1, 0.2, 0.5), -- Background tint + themeComponent = "panel" }) ``` +## Rendering Layers + +Elements with themes render in this order: + +1. **backgroundColor** - Rendered first (behind everything) +2. **Theme 9-slice** - Rendered on top of backgroundColor +3. **Borders** - Rendered on top of theme (if specified) +4. **Text** - Rendered last (on top of everything) + +This allows you to: +- Tint themed elements with backgroundColor +- Add custom borders on top of themes +- Layer visual effects + ## Component States -Buttons automatically handle three states: +Themes automatically handle visual states for interactive elements: + - **normal**: Default appearance -- **hover**: When mouse is over the button -- **pressed**: When button is being clicked +- **hover**: When mouse is over the element +- **pressed**: When element is being clicked +- **disabled**: When element.disabled = true +- **active**: When element.active = true (for inputs/focused elements) Define state-specific images in your theme: @@ -211,11 +243,110 @@ button = { pressed = { atlas = "themes/button_pressed.png", regions = { ... } + }, + disabled = { + atlas = "themes/button_disabled.png", + regions = { ... } } } } ``` +## Theme Definition Example + +```lua +-- themes/my_theme.lua +return { + name = "My Theme", + atlas = "themes/my_theme/atlas.png", + + components = { + panel = { + regions = { + topLeft = {x=0, y=0, w=8, h=8}, + topCenter = {x=8, y=0, w=8, h=8}, + topRight = {x=16, y=0, w=8, h=8}, + middleLeft = {x=0, y=8, w=8, h=8}, + middleCenter = {x=8, y=8, w=8, h=8}, + middleRight = {x=16, y=8, w=8, h=8}, + bottomLeft = {x=0, y=16, w=8, h=8}, + bottomCenter = {x=8, y=16, w=8, h=8}, + bottomRight = {x=16, y=16, w=8, h=8} + } + }, + + button = { + regions = { + topLeft = {x=24, y=0, w=8, h=8}, + topCenter = {x=32, y=0, w=8, h=8}, + topRight = {x=40, y=0, w=8, h=8}, + middleLeft = {x=24, y=8, w=8, h=8}, + middleCenter = {x=32, y=8, w=8, h=8}, + middleRight = {x=40, y=8, w=8, h=8}, + bottomLeft = {x=24, y=16, w=8, h=8}, + bottomCenter = {x=32, y=16, w=8, h=8}, + bottomRight = {x=40, y=16, w=8, h=8} + }, + states = { + hover = { + regions = { + -- Different region coordinates for hover state + topLeft = {x=48, y=0, w=8, h=8}, + -- ... etc + } + }, + pressed = { + regions = { + -- Different region coordinates for pressed state + topLeft = {x=72, y=0, w=8, h=8}, + -- ... etc + } + } + } + } + } +} +``` + +## Advanced Features + +### Automatic Highlight Disable + +Elements with `themeComponent` automatically set `disableHighlight = true` to prevent the default gray pressed overlay from interfering with theme visuals. You can override this: + +```lua +Gui.new({ + themeComponent = "button", + disableHighlight = false, -- Force enable highlight overlay + -- ... +}) +``` + +### Combining with Corner Radius + +You can use cornerRadius with themed elements: + +```lua +Gui.new({ + themeComponent = "button", + cornerRadius = 10, -- Clips theme to rounded corners + -- ... +}) +``` + +### Border Overlay + +Add custom borders on top of themes: + +```lua +Gui.new({ + themeComponent = "panel", + border = { top = true, bottom = true, left = true, right = true }, + borderColor = Color.new(1, 1, 0, 1), -- Yellow border on top of theme + -- ... +}) +``` + ## Tips 1. **Start Simple**: Begin with one component (button) before creating a full theme @@ -223,6 +354,7 @@ button = { 3. **Consistent Style**: Keep corner sizes consistent across components 4. **State Variations**: For button states, change colors/brightness rather than structure 5. **Atlas Packing**: Use tools like TexturePacker or Aseprite to create efficient atlases +6. **Transparency**: Use semi-transparent backgroundColor to tint themed elements ## Tools for Creating Atlases @@ -231,8 +363,15 @@ button = { - **Shoebox**: Free sprite sheet packer - **GIMP/Photoshop**: Manual layout with guides +## Example Themes + +See the `space/` directory for a complete theme example with: +- Panel component +- Button component with states (normal, hover, pressed, disabled) +- Compressed and uncompressed versions + ## See Also -- `default.lua` - Example theme with single atlas -- `separate_images_example.lua` - Example with separate images per component +- `space.lua` - Complete theme definition example - `ThemeSystemDemo.lua` - Interactive demo of theme system +- `ThemeLayeringDemo.lua` - Demo of backgroundColor/theme/border layering