Containers and layout policies have been a staple of GTK’s design since the very beginning. If you wanted your widget to lay out its children according to a specific policy, you had to implement GtkContainer
for handling the addition, removal, and iteration of the child widgets, and then you had to implement the size negotiation virtual functions from GtkWidget
to measure, position, and size each child.
One of the major themes of the GTK 4 development cycle is to delegate more functionality to ancillary objects instead of encoding it into the base classes provided by GTK. For instance, we moved the event handling from signal handlers described by GtkWidget
into event controllers, and rendering is delegated to GtkSnapshot
objects. Another step in that direction is decoupling the layout mechanism from GtkWidget
itself to an ancillary type, GtkLayoutManager
.
Layout Managers
A layout manager is the object responsible for measuring and sizing a widget and its children. Each GtkWidget
owns a GtkLayoutManager
, and uses it in place of the measure()
and allocate()
virtual functions—which are going away. The gist of the change: instead of subclassing a GtkWidget
to implement its layout policy, you subclass GtkLayoutManager
, and then assign the layout manager to a widget.
Just like in the old GtkWidget
code, you will need to override a virtual function to measure the layout, called measure()
, which replaces the get_preferred_*
family of virtual functions of GTK 3:
static void layout_measure (GtkLayoutManager *layout_manager, GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline)
After measuring, you need to assign the size to the layout; this happens in the allocate()
virtual function, which replaces the venerable size_allocate()
virtual function of previous GTK major versions:
static void layout_allocate (GtkLayoutManager *layout_manager, GtkWidget *widget, int width, int height, int baseline)
On the more esoteric side, you can also override the get_request_mode()
virtual function, which allows you to declare whether the layout manager requests a constant size, or if one of its sizes depend on the opposite one, like height-for-width or width-for-height:
static GtkSizeRequestMode layout_get_request_mode (GtkLayoutManager *layout_manager, GtkWidget *widget)
As you may notice, each virtual function gets passed the layout manager instance, as well as the widget that is using the layout manager.
Of course, this has bigger implications on various aspects of how GTK widgets work, the most obvious being that all the complexity for the layout code can now stay confined into its own object, typically not derivable, whereas the widgets can stay derivable and become simpler.
Another feature of this work is that you can change layout managers at run time, if you want to change the layout policy of a container; you can also have a per-widget layout policy, without adding more complexity to the widget code.
Finally, layout managers allow us to get rid of one of the special cases of GTK, namely: container child properties.
Child properties
Deep in the guts of GtkContainer
sits what’s essentially a copy of the GObject property-related code, and whose only job is to implement “child” properties for types deriving from GtkContainer
. These container/child properties exist only as long as a child is parented to a specific class of container, and are used for a variety of reasons—but, generally, to control layout options, like the packing direction in boxes and box-like containers; the fixed positioning inside GtkFixed
; or the expand/fill rules for notebook tab widgets.
Child properties are hard to use, as they require ad hoc API instead of the usual GObject
one, and thus require special casing in GtkBuilder
, gtk-doc, and language bindings. Child properties are also attached to the actual direct child of the container, so if a widget interposes a child—like, say, GtkScrolledWindow
or GtkListBox
do—then you need to keep a reference to that child around in order to change the layout that applies to your own widget.
In GTK’s master branch we got rid of most of them—either by simply removing them when there’s actual widget API that implements the same functionality, or by creating ancillary GObject types and moving child properties to those types. The end goal is to remove all of them, and the relative API from GtkContainer
, by the time GTK 4 rolls out. For layout-related properties, GtkLayoutManager
provides its own API so that objects are created and destroyed automatically once a child is added to, or removed from, a widget using a layout manager, respectively. The object created is introspectable, and does not require special casing when it comes to documentation or bindings.
You start from deriving your own type from the GtkLayoutChild
class, and adding properties just like you would for any other GObject
type. Then, you override GtkLayoutManager
‘s create_layout_child()
virtual function:
static GtkLayoutChild * create_layout_child (GtkLayoutManager *manager, GtkWidget *container, GtkWidget *child) { // The simplest implementation return g_object_new (your_layout_child_get_type (), "layout-manager", manager, "child-widget", child, "some-property", some_property_initial_state, NULL); }
After that, you can access your layout child object as long as a widget is still a child of the container using the layout manager; if the child is removed from its parent, or the container changes the layout manager, the layout child is automatically collected.
New layout managers
Of course, just having the GtkLayoutManager
class in GTK would not do us any good. GTK 4 introduces various layout managers for application and widget developers:
GtkBinLayout
implements the layout policy ofGtkBin
, with the added twist that it supports multiple children stacked on top of each other, similarly to howGtkOverlay
works. You can use each widget’s alignment and expansion properties to control their location within the allocated area, and theGtkBinLayout
will always ask for as much space as it’s needed to allocate its largest child.GtkBoxLayout
is a straight port of the layout policy implemented byGtkBox
;GtkBox
itself has been ported to useGtkBoxLayout
internally.GtkFixedLayout
is a port of the fixed layout positioning policy ofGtkFixed
andGtkLayout
, with the added functionality of letting you define a generic transformation, instead of a pure 2D translation for each child;GtkFixed
has been modified to useGtkFixedLayout
and use a 2D translation—andGtkLayout
has been merged intoGtkFixed
, as its only distinguishing feature was the implementation of theGtkScrollable
interface.GtkCustomLayout
is a convenience layout manager that takes functions that used to be GtkWidget virtual function overrides, and it’s mostly meant to be a bridge while porting existing widgets towards the layout manager future.
We are still in the process of implementing GtkGridLayout
and make GtkGrid
use it internally, following the same pattern as GtkBoxLayout
and GtkBox
. Other widgets inside GTK will get their own layout managers along the way, but in the meantime they can use GtkCustomLayout
.
The final step is to implement a constraint-based layout manager, which would let us create complex, responsive user interfaces without resorting to packing widgets into nested hierarchies. Constraint-based layouts deserve their own blog post, so stay tuned!
This looks like a great improvement!!
Thanks to everyone who works on GTK. Your efforts are appreciated.
Question: once the new layout manager is stable and ready, will Glade be ready from day 1 to take advantage of the new features? Or will we have to wait for Glade to ‘catch up’?
@JR: This is a question better asked to the Glade maintainers; in general, it’ll depend on how fast Glade is ported to GTK4.
It should be possible to assign an existing layout manager to a GTK widget through the Glade UI, but first Glade needs to know which layout manager classes are available, and how to display them.
Existing container widgets in GTK, and other widgets that use a layout manager, will of course work out of the box.