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.

This Week in GTK+ – 4

In this last week, GTK+ has seen 55 commits, with 2378 lines added and 1493 lines removed.

Planning and Status
  • Carlos Soriano has been working on further experiments on the new path bar widget in various topic branches.
  • Emmanuele Bassi has been working on a new API for simplifying the drawing entry points inside GtkWidget, in preparation for the new rendering API inside GSK.
Notable changes
  • Timm Bäder added an accessible representation for GtkStack to only show the current visible child in accessibility tools.
  • Matthias Clasen added a warning in case we emit the GtkWidget::draw call on a widget without an allocation; this is not supposed to happen, and a warning allows tracking badly behaving widgets.
  • Lapo Calamandrei fixed Adwaita with regards to menu items, info bars, and the caret color of selectable lables.
  • Matthias Clasen deprecated the GtkSizeGroup:ignore-hidden property, and documented it as broken; sizing of invisible widgets is not really possible, as they lack access to windowing system and style resources. It is recommended to use a GtkStack, instead, to reserve space for hidden widgets.
  • A new GDK device source type, GDK_SOURCE_TRACKPOINT, has been added to represent Trackpoint/point stick pointing devices; this new device type can be used to implement device-specific behavior in widgets.
  • CSS text styling properties can now be used on the value and marks of a GtkScale and GtkProgressbar widgets.
  • Georges Basile Stavracas Neto implemented the max-content-width and max-content-height properties in GtkScrolledWindow; Tristan Van Berkom fixed long-standing sizing issues with the existing min-content-width and min-content-height properties.
Bugs fixed
  • Bug 745622 Selected text not highlighted in GtkInfoBar
  • Bug 767058 GtkInfoBar: right-click/context menu all white
  • Bug 767052 Wayland: Iconifying a modal dialog makes the app unusable
  • Bug 767100 Add an input source type for trackpoints
  • Bug 767108 Separators not correctly placed in GtkPopover
  • Bug 767093 wayland: Provide information about scroll devices
  • Bug 753202 change cursor for click scrolling
  • Bug 767165 Update docs for availability of GDK_GRAB_FAILED
  • Bug 742281 GtkScrolledWindow should have max-content-height and max-content-width properties
  • Bug 674215 regression with updating tooltips
  • Bug 556254 Test properties of type GObject in the ‘object’ test
  • Bug 708148 gtk_tree_view_get_path_at_pos mistakenly identifies column of initial pixels
  • Bug 765595 Modal popover does not close when focus leaves it
  • Bug 766569 Better size requisition for GTK_SCROLL_NATURAL children
  • Bug 767238 Fix long standing regression in min-content-width/min-content-height
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+ – 3

In this last week, GTK+ has seen 26 commits, with 5081 lines added and 3618 lines removed.

Planning and Status
Notable changes
  • The documentation has seen various improvements, notably in the CSS reference and the GTK+ 2.x → 3.x migration guide.
  • Matthias Clasen added a section to the API reference that maps the release notes in the README file; this section will be used as the starting point for the migration guide from 3.x to future major releases of GTK+.
  • The gtk-builder-tool utility, which allows to validate, simplify, preview, or inspect a UI description file now leaves the GtkDialog:border-width property alone when simplifying the properties set to a default value.
Bugs fixed
  • Bug 759037 GtkInfoBar: documentation not updated wrt background colors and message types
  • Bug 747206 gtktextview: note on how to get line spacing between two paragraphs
  • Bug 766643 Frozen windows when unmapped with pending configure event
  • Bug 766122 Re-used filechooser displays $pwd half of the time when shown
  • Bug 766878 placesview: Do not mark icon name as translatable
  • Bug 764203 Default background color for the ‘textview border’ node
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+ – 2

In this last week, GTK+ has seen 90 commits, with 8502 lines added and 7914 lines removed.

Planning and Status
  • Matthias Clasen updated the GTK+ road map entry for a “tab strip” GtkStack controller widget with his prototype branch.
Notable changes
  • Matthias Clasen and Lapo Calamandrei commited some CSS changes to GtkScale that were required in order to cover all possible cases of marks on all sides. Additionally, the value node GtkScale uses when it displays a value was missing from the documentation, and has now been added.
  • Debarshi Ray fixed some allocation issues with GTK_POLICY_NEVER in GtkScrolledWindow discovered while working on libvte.
  • Matthias Clasen added two new cursor names, context-menu and no-drop, to the list of CSS cursor names supported by GTK; the cursors are provided by Adwaita.
  • Matthew Waters fixed GDK to use the thread-safe Wayland dispatch API; this is especially useful in case multiple threads are polling the Wayland file descriptor.
  • Olivier Fourdan worked on allowing the Wayland backend to try and return sensible values when asking GDK on which monitor a window is displayed; this is still a work in progress, and may require protocol extensions to avoid guesswork that can lead to false positives.
  • Ondrej Holy worked on GIO, GVFS, and GTK+ to ensure that cold-plugged drives would be appropriately detected as removable in the side bar for the file selection dialog.
  • Javier Jardón finally updated GTK+ to use upstream gettext, instead of the macro and build files modified by GLib.
Bugs fixed
  • Bug 373745 Do not use AM_GLIB_DEFINE_LOCALEDIR(GTK_LOCALEDIR) and use gettext instead
  • Bug 766405 Stack shows incorrect frame in widget factory on wayland
  • Bug 763852 gdk/wayland: event source is not multi-thread aware
  • Bug 766314 Spurious leave-notify event after touch up
  • Bug 766442 Broken drag & drop between windows
  • Bug 766440 Scale slider button has stopped discerning & rendering appropriately for scales with vs without marks
  • Bug 766566 Wayland: gdk_screen_get_monitor_at_window() unreliable under Wayland
  • Bug 766336 Crash when selecting rows with rubberbanding
  • Bug 765924 Improve external drives detection
  • Bug 766642 Switches in HeaderBars Are Badly Integrated
  • Bug 766737 stack: Only map children when necessary
  • Bug 766323 GTKPopover gives warnings if visible when reparented
  • Bug 766782 OpenGL in broadway leads to segmentation fault
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+ – 1

What’s up with GTK+ for the week of 9 May 2016 – 15 May 2016

In this last week, GTK+ has seen 51 commits, with 5375 lines added and 4970 lines removed.

Planning and Status

The 3.22 development cycle is picking up pace while we go through the items of the Roadmap.

Notable changes
  • Olivier Fourdan fixed scroll event handling in GtkMenu, to ensure that it behaves consistently on X11 and Wayland with regards to smooth and discrete scrolling
  • The list of available protocols in the Connect to Server help popover is now populated using the list of supported schemes in GVFS, thanks to Georges Basile Stavracas Neto
  • Benjamin Otte has pushed a commit that lets GtkWidget emit the style-updated signal on unrealized widgets instead of delaying until realization; the original behavior was the result of an older optimization to avoid too many invalidations during construction, but the style system has improved over the years.
  • Timm Bäder pushed various clean up commits over various widgets, like GtkListBox, GtkStack, and GtkToolbar.
Bugs fixed
  • Bug 766166 key bindings in gtk.css are ignored
  • Bug 766207 Fix build on pre-C99 compilers
  • Bug 765939 [Wayland] very slow scrolling in GtkMenu using the touchpad
  • Bug 756570 gtkplacesview no longer provides guidance on address formats
  • Bug 766120 Scale draw_value() align changed from centre/right (H/V) to left, causing significant visual regression
  • Bug 766233 Crash when server does not support XI2
  • Bug 766175 Translation of quotes may misinterpreted by GTK sidebar
  • Bug 765700 GtkPaned use causes “How does the code know the size to allocate?”
  • Bug 682080 Gtk:ERROR:gtktoolbar.c:2271:logical_to_physical: assertion failed: (logical == 0)
  • Bug 766458 widget: fix GtkLabelAccessible NULL links.
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 – 0

In order to improve the communication between the GTK team and the rest of the GNOME platform, as well as application developers, we’re going to start writing weekly status reports on what happens in GTK and the rest of the core GNOME platform.

These reports are heavily inspired by the ones that the Servo team writes.

You can expect a weekly post on this blog every Monday; we’ll look at notable changes; new contributors; new features or deprecations; and scheduled events.

If you have questions, as usual, direct them to the GTK development mailing list.

Have fun!

Hello, World! (Reprise)

Hi all, and welcome back to the GTK+ development blog.

We’ve neglected this blog for a while, after the 3.0 release, but we think it’s kind of important to have a place where to talk about GTK+ (and the rest of the core GNOME platform), how it’s changing, and where it’s going.

Going forward, we are not going to use this blog for announcements — though if you want the, you can simply subscribe to the gnome-announcement mailing list. Instead, we are going to use this space to present what is happening in GTK+; to discuss the road map; to spotlight some new feature, and maybe some old one that is not well known.

Stay tuned, and as usual: have fun!