Libtoys

Generic C++ microwidgets for UI development (proof of concept)

View the Project on GitHub arauhala/libtoys

Introduction to libtoys

libtoys is a proof-of-concept library demonstrating a design based on the simplest possible UI widgets that do exactly one things and nothing more. These 'micro widgets' are not only extremely simple, but there is also only a handful of them. In fact, the main provided micro widgets are:

These widgets can be combined to create e.g. a button (code is C++14):

auto quit = 
    sz(xy(100, 25),                                // 1. set size
       on_click([](click&){ system::exit(0); },    // 2. exit on click
                fb(lay(mid, tx("exit", f, black)), // 3. text on front middle
                   rc(rgba{200, 200, 200,0})));    // 4. rectangle background

This simplistic button looks like this:

Design and capabilities

The leading idea behind libtoys is minimalism and it follows strictly the UNIX way, where the system should consist of minimalistic tools doing one orthogonal thing right and where the power raises from the way the components are combined instead of the component's internal complexity. This extreme minimalism brings small learning overhead and make it easier to master the small tool set thus making it faster to get things done.

Also often the abstract technical primitives are forever (e.g. BSD sockets), while more specific services tend to not fully fulfil the wavering application needs. E.g bigger applications (and especially games) may rely almost entirely on custom UI components. libtoys does not even try to provide ready-to-use widgets, but instead supports you in making your own, which is anyway a major activity in UI projects.

Other design goals of libtoys are:

The expectation is that the simplicity, declarativeness, statelessness and terseness improve productivity. Also providing technical primitives instead of high level services/policy typically means that the framework won't get in your way. This reflects my own experience of having worked with similar UI kit I created for Java.

Of course: you cannot get that much done with the library yet, as it is incomplete. Also solution has its issues like verbose errors for mistakes (because heavy template usage) and there may be potential for badly scaling layouting calculations, because lack of states (all thought compiler may optimize recalculations out when widgets are composed compile-time via templates and the problem can anyway be solved by adding stateful 'cache' microwidgets in deep microwidget structures.). There are also likely bugs and design issues, because library's young age. The solution also lays lot of faith on the compiler and its optimizations.

Code sample & screenshot:

Here's a bigger code sample demonstrating the toolkit. Following example demonstrates...

  1. ...writing a simple interactive UI...
  2. ...with a manipulated state (variable c) and a dynamic property (rgbtext) that changes when c is modified
  3. ...and how to encapsulate compile-time composed widgets in separate modules via itoy interface and how to use the encapsulated widgets.
#include "toys/sdl.h"
#include <sstream>

using namespace std;
using namespace toys;
using namespace toys::sdl;
using namespace toys::sdl::vals;

unique_ptr<itoy> make_radio_button(const font& f, const function<void (bool)>& changed);
unique_ptr<itoy> make_resizing_item();

int main(int argc, char** argv) {
    // set up infra. sdltoys constructor initializes SDL  
    sdltoys i;
    font f("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 20);

    // data used by widgets
    const rgba black{0, 0, 0, 0};   // this is immutable
    rgba c{100, 100, 100, 0};       // this is mutable state used by reference
    auto rgbtxt = prop([&c]() {     // this is dynamic state produced by lambda
        std::ostringstream buf;
        buf<<"r:"<<int(c.r)<<",g:"<<int(c.g)<<",b:"<<int(c.b);
        return buf.str();
    });

    auto radio = make_radio_button(f, [&c](bool v) { c.g = (v?200:100); });
    auto titledradio = lr(0.5, lay(mid, tx("green:", f, black)), lay(mid, radio.get()));

    auto resizing = make_resizing_item();

    auto text = lay(mid, tx(rgbtxt, f, black));
    auto moreblue = on_click([&c](click&){c.b+=32; },
                             fb(lay(mid, tx("more blue", f, black)), rc(rgba{200,200,250,0})));
    auto quit = on_click([](click&){ system::exit(0); },
                         fb(lay(mid, tx("exit", f, black)), rc(rgba{200, 200, 200,0})));

    auto z = fb(ud(0.33,
                   text,
                   ud(0.5,
                      lr(0.5, titledradio, lay(mid, resizing.get())),
                         lr(0.5, lay(mid, sz(xy(0.67, 0.5), moreblue)),
                                 lay(mid, sz(xy(0.67, 0.5), quit))))),
                rc(std::ref(c)));

    window<decltype(z)&> wnd("foo", vec(), {400, 400}, z);
    return i.run(&wnd);
}

Functions make_radio_button and make_resizing are defined in a separate file. They demonstrate two different ways how micro widgets can be composed in encapsulated way that hides the internal types and implementations (as you probably don't want to define complex template instances in your shared headers). The first way sets up everything in a single function body so that widget state is split into several heap blocks, while the second builds widget in single heap area, but it requires an additional class and some boilerplate code.

The essential interface for encapsulation is the itoy class that has 3 methods that are a) size, b) draw and c) recv. owned_itoy or shared_itoy functions can be used to wrap a compile-time composed widget inside itoy interface or class can implement the interface directly as done in resizing_item class.

#include "toys/sdl.h"
#include <functional>

using namespace toys::sdl::vals;
using namespace toys::sdl;
using namespace toys;
using namespace std;

// following widget does not require separate class definition, but its state gets allocated in heap
unique_ptr<itoy> make_radio_button(const font& f, const function<void (bool)>& changed) {
    shared_ptr<bool> state(new bool(false));
    rgba black{0, 0, 10, 0};
    shared_ptr<itoy> on = shared_itoy(fb(lay(mid, tx("on", f, black)), rc(rgba{150, 250, 150, 0})));
    shared_ptr<itoy> off = shared_itoy(fb(lay(mid, tx("off", f, black)), rc(rgba{250, 150, 150, 0})));
    return
        owned_itoy(
            sz(xy(75, 50),
               on_click([state, changed](click&) {
                            *state = !*state; changed(*state);
                        },
                        prop([state, &f, on, off, black] () { 
                            return *state ? on.get() : off.get();
                        }))));
}

// composing a stateful and flat (not split into several memory blocks) widget from stateless
// microwidgets is possible, if a bit clumsy.
auto make_resizing_toy(vec* v) {
    return on_click([v](click& c){*v = (v->x() == 50) ? vec(100, 100) :vec(50,50); },
                    sz([v](vec){return *v;}, rc(rgba{255, 255, 255, 0})));
}
class resizing_item : public itoy {
private:
    vec sz_;
    decltype(make_resizing_toy(0)) toy_;
public:
    resizing_item() : sz_(50, 50), toy_(make_resizing_toy(&sz_)) {}
    vec size(const vec& size) const                    { return toy_->size(size); };
    void draw(const vec& size, graphics_type& i) const { return toy_->draw(size, i); }
    bool recv(const vec& size, ievent& e)              { return toy_->recv(size, e); }
};
unique_ptr<itoy> make_resizing_item() {
    return unique_ptr<itoy>(new resizing_item());
}

The result of the presented code is a simple UI with 4 widgets that can be clicked and 4 items with dynamic properties (resizing widget, radio button changes appearance, rgb text updates, background color changes).

When application launches, it looks following:

After clicking resizing button, putting radio widget on and clicking 'more blue' button the application looks like this:

Author

I (Antti Rauhala) am a software engineer and data scientist at Futurice, who has a background in systems programming, mobile, machine learning and search. My previous projects are freeform meta language (http://freeformlang.sourceforge.net) and re-expression method (http://arauhala.github.io/libreexpweb/) that is a formal language centric representation learning method.