Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Placeholder for documentation landing page.

IUI Sample

function love.load()
	print("Testing a LUA block")
end

Installation

IUI itself is engine-agnostic. It doesn’t know how to receive input or draw to the screen. Backend libraries connect IUI to game engines. Thus, you need to install both the IUI library and a backend library to use the toolkit.

Core Library

  • iui: required regardless which backend you choose

Backends

Sample Projects

Layout

The layout engine is built around two key concepts: panels and rows. Panels are rectangular regions within a window. Rows are placed within panels. The layout engine places widgets inside rows inside panels.

Layout diagram showing windows, panels, and rows

Panels

Rows

Margin, Spacing, Padding

In iui.style, three properties guide the layout engine, margin, spacing, and padding.

Layout diagram showing margin, spacing, and padding

The margin insets rows within a panel, creating a gap between the edges of rows and their panels.

The layout engine uses spacing to add gaps between widgets in rows. The spacing also applies to the vertical space between rows.

Finally, widgets use padding to try, room permitting, to inset their content within their boundaries. Additionally, padding is used when calculating the default row height, if you don’t pass a manual height when creating a row. The default height of a row is fontHeight + padding * 2.

The Style Stack

To add a widget to a UI, you call a widget function and pass some arguments. These arguments typically include a name, a value, and maybe a few other things. However, widgets may potentially support many different customization options. To include all these options in the function signature would require lengthy argument lists. As an alternative, IUI offers iui.style, a table-like object.

Basics

At its simplest, iui.style behaves like a table. You can assign values to keys, and widgets can retrieve those values. For example, widgets use the font stored in the "font" key when drawing text.

-- This label will use the default font.
iui.label("The default font!")

-- This label will use `someCustomFont`.
iui.style["font"] = someCustomFont
iui.label("A custom font!")

The Stack

Changing a value in iui.style will apply the new value to every future widget for the rest of the frame. But sometimes you only want to customize one or a few widgets, not every future widget. To support this, iui.style implements scoped stack semantics, using iui.style.push() and iui.style.pop().

Calling push creates a scope. Any customizations made after calling push will be undone after calling pop. Any call to push must be balanced by a call to pop. Any keys that you don’t customize in a scope will inherit from parent scopes.

-- This label will use the default font.
iui.label("The default font!")

-- This pushes a new scope on the style stack.
iui.style.push()

-- This label uses the default font, because we haven't made any changes yet.
iui.label("Still using the default font!")

-- These labels will use `someCustomFont`.
iui.style["font"] = someCustomFont
iui.label("A custom font!")
iui.label("Still using the custom font!")

-- This pops the scope off the stack.
iui.style.pop()

-- This label uses the default font, not `someCustomFont`.
iui.label("Back to the default font!")

The Default Table

There is another table at iui.style.default. This table specifies default options. If you want to set a value for your entire application once, and not have to set it every frame, you can set it in iui.style.default. This table is set as the root of the style stack on every new frame.

Dependency Injection

You don’t just have to use iui.style for presentation-oriented values for widgets. You may freely inject and retrieve any values you want, anywhere you want. You can use this to implement the dependency injection technique.

For example, imagine you’re making an app that asks the user to log in. If they’ve done so, you could inject the user object in iui.style["user"]. Then, anywhere else in your UI, you could try fetching the value for that key. If the user has logged in, you’ll get the user object back. If they haven’t, you’ll get nil. You could use this to change elements of the UI based on whether or not they’ve logged in. By injecting the user object in iui.style, you avoid needing to manually pass it through your user interface via arguments.

The sample project uses this pattern to inject app and window states into iui.style. They’re injected in sampleMain, and then retrieved throughout the app. For example, splitPrimaryPane retrieves the app state. The splitPrimaryPane function takes no arguments, and is called several layers deep in the UI. Without injecting into iui.style, the state would have had to have been passed through several functions.

Built-in Keys

Layout

KeyDefaultDescription
"margin"8The distance between widgets and the edge of panels.
"spacing"8The distance between widgets within a panel.
"padding"8The suggested padding between the border of widgets and their content.
"scrollSize"16The width of horizontal scrollbars, and height of vertical bars.

Drawing

KeyDefaultDescription
"font"A backend-provided 12-point fontThe font used to draw text.

Split Views

KeyDefaultDescription
"splitMinEdge"8The minimum width of the left panel of horizontal split views, or top panel of vertical split views.
"splitMaxEdge"8The minimum width of the right panel of horizontal split views, or bottom panel of vertical split views.

VR

KeyDefaultDescription
"vrWindowCornerRadius"16The border radius of world-space windows in VR, when panel backgrounds are drawn using iui.panelBackground.