gui.lua

Documentation on using the GUI library written for OpenComputers

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.

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 (are objects and closures the same thing?). I merely am following in the footsteps of the venerable master Qc Na and his student Anton.

Window Manager API (WM)

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.

  • 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

    • 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

  • wm.makeLabel(blurred, focused, checkedBlurred, checkedFocused): Label

    • 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

    • 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

    • 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:

      • 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

    • Creates the "root window" object

  • wm.makeWindow(name, vt): Window

    • Creates a window object with a given unique name

  • wm.makePage(): Page

    • 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

Inherits from Window

  • this.refresh()

    • Since managers are "root windows," this function redraws the entire screen

WM Widgets Documentation

Some methods will automatically mark the region defined by bounds dirty for you. These are: draw, focus, blurand move

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

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:

-- 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)

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!

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

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()

Last updated