Initial commit
This commit is contained in:
@@ -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 (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.
|
||||
@@ -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.0–1.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.0–1.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
|
||||
Reference in New Issue
Block a user