-
Notifications
You must be signed in to change notification settings - Fork 8
Traditional UI Widgets
The Rig editor itself is currently built up using a fairly traditional set of 2D UI widgets and this page explains some of the details about how these are handled.
- rut-bin.h: A widget for handling vertical and horizontal alignment and left, right, top, bottom padding
- rut-stack.h: Stacks child widgets on top of each other
- rut-box-layout.h: A widget for handling vertical or horizonal layouting of children widgets
- rut-flow-layout.h: A widget for handling vertical or horizontal flowing of mixed sized children with wrapping
- rut-split-view.h: Divides a space in two with a specific fraction
- rut-ui-viewport.h: A clipped but scrollable coordinate space for child widgets
- rut-scroll-bar.h: Handles scrollbars (used by the RutUIViewport widget)
- rut-text.h: A flexible text widget supporting editing, but does not handle drawing a background/border
- rut-text-buffer.h: A data model used by the text widget
- rut-entry.h: An editable text entry widget, including drawing a bounding shape
- rut-image.h: Displays an image with the correct aspect ratio
- rut-button.h: A simple text button widget
- rut-icon-button.h: A button widget that lets you specify "normal", "hover", "active" and "disabled" state icons and a label to go above, below or beside the icon.
- rut-toggle.h: A checkbox like widget
- rut-color-picker.h: A high level widget for selecting a colour using the HSL colour model
- rut-drop-down.h: A combo-box like widget that lets the user select one value from a list
- rut-number-slider.h: A number selection widget
- rut-prop-inspector.h: A high level widget for inspecting a property of a component
- rut-vec3-slider.h: A high level widget for editing 3 component vectors
- rut-transform-private.h: The simplest non-visual widget used to transform children relative to their parent's position
The first thing to know about is that there is a "sizeable" interface that these 2D widgets and containers all implement adding some thing like this to the _init_type() for the widget:
static RutSizableVTable sizable_vtable = {
rut_button_set_size,
rut_button_get_size,
rut_button_get_preferred_width,
rut_button_get_preferred_height,
NULL /* add_preferred_size_callback */
};
<snip>
rut_type_add_interface (type,
RUT_INTERFACE_ID_SIZABLE,
0, /* no implied properties */
&sizable_vtable);
The _set_size method can be used by a parent container widget to set a new size for a widget. For many simple 2D object they will already need getters and setters for their size - e.g. for a 2D rectangle type you might have something like rut_rectangle_set_size() and this api for setting its size can usually also just be plugged into this sizeable vtable since there's nothing special about the size being set here.
The _get_preferred_width/height functions are what a parent container can optionally use to negotiate with a child what size it should have. Since there are many kinds of objects who's height depends on their width and vica-verca these apis are designed so that a parent container can ask a child how wide or high would they prefer to be assuming a specific fixed height or width.
The add_preferred_size_callback is for a parent container to use to register for notifications whenever the child object's preferred size changes. The method returns a RutClosure pointer like other callback registration functions in Rig. It's the responsibility of each widget to notify its parent, or anything interested, via these registered callbacks whenever its preferred size changes.
The implementation of this method is optional for widgets if their preferred size never changes. If it is not implemented then the wrapper function will create a dummy closure object that will never be invoked so that the rest of the code does not have to care if the object has an implementation or not.
Besides the sizeable interface you should also understand how we drive the process of re-sizing children...
Firstly any container or widget that is interested in the preferred size of its children should use rut_sizable_add_preferred_size_callback() to register a callback to be notified of changes.
We don't immediately respond to a child changing their preferred size and calculate a new size for them because we'd do a lot of redundant work in cases where a child's preference changes many times per frame or when we need to consider the size of many children and they may all want to update their preferences in a frame.
There is a callback mechanism that lets widgets register a callback to be called just prior to painting. (rut_shell_add_pre_paint_callback())
The callback is associated with a graphable object so that the callbacks can be invoked in order of increasing depth of the objects within their scenegraph. Only one callback per graphable is allowed.
The intended usage of this mechanism is so that objects can delay layouting their children until just before painting. Whenever a property that changes a widget's layouting is altered, the widget is expected to register a callback via rut_shell_add_pre_paint_callback(). In the implementation of the callback it will actually layout its children according to its current size. Usually changing a widget's size will affect the layout so it is important that the callbacks are invoked in increasing order of depth so that the children will have the correct size while they are allocating.
It is safe and expected that an object will queue a pre-paint callback during the invocation of another pre-paint callback.
The sorting of the list of callbacks is delayed until the queue is just about to be flushed so that it doesn't matter if an object moves in the hierarchy before the scene is actually painted. However if a callback is added while the list is being flushed then it will be inserted in the right place according to its depth. This assumes that widgets won't change their parents during the pre-paint callback.
If a child is removed from the hierarchy while it still has a callback registered then nothing is done to try and remove the callback. It's expected that it won't do any harm to call it even if the graphable is not really in the scene and it may be that the object's parent doesn't affect its layouting so the callback will do the right thing anyway.
Any widget with the notion of a preferred size needs to implement this method of the sizeable interface and there's a small amount of boilerplate involved. Firstly for easy case that your widget is simply made up as a composite of other widgets you can skip this and see the section below.
First add a RutList member to your widget for tracking the callbacks registered like:
RutList preferred_size_cb_list;
Make sure to init your list when allocating your widget:
rut_list_init (&bin->preferred_size_cb_list);
Create a small convenience function for invoking all registered callbacks, something like:
static void
preferred_size_changed (RutBin *bin)
{
rut_closure_list_invoke (&bin->preferred_size_cb_list,
RutSizablePreferredSizeCallback,
bin);
}
Now whenever some property of your widget changes that affects the preferred size of your actor you can call preferred_size_changed() to notify any one that registered an interest.
Finally disconnect all the closures when freeing your widget:
rut_closure_list_disconnect_all (&bin->preferred_size_cb_list);
If your widget is simply a composite of other pre-existing widgets then you can implement the "composite sizeable" interface with something like this in your _init_type():
static RutSizableVTable sizable_vtable = {
rut_composite_sizable_set_size,
rut_composite_sizable_get_size,
rut_composite_sizable_get_preferred_width,
rut_composite_sizable_get_preferred_height,
rut_composite_sizable_add_preferred_size_callback
};
<snip>
rut_type_add_interface (type,
RUT_INTERFACE_ID_SIZABLE,
0, /* no implied properties */
&sizable_vtable);
rut_type_add_interface (type,
RUT_INTERFACE_ID_COMPOSITE_SIZABLE,
offsetof (TYPE, top_widget),
NULL); /* no vtable */
And notice that when adding the RUT_INTERFACE_ID_COMPOSITE_SIZABLE interface you need to specify the offset to a member in your widget's structure that is another container widget. This will have the effect of proxying the sizeable interface to the widget specified here.