This Week in GTK+ – 11

In this last week, the master branch of GTK+ has seen 22 commits, with 6199 lines added and 1763 lines removed.

Planning and status
  • Matthias Clasen released GTK+ 3.21.4 and GLib 2.49.4, two new developers’ snapshots.
Notable changes
  • William Hua started merging the patches for relative positioning of menus and popups in the master branch.
  • Lapo Calamandrei did a small visual refresh of the styling of GtkCalendar in Adwaita.
  • Carlos Garnacho updated GDK to use the 2.0 version of the Wayland graphics tablet input protocol.
  • Matthias Clasen updated GTK+ to opt into the new structured logging API provided by GLib.

 

Bugs fixed

  • 769003 Adwaita: GtkCalendar uses a confusing style for week of year
  • 756579 GTK should let GDK position menus
Getting involved

Interested in working on GTK+? Look at the list of bugs for newcomers and join the IRC channel #gtk+ on irc.gnome.org.

This Week in GTK+ – 10

In this last week, the master branch of GTK+ has seen 16 commits, with 2500 lines added and 2092 lines removed.

Planning and status
  • Jonas Ådahl and William Hua are working on the feature branch that provides a new, declarative API for positioning popups and menus; you can read about it in the blog post that was published last Friday, as well as on bug 756579.
Notable changes
  • Matthias Clasen merged the patches, written by Philip Withnall, that implement a structured logging API in GLib. This work is still ongoing.
Bugs fixed
  • 768657 places-view: fix open action for locations without a mount or volume
  • 768659 gtk/gtkfilechoosernativeportal.c: Don’t use g_autoptr()
  • 768756 GtkFileChooserNativePortal uses incorrect response id
Getting involved

Interested in working on GTK+? Look at the list of bugs for newcomers and join the IRC channel #gtk+ on irc.gnome.org.

Future of relative window positioning

With emerging display server technologies, toolkits sometimes need to adapt how they implement the features they provide. One set of features that needs adaptation is how GTK+ positions popup windows such as menus, popovers and tooltips, so that they will be placed within the work area of the monitor.

In the old days, when GTK+ wanted to position a menu, it would first look up the global position of the parent window of the menu. It would then look up the work areas of all the monitors connected. With the given work areas, the global position of the parent window, and the intended menu position relative to the parent window it wanted to place the menu, GTK+ would use a clever algorithm calculating a reasonable position for the menu to be placed so that it would be visible to the user. For example, if the File menu doesn’t have enough space to popup below the parent menu item, then GTK+ would re-position it above the parent menu item instead.

popup-flip

For various reasons the concept of “global window positions” has been removed from clients in these new display server technologies, meaning we cannot use our clever algorithm in GTK+ any more.

But we still want to make our menus, tooltips, popovers, etc. fully visible to the user expecting to interact with them, so how can we ensure this without knowing anything about where our windows are positioned?

To tackle this in GTK+, we had to solve a number of problems.

  • The positioning logic needs to be moved to GDK, while still allowing GTK+ to affect how the menu positioning behaves should the initially intended position end up outside of the work area.
  • Different GDK backends might do things differently.
  • Some types of windows need to know the position it ended up at, so they can adapt how they draw them self.
  • Some windows simply want to take up as much space as they may (for example a menu with far too many choices should not be taller than the screen).

Last year, William Hua and I started working on bringing GTK+ into the bright future of global position-less menu windows. After having come up with a set of patches doing just that, the discussion of how such an API would actually look like started. After 200-300 comments, we decided we should probably discuss this in person.

Enter GTK2016 in Toronto!

At the hackfest, we got the chance to sit down with a whiteboard in front of us and go through the different use cases, the problems that needed to be solved, how backends would work, and eventually we came up with an API.

william-draws-whiteboard(photo credit: Allison Lortie)

The API we came up with looks as this:

From the GDK side we introduce a new function (so far without any API stability promises; it’s intended to be used only by GTK+ so far) gdk_window_move_to_rect () which takes a set of arguments describing how the application wants its window to be placed in relation to some parent surface. It takes

  • a transient-for window

The parent window it is to be placed relative to an anchor rectangle on the parent window a popup or a menu will often want to be placed in relation to a rectangle on the parent, for example a right-click context menu should expand in some direction from the pixel the pointer was located at when clicking, or a file menu should be placed either below or above the file menu item rectangle on the parent window.

  • a rectangle anchor gravity

Different popup menus might want to open in a certain direction. For example a vertical menu might want to open to the right, while a horizontal menu might want to open downwards.

  • a window anchor gravity

Different popup menus might want to be aligned to the anchor rectangle of the parent anchor rectangle differently. For example, while a combo box might want to expand in a certain direction, it will want to cover the rectangle it expanded from.

  • an anchor hint

Different popup menus want to adjust their positions differently; some will want to expand in different directions from the parent anchor rectangle, some will want to just be slid into visibility, some will want to be resized, while some will want some combination of all the three.

  • an rectangle anchor offset

The offset is simply a nudge factor for a common use case where a popup menu will offset its position relative to an anchor.

By having GTK+ come up with a declarative description of how it wants its menu to be positioned, we allow GDK to implement the actual positioning differently depending on how the display server system is designed. On Mir, a MirSurfaceSpec is created, while on Wayland an xdg_positioner object is created. On X11, Windows and Mac OS X, the backends can use the available global positions as well as monitor work areas and calculate an optimal position just as before.

Application developers are, however, not expected to use this API directly yet. Normally what is wanted is to create a menu, a popover, a combo box, and for this we have introduced a set of parameters and helper functions to make this very convenient. The API consists of a few new properties:

  • GtkMenu:anchor-hints – the positioning strategy.
  • GtkMenu:rect-anchor-dx – horizontal offset to shift window.
  • GtkMenu:rect-anchor-dy – vertical offset to shift window.
  • GtkMenu:menu-type-hint – a window type – this is still needed so that the X11 backend can let the window manager know what type of popup window is being mapped.

and a few more functions:

  • gtk_menu_popup_at_rect () – given the parameters set, popup a menu relative to a given rectangle on a parent window.
  • gtk_menu_popup_at_widget () – given the parameters set, popup a menu relative to a given widget on a parent window.
  • gtk_menu_popup_at_pointer () – given the parameters set, popup a menu relative to where the user just clicked.

With these functions, developers of custom widgets can now position popup menus in a portable manner. So far, GTK+’s own popup menus have already been ported to use these new functions. There is already a basic proof-of-concept in the Mir backend, and a Wayland implementation is in progress.

Head over to the bug to see all the details of how to place your menus in the future.

gnome-sponsored-badge-shadow

This Week in GTK+ – 9

In this last week, the master branch of GTK+ has seen 26 commits, with 2416 lines added and 180 lines removed.

Planning and status
  • Emmanuele Bassi is working on his GSK rendering branch, and Alex Larsson gave an initial review. Emmanuele also wrote a blog post on his GSK work.
  • The discussion on the release strategy is still going on on gtk-devel-list; opinions are still welcome.
  • William Hua has been rebasing his branch on the new, Wayland and Mir ready popup positioning API, which replaces positioning via global coordinates.
Notable changes
  • Matthias Clasen landed the “portals” implementation for negotiating access to resources outside of a sandbox in both GLib and GTK+. You can read more about it on his blog.
Bugs fixed
  • 768485 Change the priority of the window-close idle to G_PRIORITY_DEFAULT
  • 768499 portal support for gtk+
  • 768546 Wrong documentation for the “move-viewport” signal of GtkTextView
Get Involved

Interested in working on GTK+? Look at the list of bugs for newcomers and join the IRC channel #gtk+ on irc.gnome.org.

This Week in GTK+ – 8

In this last week, the master branch of GTK+ has seen 23 commits, with 1154 lines added and 121 lines removed.

Planning and Status
  • Matthias continued to work on the portal branches of GLib and GTK+.
  • Emmanuele continued to make gsk render widgets on the gsk-renderer branch.
  • Philip Withnall is working on a patch series to make GLib support structured logging to the systemd journal here.
Notable Changes
  • Carlos Garnacho improved compatibility of Wayland clipboard handling with legacy X clients.
  • Matthias made it possible to hide the preview button in print dialogs.
  • Eric Koegel added support for the Xfce session manager to GtkApplication
  • Georges Basile Stavracas Neto added support for background-blend-mode to the GTK+ CSS machinery.
  • Over in GLib, Matthias added a new gio utility that combines the functionality of the various gvfs commandline tools into one.
Bugs fixed
  • Bug 767965Improve heuristics to detect remote filesystems
  • Bug 768184headerbar: don’t throw a warning if title widget is hidden
  • Bug 768082 – Copying from Wayland to NEdit doesn’t work
  • Bug 768177 CLIPBOARD target request after PRIMARY request times out
  • Bug 768142 Incorrect order of $(LIBS) and $(OBJS) in Makefile.example caused “undefined reference”
  • Bug 693203GtkApplication does not support Xfce session manager
  • Bug 768305Gtk+ should support background-blend-mode
Get Involved

Interested in working on GTK+? Look at the list of bugs for newcomers and join the IRC channel #gtk+ on irc.gnome.org.

This Week in GTK+ – 7

In this last week, the master branch of GTK+ has seen 29 commits, with 4744 lines added and 4340 lines removed.

Planning and Status
  • GTK+ 3.21.3 was released
  • Matthias continued to work on the portal branches of GLib and GTK+.
  • William Hua is reworking the menu positioning API from bug 756579 following the hackfest discussion.
  • Emmanuele pushed a new revision of the gsk branch.
  • Carlos Soriano is working on a new pathbar implementation for the file chooser and nautilus.
Notable changes
  • Emmanuele fixed some fallout in firefox from the GdkDrawingContext introduction.
  • Ray Strode cleaned up some headerbar code and added support for expanding children
  • Matthias fixed a crash in GtkColorChooser and another one in GtkInspector
Bugs fixed
  • Bug 767851 – popover arrows broken in some orientations
  • Bug 767849 – crash in focus handling
  • Bug 724332 – GtkHeaderBar need to support an expand property
  • Bug 768025 – entry.warning & entry.error broken
Get Involved

Interested in working on GTK+? Look at the list of bugs for newcomers and join the IRC channel #gtk+ on irc.gnome.org.

Adwaita

Today, Jakub Steiner from the GNOME design team is going to talk about Adwaita, the default theme for GTK+; the tools that the designers can use to style GTK+; and how the toolkit changed to allow a better design workflow.

Adwaita is the user facing façade of GTK+. In the past GTK+ had no face; there was no properly defined look for the toolkit. Like many things in the FOSS world, it was a bring your own. There was Raleigh, a fallback skin that only showed up if something went sideways with theming or the system settings. And you didn’t really want to see that.

Adwaita

CSS

With GTK+ 3.0 a bold new effort has started. An effort to put visual designers in charge of visual design, using tools they understand. Instead of resorting to theme engines to draw unique controls, a styling engine used on the web has been chosen. The “everything is a box” CSS model applied to GTK+ rather well. It took a lot of effort, mainly on the shoulders of Benjamin Otte, who over the years managed to give us what we dreamed of: a CSS-like box model, allowing us to space elements/controls using padding, margins, borders and nifty features like minimum width. On the selector side, we aren’t dealing with the direct nested widget structure that changes from release to release, but we were given an abstracted, HTML-like DOM structure, with nodes and classes. Nodes are also consistently carrying state and are easier to animate.

SCSS

In GTK+ there are a lot of controls that look like a button but aren’t a button. Every programmer is on the lazy side, and that’s a good thing. Designers aren’t any different. It’s so positive that there is an acronym for it, DRY — don’t repeat yourself. So in the old Adwaita, when we designed the look of some things that all looked the same, we only had one block of properties and a ton of selectors — the targets of that look. Buttons, dropdowns, you name it. Not much typing, but insanity to alter.

SASS came to the rescue by providing means to define a common drawing procedure once, but reuse it in a well structured stylesheet. You would be able to draw things “like a button” but not define it as a button. You would still find a dropdown nicely semantically organized in a dropdown section. SASS calls these macros mixins and you will find our drawing ones in src/gtk/theme/Adwaita/_drawing.scss.

/* Switch Slider being a button */
slider {
/* ... */
@include button(normal, $edge: $shadow_color);
}
Inspector

A massive improvement for the designer’s workflow has been the introduction of the Inspector. The inspector is an invaluable tool to test out new style interactively or to figure out why a particular selector isn’t working. There are a couple of powerful tools it provides:

  • Widget selector. You can interactively point at a widget to learn about its properties or where it lives in the widget tree stack. Since 3.20 you can also learn about its CSS nodes, learn what sort of states it can get to, learn all the classes it has been assigned. It can also tell you where in the stylesheet the set property has been defined. This helps you figure out why your selector isn’t working. Somewhat. It would be real nice to see all matching selectors, even those that have been overriden by those that take precedence.
  • Interactive CSS stylesheet. You can write a CSS rule and have it applied in real time. This is not only useful to figure out a proper selector, but also experiment with drawing using GTK+ directly rather than using tools like Inkscape. Being able to iterate fast and try out things results in better design.

If this all sounds very similar to what modern browsers provide, it’s not much of a coincidence.

CSS Nodes in the Inspector
CSS Nodes in the Inspector
Future Improvements

A major factor that’s making us less flexible in terms of being able to alter Adwaita are the graphic assets. There are still a couple of things that we have to resort to using image assets for. Those are actually in a large asset sheet SVG and we have a bunch of scripts to chop up multiple sized images (for HiDPI). It remains a hassle to add or change a particular bit.

To make this a little less boring, here’s a little web demo of how we could possibly avoid using image assets to draw GtkScale sliders and use simple CSS boxes instead:

<div id="scale" class="scale">
  <div class="trough"></div>
  <div id="slider" class="slider run-animation"></div>
</div>

<style type="text/css">
.scale {
  position: relative;
  width: 100%;
  height: 64px;
}
.scale:hover { }
.trough {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  height: 8px;
  border-style: solid;
  border-width: 2px;
  border-radius: 4px;
  left: 0; right: 0;
  border-color: #a7a7a7;
  background-color: #b1b3b1;
  background-image: linear-gradient(to bottom, #a7a7a7, #bebebe);
  box-shadow: 0 1px 0 rgba(255,255,255,0.8);
}
.slider {
  position: absolute;
  width: 48px; height: 48px;
  top: 50%;
  left: 0%;
  transform: translateY(-50%) translateX(0%) rotate(0deg);
  border-style: solid;
  border-width: 2px;
  border-color: #a7a7a7;
  border-radius: 0;
  background-color: #e0e0e0;
  background-image: linear-gradient(135deg, #ededed, #d3d3d3);
  box-shadow: inset 0 0 0 2px rgba(255,255,255,0.2),
              2px 2px 2px rgba(0,0,0,0.1);
}
.slider.run-animation {
  animation-name: morph, progress;
  animation-delay: 6s,10s;
  animation-duration: 3s,3s;
  animation-iteration-count:1,infinite;
  animation-direction: normal, alternate;
  animation-timing-function: ease-in-out;
  animation-fill-mode: forwards;
}
.slider.run-animation:hover {
  /* the best way to reset CSS animations is switching between identical keyframes */
  animation-name: morphClone, progressClone;
}
@keyframes morph {
  0% {
    border-radius: 0;
    transform: translateY(-50%) translateX(0%) rotate(0deg);
  }
  90% {
    border-radius: 50% 50% 0 50%;
    transform: translateY(-50%) translateX(0%) rotate(0deg);
  }
  100% {
    border-radius: 50% 50% 0 50%;
    transform: translateY(-60%) translateX(0%) rotate(45deg);
  }
}
@keyframes morphClone {
  0% {
    border-radius: 0;
    transform: translateY(-50%) translateX(0%) rotate(0deg);
  }
  90% {
    border-radius: 50% 50% 0 50%;
    transform: translateY(-50%) translateX(0%) rotate(0deg);
  }
  100% {
    border-radius: 50% 50% 0 50%;
    transform: translateY(-60%) translateX(0%) rotate(45deg);
  }
}
@keyframes progress {
  0% {
    left: 0%;
    transform: translateY(-60%) translateX(0%) rotate(45deg);
  }
  100% {
    left: 100%;
    transform: translateY(-60%) translateX(-100%) rotate(45deg);
  }
}
@keyframes progressClone {
  0% {
    left: 0%;
    transform: translateY(-60%) translateX(0%) rotate(45deg);
  }
  100% {
    left: 100%;
    transform: translateY(-60%) translateX(-100%) rotate(45deg);
  }
}
</style>

Essentially the slider is a box with 3 corners rounded and rotated by 45 degrees. All we need is boxes.

    /* transforms-based scale slider on the web */
.slider {
  position: absolute;
  width: 48px; height: 48px;
  top: 50%;
  left: 0%;
  /* move up slightly after rotation, thus not 50% */
  transform: translateY(-60%) rotate(45deg);
  border-style: solid;
  border-width: 2px;
  border-color: #a7a7a7;
  border-radius: 50% 50% 0 50%;
  background-color: #e0e0e0;
  background-image: linear-gradient(135deg, #ededed, #d3d3d3);
  box-shadow: inset 0 0 0 2px rgba(255,255,255,0.2),
              2px 2px 2px rgba(0,0,0,0.1);
}

This Week in GTK+ – 6

In this last week, GTK+ has seen 20 commits, with 1852 lines added and 1234 lines removed.

Planning and Status
  • With the GTK+ hackfest going on for most of the week, not much happened in the Git repository.
  • Each day of the hackfest has its own notes on the wiki: 1, 2, 3, 4.
  • The roadmap has been cleaned up and updated.
Notable changes
  • Ray Strode worked on the GDK Wayland backend to ensure that it can be built and used with older Linux kernels without memfd support.
  • Philip Chimento added the ability for GtkStyleContext hierarchies to include multiple contexts; this is the first step in allowing style contexts to cascade properly within childred of a widget.
Bugs fixed
  • Bug 766341 Do not rely on memfd as it requires a fairly recent kernel
  • Bug 767766 CUPS 2.X detected incorrectly by configure
  • Bug 767795 Warning when the “accelerator” property of GtkShortcutsShortcut is set to “less”
  • Bug 751409 gtk_style_context_add_provider() does not propagate to children
  • Bug 767705 GtkActionHelper: Change a message to a warning
  • Bug 767468 Popover over a treeview cellrenderer is hidden immediately after being shown
Get Involved

Interested in working on GTK+? Look at the list of bugs for newcomers and join the IRC channel #gtk+ on irc.gnome.org.

Drawing in GTK+

The topic of how GTK+ draws the content of a window is a fairly complex one; it involves drilling down from GtkWidget, to GdkWindow, to Cairo, to the windowing system currently in use. This task can seem somewhat daunting, even for people that are familiar with the GTK+ API from an application development standpoint, so I decided to write down a quick introduction of how GTK+ draws, going from widgets, to windows, to surfaces, to native windowing resources.

How it starts

GTK+ always draws because something asked it to. This request may come from the windowing system — for instance, because the window manager presented your application window to the user, or because the user resized it — but more often it’ll come from a widget updating its contents. Let’s say, a progress bar going from 50% to 60%; or a label, changing its text; or a spinner, doing a new iteration. This request invalidates the backing GdkWindow of the widget — which usually it’s the GdkWindow of the top-level GtkWindow that contains the widget. Each invalidation carries with itself the region of the window to be invalidated (the “damage”), so that when we get to actually drawing, we know which parts of the window need to be updated, and we can avoid drawing outside of the damaged areas.

Race the clock

The first invalidation will start the “frame clock”; this clock is an object that keeps track of each phase inside a frame, like painting windows, laying out widgets, or processing the event queue. This allows GTK+ to be synchronized to things like the windowing system compositor, and to avoid performing unnecessary work that won’t be seen by the user — for instance, drawing something at 1000 frames per second when your display can only run at 60 Hz.

Once the clock reaches the “paint” phase, we process all the scheduled updates on a window; this will cause a GDK_EXPOSE event to be emitted. The GDK_EXPOSE event contains the GdkWindow that needs to be updated, and the union of all the invalidated areas. It’s important to note that, by and large, only top level windows will receive a GDK_EXPOSE event; for historical reasons, though, some widgets may apply a particular event mask that will cause GDK_EXPOSE events to be delivered to them as well. You should not write code that depends on that, and if you have legacy code ported from older versions of GTK+ 2.x you should really consider dropping the GDK_EXPOSURE_MASK from the event mask.

Rendering

GTK+ takes the window and invalidated region out of the GDK_EXPOSE event and figures out which top level widget they belong to. Once that’s found, GTK+ will begin the actual rendering process. First of all, GTK+ will ask the GdkWindow to create a buffer where to draw the contents of the window; the buffer is going to be clipped to the region that needs to be drawn, and will be cleared with the background color of the window. GDK will create a “drawing context” — a transient object that keeps track of things like OpenGL and Cairo drawing. Then, GTK+ will ask the widget to draw itself using a Cairo context. For leaf widgets this means drawing themeselves on that context; for container widgets, this additionally means recursing through all their children. At the end of this process, GTK+ will end the frame by telling GDK to take the buffer that contains all the rendered widgets and use it to replace the current contents of the window. GDK will then ask the windowing system to present the window to the user, whenever it’s more appropriate.

Changing History

The process outlined above has various caveats, and the code that deals with invalidation and validation of windows inside GDK is fairly complex; it also has a long history, which means that its API is littered by the headstones of ages past.

Before GTK+ 3.0, for instance, you were supposed to handle the “expose” events yourself, and create a Cairo context to draw on a widget by using gdk_cairo_create(); this has long since been unnecessary, because the GtkWidget::draw virtual function already provides us with a Cairo context with which to draw. The gdk_cairo_create() function, though, has been deprecated in GTK+ 3.22, and should not be used in newly written code; if you need a Cairo context you should create a similar Cairo surface, call cairo_create() on it, and then use the surface as the source for the Cairo context that GTK+ provides to you when drawing a widget. On the other hand, if you were using gdk_cairo_create() to draw on a top-level, native GdkWindow in response to a GDK_EXPOSE event then you should use the newly added gdk_window_begin_draw_frame(), gdk_window_end_draw_frame(), and GdkDrawingContext API instead.

Shaping Future

The internals of the drawing code in GTK+ have been progressively updated over the years, to cope with things like new windowing systems, as well as other rendering API. It’s fairly certain that they will change again, especially when it comes to improving the rendering performance. Many of the changes that may seem arbitrary are, in reality, stepping stones towards reducing the time spent inside the toolkit in each frame, and leave more time to the application logic.

This Week in GTK+ – 5

In this last week, GTK+ has seen 35 commits, with 3140 lines added and 2353 lines removed.

Planning and Status
  • The GTK+ hackfest starts today; topics include: CSS, layout management, portals for sandboxing, and GDK.
  • Matthias Clasen and Alex Larsson are working on an initial implementation of the “portal” for opening files in a sandboxed application.
Notable changes
  • Emmanuele Bassi merged his branch for simplifying the GdkWindow drawing entry points; this branch introduced a few new functions and a GdkDrawingContext class, while deprecating the old gdk_window_begin_paint* family of functions; gdk_window_end_paint(); gdk_cairo_create(); and gtk_widget_send_expose().
  • Tristan Van Berkom worked on fixing the fallout from the introduction of the new content sizing properties in GtkScrolledWindow, and ensured that the sizing requests are consistent.
Bugs fixed
  • Bug 767312 gtk_widget_path_append_for_widget() misses classes unless gtk_widget_get_style_context() has been called
  • Bug 79229 GtkScale with a big number of digits and value pos set to GTK_POS_TOP/BOTTOM is panted incorrectly
  • Bug 118959 GtkScale value ‘-0’
  • Bug 710471 Make gtk_scrolled_window_remove() smart
  • Bug 767310 High-contrast theme doesn’t show focus rectangle in default button
  • Bug 766860 tiled (snapped, half-maximized) windows in Wayland aren’t GDK_WINDOW_STATE_TILED
  • Bug 766675 Add appropriate frame drawing API to GdkWindow
Get Involved

Interested in working on GTK+? Look at the list of bugs for newcomers and join the IRC channel #gtk+ on irc.gnome.org.