Version 0.8j
- 1. About tekUI
- 1.1. License
- 1.2. Status, requirements
- 1.3. Development model
- 1.4. Support and services
- 1.5. Authors and contact
- 2. Technical overview
- 2.1. General features
- 2.2. Supported user interface elements
- 2.3. Deployment
- 2.4. Style sheets and themes
- 2.5. C library
- 2.6. Lua, C and object model
- 2.7. Documentation system
- 2.8. Fitness for the use in embedded systems
- 3. Building and installing
- 3.1. Requirements
- 3.2. Adjusting the build environment
- 3.2.1 FreeBSD notes
- 3.2.2 Windows notes
- 3.2.3 X11 notes
- 3.2.4 Nano-X notes
- 3.2.5 Raw framebuffer notes
- 3.3. Building
- 3.4. Installation
- 3.5. Environment variables
- 4. User's Guide
- 4.1. Hello, World!
- 4.2. Reacting on input
- 4.3. List of predefined handlers
- 4.4. Ad-hoc setup of classes
- 5. Developer's Guide
- 5.1. The lifecycle of an element
- 5.2. Debug library
- 5.3. Proxied object model
- 5.4. Class setup
- 5.5. Class documentation system
- Class Reference Manual – The entrypoint is the tek.ui module. See there for a quick start.
- Changelog
TekUI is a small, freestanding and portable graphical user interface (GUI) toolkit written in Lua and C. It was developed for the X Window System initially and has been ported to DirectFB, Windows, Nano-X and a raw framebuffer since.
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 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.
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 support.
TekUI is in early stage of development. It is available for the Linux, Windows, and FreeBSD platforms. Display drivers are included for X11, Windows, and DirectFB. See also TODO for a list of known bugs and missing features, and requirements for a list of the required libraries.
Bug fixes and additions are welcome. Submit your patches, and we will check them for inclusion to the public source code repository. Regular contributors are welcome also.
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 commercial version with additional components (such as a Nano-X and raw framebuffer driver and XML support) is available on request.
Authors:
- Timm S. Müller <tmueller at schulze-mueller.de>
- Franciska Schulze <fschulze at schulze-mueller.de>
Open source project website:
Product website:
Mailing list:
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.
- Automatic layouting and scalability
- Font-sensitivity, support for antialiased fonts
- Full Unicode (UTF-8) support
- Support for cascading style sheets (CSS) and themes
- Allows for multi-windowed, tabbed and fullscreen applications
- Fully incremental refresh logic, backbuffers are not required
- Concurrency thanks to inbuilt support for dispatching to coroutines
- Works unmodified with stock (double precision), single precision and integer versions of the Lua VM
- Supplied with a documentation system supporting tekUI's object model
- Canvas
- Checkmark
- Directory lister
- File requester
- Floating text
- Gauge
- Group (horizontal, vertical, grids, in pages, scrollable)
- Handle for group balancing
- Images
- Lister, also multi-column and multi-selection
- Menu (popup and window), also nested
- Popup item, also nested
- Popup list ('combo box')
- Radiobutton
- Scrollgroup
- Slider
- Spacer
- Text button (and label), also multi-line
- Text input (single line)
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:
- GUIs integrate smoothly even into I/O-burdened applications (clients, servers)
- strict separation of GUI and functionality; the GUI runs in a thread or process of its own
- Faulty GUI application code, as it is written in a safe language, cannot corrupt the heap or otherwise crash the device, even if it is without a MMU.
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.
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.
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 and the default layouter). 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 the possibility of a virtual filesystem interface.
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.
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.
Complete solutions based on tekUI are in the reach of 512kB ROM or flash memory: A typical application and the required modules, 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.
Generic, unmodified versions of tekUI have 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, by porting certain classes to C, using display drivers which are more tightly connected to the hardware, etc.
Development libraries alongside with their tested versions (as of this writing) are given below:
- >=Lua-5.1.2
- >=libX11-1.1.3 (for the x11 driver)
- >=libXft-2.1.12 (for the x11 driver)
- XFree86VidMode extension (for the x11 driver)
- >=fontconfig-2.5.0 (for the x11 driver)
- >=freetype-2.3.5 (for the x11 and directfb drivers)
- >=DirectFB-0.9.25.1 (for the directfb driver)
- MinGW (for the Windows platform)
- microwindows-0.91 (for the Nano-X driver)
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
, libxxf86vm-dev
.
This release has been tested on and should compile and run out of the box on
- Ubuntu Linux 9.10, 8.04 and 7.10
- Windows 2000, Wine
- FreeBSD 7.0 (special precautions needed, see annotations below)
- Gentoo Linux x86/10.0, x86/2008.0, amd64/2007.0, ppc/2007.0
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).
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.
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.
The XFree86VidMode extension is linked against by default. It can
be disabled by removing -DENABLE_XVID
from X11_DEFS
in the
config file.
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.
For the Nano-X driver 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
Drawing primitives, clipping, and antialiased fonts are provided, and rendering can be targeted to user-supplied memory. This driver implements no input and serves as a template (or skeleton) for specializations in a target context, like custom hardware or a 3D game or simulation. For testing and debugging, the raw framebuffer driver is recursive; by plugging in another display driver, an instant visualization device and input source can be provided.
To see all build targets, type
# make help
The regular build procedure is invoked with
# make all
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
/usr/local/lib/lua/5.1
/usr/local/share/lua/5.1
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
NOCURSOR
-"true"
to disable the mouse pointer over a tekUI application. This may be useful when running on a touch screen.FULLSCREEN
-"true"
to try opening an application in fullscreen mode. Fullscreen support varies and depends largely on the display driver in use.THEME
- Theme names corresponding to style sheet files intek/ui/style/
. Multiple themes can be separated by spaces. The default is"default desktop"
. The desktop theme tries to import the color scheme found in a GTK2 configuration file.GTK2_RC_FILES
- Possible locations of a gtkrc-2.0 configuration file.
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 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.
There are different ways for reacting to presses on the 'Hello,
World' button. The simplest form is to place an onClick
function into the Text object:
ui = require "tek.ui" ui.Application:new { Children = { ui.Window:new { Title = "Hello", Children = { ui.Text:new { Text = "Hello, World!", Class = "button", Mode = "button", 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 onPress
handler receives the current
state of the Pressed
variable as an argument, so it can be used
to catch not only releases, but also presses of the mouse button:
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) ui.Text.onPress(self, pressed) print("Pressed:", 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.
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.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 }) win:addMember(text) app:addMember(win) 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 Area and Widget classes for the most important state variables.
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¹
|
TextInput:onEnter()
|
TextInput |
change of Enter attribute¹
|
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:onHover()
|
Widget |
change of the Hover 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¹
|
Canvas:onSetChild()
|
Canvas |
change of the Child attribute¹
|
Element:onSetClass()
|
Element |
change of the Class attribute¹
|
Lister:onSetCursor()
|
Lister |
change of the CursorLine attribute¹
|
Numeric:onSetMax()
|
Numeric |
change of the Max attribute¹
|
Numeric:onSetMin()
|
Numeric |
change of the Min attribute¹
|
PageGroup:onSetPageNumber()
|
PageGroup |
change of the PageNumber attribute¹
|
Slider:onSetRange()
|
Slider |
change of the Range attribute¹
|
Element:onSetStyle()
|
Element |
change of the Style attribute¹
|
Text:onSetText()
|
Text |
change of Text attribute¹
|
FloatText:onSetText()
|
FloatText |
change of Text attribute¹
|
Numeric:onSetValue()
|
Numeric |
change of the Value attribute¹
|
¹ using Object:setValue()
and the notification mechanism
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:
- Global classes are written as separate source files, located in the system-wide installation path under
tek/ui/class
and set up using a procedure as described in the class setup section.- Application classes are created in the same way, but they are located in
tek/ui/class
relative to the application's local program directory.- Another scope is inside a running application or module. We call this the ad-hoc style, because new classes are often created out of a spontaneous need.
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.
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):
- 1. Object.init() - gets called when an element is created
- 2. Element:connect() - connects the element with a parent element
- 3. The element's properties are decoded.
- 4. Element:setup() - registers the element with the application
- 5. Area:askMinMax() - queries the element's minimal and maximal dimensions
- 6. The window is opened.
- 7. Area:show() - gets called when the element is about to be shown
- 8. Area:layout() - layouts the element into a rectangle
- 9. Area:draw() - paints the element onto the screen
- 10. Area:hide() - gets called when the element is about to hide
- 11. The window is closed.
- 12. Element:cleanup() - unregisters the element
- 13. Element:disconnect() - disconnects the element from its parent
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
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.
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.
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
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 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.
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.
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
.