Abstractions, Keyboard Handling and Drawing

When doing things with user interface widgets I find that I tend to over-think things, making them more complicated than they need to be. It's too easy to take a step back and try to create a general framework when the problem at hand is fairly simple. I suppose that I want to avoid making decisions that will limit the usefulness of what I write, resulting in code needing to be replaced later.

Abstractions

Limbo provides abstract data types (ADTs) which are a bit like structs in C but with member functions. For someone who has used object-oriented frameworks in C++ and Python, it's challenging to adapt to a different way of describing and expressing user interface elements. I've tried to handle this before in different ways, usually falling back on the pick adt feature in Limbo that lets you define what are effectively subtypes of a data type and select the relevant one at run-time.

In the following code, assigning widget (with type Widget) to inst in a pick statement lets the program handle the actual type of widget via inst.

pick inst := widget
{
    Label =>
        widget.drawtext(inst.text, inst.alignment, inst.font, im);

    LineEdit =>
        widget.drawtext(inst.text, inst.alignment, inst.font, im);
        widget.drawcursor(inst.text, inst.alignment, inst.font,
                          inst.cursorpos, im);
}

Each of the possibilities is handled in a similar way to a case statement, with the type of inst selecting which group of statements to execute. So, if the object actually has the type Widget.Label then inst is given that type and the first group of statements are executed.

Accessing the subtype-specific features of widget like this is a bit like casting to more specific types in C++ but in a way that covers multiple possibilities in a statement, and which seems less error-prone.

Keyboard handling

The application I want to write will need to access the /dev/keyboard device to accept keyboard input. The code to do this is similar to some I found in Inferno's /appl/lib/wmlib.b file. My version of the code sends key codes to a widget's key method, but the basic file reading and conversion is unchanged.

f := sys->open("/dev/keyboard", Sys->OREAD);
buf := array[12] of byte;

for (;;) {
    while ((n := sys->read(f, buf, len buf)) > 0) {
        s := string buf[:n];
        for (i := 0; i < len s; i++)
            edit.key(s[i]);

        edit.draw();
    }
}

The critical part is performing the conversion of input bytes into a string and extracting the characters individually as integers. If it works for wm then it should be good enough for my application!

Drawing

I often spend a lot of time trying to figure out how to use the Draw API, relying on some memory of how it is supposed to be used. After some experimentation, I managed to get some basic widgets up and running.


A simple line edit widget for entering text

The keyboard handling code is currently in the main program, and the widgets aren't really grouped or organised in any meaningful way. That will be the next thing to do.


David Boddie
21 December 2021