jeopardy/gui/docs/gui-library-docs.md
2026-05-06 19:39:08 -07:00

867 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.