making a progress bar in tophat

2022-03-28, altered 2022-06-13 and 2022-09-26

Tophat offers a UI library by default. It doesn’t come with many elements, but is made to be very extensible. This article shows how to make a simple UI element yourself, using progress bar as an example.

GIF of the result

UI elements have to implement the ui.Element interface. The interface defines three functions: get, handle and draw. get returns a pointer to the ui.Generic struct contained in the element (this is basically inheritance). ui.Generic defines many useful things like the element’s transform, callback function and theme. Every element needs to contain a generic.

The progress bar will also need to hold the current progress value. In this example, a single fu will be used, representing a value between 0 and 1. It is obviously possible to store this as a set of two values - min and max.

Knowing what a progress bar has to contain, the struct will look like this:

type Bar* = struct {
    g: ui.Generic
    value: th.fu
}

Now that we have the struct ready, we need to define the methods needed to implement the ui.Element interface. The get method is very straightforward, so let’s get it out of the way first:

// implement the ui.Element interface
fn (b: ^Bar) get*(): ^ui.Generic {
    return &b.g
}

Next is the handle function. There are only two things to do in our case. The first is input handling. Thankfully ui.Generic provides a method handling input for us. This is enough in many cases, but some elements need to have more control over the input handling (for example the itemlist example). The second thing is making sure the value is not larger than one. The handle function can look like this:

fn (b: ^Bar) handle*() {
    // handles input like clicks and hover
    b.g.handle()

    if b.value > 1 {
        b.value = 1
    }
}

Now we have only one function to go. The draw function. The drawing is comprised of three parts: the background, the foreground and the border. The background can be drawn as a rectangle covering the area of the element. The foreground is similar, except the width of the rectangle is multiplied by the value of the progress bar. Drawing the border is also very simple, since ui.um provides a function for drawing it.

fn (b: ^Bar) draw*() {
    r := b.g.r

    // draw the background
    canvas.drawRect(b.g.theme.bg, r)
    // draw the progress line
    canvas.drawRect(
        b.g.theme.fg,
        rect.mk(r.x, r.y, r.w * b.value, r.h))

    // draw the border
    ui.drawBorder(th.Vf2{r.x, r.y}, th.Vf2{r.w, r.h}, b.g.theme)
}

Finally, it is a good practise to make a mk function for your element. The only thing we need to do there is properly initialize the generic. This is needed to prevent future crashes in input handling.

fn mk*(): Bar {
    bar := Bar{}
    bar.g = ui.mkGeneric()
    return bar
}

Making tophat ui elements is very easy. I managed to make this progress bar in under 5 minutes. I recommend checking out the item list example, which shows more advanced things like custom input handling and text rendering. The whole progress bar is available for download here (zip).

More reading:

This article along with the code is in the public domain.