Files
FlexLove/themes/README.md
Michael Freno 5026f780cf simplifying
2025-11-13 17:39:26 -05:00

424 lines
12 KiB
Markdown

# FlexLove Theme System
## Overview
FlexLove supports a flexible 9-patch (NinePatch) 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
You have **three ways** to organize your theme images:
### Option 1: Separate Images Per Component
Each component gets its own image file:
```
themes/
panel.png (24x24 pixels - 9-patch for panels)
button_normal.png (24x24 pixels - 9-patch for buttons)
button_hover.png (24x24 pixels - hover state)
button_pressed.png (24x24 pixels - pressed state)
input.png (24x24 pixels - 9-patch for inputs)
```
**Theme definition:**
```lua
return {
name = "My Theme",
components = {
panel = {
atlas = "themes/panel.png",
regions = { ... }
},
button = {
atlas = "themes/button_normal.png",
regions = { ... },
states = {
hover = {
atlas = "themes/button_hover.png",
regions = { ... }
},
pressed = {
atlas = "themes/button_pressed.png",
regions = { ... }
}
}
}
}
}
```
### Option 2: Single Atlas
All components in one texture atlas:
```
themes/
default_atlas.png (96x48 pixels containing all components)
```
**Theme definition:**
```lua
return {
name = "My Theme",
atlas = "themes/default_atlas.png", -- Global atlas
components = {
panel = {
regions = {
topLeft = {x=0, y=0, w=8, h=8},
-- ... regions reference positions in atlas
}
},
button = {
regions = {
topLeft = {x=24, y=0, w=8, h=8}, -- Different position in same atlas
-- ...
}
}
}
}
```
### Option 3: Hybrid
Mix global atlas with component-specific images:
```lua
return {
name = "My Theme",
atlas = "themes/global_atlas.png", -- Fallback atlas
components = {
panel = {
-- Uses global atlas
regions = {x=0, y=0, ...}
},
button = {
atlas = "themes/button.png", -- Override with specific image
regions = {x=0, y=0, ...}
}
}
}
```
## 9-patch Structure
Each component image is divided into 9 regions:
```
┌─────┬──────────┬─────┐
│ TL │ TC │ TR │ (Top: fixed height)
├─────┼──────────┼─────┤
│ ML │ MC │ MR │ (Middle: stretches)
├─────┼──────────┼─────┤
│ BL │ BC │ BR │ (Bottom: fixed height)
└─────┴──────────┴─────┘
Fixed Stretch Fixed
```
- **Corners** (TL, TR, BL, BR): Fixed size, never stretched
- **Edges** (TC, BC, ML, MR): Stretched in one direction
- **Center** (MC): Stretched in both directions
## Creating 9-patch images
I would recommend using [android studio](https://developer.android.com/studio) to create these, you can read
about how to do it in the editor [here](https://developer.android.com/studio/write/draw9patch)
### Image Requirements
1. **Format**: PNG with transparency
2. **Color Mode**: RGBA
3. **Border Style**: Draw borders in the corner/edge regions
4. **Center**: Can be solid color or transparent
## Example: Creating a Button Image
For a button with rounded corners and a border:
```
button_normal.png (24x24 pixels)
Pixel layout:
┌────────┬────────────┬────────┐
│ ●●●●●● │ ██████████ │ ●●●●●● │ 8px
│ ●●●●●● │ ██████████ │ ●●●●●● │
├────────┼────────────┼────────┤
│ ██████ │ ░░░░░░░░░░ │ ██████ │ 8px (stretch)
│ ██████ │ ░░░░░░░░░░ │ ██████ │
├────────┼────────────┼────────┤
│ ●●●●●● │ ██████████ │ ●●●●●● │ 8px
│ ●●●●●● │ ██████████ │ ●●●●●● │
└────────┴────────────┴────────┘
8px 8px(stretch) 8px
Legend:
● = Corner (fixed)
█ = Border edge (stretched)
░ = Fill/background (stretched both ways)
```
## Usage in Code
```lua
local FlexLove = require("FlexLove")
local Theme = FlexLove.Theme
local Color = FlexLove.Color
FlexLove.init({
baseScale = { width = 1920, height = 1080 },
theme = "my_theme" -- only supports autoload of one theme
immediateMode = true -- Optional: enable immediate mode (default: false)
})
-- you can dynamically add more
Theme.load("my_theme2")
-- Create themed button
local button_my_theme = FlexLove.new({
width = 150,
height = 40,
text = "Click Me",
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
onEvent = function(element, event)
if event.type == "click" then
print("Clicked!")
end
end
})
-- set active to switch between
Theme.setActive("my_theme2")
local button_my_theme2 = FlexLove.new({
width = 150,
height = 40,
text = "Click Me",
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
onEvent = function(element, event)
if event.type == "click" then
print("Clicked!")
end
end
})
```
## Rendering Layers
Elements with themes render in this order:
1. **backgroundColor** - Rendered first (behind everything)
2. **Theme 9-patch** - 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
Themes automatically handle visual states for interactive elements:
- **normal**: Default appearance
- **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:
```lua
button = {
atlas = "themes/button_normal.png",
regions = { ... },
states = {
hover = {
atlas = "themes/button_hover.png",
regions = { ... }
},
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
-- ...
})
```
## Corner Scaling
By default, 9-patch corners and non-stretched edges are rendered at their original pixel size (1:1). You can scale corners using a numeric multiplier:
### Corner Scaling
```lua
-- themes/my_theme.lua
return {
name = "My Theme",
components = {
button = {
atlas = "themes/button.png",
insets = { left = 8, top = 8, right = 8, bottom = 8 },
scaleCorners = 2, -- Scale corners by 2x (numeric multiplier)
scalingAlgorithm = "bilinear" -- "bilinear" (smooth) or "nearest" (sharp/pixelated)
}
}
}
```
**`scaleCorners` values:**
- Number (e.g., `2`, `1.5`, `0.5`) - Direct scale multiplier
- `2` = double size
- `0.5` = half size
- `1` = original size
- `nil` (default) - No scaling, 1:1 pixel perfect
### Scaling Algorithms
- **`bilinear`** (default): Smooth interpolation between pixels. Best for most use cases.
- **`nearest`**: Nearest-neighbor sampling. Best for pixel art that should maintain sharp edges.
### When to Use Corner Scaling
- **Pixel art themes**: Use `scaleCorners = 2` with `scalingAlgorithm = "nearest"` to maintain crisp pixel boundaries
- **High DPI displays**: Use `scaleCorners = 1.5` or `2` with `scalingAlgorithm = "bilinear"` for smooth scaling
- **Fixed-size UI**: Keep `scaleCorners = nil` (default) for pixel-perfect rendering at original size
### Per-State Scaling
You can also set scaling per-state:
```lua
button = {
atlas = "themes/button_normal.png",
scaleCorners = 2,
scalingAlgorithm = "bilinear",
states = {
hover = {
atlas = "themes/button_hover.png",
scaleCorners = 2.5, -- Can override per state
scalingAlgorithm = "nearest" -- Different algorithm for this state
}
}
}
```
## Tips
1. **Start Simple**: Begin with one component (button) before creating a full theme
2. **Test Scaling**: Make sure your 9-patch regions stretch properly at different sizes
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
7. **Corner Scaling**: Enable for pixel art or responsive UIs; disable for pixel-perfect rendering
## Tools for Creating Atlases
- **TexturePacker**: Professional sprite sheet tool
- **Aseprite**: Pixel art editor with export options
- **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
- `space.lua` - Complete theme definition example
- `ThemeSystemDemo.lua` - Interactive demo of theme system
- `ThemeLayeringDemo.lua` - Demo of backgroundColor/theme/border layering