Initial commit

This commit is contained in:
2026-05-06 19:39:08 -07:00
commit ea48705ee4
56 changed files with 13592 additions and 0 deletions
+866
View File
@@ -0,0 +1,866 @@
# 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 (01) 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 (01) |
| `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 (01) |
| `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 (01) |
### 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 (01) |
| `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.
+186
View File
@@ -0,0 +1,186 @@
# ── Full GUI YAML reference ─────────────────────────────────────────
# Every element shares these fields:
type: frame # frame | virtual-frame | visual-frame |
# label | button | textbox |
# image | image-button | video
# ── Position & size (dual-dimension system) ─────────────────────────
# Offset (pixels):
x: 10
y: 10
w: 200
h: 50
# Or as lists:
pos: [10, 10]
size: [200, 50]
# Scale (0.01.0 of parent):
sx: 0.0 # scale pos x
sy: 0.0 # scale pos y
sw: 0.5 # scale size x — 50% of parent width
sh: 1.0 # scale size y — 100% of parent height
# Or as lists:
scale-pos: [0.0, 0.0]
scale-size: [0.5, 1.0]
# Shorthand for "fill parent completely":
full-frame: true
# ── Appearance ───────────────────────────────────────────────────────
color: "#3a7bd5" # hex string
color: [58, 123, 213] # RGB 0-255
color: [0.23, 0.48, 0.84] # RGB 0-1
border-color: "#000000"
draw-border: true
visible: true
active: true
visibility: 1.0 # 0.01.0 alpha for the background rect
rotation: 45 # degrees
# ── Form factor ──────────────────────────────────────────────────────
form: rectangle # default — no extra fields needed
form: circle
radius: 40 # optional; derived from w if omitted
segments: 32
form: arc
radius: 60
arc-type: open # open | closed | pie
angle-start: 0
angle-end: 3.14159
segments: 32
# ── Roundness ────────────────────────────────────────────────────────
roundness: 8 # uniform rx/ry
roundness: [8, 8, 30] # rx, ry, segments
roundness: top # "top" or "bottom" special mode
roundness:
side: top # full table form
# ── Tags ─────────────────────────────────────────────────────────────
tag: "my-element" # single fast-lookup tag (gui:tag)
tags: # multi-tag (gui:setTag)
- draggable-panel
- visual
# ── Stack order ──────────────────────────────────────────────────────
stack: top # bring to front
stack: bottom # send to back
# ── Centering ────────────────────────────────────────────────────────
center-x: true # horizontally center within parent
center-y: true # vertically center within parent
# ── Dragging ─────────────────────────────────────────────────────────
draggable: 1 # mouse button (1=primary, 2=secondary, 3=middle)
# false/omit to disable
# ── Hierarchy & clipping ─────────────────────────────────────────────
respect-hierarchy: true # blocks presses when covered by sibling
clip-descendants: true # scissor-clips all children to this rect
# ── Square locking ───────────────────────────────────────────────────
square: w # force height = width
square: h # force width = height
# ── Text element fields (label, button, textbox) ─────────────────────
text: "Hello, world!"
align: left # left | center | right
text-color: "#ffffff"
text-visibility: 1.0
text-scale: [1.0, 1.0] # [scaleX, scaleY]
text-offset: [0, 0] # [offsetX, offsetY]
text-shear: [0, 0] # [shearX, shearY]
font: 16 # size, uses default font
font: "fonts/Roboto-Regular.ttf" # path, uses font-size below
font-size: 18
font:
file: "fonts/Roboto-Regular.ttf"
size: 18
fit-font: true # auto-fit font to element bounds
fit-font:
min: 8
max: 200
scale: 0.95 # shrink slightly from computed best
center-font: true # vertically center glyphs in box
center-font: 10 # with y_offset
# ── Image element fields (image, image-button) ───────────────────────
source: "assets/logo.png"
tile: [0, 0, 64, 64] # sub-quad [x, y, w, h]
scale-x: 1.0
scale-y: 1.0
flip: horizontal # horizontal | vertical | both
image-color: "#ffffff"
image-visibility: 1.0
# Gradient (replaces solid image with a generated gradient image):
gradient:
direction: vertical # vertical | horizontal
colors:
- [255, 80, 80, 255]
- [80, 80, 255, 255]
# ── Video element fields ──────────────────────────────────────────────
source: "assets/intro.ogv"
volume: 0.8
autoplay: true
video-color: "#ffffff"
video-visibility: 1.0
# ── Events ───────────────────────────────────────────────────────────
# Value can be a global function name (string) or inline Lua source.
on-pressed: "myPressHandler"
on-released: "myReleaseHandler"
on-released-outer: "myOuterRelease"
on-pressed-outer: "myOuterPress"
on-enter: "onHoverEnter"
on-exit: "onHoverExit"
on-moved: "onMouseMoved"
on-drag-start: "onDragStart"
on-dragging: "onDragging"
on-drag-end: "onDragEnd"
on-wheel: "onWheel"
on-size-changed: "onResized"
on-position-changed: "onMoved"
on-destroy: "onDestroy"
on-load: "onLoaded"
on-return: "onSubmit" # textbox only
on-update: "onUpdate" # called every frame
# Inline Lua (multi-line string):
on-pressed: |
print("pressed!", self.text)
# Per-element hotkeys:
hotkeys:
- keys: [lctrl, s]
action: "saveDocument"
- keys: [escape]
action: "closeDialog"
# ── Children ─────────────────────────────────────────────────────────
children:
- type: label
text: "I am a child"
x: 10
y: 10
w: 180
h: 30
color: "#2a2a2a"
text-color: "#ffffff"
align: center
- type: button
text: "Click me"
x: 10
y: 50
w: 100
h: 36
on-pressed: "handleClick"
children: [] # buttons can also have children