tekUI GUI Toolkit

Version 1.01


Table of contents

Links


1. About tekUI

TekUI is a small, freestanding and portable graphical user interface (GUI) toolkit written in Lua and C. It was initially developed for the X Window System and has been ported to DirectFB, Windows, Nano-X and a raw framebuffer since. A VNC server option is available that allows the remote control of tekUI applications.

Its main focus is the rapid development of custom-made applications with a custom appearance, such as for embedded devices with a display controller (see also fitness for the use in embedded systems). In the long term, it is intended to feature a class library supporting regular, general-purpose desktop and mobile applications.

Being mostly written in the Lua scripting language and using a simple inheritance scheme and class library, tekUI is easily extensible with new user interface elements. New controls can be written (or prototyped) in Lua and ported to C later. The creation of new styles and themes and fitting the software to a new device are equally simple.

See below for a more detailed technical overview.

1.1. License

TekUI is free software under the same license as Lua itself: It can be used for both academic and commercial purposes at no cost. It qualifies as Open Source software, and its license is compatible with the GPL – see copyright. Additional components are available under a commercial license – see below.

1.2. Status, requirements

TekUI is slowly approaching feature completeness ("beta" stadium). It is available for Unix-like operating systems such as Linux and FreeBSD, and it may work on other flavours of Unix with few modifications. A rudimentary Windows display driver and build setup is included, but not very well supported at this time.

Display drivers are included for X11, Windows, DirectFB, and raw memory. See also TODO for a list of known bugs and missing features, and requirements for a list of the required libraries.

1.3. Support and services

Special adaptations, custom display drivers, new user interface elements, and support and services for the integration of tekUI into your products are available from the authors – see below. A commercially licensed and supported version is available on request.

1.4. Authors and contact

Authors:

Open source project website:

Product website:

Mailing list:


2. Technical overview

TekUI is not a binding to an existing GUI library, and it makes no attempt to conform to the looks of a host's native GUI (although this can be emulated to a certain degree – see below for details). The implementation is comparatively fast and resource-friendly, despite its script-heavy nature. Among the benefits for developers are its simplicity, transparency and modularity; in particular, custom interface elements can be created with little effort.

2.1. General features

2.2. Supported interface elements

2.3. Deployment

The only language currently supported for writing applications in is Lua, and application programmers will have to use existing Lua modules or create their own C bindings for communicating with their host.

The package comes with examples that demonstrate the communication via file streams, datagram sockets (using Luasocket), and pipes between parent and child processes (using luaposix).

2.4. Style sheets and themes

TekUI comes with a cascading style sheets (CSS) engine for the import of properties for element classes, custom classes, pseudo classes, as well as for individual, direct and hardcoded class formattings.

The only admission to a more common appearance is that tekUI tries to import the color scheme found in a GTK+ configuration file, if the 'desktop' style sheet is used (which is the default, see also environment variables). If you are using KDE and check the Apply colors to non-KDE applications option, KDE, GTK+ and tekUI all share the same color scheme.

2.5. C library

The C library is based on the TEKlib middleware project. All required modules are contained in tekUI's source code distribution, to reduce the hassle of building and installing the software.

The C library isolates from the host and provides performance-critical routines (e.g. the region management, default layouter and string library). Rendering and input handling are implemented as display drivers, which allow for easy exchangeability.

Aside from the display driver interface, the C library implements OS-like facilities such as portable threads, a timer device and can support a virtual filesystem interface.

2.6. Lua, C and object model

Regardless of whether classes are written in Lua or C, they share a common, single-inheritance object model, which is determined by the Lua virtual machine and provides a referencing scheme and automatic memory management.

Classes can be written in Lua and C interchangeably, meaning that they can inherit from each other regardless of the language they are written in. This makes it possible to prototype applications quickly, and, should the need arise, to replace performance-crititcal classes with their counterparts in C. When porting classes to C, there is a certain degree of freedom in how much reliance on the Lua VM and its automatic resource management is desired.

2.7. Writing applications in XML

TekUI applications can be written as nested expressions in Lua, and this makes it easy to convert hierarchically structured formats such as XML to fully functional applications that can be executed on the Lua virtual machine. An examplary XML runner is included, e.g.:

# bin/runxml.lua -e tutorial-5.xml

One benefit of writing an application in XML is that some validity checks can be performed against a document type, and also against tekUI's actual implementation:

# bin/runxml.lua -c tutorial-5.xml

For this, it is required that you are using a debug version of tekUI's base class, see Debugging objects.

XML support is still somewhat experimental. See the accompanying examples on how to express interactions between elements using notifications and the embedding of methods.

2.8. Documentation system

TekUI comes with a documentation generator supporting its own object model. It is capable of generating a function reference and hierarchical class index from specially crafted comments in the source code. To regenerate the full documentation, invoke

# make docs

Note that you need LuaFileSystem for the document generator to process the file system hierarchy.

2.9. Fitness for the use in embedded systems

Complete solutions based on tekUI are in the reach of 512KiB ROM or flash memory: A typical application and the required modules, if compiled to Lua bytecode, can fit into 256KiB. Add to that approx. 128KiB for the C modules and another 128KiB for the Lua virtual machine.

Generic, unmodified versions of tekUI have been deployed on PPC, ARM, x86, and MIPS based microcontrollers with clock rates as low as 200MHz, while maintaining good reactivity and a pleasant look & feel. Lower clock rates are workable with some adaptations, by disabling expensive features like style sheet support, by porting certain classes to C, using display drivers which are more tightly connected to the hardware, etc.

2.10. Future plans

One future development goal is to fold the tekUI framework into a freestanding C library, which enables applications to create asynchronous GUI objects, communicating with their main program using an application-level protocol. From this, the following benefits are envisioned:

Lua would continue to act as the toolkit's internal scripting language, which can be used for complex interconnections between GUI elements as well as many application tasks.


3. Building and installing

3.1. Requirements

Development libraries alongside with their tested versions (as of this writing) are given below:

Lua 5.1, 5.2 and LuaJIT 2.0 are supported out of the box, if Lua 5.2 is compiled with LUA_COMPAT_MODULE.

TekUI is free of floating point arithmetics and works with a 32bit integer version of the Lua virtual machine, with the following, noncritical exceptions:

TekUI does not strictly depend on any external Lua library. The following libraries are supported:

3.2. Adjusting the build environment

TekUI was at some point tested and should compile and run out of the box on

If building fails for you, you may have to adjust the build environment, which is located in the config file on the topmost directory level. Supported build tools are gmake (common under Linux) and pmake (common under FreeBSD).

3.2.1. Linux notes

By popular request, these are the names of packages required to compile and run tekUI on Ubuntu Linux: lua5.1, liblua5.1-0-dev, liblua5.1-filesystem0, libfreetype6-dev, libxft-dev, libxext-dev, libxxf86vm-dev.

3.2.2. FreeBSD notes

You need a Lua binary which is linked with the -pthread option, as tekUI is using multithreaded code in shared libraries, which are dynamically loaded by the interpreter.

3.2.3. Windows notes

Not much time has been spent on the Windows version yet. As a result, this display driver is still incomplete and in an experimental state. For example, it is not possible to select an entry in a popup without releasing the mouse button first. For reasons not yet investigated, this display driver feels disproportionately sluggish. For adjusting the build environment, see the config file.

3.2.4. X11 notes

By default, libXft and fontconfig are needed for building the x11 driver, but tekUI falls back to the core x11 font API if they are unavailable at runtime. Also, libXft is unnecessary if you stack the rawfb driver on top of the x11 driver. Disable it by removing -DENABLE_XFT from X11_DEFS.

The XFree86VidMode extension is linked against by default. It can be disabled by removing -DENABLE_XVID from X11_DEFS in the config file.

3.2.5. Raw framebuffer notes

The raw framebuffer driver performs all rendering in a chunk of memory that can be supplied by the user. Color depth may be 16 or 32 bit. This driver is recursive in that another display driver may be plugged into it as a visualization and input device. It was originally intended to act as a template (or skeleton) for specializations in a target context, like custom hardware or a 3D game or simulation.

A VNC server option (through LibVNCServer) is available for this driver, which allows a tekUI application to be shared over the network. This works both "headless" and with a local display. To enable LibVNCServer support, enable the raw framebuffer and the VNCSERVER_ defines in the config file.

3.3. Building

To see all build targets, type

# make help

The regular build procedure is invoked with

# make all

3.4. Installation

A system-wide installation of tekUI is not strictly required – in so far as the majority of display drivers is concerned. The DirectFB driver, in contrast, looks up fonts and cursors globally and must be installed in any case.

Once tekUI is built, it can be worked with and developed against, as long as you stay in the top-level directory of the distribution; all required modules and classes will be found if programs are started from there, e.g.:

# bin/demo.lua

If staying in the top-level directory is not desirable, then tekUI must be installed globally. By default, the installation paths are

It is not unlikely that this is different from what is common for your operating system, distribution or development needs, so be sure to adjust these paths in the config file. The installation is conveniently invoked with

# sudo make install

3.5. Environment variables


4. User's Guide

4.1. Hello, World!

The GUI version of the 'Hello, World!' program:

ui = require "tek.ui"
ui.Application:new
{
  Children =
  {
    ui.Window:new
    {
      Title = "Hello",
      Children =
      {
        ui.Text:new
        {
          Text = "Hello, World!",
          Class = "button",
          Mode = "button",
          Width = "auto"
        }
      }
    }
  }
}:run()

As can be seen, tekUI allows a fully functional application to be written in a single nested expression. The UI library comes with an on-demand class loader, so whenever a class (like Application, Window or Text) is accessed for the first time, it will be loaded from tek/ui/class/ in the file system.

Note that a button class is not strictly needed: a button is just a Text element behaving like a button with a frame giving it the appearance of a button. We will later explain how you can write a button class yourself, to save you some typing.

To quit, click the window's close button. Closing the Application's last open window will cause the run method to return to its caller.

4.2. Reacting on input

There are different ways for reacting to presses on the 'Hello, World' button. The simplest form is to place an Object:onClick function into the Text object:

ui = require "tek.ui"
ui.Application:new
{
  Children =
  {
    ui.Window:new
    {
      Title = "Hello",
      Children =
      {
        ui.Button:new
        {
          Text = "Hello, World!",
          Width = "auto",
          onClick = function(self)
            print "Hello, World!"
          end
        }
      }
    }
  }
}:run()

But onClick is just a convenient shortcut for the most trivial of all cases. To catch more events, you can override handler functions which react on changes to an element's state variables, like Pressed and Selected, to name but a few. They contain booleans and are indicative of the Element's state, such as: Is it in selected state, is it pressed down, is it hovered by the mouse, is it receiving the input? The Widget:onPress handler, for example, can be used to catch not only releases, but also presses on the element:

ui = require "tek.ui"
ui.Application:new
{
  Children =
  {
    ui.Window:new
    {
      Children =
      {
        ui.Text:new { },
        ui.Button:new
        {
          Text = "Click",
          Width = "auto",
          onPress = function(self)
            ui.Button.onPress(self)
            self:getPrev():setValue("Text", tostring(self.Pressed))
          end
        }
      }
    }
  }
}:run()

When you overwrite a handler, you should forward the call to the original implementation of the same method, as seen in the example.

Setting a value using Object:setValue may invoke a notification handler. In our example, the neighboring element in the group will be notified of an updated text, which will cause it to be repainted.

For regular applications it is normally sufficient to stick to overwriting the available handlers as in the previous example. But the underlying mechanism to register a notification handler can be interesting as well, especially if you plan on writing new GUI classes yourself:

ui = require "tek.ui"
app = ui.Application:new()
win = ui.Window:new { Title = "Hello", HideOnEscape = true }
text = ui.Button:new { Text = "_Hello, World!", Width = "auto" }
text:addNotify("Pressed", false, {
  ui.NOTIFY_SELF,
  ui.NOTIFY_FUNCTION,
  function(self)
    print "Hello, World!"
  end
})
win:addMember(text)
app:addMember(win)
app:run()

See also Object:addNotify for all the hairy details on notification handlers, and the Area and Widget classes for the most important state variables.

4.3. Controlling the layout

By default a tekUI layout is dynamic. This means it will be calculated and, if necessary, recalculated at runtime¹. Screen and font characteristics are taken into account as well as style properties controlling the appearance of individual elements.

Group layouting attributes

Common layouting attributes (apply to all elements)

Note that the Min/Max and Width/Height properties will not override the actual size requirements of an element. An element will not claim a larger or smaller size than what it is capable of displaying. Style properties will be used as additional hints when an element's size is flexible. As most elements are scalable by nature, the style properties are normally considered.


¹ More layouting options are available, see also the fixed.lua and layouthook.lua examples on how to use and implement fixed and custom layouting strategies.

4.4. List of predefined handlers

Name Base Class Cause
Widget:onActivate() Widget change of the Active attribute
Window:onChangeStatus() Window change of Status attribute
Widget:onClick() Widget caused when Pressed changes to false
Widget:onDisable() Widget change of the Disabled attribute
Widget:onDblClick() Widget change of the DblClick attribute
Input:onEnter() Input change of Enter attribute, pressing enter
Widget:onFocus() Widget change of the Focus attribute
Window:onHide() Window window close button, Escape key
Widget:onHilite() Widget change of the Hilite attribute
Widget:onHold() Widget change of the Hold attribute
Widget:onPress() Widget change of the Pressed attribute
Widget:onSelect() Widget change of the Selected attribute
DirList:onSelectEntry() DirList item selected by the user
Lister:onSelectLine() Lister change of the SelectedLine attribute
PopList:onSelectLine() PopList change of the SelectedLine attribute
Input:onSetChanged() Input setting the Changed attribute, on text changes
Canvas:onSetChild() Canvas change of the Child attribute
Element:onSetClass() Element change of the Class attribute
Lister:onSetCursor() Lister change of the CursorLine attribute
ImageWidget:onSetImage() ImageWidget change of the Image attribute
Numeric:onSetMax() Numeric change of the Max attribute
ScrollBar:onSetMax() ScrollBar change of the Max attribute
Numeric:onSetMin() Numeric change of the Min attribute
ScrollBar:onSetMin() ScrollBar change of the Min attribute
PageGroup:onSetPageNumber() PageGroup change of the PageNumber attribute
ScrollBar:onSetRange() ScrollBar change of the Range attribute
Slider:onSetRange() Slider change of the Range attribute
Element:onSetStyle() Element change of the Style attribute
FloatText:onSetText() FloatText change of Text attribute
Input:onSetText() Input change of Text attribute
Text:onSetText() Text change of Text attribute
Numeric:onSetValue() Numeric change of the Value attribute
ScrollBar:onSetValue() ScrollBar change of the Value attribute

4.5. Ad-hoc setup of classes

To inherit properties and functionality from existing classes and to consequently reuse existing code, it is often desirable to create new classes yourself. There are different scopes in which new classes can be useful:

For the ad-hoc style, it is not necessary to create a new source file or module. For example, a Button class can be derived from the Text class whereever you see fit:

local Button = ui.Text:newClass { _NAME = "_button" }

ad-hoc classes may be named arbitrarily, but their names should be prefixed with an underscore to distinguish them from global classes. You can even do without a name, as tekUI will create one for you if necessary, but you will find it difficult to reference such a class in a style sheet.

From this point, the new class can be extended, e.g. for initializations which turn a Text into a Button:

function Button.init(self)
  self.Class = "button"
  self.Mode = self.Mode or "button"
  self.KeyCode = true
  return ui.Text.init(self)
end

As shown in the example, we also passed the call on to our super class, which we expect to perform the missing initializations.

Finally, a new object from our new class can be created:

button = Button:new { Text = "_Hello, World!" }

Also refer to the Class reference and the Class setup section for further information.


5. Developer's Guide

5.1. The lifecycle of an element

A GUI element is set up in several stages, all of which are initiated by the tekUI framework. Normally, you do not call any of these methods yourself (aside from passing a call on to the same method in your super class):

Drawing an element

In the drawing method, the control flow is roughly as follows:

function ElementClass:draw()
  if SuperClass.draw(self) then
    -- your rendering here
    return true
  end
end

There are rare cases in which a class modifies the drawing context, e.g. by setting a coordinate displacement. Such modifications must be performed in Area:drawBegin() and reverted in Area:drawEnd(), and the control flow looks like this:

function ElementClass:draw()
  if SuperClass.draw(self) then
    if self:drawBegin() then
      -- your rendering here
      self:drawEnd()
    end
    return true
  end
end

5.2. Debug library

The debug library used throughout tekUI is tek.lib.debug. The default debug level is 10 (ERROR). To increase verbosity, set level to a lower value, either by modifying tek/lib/debug.lua, or by setting it after including the module:

db = require "tek.lib.debug"
db.level = db.INFO

See also the module's documentation for redirecting the output.

5.3. Debugging objects

If you wish to use validation of XML files against tekUI's implementation, of if you plan on extending existing classes or develop your own, you are advised to set the following configurable parameters in tek.class, the base class of all tekUI classes:

local PROXY = true
local DEBUG = true

The PROXY option allows for intercepting read/write accesses to objects, which will be harnessed by the DEBUG option for tracking accesses to uninitialized class members. So whenever a nil value is read from or written to an object, this causes tek.class to bail out with an error and a meaningful message.

As a result, all member variables must be initialized during new() or init() – or more specifically, before the class metatable is attached and an object is becoming fully functional. This will assist in keeping variables neatly together, and you won't end up in a fluff of variables of limited scope and significance, getting initialized at random places. This also means that you cannot assign a distinct meaning to nil for a class member – you will have to use false instead, or find another arrangement. (This convention of not using nil for class variables is found throughout the whole tekUI framework.)

Once your application is tested and ready for deployment, you can disable PROXY, as this will improve performance and reduce memory consumption.

5.4. Class setup

A class is usually set up in a prologue like this:

local Widget = require "tek.ui.class.widget"
module("tek.ui.class.button", tek.ui.class.widget)
_VERSION = "Button Widget 1.0"
local Button = _M
Widget:newClass(Button)

The second argument to module is the super class to derive the new class from (see also tek.class for details on how this is supposed to work). By convention, we then put the module table (the class) into a local variable. The last line is for compatibility with Lua 5.2, as the second argument to module doesn't work with Lua 5.2 anymore.

Finally, methods in the newly created class may look like this (note that, thanks to the Button variable, the second example provides an implicit self):

function Button.new(class, self)
  ...
  return Widget.new(class, self)
end

function Button:method()
  Widget.method(self)
  ...
end

Also, don't forget to add a _VERSION variable, as it will be used by the documentation system – see also the next section.

5.5. Class documentation system

Don't stray off too far from the class setup described in the previous section, as it contains valuable informations for tekUI's documentation generator.

Most notably, the second argument to module should be written out in full – in the example above, one might be tempted to use Widget instead of tek.ui.class.widget; but then, the path information would be lost for the source code parser, which tries to assemble a self-contained class hierarchy from individual class / child class relations.

Tokens for markup

Aside from the aforementioned module and _VERSION keys (see section Class setup), the source code parser reacts on the following tokens.

Long lines of dashes signify the beginnings and endings of comment blocks that are subject to processing markup notation, e.g.

----------------------------------------------------------------
--  OVERVIEW::
--    Area - implements margins, layouting and drawing
----------------------------------------------------------------

The other condition that must be met for the following text to appear in the documentation is the recognition of either a definition (as seen in the example) or function marker inside such a comment block. The template for a definition is this:

DEFINITION::

And the function template:

ret1, ret2, ... = function(arg1, arg2, ...): ...

The marker and the following text will then become part of the documentation. (In other words, by avoiding these markers, it is also possible to write comment blocks that do not show up in the documentation.)

Functions inside classes will automatically receive a symbolic name as their class prefix (from assigning the module table _M to a local variable, see Class setup). Hereinafter, they can be cross-referenced using the following notations:

Class:function()
Class.function()

For further information, consult the sources in the class hierarchy as examples, and the source code containing the markup notation reference, which can be found in tek.class.markup.