Tutorials

Hello, World!

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

#/usr/bin/env lua
ui = require "tek.ui"
ui.Application:new
{
  Children =
  {
    ui.Window:new
    {
      Title = "Hello",
      Children =
      {
        ui.Text:new
        {
          Text = "_Hello, World!",
          Style = "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 there is no button class in tekUI: 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.) Also note that by placing an underscore in front of a letter in its caption, we are creating a keyboard shortcut for the Text element.

To quit, press the window's close button or the Escape key – closing the Application's last open window will cause the run method to return to its caller.

Reacting on input

There are different ways for reacting to presses on the "Hello, World" button. The simplest method is to just place an onPress function into the Text object, which will overwrite the notification handler reacting on setting the Pressed variable. This variable is set to true when a Gadget is getting activated, and to false when it is getting released:

#/usr/bin/env lua
ui = require "tek.ui"
ui.Application:new
{
  Children =
  {
    ui.Window:new
    {
      Title = "Hello",
      Children =
      {
        ui.Text:new
        {
          Text = "_Hello, World!",
          Style = "button",
          Mode = "button",
          onPress = function(self, pressed)
            if pressed == false then
              print "Hello, World!"
            end
          end,
        },
      },
    },
  },
}:run()

Another, more elobarate way is to equip the element with a notification handler yourself, which may look like this:

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

Even though notification handlers can be written in the same expression as the rest of the application, we are now switching to a different setup style, because the deep levels of recursion are starting to interfere with clarity:

#/usr/bin/env lua
ui = require "tek.ui"
app = ui.Application:new()
win = ui.Window:new { Title = "Hello" }
text = ui.Text:new {
  Text = "_Hello, World!",
  Style = "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()

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

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:

  • Global classes are written as separate sources, 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 the application's local program directory (also under tek/ui/class). They would also allow an application to be shipped with modified versions of the standard classes, as the application's local directory will be searched for classes first.
  • Another scope is inside the source code of an existing application or module. We might call this the 'ad-hoc' style, as new classes are often created out of a spontaneous need.

For the ad-hoc style, it is not necessary to create a new source. For example, to derive a button class from the Text class:

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

By convention, ad-hoc classes can be named arbitrarily, but their names should be prefixed with an underscore to distinguish them from global classes.

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

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

As shown in the example, we also passed the call on to our superclass, 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.

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.

Proxied object model

If you plan on extending existing classes or develop your own, you should 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 of the tekUI framework.

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

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.

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::

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). Consecutively, 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.