867 lines
26 KiB
Markdown
867 lines
26 KiB
Markdown
# GUI Library Documentation
|
||
|
||
A component-based UI framework for LÖVE2D (Love2D) built on top of the `multi` concurrency library. The library provides a scene graph with dual-dimension layout, event-driven input handling, and a rich set of built-in element types.
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Setup & Initialization](#setup--initialization)
|
||
2. [Core Concepts](#core-concepts)
|
||
- [The Scene Graph](#the-scene-graph)
|
||
- [Dual-Dimension Layout (DualDim)](#dual-dimension-layout-dualdim)
|
||
- [Element Types (Bitmask)](#element-types-bitmask)
|
||
- [Form Factors](#form-factors)
|
||
3. [Creating Elements](#creating-elements)
|
||
- [Frames](#frames)
|
||
- [Text Labels](#text-labels)
|
||
- [Text Buttons](#text-buttons)
|
||
- [Text Boxes (Input)](#text-boxes-input)
|
||
- [Image Labels](#image-labels)
|
||
- [Image Buttons](#image-buttons)
|
||
- [Videos](#videos)
|
||
4. [Layout & Positioning](#layout--positioning)
|
||
5. [Events & Connections](#events--connections)
|
||
- [Global GUI Events](#global-gui-events)
|
||
- [Per-Element Events](#per-element-events)
|
||
- [Hot Keys](#hot-keys)
|
||
6. [Element Methods](#element-methods)
|
||
- [Positioning & Sizing](#positioning--sizing)
|
||
- [Visual Properties](#visual-properties)
|
||
- [Hierarchy & Parenting](#hierarchy--parenting)
|
||
- [Utilities](#utilities)
|
||
7. [Text Elements](#text-elements)
|
||
- [Font Management](#font-management)
|
||
- [Text Box Internals](#text-box-internals)
|
||
8. [Image Elements](#image-elements)
|
||
9. [Clipping & Scissor](#clipping--scissor)
|
||
10. [Roundness & Shape](#roundness--shape)
|
||
11. [Aspect Ratio & Resize Handling](#aspect-ratio--resize-handling)
|
||
12. [The `apply` Helper](#the-apply-helper)
|
||
13. [Tagging System](#tagging-system)
|
||
14. [Cloning Elements](#cloning-elements)
|
||
15. [Processors & Threading](#processors--threading)
|
||
16. [Drawing Internals](#drawing-internals)
|
||
17. [Virtual GUI](#virtual-gui)
|
||
|
||
---
|
||
|
||
## Setup & Initialization
|
||
|
||
```lua
|
||
local gui = require("path.to.gui")
|
||
```
|
||
|
||
The library self-initializes on `require`. It hooks into LÖVE's callback system automatically (quit, resize, mouse, keyboard, touch, gamepad, etc.) and starts its internal update and draw processors.
|
||
|
||
In your `love.update` and `love.draw`:
|
||
|
||
```lua
|
||
function love.update(dt)
|
||
gui.update(dt)
|
||
end
|
||
|
||
function love.draw()
|
||
gui.draw()
|
||
end
|
||
```
|
||
|
||
> **Note:** The library hooks LÖVE callbacks via a `Hook` function that wraps any pre-existing handler you define. Define your own `love.*` callbacks **before** `require`-ing the library, or they will be chained automatically.
|
||
|
||
---
|
||
|
||
## Core Concepts
|
||
|
||
### The Scene Graph
|
||
|
||
The library maintains two root nodes:
|
||
|
||
| Root | Description |
|
||
|---|---|
|
||
| `gui` | The main scene root. All elements created with `gui:newXxx()` are parented here by default. |
|
||
| `gui.virtual` | A secondary root for off-screen or hidden elements. Children here are not drawn but still have their absolute positions updated. |
|
||
|
||
Elements form a tree. Every element has a `parent`, a `children` table, and inherits methods from `gui` via `__index`.
|
||
|
||
### Dual-Dimension Layout (DualDim)
|
||
|
||
Every element stores its position and size as a **dual dimension**: a combination of a scale component (relative to the parent) and an offset component (absolute pixels).
|
||
|
||
```
|
||
actualX = parent.w * scale.pos.x + offset.pos.x + parent.x
|
||
actualY = parent.h * scale.pos.y + offset.pos.y + parent.y
|
||
actualW = parent.w * scale.size.x + offset.size.x
|
||
actualH = parent.h * scale.size.y + offset.size.y
|
||
```
|
||
|
||
Constructor signature for `newDualDim` / all `newXxx` creation functions:
|
||
|
||
```
|
||
x, y, w, h -- pixel offset for position and size
|
||
sx, sy, sw, sh -- scale (0–1) for position and size
|
||
```
|
||
|
||
Examples:
|
||
|
||
```lua
|
||
-- 200×100 box at pixel position (50, 50):
|
||
gui:newFrame(50, 50, 200, 100)
|
||
|
||
-- Full-screen frame (uses scale only):
|
||
local f = gui:newFrame()
|
||
f:fullFrame() -- sets scale size to (1,1) and offset to (0,0,0,0)
|
||
|
||
-- Half-width, 40px tall, starting at 25% from left:
|
||
gui:newFrame(0, 100, 0, 40, 0.25, 0, 0.5, 0)
|
||
```
|
||
|
||
Retrieve the computed screen-space rectangle at any time:
|
||
|
||
```lua
|
||
local x, y, w, h = element:getAbsolutes()
|
||
```
|
||
|
||
### Element Types (Bitmask)
|
||
|
||
Types are stored as a bitmask so an element can have multiple roles:
|
||
|
||
| Constant | Value | Meaning |
|
||
|---|---|---|
|
||
| `gui.TYPE_FRAME` | 0 | Basic container |
|
||
| `gui.TYPE_IMAGE` | 1 | Renders an image |
|
||
| `gui.TYPE_TEXT` | 2 | Renders text |
|
||
| `gui.TYPE_BOX` | 4 | Text input cursor/selection overlay |
|
||
| `gui.TYPE_VIDEO` | 8 | Renders a video |
|
||
| `gui.TYPE_BUTTON` | 16 | Interactive button (sets hand cursor) |
|
||
| `gui.TYPE_ANIM` | 32 | Animation / spritesheet |
|
||
|
||
Test membership:
|
||
|
||
```lua
|
||
if element:hasType(gui.TYPE_TEXT) then ... end
|
||
if element:hasType(gui.TYPE_TEXT + gui.TYPE_BOX) then ... end -- is a text box
|
||
```
|
||
|
||
### Form Factors
|
||
|
||
Controls the shape used for both fills and hit-testing:
|
||
|
||
| Constant | Shape |
|
||
|---|---|
|
||
| `gui.FORM_RECTANGLE` | Rounded or plain rectangle (default) |
|
||
| `gui.FORM_CIRCLE` | Circle; `w` and `h` are set to `2*r` |
|
||
| `gui.FORM_ARC` | Arc segment |
|
||
|
||
---
|
||
|
||
## Creating Elements
|
||
|
||
All creation functions are called on a **parent** element (or on `gui` itself for top-level elements). The new element is automatically inserted into the parent's `children` table.
|
||
|
||
### Frames
|
||
|
||
A plain container with a background fill and optional border.
|
||
|
||
```lua
|
||
local frame = parent:newFrame(x, y, w, h, sx, sy, sw, sh)
|
||
```
|
||
|
||
A **virtual frame** is parented to `gui.virtual` regardless of the caller:
|
||
|
||
```lua
|
||
local vframe = parent:newVirtualFrame(x, y, w, h, sx, sy, sw, sh)
|
||
```
|
||
|
||
A **visual frame** is a regular frame tagged `"visual"`. Mouse events on it and its descendants are suppressed (useful for purely decorative overlays):
|
||
|
||
```lua
|
||
local overlay = parent:newVisualFrame(x, y, w, h, sx, sy, sw, sh)
|
||
```
|
||
|
||
### Text Labels
|
||
|
||
A non-interactive text element.
|
||
|
||
```lua
|
||
local label = parent:newTextLabel("Hello world", x, y, w, h, sx, sy, sw, sh)
|
||
```
|
||
|
||
### Text Buttons
|
||
|
||
A text element that fires pointer events and shows a hand cursor on hover.
|
||
|
||
```lua
|
||
local btn = parent:newTextButton("Click me", x, y, w, h, sx, sy, sw, sh)
|
||
btn.OnPressed(function(self, x, y) print("pressed!") end)
|
||
```
|
||
|
||
### Text Boxes (Input)
|
||
|
||
A single-line text input field.
|
||
|
||
```lua
|
||
local box = parent:newTextBox("default text", x, y, w, h, sx, sy, sw, sh)
|
||
box.OnReturn(function(self, text) print("Submitted:", text) end)
|
||
```
|
||
|
||
Keyboard navigation, backspace/delete, selection (click-drag or Ctrl+A), copy/paste/cut, and undo/redo are all handled automatically when the box has focus.
|
||
|
||
### Image Labels
|
||
|
||
A non-interactive image element.
|
||
|
||
```lua
|
||
local img = parent:newImageLabel("path/to/image.png", x, y, w, h, sx, sy, sw, sh)
|
||
```
|
||
|
||
GIF files are detected automatically by the `.gif` extension and animated.
|
||
|
||
### Image Buttons
|
||
|
||
An image element that fires pointer events and shows a hand cursor on hover.
|
||
|
||
```lua
|
||
local ibtn = parent:newImageButton("icon.png", x, y, w, h, sx, sy, sw, sh)
|
||
ibtn.OnPressed(function(self, x, y) print("image clicked") end)
|
||
```
|
||
|
||
### Videos
|
||
|
||
Wraps a LÖVE `Video` object.
|
||
|
||
```lua
|
||
local vid = parent:newVideo("clip.ogv", x, y, w, h, sx, sy, sw, sh)
|
||
vid:play()
|
||
vid.OnVideoFinished(function(self) print("done") end)
|
||
```
|
||
|
||
Video methods:
|
||
|
||
| Method | Description |
|
||
|---|---|
|
||
| `vid:setVideo(path_or_video)` | Load or swap the video source |
|
||
| `vid:play()` | Start playback |
|
||
| `vid:pause()` | Pause without rewinding |
|
||
| `vid:stop()` | Pause and rewind |
|
||
| `vid:rewind()` | Seek to start |
|
||
| `vid:seek(seconds)` | Jump to position |
|
||
| `vid:tell()` | Return current playback position (seconds) |
|
||
| `vid:getDuration()` | Return total duration (seconds) |
|
||
| `vid:setVolume(vol)` | Set audio volume (0–1) |
|
||
| `vid:getVideo()` | Return the underlying LÖVE Video object |
|
||
|
||
---
|
||
|
||
## Layout & Positioning
|
||
|
||
### Setting the Dual Dimension
|
||
|
||
```lua
|
||
-- Fires OnSizeChanged
|
||
element:setDualDim(x, y, w, h, sx, sy, sw, sh)
|
||
|
||
-- Silent version (no event)
|
||
element:rawSetDualDim(x, y, w, h, sx, sy, sw, sh)
|
||
|
||
-- Read back
|
||
local x, y, w, h, sx, sy, sw, sh = element:getDualDim()
|
||
```
|
||
|
||
Pass `nil` for any argument to keep the current value.
|
||
|
||
### Moving and Resizing
|
||
|
||
```lua
|
||
-- Delta move (fires OnPositionChanged)
|
||
element:move(dx, dy)
|
||
|
||
-- Delta resize (fires OnSizeChanged)
|
||
element:size(dw, dh)
|
||
|
||
-- Move but clamp to parent bounds
|
||
element:moveInBounds(dx, dy)
|
||
```
|
||
|
||
### Centering
|
||
|
||
```lua
|
||
element:centerX(true) -- horizontally center within parent
|
||
element:centerY(true) -- vertically center within parent
|
||
```
|
||
|
||
These attach internal loops that continuously recompute the offset whenever the element's size or position changes.
|
||
|
||
### Convenience
|
||
|
||
```lua
|
||
element:fullFrame() -- scale size (1,1), offset (0,0,0,0) — fills parent
|
||
```
|
||
|
||
### Dragging
|
||
|
||
```lua
|
||
element:enableDragging(button) -- button = love mouse button number (1=left, 2=right, …)
|
||
element:enableDragging(nil) -- disable dragging
|
||
```
|
||
|
||
While dragging, `OnDragging`, `OnDragStart`, and `OnDragEnd` are fired.
|
||
|
||
### Z-Order
|
||
|
||
```lua
|
||
element:topStack() -- move to end of parent.children (drawn last = on top)
|
||
element:bottomStack() -- move to front of parent.children (drawn first = behind)
|
||
```
|
||
|
||
---
|
||
|
||
## Events & Connections
|
||
|
||
Events use the `multi` connection system. Connect a handler by calling the connection as a function:
|
||
|
||
```lua
|
||
element.OnPressed(function(self, x, y, button, istouch, presses)
|
||
-- ...
|
||
end)
|
||
```
|
||
|
||
Connections support composition:
|
||
|
||
```lua
|
||
-- OR: fires when either fires
|
||
(connA + connB)(handler)
|
||
|
||
-- AND: fires only when both conditions are met
|
||
(connA * connB)(handler)
|
||
```
|
||
|
||
### Global GUI Events
|
||
|
||
These fire for the entire application window regardless of which element is focused.
|
||
|
||
| Event | LÖVE callback | Arguments |
|
||
|---|---|---|
|
||
| `gui.Events.OnQuit` | `love.quit` | — |
|
||
| `gui.Events.OnDirectoryDropped` | `love.directorydropped` | `dir` |
|
||
| `gui.Events.OnDisplayRotated` | `love.displayrotated` | `index, orient` |
|
||
| `gui.Events.OnFilesDropped` | `love.filedropped` | `file` |
|
||
| `gui.Events.OnFocus` | `love.focus` | `focused` |
|
||
| `gui.Events.OnMouseFocus` | `love.mousefocus` | `focused` |
|
||
| `gui.Events.OnResized` | `love.resize` | `w, h` |
|
||
| `gui.Events.OnVisible` | `love.visible` | `visible` |
|
||
| `gui.Events.OnKeyPressed` | `love.keypressed` | `key, scancode, isrepeat` |
|
||
| `gui.Events.OnKeyReleased` | `love.keyreleased` | `key, scancode` |
|
||
| `gui.Events.OnTextEdited` | `love.textedited` | `text, start, length` |
|
||
| `gui.Events.OnTextInputed` | `love.textinput` | `text` |
|
||
| `gui.Events.OnMouseMoved` | `love.mousemoved` | `x, y, dx, dy, istouch` |
|
||
| `gui.Events.OnMousePressed` | `love.mousepressed` | `x, y, button, istouch, presses` |
|
||
| `gui.Events.OnMouseReleased` | `love.mousereleased` | `x, y, button, istouch, presses` |
|
||
| `gui.Events.OnWheelMoved` | `love.wheelmoved` | `x, y` |
|
||
| `gui.Events.OnTouchMoved` | `love.touchmoved` | `id, x, y, dx, dy, pressure` |
|
||
| `gui.Events.OnTouchPressed` | `love.touchpressed` | `id, x, y, dx, dy, pressure` |
|
||
| `gui.Events.OnTouchReleased` | `love.touchreleased` | `id, x, y, dx, dy, pressure` |
|
||
| `gui.Events.OnGamepadPressed` | `love.gamepadpressed` | `joystick, button` |
|
||
| `gui.Events.OnGamepadReleased` | `love.gamepadreleased` | `joystick, button` |
|
||
| `gui.Events.OnGamepadAxis` | `love.gamepadaxis` | `joystick, axis, value` |
|
||
| `gui.Events.OnJoystickAdded` | `love.joystickadded` | `joystick` |
|
||
| `gui.Events.OnJoystickRemoved` | `love.joystickremoved` | `joystick` |
|
||
| `gui.Events.OnJoystickHat` | `love.joystickhat` | `joystick, hat, dir` |
|
||
| `gui.Events.OnJoystickPressed` | `love.joystickpressed` | `joystick, button` |
|
||
| `gui.Events.OnJoystickReleased` | `love.joystickreleased` | `joystick, button` |
|
||
| `gui.Events.OnCreated` | internal | `element` — fires when any element is created |
|
||
| `gui.Events.OnObjectFocusChanged` | internal | `old, new` — fires when click focus changes |
|
||
|
||
### Per-Element Events
|
||
|
||
These are attached to each element instance. All mouse/pointer events are automatically pre-filtered: they only fire when the element is `active` and (for most events) when the pointer is within the element's bounds.
|
||
|
||
| Event | Fires when… |
|
||
|---|---|
|
||
| `OnLoad` | (manual) element is "loaded" — user-defined |
|
||
| `OnPressed` | pointer pressed **inside** element |
|
||
| `OnPressedOuter` | pointer pressed **outside** element |
|
||
| `OnReleased` | pointer released **inside** element |
|
||
| `OnReleasedOuter` | pointer released **outside** (but was pressed inside) |
|
||
| `OnReleasedOther` | pointer released with no relevant press history |
|
||
| `OnDragStart` | drag begins (element must have `enableDragging` set) |
|
||
| `OnDragging` | pointer moves while dragging |
|
||
| `OnDragEnd` | drag ends |
|
||
| `OnEnter` | pointer enters the element bounds |
|
||
| `OnExit` | pointer leaves the element bounds |
|
||
| `OnMoved` | pointer moves while inside (or while dragging) |
|
||
| `OnWheelMoved` | scroll wheel moves while pointer is inside element |
|
||
| `OnSizeChanged` | `setDualDim` or `size` called |
|
||
| `OnPositionChanged` | `setDualDim` or `move` called |
|
||
| `OnDestroy` | element is about to be destroyed |
|
||
| `OnCreated` | element was created (forwarded from `gui.Events.OnCreated`) |
|
||
| `OnReturn` | (text boxes only) Enter/Return key pressed |
|
||
| `OnFontUpdated` | (text elements only) font changed via `setFont` |
|
||
| `OnVideoFinished` | (video elements only) video reaches its end |
|
||
| `OnLeftStickUp/Down/Left/Right` | gamepad left-stick events |
|
||
| `OnRightStickUp/Down/Left/Right` | gamepad right-stick events |
|
||
|
||
#### Hierarchy Mode
|
||
|
||
By default events fire if another element is not on top. Call:
|
||
|
||
```lua
|
||
element:respectHierarchy(false) -- events will fire regardless
|
||
```
|
||
|
||
to make `OnPressed`, `OnReleased`, `OnEnter`, and `OnMoved` skip when the element is covered by a sibling.
|
||
|
||
---
|
||
|
||
### Hot Keys
|
||
|
||
Register a keyboard shortcut that fires a connection:
|
||
|
||
```lua
|
||
local conn = element:setHotKey({"lctrl", "s"}) -- returns a connection
|
||
conn(function(ref) print("Ctrl+S on", ref) end)
|
||
```
|
||
|
||
You may pass an existing connection as the second argument to reuse it.
|
||
|
||
#### Built-in Hot Keys
|
||
|
||
| Hot Key | Trigger |
|
||
|---|---|
|
||
| `gui.HotKeys.OnSelectAll` | Ctrl+A |
|
||
| `gui.HotKeys.OnCopy` | Ctrl+C |
|
||
| `gui.HotKeys.OnPaste` | Ctrl+V |
|
||
| `gui.HotKeys.OnCut` | Ctrl+X |
|
||
| `gui.HotKeys.OnUndo` | Ctrl+Z |
|
||
| `gui.HotKeys.OnRedo` | Ctrl+Y / Ctrl+Shift+Z |
|
||
|
||
These are already wired to the currently-focused text box for standard editing operations.
|
||
|
||
---
|
||
|
||
## Element Methods
|
||
|
||
### Positioning & Sizing
|
||
|
||
| Method | Description |
|
||
|---|---|
|
||
| `el:getAbsolutes([transform])` | Returns `x, y, w, h` in screen space. Optional `transform` function is applied to each value. |
|
||
| `el:setDualDim(x,y,w,h,sx,sy,sw,sh)` | Set layout, fires `OnSizeChanged`. |
|
||
| `el:rawSetDualDim(...)` | Set layout, no event. |
|
||
| `el:getDualDim()` | Returns all 8 dual-dim components. |
|
||
| `el:move(dx, dy)` | Translate by delta, fires `OnPositionChanged`. |
|
||
| `el:size(dw, dh)` | Resize by delta, fires `OnSizeChanged`. |
|
||
| `el:moveInBounds(dx, dy)` | Translate while keeping element inside parent. |
|
||
| `el:fullFrame()` | Fill parent entirely. |
|
||
| `el:centerX(bool)` | Auto-center horizontally. |
|
||
| `el:centerY(bool)` | Auto-center vertically. |
|
||
| `el:getLocalCords(mx, my)` | Convert screen coordinates to element-local coordinates. |
|
||
|
||
### Visual Properties
|
||
|
||
| Property | Type | Default | Description |
|
||
|---|---|---|---|
|
||
| `color` | `{r,g,b}` | `{0.6, 0.6, 0.6}` | Background fill color |
|
||
| `borderColor` | `{r,g,b}` | black | Border color |
|
||
| `drawBorder` | boolean | `true` | Whether to draw the border |
|
||
| `visibility` | number | `1` | Background alpha (0–1) |
|
||
| `rotation` | number | `0` | Rotation in degrees |
|
||
| `active` | boolean | `true` | When `false`, element and all descendants ignore input |
|
||
| `visible` | boolean | `true` | Controls `getAllChildren` visibility filter |
|
||
| `ignore` | boolean | — | When `true`, element is skipped in coverage tests |
|
||
|
||
Set color (also sets `visibility` if a 4th component is present):
|
||
|
||
```lua
|
||
element:setColor("color", {1, 0, 0, 0.8})
|
||
element:setColor("borderColor", {0, 0, 0})
|
||
```
|
||
|
||
Apply a LÖVE shader:
|
||
|
||
```lua
|
||
element.shader = love.graphics.newShader(...)
|
||
```
|
||
|
||
Apply an effect wrapper (called around the draw call):
|
||
|
||
```lua
|
||
element.effect = function(drawFunc)
|
||
love.graphics.push()
|
||
-- setup
|
||
drawFunc()
|
||
love.graphics.pop()
|
||
end
|
||
```
|
||
|
||
Apply a post-draw hook:
|
||
|
||
```lua
|
||
element.post = function(self)
|
||
-- called after drawing, inside the same scissor/shader state
|
||
end
|
||
```
|
||
|
||
### Hierarchy & Parenting
|
||
|
||
| Method | Description |
|
||
|---|---|
|
||
| `el:setParent(newParent)` | Re-parent element. Pass `nil` to detach. |
|
||
| `el:getChildren()` | Returns direct children table. |
|
||
| `el:getAllChildren([includeHidden])` | Returns all visible descendants recursively. |
|
||
| `el:isDescendantOf(obj)` | Returns `true` if `obj` is an ancestor of `el`. |
|
||
| `el:topStack()` | Draw on top of siblings. |
|
||
| `el:bottomStack()` | Draw behind siblings. |
|
||
| `el:destroy()` | Destroy element, its children, and all connections. |
|
||
| `el:removeChildren()` | Destroy all children but leave element itself. |
|
||
| `el:isActive()` | `true` if `active` and not parented under `gui.virtual`. |
|
||
| `el:isOffScreen()` | `true` if element rect is entirely outside screen bounds. |
|
||
|
||
### Utilities
|
||
|
||
| Method | Description |
|
||
|---|---|
|
||
| `el:hasType(t)` | Bitmask type test. |
|
||
| `el:canPress(mx, my)` | `true` if point is inside element (respects clip area). |
|
||
| `el:isBeingCovered(mx, my)` | `true` if a sibling is in front of this element at the given point. |
|
||
| `el:intersecpt(x, y, w, h)` | Returns intersection rect with a given AABB. |
|
||
| `el:newThread(func)` | Spawn a coroutine-style thread scoped to this element. |
|
||
| `el:getObjectFocus()` | Returns the currently focused element. |
|
||
| `el:getProcessor()` | Returns the internal updater processor. |
|
||
|
||
---
|
||
|
||
## Text Elements
|
||
|
||
All text elements (`newTextLabel`, `newTextButton`, `newTextBox`) inherit from `newTextBase`.
|
||
|
||
### Properties
|
||
|
||
| Property | Type | Default | Description |
|
||
|---|---|---|---|
|
||
| `text` | string | — | Displayed string |
|
||
| `textColor` | `{r,g,b}` | black | Text color |
|
||
| `font` | Font | 12px default | LÖVE Font object |
|
||
| `align` | constant | `ALIGN_LEFT` | `gui.ALIGN_LEFT`, `ALIGN_CENTER`, `ALIGN_RIGHT` |
|
||
| `textOffsetX/Y` | number | `0` | Additional pixel offset for text drawing |
|
||
| `textScaleX/Y` | number | `1` | Scale applied to text rendering |
|
||
| `textShearingFactorX/Y` | number | `0` | Shearing factor for text transform |
|
||
| `textVisibility` | number | `1` | Text alpha (0–1) |
|
||
|
||
### Font Management
|
||
|
||
```lua
|
||
-- By size (default font)
|
||
element:setFont(14)
|
||
|
||
-- By path and size
|
||
element:setFont("fonts/myfont.ttf", 18)
|
||
|
||
-- By LÖVE font object
|
||
element:setFont(love.graphics.newFont("fonts/myfont.ttf", 18))
|
||
```
|
||
|
||
Automatically resize font to fill element bounds:
|
||
|
||
```lua
|
||
-- Binary-search fit between min and max size
|
||
element:fitFont(minSize, maxSize, {scale = 1})
|
||
-- Returns bestFont, bestSize
|
||
```
|
||
|
||
Center text vertically inside the element:
|
||
|
||
```lua
|
||
element:centerFont(y_offset)
|
||
```
|
||
|
||
Calculate where the top and bottom of rendered text actually are (pixel offsets within element):
|
||
|
||
```lua
|
||
local top, bottom = element:calculateFontOffset(font, adjust)
|
||
```
|
||
|
||
### Text Box Internals
|
||
|
||
| Property | Description |
|
||
|---|---|
|
||
| `cur_pos` | Integer cursor position (0 = before first character) |
|
||
| `selection` | `{start, stop}` character indices (may be reversed) |
|
||
| `bar_show` | `true` when the cursor bar should be visible (blinks via internal thread) |
|
||
| `doSelection` | `true` while a drag-selection is in progress |
|
||
|
||
Methods:
|
||
|
||
```lua
|
||
box:HasSelection() -- returns true/false
|
||
box:GetSelection() -- returns start, stop (always start ≤ stop)
|
||
box:GetSelectedText() -- returns selected substring
|
||
box:ClearSelection() -- clear selection state
|
||
```
|
||
|
||
---
|
||
|
||
## Image Elements
|
||
|
||
All image elements (`newImageLabel`, `newImageButton`) inherit from `newImageBase`.
|
||
|
||
### `setImage`
|
||
|
||
```lua
|
||
-- From a file path (PNG, JPG, etc.)
|
||
element:setImage("path/to/image.png")
|
||
|
||
-- GIF animation (auto-detected by extension)
|
||
element:setImage("path/to/anim.gif")
|
||
|
||
-- From a LÖVE Image object
|
||
element:setImage(loveImageObject)
|
||
```
|
||
|
||
### Properties
|
||
|
||
| Property | Description |
|
||
|---|---|
|
||
| `imageColor` | Tint color applied when drawing |
|
||
| `imageVisibility` | Image alpha (0–1) |
|
||
| `scaleX / scaleY` | Flip/scale. Negative values flip the axis. |
|
||
| `quad` | LÖVE Quad used for rendering (sub-region) |
|
||
|
||
### Flipping
|
||
|
||
```lua
|
||
element:flip(false) -- flip horizontally
|
||
element:flip(true) -- flip vertically
|
||
```
|
||
|
||
### Gradient
|
||
|
||
Apply a gradient as the image of any element:
|
||
|
||
```lua
|
||
element:applyGradient("horizontal", {r,g,b,a}, {r,g,b,a}, ...)
|
||
element:applyGradient("vertical", {r,g,b,a}, {r,g,b,a}, ...)
|
||
```
|
||
|
||
### Image Caching
|
||
|
||
```lua
|
||
-- Pre-load a single image into the cache
|
||
gui.cacheImage(gui, "path/to/img.png")
|
||
|
||
-- Pre-load multiple images; reports progress via OnStatus
|
||
gui.cacheImage(gui, {"img1.png", "img2.png"})
|
||
|
||
-- Tile helper: returns imagedata and quad
|
||
local imgdata, quad = gui:getTile("sheet.png", tileX, tileY, tileW, tileH)
|
||
```
|
||
|
||
---
|
||
|
||
## Clipping & Scissor
|
||
|
||
Clipping is set on a **parent** and affects all descendants:
|
||
|
||
```lua
|
||
parent.clipDescendants = true
|
||
```
|
||
|
||
During each draw pass, the parent propagates its screen-space rectangle to each child's `__variables.clip`. Children then apply LÖVE's scissor test to avoid drawing outside the parent.
|
||
|
||
---
|
||
|
||
## Roundness & Shape
|
||
|
||
```lua
|
||
-- Rounded corners
|
||
element:setRoundness(rx, ry, segments, side)
|
||
-- rx, ry: x/y radius (default 5)
|
||
-- segments: arc segments (default 30)
|
||
-- side: "top", "bottom", or true (all corners)
|
||
|
||
-- Directional override
|
||
element:setRoundnessDirection(horizontal, vertical)
|
||
```
|
||
|
||
Circle and arc shapes are set at creation time:
|
||
|
||
```lua
|
||
-- Circle
|
||
element:makeCircle(x, y, radius, sx, sy, sr, segments)
|
||
|
||
-- Arc
|
||
element:makeArc(arcType, x, y, radius, sx, sy, sr, startAngle, endAngle, segments)
|
||
-- arcType: "open", "closed", or "pie" (passed to love.graphics.arc)
|
||
-- Angles in radians
|
||
```
|
||
|
||
---
|
||
|
||
## Aspect Ratio & Resize Handling
|
||
|
||
Lock the root GUI to a design resolution:
|
||
|
||
```lua
|
||
gui:setAspectSize(1920, 1080) -- set design resolution
|
||
gui.aspect_ratio = true -- enable aspect-ratio mode
|
||
```
|
||
|
||
When the window resizes, the library calculates letterbox/pillarbox offsets and adjusts `gui.x`, `gui.y`, `gui.w`, `gui.h` (and the same on `gui.virtual`) so all elements remain proportional.
|
||
|
||
Disable it:
|
||
|
||
```lua
|
||
gui:setAspectSize(nil, nil)
|
||
gui.aspect_ratio = false
|
||
```
|
||
|
||
Utility to compute the scaled size manually:
|
||
|
||
```lua
|
||
local nw, nh, offsetX, offsetY = gui:GetSizeAdjustedToAspectRatio(windowW, windowH)
|
||
```
|
||
|
||
---
|
||
|
||
## The `apply` Helper
|
||
|
||
`gui.apply` is a batch property setter that inspects each field name for a prefix:
|
||
|
||
| Prefix | Meaning |
|
||
|---|---|
|
||
| `C_` | Connect to the named connection (value = handler function) |
|
||
| `I_` | Invoke the named method with args from a table |
|
||
| *(none)* | Direct assignment or smart detection (connection vs function vs value) |
|
||
|
||
```lua
|
||
gui.apply({
|
||
color = {1, 0, 0},
|
||
C_OnPressed = function(self) print("pressed") end,
|
||
I_setFont = {"fonts/bold.ttf", 16},
|
||
}, buttonA, buttonB, buttonC)
|
||
```
|
||
|
||
---
|
||
|
||
## Tagging System
|
||
|
||
Arbitrary string tags can be attached to any element:
|
||
|
||
```lua
|
||
element:setTag("draggable")
|
||
element:setTag("ui-panel")
|
||
|
||
element:hasTag("draggable") -- true / false (direct tag)
|
||
element:parentHasTag("ui-panel") -- true if any ancestor has the tag
|
||
```
|
||
|
||
The built-in `"visual"` tag suppresses all mouse event connections:
|
||
|
||
```lua
|
||
local deco = parent:newVisualFrame(...) -- automatically gets "visual" tag
|
||
```
|
||
|
||
---
|
||
|
||
## Cloning Elements
|
||
|
||
Deep-copy an element and optionally its connection handlers:
|
||
|
||
```lua
|
||
local copy = element:clone({
|
||
copyTo = targetParent, -- parent for the clone (default: gui.virtual)
|
||
connections = true, -- also copy connection handlers
|
||
})
|
||
```
|
||
|
||
`clone` recurses through all children. Connection handlers from the original are **bound** (not moved) to the clone's connections, so both elements remain independently connected.
|
||
|
||
---
|
||
|
||
## Processors & Threading
|
||
|
||
The library uses two internal processors from the `multi` library:
|
||
|
||
| Processor | Purpose |
|
||
|---|---|
|
||
| `updater` | Input hooks, hot keys, text-box blink, video completion, image loading |
|
||
| `drawer` | Per-frame draw loop, virtual element position pass |
|
||
|
||
Create a new processor that participates in `gui.update`:
|
||
|
||
```lua
|
||
local proc = gui:newProcessor("MyProcessor")
|
||
-- proc is a multi Processor; attach tasks/loops to it normally
|
||
```
|
||
|
||
Spawn a coroutine thread scoped to an element:
|
||
|
||
```lua
|
||
element:newThread(function(self, thread)
|
||
while true do
|
||
thread.sleep(1)
|
||
print("tick", self.text)
|
||
end
|
||
end)
|
||
```
|
||
|
||
Attach a per-frame update callback (called every update loop):
|
||
|
||
```lua
|
||
gui:OnUpdate(function(self, dt)
|
||
-- called every frame
|
||
end)
|
||
|
||
element:OnUpdate(function(self, dt)
|
||
-- called every frame with element as self
|
||
end)
|
||
```
|
||
|
||
Create a one-shot or reusable function that runs asynchronously:
|
||
|
||
```lua
|
||
local fn = gui.newFunction(function(arg1, arg2)
|
||
-- runs in updater context
|
||
end)
|
||
fn(arg1, arg2)
|
||
```
|
||
|
||
---
|
||
|
||
## Drawing Internals
|
||
|
||
The draw loop iterates `gui:getAllChildren()` each frame and calls `draw_handler` on each element in order (back-to-front).
|
||
|
||
`draw_handler` does, in order:
|
||
|
||
1. Compute and cache `child.x/y/w/h` via `getAbsolutes`.
|
||
2. Propagate clip rects to descendants if `clipDescendants` is set.
|
||
3. Activate shader if present.
|
||
4. Apply LÖVE scissor (clip or roundness-based).
|
||
5. Fill background with `child.color` and `child.visibility`.
|
||
6. Draw border with `child.borderColor`.
|
||
7. Handle special roundness sides ("top"/"bottom").
|
||
8. Dispatch to type-specific draw functions (video → image → text → box cursor/selection).
|
||
9. Call `child:post()` if defined.
|
||
10. Remove scissor and shader.
|
||
|
||
`gui.draw_handler` is exposed publicly so custom renderers can call it directly.
|
||
|
||
---
|
||
|
||
## Virtual GUI
|
||
|
||
`gui.virtual` is a root node whose children are never rendered on screen but still participate in the layout pass (absolute positions are computed). Use it to keep pre-built off-screen components ready to be re-parented:
|
||
|
||
```lua
|
||
-- Create off-screen
|
||
local popup = gui.virtual:newFrame(0, 0, 400, 300)
|
||
|
||
-- Show it by re-parenting
|
||
popup:setParent(gui)
|
||
|
||
-- Hide it again
|
||
popup:setParent(gui.virtual)
|
||
```
|
||
|
||
`gui.virtual` shares the same screen dimensions as `gui`, so positions remain correct when an element moves between them.
|