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.
The widget: Everything displayed on the screen is a widget. Windows are widgets and even the screen is a widget.
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.
The window: A window is an abstraction for a group of widgets. The
wmwill 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)
wm.createEventObject(): EventHandlerAn EventHandler object defines 2 methods (
addandremove) plus the metatable__callevent. 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 handlerMultiple callbacks can be assigned to one event
wm.makeWidget(vt: Table): WidgetCreates a new widget. An optional virtual function table can be passed as
vtvtcan be nil or a table with any combination of these keys:Function pointers:
draw,focus,blur,layer,bounds
wm.makeBox(width, height, fgcolor, bgcolor): Boxwm.makeLabel(blurred, focused, checkedBlurred, checkedFocused): LabelCreates 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): ComboBy 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): InputCreates a single-line user input field of a fixed
widthEach state parameter (
blurred,focusedandcursor) 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): ManagerCreates the "root window" object
wm.makeWindow(name, vt): WindowCreates a window object with a given unique name
wm.makePage(): PageCreates a blank page. Pages must be manually bound to windows. See the
Pageobject 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
Inherits from Widget
this.namePropertythis.parentPropertythis.currentWindow(): WindowGets 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): WindowAdds a given window object to the parent at the specified location.
this.setPage(x: Page)this.currentPage(): Page
Windows have no built-in mechanism to track pages. It is up to the user to manually swap pages when necessary.
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(): Widgetthis.focusedIndex(): numberIndex starts at 0
this.focus(f: number)Focuses an item given the tab index
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(): numberandthis.y(): numberReturns the x and y coordinates of the control relative to the parent
this.screenX(): numberandthis.screenY(): numberReturns 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, y1Draws 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(): booleanthis.focused(): booleanthis.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(): TableThe boundary of the main body of the widget (defaults to
bounds())
this.layers: TablePropertythis.handlers: EventHandlerPropertythis.disableNoFlashProperty
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.regionsoutside the regions defined under
dri.negateinside the uppermost mask of
dri.masksstack
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.bufferanddri.masksare 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)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
Was this helpful?