GTK is a powerful framework, yet also carries along some legacy and homegrown structures, hidden within a huge code base. While there are excellent beginner tutorials, definitive technical documentation is scarce, and design decisions must be inferred from the existing code sometimes. Since Lumiera is on the mission of building a quite elaborate user interface — well beyond the abilities of a typical office application — we’re often forced to identify possible extension points and or work out reliable and future proof techniques to create behaviour beyond the original intentions of the GTK developers.
Allocation of Screen Space
GTK is built from ground up with the assumption of a dynamical layout — designing the UI layout with fixed sizes and widget placements is not seen as a viable option and only marginally supported as an exotic corner case. An user interface built with GTK is comprised of widgets arranged into a hierarchical structure, and styled by matching a corresponding structure of layout nodes against a set of cascading style rules (CSS). When a new widget is attached into this structure, it has to progress through several stages
-
creation / allocation
-
wiring
-
“map” : associate a display window (GDK window) and dedicated extension on screen
-
“realize” : bring all structures into workable state
-
“draw” : render the visuals into pixels for display
-
“unrealize” : remove from active interconnections
-
“unmap” : release the ties to the associated (GDK) window
-
“destroy” : detach from managers and deallocate data
After passing through the realize phase, a widget holds unto a Allocation
— a struct defining
the position of the upper left corner, and its extension (width, height) in pixel coordinates. Due
to dynamic interaction responses, a widget can be resized, causing the emission of a resize event.
Processing this resize event within the GTK Event Loop will create and assign an updated allocation,
followed by redrawing the widget. GTK uses double buffering, and thus it is sufficient to draw the
widget in its new shape, without having to clear out the old state from the display buffer.
Allocation strategy
In GTK, as a rule, screen extension is never squeezed, but rather expanded to fit. Every widget is queried through a set of virtual functions (“vfunc”) to define its basic layout trend (the “size request mode”), and its minimal and natural extension
- minimal
-
the absolute minimum required by the widget to work properly
- natural
-
the extension necessary to use the widget properly, without wasting screen estate
- size request mode
-
by implementing ´get_request_mode_vfunc()`, the widget defines how its allocation shall be treated…
-
SIZE_REQUEST_HEIGHT_FOR_WIDTH
: start with a desired width and then accommodate the vertical extension -
SIZE_REQUEST_WIDTH_FOR_HEIGHT
: start with a desired height and expand horizontally as needed -
SIZE_REQUEST_CONSTANT_SIZE
(this seems to be there for sake of completeness, but is typically not treated explicitly as a distinct case in layout code; expect to fall back to the default, which is height-for-width)
-
Starting from these requirements as defined by the widget, next the CSS definitions are accessed through
the CSS Gadget, which is associated internally with each widget. This typically causes the allocation
to be increased to allow for borders, margins, padding and drop shadows. In case the widget is placed
into a container with fill-layout, the widget may be expanded further, or margins will be created by
shifting the pixel coordinates. From the allocation worked out thus far, all headroom necessary for
proper drawing is then subtracted, and the resulting bare allocation is passed to the widget through
the function gtk_widget_set_allocation()
, which also invokes the Gtk::Widget::on_size_allocate()
hook. Note that the fully expanded allocation is not stored; GTK just assumes that widgets will
draw themselves properly, including their decorations, thereby possibly using extended screen space.
Minimal vs. natural size request
In short: you need to define both, while the natural size request is more relevant in most cases.
All top level entities and most layout containers will start with the natural size. However, some
containers initiate the with-for-height (or height-for-width) request sequence with the minimal
extension. Most notably, the canvas control, Gtk::Layout
allocates widgets according to
this scheme (see gtk_layout_allocate_child()
in gtklayout.c).
judging from the code, it is recommended to implement both get_preferred_height|width() ,
irrespective of the layout trend. However, it is only necessary to implement the derivative
function matching the trend, e.g. Gtk::Widget::get_preferred_height_for_width_vfunc() in
case of `SIZE_REQUEST_HEIGHT_FOR_WIDTH , since the default implementation will fall back
on get_preferred_with() for the other one. |
However — in most cases (custom) widgets are assembled by arranging pre defined standard widgets into some layout container (Box or Grid or Tree); in those cases, the default implementation works out the required extension bottom-up from the building blocks, and there is no need to define any specific size request
Explicit size_request
There is a set of functions set|get_size_request()
. These seem to be a mostly obsolete leftover from
earlier days. They are implemented by forcibly increasing the minimal size request — and since many
standard containers today (as of 2022) work based on the natural size request rather, this information
is not treated coherently, and sometimes leads to surprising behaviour.