---
description: Documentation on using the GUI library written for OpenComputers
---

# gui.lua

## Overview

The GUI library comes in 2 parts, the Window Manager (`wm`) and the Display Rendering Interface (`dri`). A small undocumented util library `beans` is also provided.

```lua
package.loaded.gui = nil -- Optional, will force require() to reload library
local wm, dri, beans = require("gui")
```

The library itself is very lightweight and small (\~500 lines). Three fundamental structures that make up the GUI (all under the `wm` library): the widget, the page and the window.

1. **The widget**: Everything displayed on the screen is a widget. Windows are widgets and even the screen is a widget.
2. **The page**: A page describes the tab index of widgets. By pressing TAB, the user can bring different widgets into focus. The page describes which widgets can be focused and in what order.
3. **The window**: A window is an abstraction for a group of widgets. The `wm` will redirect events to the currently active window.

Objects are implemented through [closures ](http://lua-users.org/wiki/ObjectBenchmarkTests)(are objects and closures the [same thing](http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent)?). I merely am following in the footsteps of the _venerable master Qc Na and his student Anton._

## Window Manager API (WM)

{% hint style="info" %}
Type hints will be provided when necessary (in colon notation). If no type hints are present, use common sense. Non-standard Lua types (i.e., `Label`) are assumed to be objects (Lua tables with specific structures). All objects are documented in the subsections below.
{% endhint %}

* `wm.createEventObject(): EventHandler`
  * An EventHandler object defines 2 methods (`add` and `remove`) plus the metatable `__call` event. See more in Examples.
  * `add(this: EventHandler, evt: string, fn)`
  * `remove(this: EventHandler, evt: string, fn)`
  * `this(evt: string, ...)` Calling the object will invoke the appropriate handler
  * Multiple callbacks can be assigned to one event
* `wm.makeWidget(vt: Table): Widget`&#x20;
  * Creates a new widget. An optional virtual function table can be passed as `vt`
  * `vt` can be **nil** or a table with any combination of these keys:
    * Function pointers: `draw`, `focus`, `blur`, `layer`, `bounds`
* `wm.makeBox(width, height, fgcolor, bgcolor): Box`&#x20;
* `wm.makeLabel(blurred, focused, checkedBlurred, checkedFocused): Label`&#x20;
  * Creates a new label with the specified states in the parameters
  * Each parameter can be **nil** or a table with any combination of these keys:
    * `text`, `fg`, `bg`
  * Labels are checkable and focusable
* `wm.makeCombo(w, h): Combo`&#x20;
  * By using the arrow keys (up and down), the user can change which child item of the combo group is checked. Only direct child layers of the combo group will be affected
  * When a selection is changed, the target child item is focused and checked
  * The height is inadequate, a vertical scrollbar will appear
* `wm.makeInput(width: number, blurred, focused, cursor): Input`&#x20;
  * Creates a single-line user input field of a fixed `width`
  * Each state parameter (`blurred`, `focused` and `cursor`) can be **nil** or a table with any combination of these keys:&#x20;
    * `fg`, `bg`
  * The user can use arrow keys (left and right) to move the cursor
  * Backspace will remove the character preceding the cursor
  * Home and end will move the cursor to the beginning and end of the text respectively
  * Any other non-control character will be inserted in the space specified by the cursor
* `wm.makeManager(vt): Manager`&#x20;
  * Creates the "root window" object
* `wm.makeWindow(name, vt): Window`
  * Creates a window object with a given unique name
* `wm.makePage(): Page`&#x20;
  * Creates a blank page. Pages must be manually bound to windows. See the `Page` object documentation for more details
* `wm.run(ctx: Manager)`
  * Paints and begins the event loop for a given manager

### WM Object Documentation

{% tabs %}
{% tab title="Manager" %}
Inherits from `Window`

* `this.refresh()`
  * Since managers are "root windows," this function redraws the entire screen
{% endtab %}

{% tab title="Window" %}
Inherits from `Widget`

* `this.name` **Property**
* `this.parent` **Property**
* `this.currentWindow(): Window`&#x20;
  * Gets the currently focused window. Returns **nil** if none found.
* `this.focusWindow(name: string)`
  * Brings the window with a given name into focus (top)
* `this.blurAll()`
  * Blurs the currently focused window (if any).
* `this.refresh()`
  * Clears the dirty regions' queue and redraws each region.
* `this.addWindow(win: Window, x, y): Window`
  * Adds a given window object to the parent at the specified location.
* `this.setPage(x: Page)`
* `this.currentPage(): Page`

{% hint style="warning" %}
Windows have no built-in mechanism to track pages. It is up to the user to manually swap pages when necessary.
{% endhint %}
{% endtab %}

{% tab title="Page" %}
Pages allow users to focus elements in the order they are added to the page.

* `this.addFocusable(...)`
  * Inserts an ordered list of focusable items
* `this.focusedItem(): Widget`
* `this.focusedIndex(): number`
  * Index starts at 0
* `this.focus(f: number)`
  * Focuses an item given the tab index
{% endtab %}
{% endtabs %}

### WM Widgets Documentation

{% tabs %}
{% tab title="Widget" %}
{% hint style="success" %}
Some methods will automatically mark the region defined by `bounds` dirty for you.\
These are: `draw`, `focus`, `blur`and `move`
{% endhint %}

Widget virtual table functions:

* `preDraw: function(x, y)`
* `draw: function(x, y)`
* `postDraw: function(x, y)`
* `blur: function()`
* `bounds: function(x, y)`
* `focus: function()`
* `layer: function(f, x, y)`

Widget methods:

* `this.x(): number` and `this.y(): number`
  * Returns the x and y coordinates of the control relative to the parent
* `this.screenX(): number` and `this.screenY(): number`
  * Returns the x and y screen coordinates of the control
* `this.move(x, y)`
  * Sets coordinate of control relative to the parent
* `this.draw(x, y): r: boolean, x1, y1`
  * Draws the control relative to some point (i.e., the drawer is the parent)
  * The first return value specifies whether or not a region mask should be popped (see the [DRI documentation](#display-rendering-interface-dri)). The second and third values specify offsets for children to be drawn at. If any are **nil** default values of false and 0 will be assigned appropriately.
* `this.focus()`
* `this.blur()`
* `this.visible(): boolean`
* `this.focused(): boolean`
* `this.layer(f: Widget, x, y)`
  * Adds a child widget to the parent at the specified relative coordinates
  * Order of addition dictates paint order
* `this.update()`
  * Dummy method - child classes override to update control state
* `this.bounds(): Table { x1, x2, y1, y2 }`
  * The boundary of the current widget
* `this.bodyBounds(): Table`
  * The boundary of the main body of the widget (defaults to `bounds()`)
* `this.layers: Table` **Property**
* `this.handlers: EventHandler` **Property**
* `this.disableNoFlash` **Property**
{% endtab %}
{% endtabs %}

## Display Rendering Interface (DRI)

DRI allows us to only render areas that need to be rendered. It also allows child widgets to only be rendered within a region defined by the parent. **This reduces flickering and enables double buffering-like behaviour.**

* `dri.set(x, y, t)`
  * Will draw some text to the screen only if x, y satisfies all of:
    * inside a dirty region as defined under `dri.regions`
    * outside the regions defined under `dri.negate`
    * inside the uppermost mask of `dri.masks` stack
* `dri.pushMask(x: Table { x1, x2, y1, y2 }, t: boolean)`
  * The parameter `t` (true if should negate) is rarely used and should be ignored
* `dri.popMask()`
* `dri.setColors(fg, bg)`
* `dri.markDirty(x: Table { x1, x2, y1, y2 })`
  * Appends the dirty area in `dri.buffer`
* `dri.regions`, `dri.negate`, `dri.buffer` and `dri.masks` are all tables

### Usage

The DRI is quite complex and there are some nuances that you need to be aware of.

## Examples

### Hello, World!

A minimal example with a fullscreen window and a single label would look like this:

```lua
-- Clear screen
local component = require("component")
component.gpu.setResolution(80, 25)
component.gpu.fill(1, 1, 80, 25, " ")

-- Set up GUI
package.loaded.gui = nil
local wm = require("gui")
local ctx1 = wm.makeManager()
local win1 = ctx1.makeWindow("win1", 0, 0)

-- Your code here
local lbl1 = wm.makeLabel({text="Hello, World!"})
win1.layer(lbl1, 1, 1)

-- Go, go, go!
ctx1.focus("win1")
wm.run(ctx1)
```

![Yay! It works. Feel the excitement. The possibilities are endless from here.](https://632107968-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MA8G-aHMjgxotaJvSks%2F-MAE5w-Sz_oSrczEnNh1%2F-MAE6J2P5UAKSQM15aAj%2F1.PNG?alt=media\&token=6c30816f-384a-476f-9998-e41ae79bc129)

{% hint style="info" %}
Note that windows will not have any decorations drawn by default. Windows by default have no drawing routines. They mainly provide event redirection and such boring (but important) features. However, nothing is stopping you from adding routines!
{% endhint %}

### Pages Upon Pages of Fun!

Pages allow users to focus elements in the order they are added to the page. The boilerplate code has been omitted for brevity. Running this example and pressing tab allows you to cycle through the 3 buttons. Note the order they appear (see line 13).

```lua
local box1 = wm.makeBox(34, 1, wm.DEFAULT_FG, 0x0000FF)
local btn1 = wm.makeLabel({text=" Button 1 "},{text=" >I'm 1!  ",bg=0xFF0000})
local btn2 = wm.makeLabel({text=" Button 2 "},{text=" >I'm 2!  ",bg=0xFF0000})
local btn3 = wm.makeLabel({text=" Button 3 "},{text=" >I'm 3!  ",bg=0xFF0000})

box1.layer(btn1,  2, 1)
box1.layer(btn2, 13, 1)
box1.layer(btn3, 24, 1)
win1.layer(box1,  3, 2)

local pag1 = wm.makePage()
win1.setPage(pag1)
pag1.addFocusable(btn1, btn2, btn3)
btn1.focus()
```

![Wohoo!](https://632107968-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MA8G-aHMjgxotaJvSks%2F-MAEhVISthrvUdTgqcDg%2F-MAEhezjgmJ6kNCQZmoW%2F2.PNG?alt=media\&token=94b3be8e-ebd6-470b-8105-f8f57574cfba)
