CSS goes in, styled windows come out. You can't explain that.
And quite frankly, that sucks. So here's a high-level overview of how the GTK CSS machinery works in master. I'll also include some mentions of changes I expect to make in the future to improve per- and conformance, but I'll be sure to mention it's not yet implemented.
First, a disclaimer: This mail does not care in the slightest how the CSS gets applied. So the GtkWidget object is out of scope, no widgets will be harmed in the making of this email. It's only about how GTK goes from the styling information provided in CSS to styling information that can then be applied by whoever wants to.
So let's start with the design goals that I had when doing this, as they can hopefully explain a bunch of the decisions I made.
- Be like CSS You put CSS on a website, it becomes the website. You put CSS in a widget, it becomes the widget. CSS has a lot of very nice qualities that are not obvious. I can tell you about a few after working on this, but I'm sure there's more I haven't yet realized. And I want those qualities in GTK. And when somene does a half-assed implementation of something that looks like CSS but doesn't work like it, he'll probably not get these qualities. Also, designers know how CSS works. And when they try to write a theme, but it doesn't work, they become frustrated and stop. Instead of writing an awesome theme.
- Do CSS 3
CSS 2 is an old hat. It's also not really powerful enough for what designers want to do. So when I implement a feature, I look at the CVS checkout (yes the CSS spec guys use CVS) of the work-in-progress spec and implement that. http://dev.w3.org/csswg/ has all of those (even the discarded ones) in HTML format, but of course we don't implement all of them. But if we implement one, that's where you go for a reference.
- Try not do anything not in CSS Mostly because I'm sure we'll do it wrong. And once themes start using it, it's kinda like API. Of course, if there's a useful feature, we'll go for it. But I haven't really found one yet. (Maybe define-color, but I'm not really sure about it anymore. Plus, define-color is very under-specified from a CSS POV.)
- Do all of it When there's a part of CSS that I implement, I want to implement it completely. In CSS, the power of one feature often only unfolds once you use it with some other feature. And if that other feature isn't implemented... So I want percentages and lengths in em, I want asynchronous changes (like with loading images) and so on. Not all of that is there yet, but it's getting there.
- Do not expose any APIs I'm doing it wrong. I'm pretty sure about that. I pointed out above I don't know the fine things about CSS yet, so I expect I'll need to change lots of things later. Public API is bad for that.
- Be backwards-compatible We froze an API with 3.0, and we can't just ditch that. Also, I like to be backwards compatible even if I don't have to. Because that allows for smaller commits and that allows for easier bisecting and that allows for less bugs.
- Ditch theme engines I don't care about theme engines. They were a bad idea in GTK2, they are an even worse idea in GTK3. With the expressiveness of CSS, you cannot reasonably expect anyone to write a theme engine that does not break CSS, rendering, performance or - more likely - all of it horribly. Or for that matter: write a theme engine that provides a feature that CSS 3 doesn't have.
So with this, here is how things work today.
First, a disclaimer: All of this is totally ignorant of widget style properties. Widget style properties are a wart that is still left over from GTK2. Everybody using them will be pitied by me. Everybody implementing new ones will be ridiculed by me. They are fundamentally broken by design and cannot ever work. All of this is about real CSS properties. Every property that you can specify in the CSS file is represented by a GtkStyleProperty. The class tree for style properties looks like this:
So, what are these for?
GtkStyleProperty: http://git.gnome.org/browse/gtk+/tree/gtk/gtkstylepropertyprivate.h A style property can do essentially 3 things:
- look them up by name: _gtk_style_property_lookup()
- parse its value from a CSS file: _gtk_style_property_parse_value()
get and set the property in the public API of GtkStyleContext and GtkStyleProperties: _gtk_style_property_query() and _gtk_style_property_assign(). The GTK theming code often does not use this. This is problematic because the types used for them don't actually expose the features of CSS properly. The pubic API exposes a border-radius property for example that's an int. In reality border-radius is 8 lengths: Either pixels or percentages for the vertical and horizontal radius of the 4 corners. So we have wrapper functions that do the conversion for you as best as they can and don't break your code if you do it.
- Get the property's ID. IDs are given continuously for every property. That way, one can use arrays of GValue everywhere and have code be fast when it constructs the CSS for a style context - no hash tables involved, just array indices.
- Query information about the property: What GType are we using, what generic behaviors does the property have?
- Do the conversions necessary in the construction of the GValue I'll outline this whole process in more detail below. Anyway, the important thing is: This object is where things happen. The rest is mostly niceties.
Implementations: none in GTK, but they'll all use http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssstylefuncs.c
Here again is a wrapper for API that I'd like to see gone. Theme engines etc can register their own CSS properties, with for example gtk_style_properties_register_property(). Each of those will get a GtkCssCustomProperty created for it. This class just implements the vfuncs of all the other classes in a generic way. And it allows code to quickly detect if this is a 'real' property or added on by custom code. In essence it's just a regular GtkCssStyleProperty.
So, now that you know who keeps track of all the values, how is this done? The relevant spec is here: http://dev.w3.org/csswg/css3-cascade/#value-stages I sugest that you probably wanna read that paragraph, because this is quite important as this is what the code is based on. In essence, the values for each property run through the 4-step process outlined in that document to arrive at the value that actually gets used. This is driven by a GtkStyleContext. Whenever the style context needs data for a widget path + state it doesn't yet have cached - either because it wasn't needed before or because gtk_style_context_invalidate() has been called - it calls build_properties() via style_data_lookup() (This is the function you wanna look at in your profiler to know if CSS lookups are an issue and who's causing them:
build_properties() will then go and do this:
Create a GtkCssLookup object to be filled: http://git.gnome.org/browse/gtk+/tree/gtk/gtkcsslookupprivate.h?id=85da4ca5bde42af0d0f61b4e7194dd1e5d86874e This object is just an array of pointers to GValues (indexed by the ID of the style properties) to be filled with (pointers to) the "cascading value" for each property. Imagine it as on-stack, even if it is heap-allocated today.
- Take all the registered style providers and tell them to fill the lookup object:
http://git.gnome.org/browse/gtk+/tree/gtk/gtkstyleproviderprivate.h?id=85da4ca5bde42af0d0f61b4e7194dd1e5d86874e http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssprovider.c?id=85da4ca5bde42af0d0f61b4e7194dd1e5d86874e#n1518 The CSS code than goes and looks at which rulesets from the CSS match and assigns the values present in those to the lookup. This is the most performance intensive code by far, because it needs to do selector matching.
Create a GtkCssComputedValues object to actually hold the computed values http://git.gnome.org/browse/gtk+/tree/gtk/gtkcsscomputedvaluesprivate.h?id=85da4ca5bde42af0d0f61b4e7194dd1e5d86874e This object holds the real values. It's what GtkStyleContext will use as the actual storage. Again, this object is just an array of GValues indexed by style property ID.
- Compute the specified value from the cascading value
http://git.gnome.org/browse/gtk+/tree/gtk/gtkcsscomputedvalues.c?id=85da4ca5bde42af0d0f61b4e7194dd1e5d86874e#n77 This is done in that function and properly commented inline. Read it there.
- Tell the style property to convert the specified to the computed value
http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssstyleproperty.c?id=85da4ca5bde42af0d0f61b4e7194dd1e5d86874e#n380 This is a per-property vfunc. It oten does nothing but a g_value_copy(), but in some cases things happen, such as:
- Symbolic colors are resolved
- Values that depend on other properties are converted based on those (like border widths based on border styles)
- Dimensions are converted to their 'canonical' unit - lengths to pixels, angles to degrees
Get rid of the lookup object and start using the GtkCssComputedValues in the style context. This today happens either via gtk_style_context_get() (which will then go via _gtk_style_property_query()) or via _gtk_style_context_peek_property() directly. Note that so far there is no process that converts from the computed value to the actual or used value. When these differ (like for images), this is done on-demand when rendering. If you want to have a look at how that looks today, see
One thing I wanna emphasize is that currently there might be 3 different GTypes for every property's GValue depending on what you are doing:
- the specified type
This is the type for values until they get to stage (5) above. Up to stage (4), the additional type GTK_TYPE_CSS_SPECIAL_VALUE is also allowed, which indicates the special values 'inherit' and 'initial' as outlined in http://dev.w3.org/csswg/css3-cascade/#cascade The initial value is the value returned by _gtk_css_style_property_get_initial_value(). The inherited value is the value stored in the parent GtkStyleContext for this property (or the initial value if no parent property exists).
- the computed type
This is the type that is stored in the stye context. It is also the type stored in GtkStyleProperties.
- the 'value' type This is the type returned in public API. It is the only type that shorthand properties support. It is also the only type that might not be supported by a property. In that case, the property is not usable by public API and effectively private to GTK. You'll get a warning if you try to use it via gtk_style_context_get().
So, what parts of CSS 3 are actually implemented? And which ones will be? The important thing to notice that a property being 'implemented' does not mean that it's actually used by widgets. Widgets have often not been touched to actually conform to CSS at all and often chose to behave like they did in GTK 2. So when you set a property, the CSS code doesn't complain, but still nothing happens, there's a good chance somebody needs to update the widget you're trying to style. This is scheduled for GTK 3.6 where I want to introduce render objects, but it's still up in the air. So from a parsing and handling correctly if the widget works well, GTK currently does implement (or I'm in the process of implementing):
in a way that works as well as what browsers do (or better). And this is the area I will be focusing on, as that's what's important for styling. I do not expect to ever implement layout properties like 'display', 'position' and the like, because layout in GTK is the job of the application, not of the style. However, things which I want to implement (and you are invited to join) are:
But those still require a bunch of work in GTK to be useful.
- This even means you could reasonably attempt to try to use the GTK CSS machinery for styling other things (like librsvg or even ST/MX) if you wanted. Though that has certainly never been a design goal.
- In fact, almost all the public API introduced for CSS with GTK 3.0 is already obsolete in the sense that the CSS machinery doesn't use it anymore. And It'll become very slow if somebody else tries to use it, because it will run a bunch of compatibility code.