tekUI GUI Toolkit

Version 0.8f

Table of contents


1. About tekUI

tekUI is a small, freestanding and portable graphical user interface (GUI) toolkit written in Lua and C. It was developed initially for the X Window System and can serve as a general-purpose GUI library for desktop applications.

Its main focus is on rapid development of applications for custom-made devices, often with a custom appearance and under resource-constrained conditions, such as for embedded devices with a display controller.

Being mostly written in the Lua scripting language, it is easily extensible with new user interface elements, which can be rewritten in 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. Note: The commercially supported version contains additional components which may be licensed differently. See copyright.

1.2. Status, requirements

TekUI is in early stage of development. It is available for the Linux, Windows, and FreeBSD platforms. Display drivers are available for X11, Windows, DirectFB, and Nano-X. See also TODO for a list of known bugs and missing features, and requirements for a list of the required packages.

1.3. Development model

If you find this software useful, you have probably arranged to fit it to a certain device or application – submit your additions and we will check them for inclusion to the public source code repository. Regular developers are welcome also.

1.4. Support and services

Vendors of commercial products can help advance the development by assigning projects based on tekUI, or license our commercially supported version. Support and services for the integration of tekUI as well as special versions, new display drivers, custom classes etc. are available from the authors – see below.

1.5. Authors and contact

Authors:

Open source project website:

Public mailing list:

Commercial product website:


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 comes straight to the point, and it is comparatively fast and resource-friendly, despite its script-heavy nature. Among the benefits for developers are its transparency and modularity; in particular, custom interface elements can be created with little effort. Note, however, that the programming interfaces are not fully stable yet, so be prepared for structural and API changes.

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. Creating C bindings is an easy and well-documented task in Lua, and the tekUI package contains numerous examples.

The next 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.

2.4. Themes

The only admission to a more common appearance is that tekUI tries to import the color scheme found in a GTK+ configuration file. It can be conveniently created in the KDE configuration panel; thus, if you are using KDE and check the appropriate option, KDE, GTK+ and tekUI can all share the same color scheme.

The current default theme tries to be as conservative and minimal as possible. Custom themes can be created by modifying (or deriving from) the Theme class.

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 a few performance-critical routines. Rendering and input handling are provided in the form of display drivers, which allow for easy exchangeability. Display drivers currently supported are for X11, Windows, DirectFB, and Nano-X.

Aside from the display driver interface, the C library features OS-like facilities such as portable threads, a timer device and virtual filesystem interfaces. Depending on your application and its kind of deployment, it can be extended with more input drivers, file format codecs, etc.

2.6. Lua, C and object model

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

Most user interface elements are currently implemented in Lua, which is beneficial for fast prototyping of custom classes and styles, for heavy customization, and generally in early stages of development.

2.7. 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.8. Notes on uses in embedded systems

Complete solutions based on tekUI are well in the reach of 512kB of ROM/Flash: A typical application and the modules that it requires, if compiled to Lua bytecode, can fit into 256kB. Add to that approx. 128kB for the C modules and another 128kB for the Lua virtual machine.

The generic, unmodified version of tekUI has been deployed on PPC and ARM 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, namely by disabling expensive features like style sheet support, porting certain classes to C, using display drivers which are more tightly connected to the hardware, etc.


3. Building and installing

3.1. Requirements

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

LibXft and fontconfig are needed for building the x11 driver, but tekUI falls back to the core X11 API if they are unavailable at runtime. In addition to that, for the documentation system and the DirList class to work, an installation of the LuaFileSystem package is recommended.

Linux note: 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.

3.2. Adjusting the build environment

This release has been tested on and should compile and run out of the box on

If building fails for you, you 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. 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.2. X11 notes

In case of problems with texts and labels, try removing -DENABLE_XFT from X11_DEFS in the config file. Some versions of X11 are known to not support the Composite extension and antialiased fonts using Xft at the same time, and we tried to work around this problem by falling back to the core X11 API if the Composite extension was detected. Recent tests indicate that this has been corrected in later versions of X11, and so we removed our workaround. Unfortunately, we got reports of other, possibly unrelated problems with Xft in the meantime.

3.2.3. Nano-X notes

The Nano-X driver is available as part of the commercially supported version. You will almost certainly have to adjust the config file. Due to some limitations of Nano-X, tekUI applications will probably make sense only when running in fullscreen mode without a window manager. See also tek/ui/style/nanox.css for more information. Example:

# nano-X & THEME=nanox FULLSCREEN=true bin/demo_custom.lua

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",
        },
      },
    },
  },
}: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 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 method is to place an onPress function into the Text object, which will overwrite the handler reacting on setting the Pressed variable. This variable is set to true when a Gadget is hit with the left mouse button, and to false when it is getting released:

ui = require "tek.ui"
ui.Application:new
{
  Children =
  {
    ui.Window:new
    {
      Title = "Hello",
      Children =
      {
        ui.Text:new
        {
          Text = "Hello, World!",
          Class = "button",
          Mode = "button",
          onPress = function(self, pressed)
            if pressed == false then
              print "Hello, World!"
            end
            ui.Text.onPress(self, pressed)
          end,
        },
      },
    },
  },
}:run()

When overwriting a method like onPress, also forward the call to the original implementation of the same method.

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 creating new GUI classes yourself:

...
ui.Text:new
{
  Text = "_Hello, World!",
  Class = "button",
  Mode = "button",
  Notifications =
  {
    ["Pressed"] =
    {
      [false] =
      {
        {
          ui.NOTIFY_SELF,
          ui.NOTIFY_FUNCTION,
          function(self)
            print "Hello, World!"
          end,
        },
      },
    },
  },
},
...

Even though notification handlers can be initialized statically (i.e. written in the same expression as the rest of the application), this is not always recommendable, as the deep levels of indentation are getting in the way of clarity. Notification handlers are more commonly added using the addNotify() method, as shown in the next example:

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

In this example we have also set the HideOnEscape attribute for the application to quit on pressing the Escape key, and the KeyCode attribute, so that a shortcut for the element is created by placing an underscore in front of a letter in its caption.

See also Object:addNotify for all the hairy details on notification handlers, and the Gadget class for some of the possible actions to react on.

4.3. Ad-hoc setup of classes

To inherit properties and functionality from existing classes and to reuse existing code consequently, 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. 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.2. Proxied object model

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.3. Class setup

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

local Gadget = require "tek.ui.class.gadget"
module("tek.ui.class.button", tek.ui.class.gadget)
_VERSION = "Button Gadget 1.0"
local Button = _M

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.

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 Gadget.new(class, self)
end

function Button:method()
  ...
  Gadget.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.4. 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 Gadget instead of tek.ui.class.gadget; 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.