Creating Widgets

It is very easy to create a custom widget.

Any widget class inherits from the parent Widget class which deals with all the technical aspects of the widget. All that is required of your widget class is a suitable constructor and a draw() function.

Take this example Gauge widget. The constructor is used to initialize the parent Widget class and store a "size" value.

The draw() function simply creates a temporary Framebuffer565 object (this is not necessary, it is only incredibly convenient), draws the widget (three circles, a few dots, and a line) into that framebuffer, then renders the framebuffer out to the provided device at the specified location.

class Gauge : public Widget {
private:
    int _size;
    
public:
    Gauge(Touch &ts, DisplayCore &dev, int16_t x, int16_t y, int size) :
        Widget(ts, dev, x, y),
        _size(size) {}
      
    void draw(DisplayCore *dev, int16_t x, int16_t y) {
        uint16_t data[_size * _size];
        float r = _size / 2.0;
        int r_outer = 32;
        int r_inner = 30;
        int r_dot = 28;
        int r_hand = 24;

    // 360 degrees has twelve 30 degree slots.
    // 6 is at 180 degrees. Two more is the start point.
    // Four from 12 is 8.

        float deg30 = 0.523598776;
        float a_start = deg30 * 8;
        float a_range = deg30 * 8;

        Framebuffer565 fb(_size, _size, data);
        fb.fillScreen(Color::Black);
        fb.fillCircle(r, r, r_outer, Color::Gray70);
        fb.fillCircle(r, r, r_inner, Color::Gray15);

        for (int i = 0; i < 11; i++) {
            float a = i / 10.0 * a_range + a_start;
            int px = sin(a) * r_dot;
            int py = cos(a) * r_dot;
            fb.setPixel(r + px, r - py, Color::Gray90);
        }

        float angle = _value / 100.0 * a_range + a_start;
        int ex = sin(angle) * r_hand;
        int ey = cos(angle) * r_hand;
        fb.drawLine(r, r, r + ex, r - ey, 4, Color::Gray70);
        fb.fillCircle(r, r, 5, Color::Gray40);
        fb.draw(dev, x, y);
    }
};

All the value management is already handled, as well as the decision whether or not the widget needs to be redrawn.

It is possible to override the void setValue(int) method to provide special value handling should it be needed - just remember to call redraw() if the value has changed.

To make a widget touch-sensitive you just have to set certain values in the constructor.

    Gauge(Touch &ts, DisplayCore &dev, int16_t x, int16_t y, int size) :
        Widget(ts, dev, x, y),
        _size(size) {
            _touch = true;
            _sense_x = 0;
            _sense_y = 0;
            _sense_w = size;
            _sense_h = size;
    }

The boolean _touch turns touch sensitivity on or off, and the _sense_* variables define a rectangle within the bounds of the widget (that is relative to the top-left point of the widget) where the touch is sensed.

Besides using a framebuffer to render the widget other techniques include diretly drawing primitives and text to the provided device, and opening a render window to the device and directly streaming a rectangle of calculated (or otherwise generated) image data.

If direct drawing of primitives is used it is recommended to enable buffering so that devices which suppor buffering benefit from a huge speed increase.

dev->startBuffer();
// ... Drawing operations
dev->endBuffer();