Customizing visual appearance of widgets

wordwraplabel.C gives an example of using so-called appearance objects to adjust a widget's visual appearance. The visual appearance of LibCXXW's widgets defaults to the current display theme. wordwraplabel.C overrides the default window background color to light yellow for showing text in blue and black colors.

An appearance object is a reference-counted object accessed using LibCXX's smart pointers. The appearance objects are constant objects, and their smart pointer handles follow the default LibCXX naming conventions. A main window's appearance object handle is named x::w::const_main_window_appearance. Each LibCXXW's class that gets used to create a widget has an appearance object attached to it, and the appearance object remains attached to the widget for the duration of its existence.

Modifying an appearance object

The connection thread uses appearance objects for rendering. The appearance objects are thread-safe by the virtue of them being constant objects. An existing appearance object never gets modified directly. Each appearance object's modify() method creates a duplicate copy of the original appearance object which then gets modified, and modify() returns a new, constant, appearance object.

A new main window gets created using a x::w::main_window_config whose appearance member specifies the main window's default visual appearance. This is a design pattern that's universally shared by all widgets. For example, an x::w::new_listlayoutmanager's appearance object specifies the visual appearance of a new list.

The appearance is always a x::const_ref. It gets dereferenced to a constant object that cannot be modified. The initial, default appearance is always a cached, constant object that sets the visual appearance from the current display theme. Implementing a custom appearance involves using the appearance object's modify() to create a modifiable copy of itself. The resulting object gets modified, and placed back into the original object where the appearance came from:

#include <x/w/main_window_appearance.H>
#include <x/w/rgb.H>

x::w::main_window_config config;

x::w::const_main_window_appearance appearance=config.appearance;

appearance=appearance->modify
    ([]
     (const x::w::main_window_appearance &appearance)
     {
         appearance->background_color=x::w::white;
     });

config.appearance=appearance;

x::w::main_window_config's default constructor installs the default, cached, appearance reflecting the current display theme. Its handle name is x::w::const_main_window_appearance reflecting that it's a reference to a const object.

An appearance object's modify() method takes a callable object, or a closure, as its parameter. The closure receives a modifiable copy of the original appearance object, an x::w::main_window_appearance. The closure makes changes to the appearance object, and modify() returns a new constant appearance object that replaces x::w::main_window_config's original one.

Appearance-objects, like all other library objects, are reference-counted objects. This convention ensures by contract that appearance objects referenced by active widgets are always constant and thread-safe. This approach makes appearance objects modifiable only after they're copied from an existing constant object, and the modified appearance objects remain constant thereafter.

Caching appearance objects

Each call to an existing appearance object's modify() creates a new object. It is more efficient to use the same appearance object with multiple widgets that should have the same visual appearance.

If all widgets with the same appearance get created together, it's simple enough to create their common appearance object and use them when creating each one. When widgets get created in different places, taking advantage of static local function scope is a basic, thread-safe singleton implementation:

#include <x/w/progressbar_appearance.H>

static x::w::const_progressbar_appearance create_custom_appearance()
{
    return x::w::progressbar_appearance::base::theme()->modify
        ([]
         (const auto &appearance)
         {
             appearance->label_font=x::w::font{"liberation mono; point_size=24"};
         });
}

static x::w::const_progressbar_appearance &custom_progressbar()
{
    static const x::w::const_progressbar_appearance obj=
        create_custom_appearance();

    return obj;
}

// ...

x::w::progressbar_config config;

    config.appearance=custom_progressbar();

    factory->create_progressbar
        ([]
         (const LIBCXX_NAMESPACE::w::progressbar &container)
         {
              // ...
         },
         config);

custom_progressbar() returns a native reference to the same appearance object, that gets automatically constructed in a thread-safe manner. All progress bars will share, efficiently, the same appearance object.

Using an XML stylesheet to create an appearance object

It is also possible to create a new appearance object from an XML stylesheet. See Appendix B, LibCXXW theme files for more information.