Today we’re going to have guest author Timm Bäder, maintainer of Corebird and a contributor of GTK+, talk about the changes in store for writing composite widgets in GTK+ 4.0.
(Note: Some of the information here is based on branches that have not been merged into master yet, but I’m confident that they will be in the near future)
In GTK+3, only GtkContainer subclasses can have child widgets. This makes a lot of sense for “public” container children like we know them, e.g. GtkBox — i.e. the developer can add, remove and reorder child widgets arbitrarily and the container just does layout.
However, there are more complicated widgets in GTK+3 which don’t inherit from GtkContainer, e.g. GtkSpinButton or GtkSwitch. These never have real GtkWidget children. Consider, for example, the two clickable areas in a GtkSpinButton. I’m not calling them “buttons” here for a reason, since in GTK+3, they are not actual GtkButton instances, as GtkSpinButton is not a GtkContainer. Instead, GtkSpinButton has to work around that fact, and create two GdkWindows for the up/down areas and then render two icons in there; care about hover and CSS states; various button up/down events; and the GdkWindow lifetime, etc. In order to work around the GtkContainer requirement, in GTK+3 we introduced gadgets (GtkCssGadget). On the styling side, a gadget corresponds to a CSS box and therefore represents one node in the CSS tree. On the widget side they were being used to have “widget-like”, CSS-stylable, children for non-container widgets.
GtkWidget Changes
Of course there were plenty of changes needed to support these use cases in GTK+4. I’m not going to list all of them here (in particular the more ugly ones like focus handling), but I think most of them are quire interesting and important for application developers and custom widget authors. Generally, we’re trying to get a away from special cases and go a more general way by re-using widgets wherever we can. So, instead of using a PangoLayout to display text, widgets should use a GtkLabel. If you have a clickable area with button-like semantics, try to use a GtkButton. If you want to lay out widgets in horizontal or vertical orientation, use a GtkBox. This way we have one widget tree that both input and rendering can operate on. In practice, this means mostly getting rid of all the gadgets used inside widgets, as well as standalone GtkCssNode instances.
Iterating over child widgets
- gtk_widget_get_first_child()
- gtk_widget_get_last_child()
- gtk_widget_get_prev_sibling()
- gtk_widget_get_next_sibling()
GtkWidget *widget; GtkWidget *child; for (child = gtk_widget_get_first_child (widget); child != NULL; child = gtk_widget_get_next_sibling (child)) { /* Do stuff with @child */ g_assert (gtk_widget_get_parent (child) == widget); }
Adding widgets to a non-container parent
In GTK+4, gtk_widget_set_parent() still works and adds the widget to the end of the child widget list of the parent. However, we clearly also want to manage the order of the child widgets, as well as where we add new children in the list, so we have:
- gtk_widget_insert_before()
- gtk_widget_insert_after()
Widget CSS names
Examples of Converted Widgets
GtkSwitch
gtkswitch.c
. Theoretically we also have more features and can e.g. use the limited support for the text-decoration CSS property that GtkLabel supports, but I’m just going to doubt that this is very useful.GtkSpinButton
gtkspinbutton.c
. Through using GtkButtons, the old icon helper gadgets also become actual GtkImage instances. Unfortunately we have to implement some of the GtkGesture magic ourselves here since GtkSpinButton also supports middle and right clicks on its buttons while GtkButton::clicked only reacts to single, primary mouse button clicks.GtkLevelBar
GtkProgressBar
In master, both trough and progress are widgets and the PangoLayout is a GtkLabel of course. Not having to listen to GtkWidget::style-changed (which gets done automatically for widgets) and not having to draw the PangoLayout ourselves (which the GtkLabel now takes care of) yields a nice code size saving of around 200 lines, however.
GtkExpander
Accidental GtkBox & GtkButton subclasses
General Restructuring Rules and Future
testsuite/css/nodes.c
.
I’m just wondering about the performance overhead due to not using pango directly, but through GtkLabels.
Thanks for the write-up, it’s cool to see the GTK+ internals being simplified.
As long as there aren’t useful features being deprecated, i’m all for code simplification.
@Salamandar: It shouldn’t be any difference regarding performance. In the case of a simple text label (no wrapping, links or ellipsization), GtkLabel is basically a wrapper around a PangoLayout.
[WORDPRESS HASHCASH] The poster sent us ‘0 which is not a hashcash value.
If your API is still in flux, it might sense to create an iterator object for iterating over children.
Since that is likely to be a common operation, and using a custom structure might lead to significant performance improvements, depending on the list implementation.
But maybe not, just a suggestion :-)
Something like;
GtkWidget *widget;
GtkWidget *child;
GtkWidgetIterator * iter = gtk_widget_iter_init(widget);
for (gtk_widget_iter_has_next(iter);
child = gtk_widget_iter_next (iter))
{
/* Do stuff with @child */
g_assert (gtk_widget_get_parent (child) == widget);
}