How GTK styling works
- From: Benjamin Otte <otte gnome org>
- To: gtk-devel-list <gtk-devel-list gnome org>
- Subject: How GTK styling works
- Date: Mon, 16 Jan 2012 22:04:33 +0100
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.[1] 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.
1) 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.
2) 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.
3) 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.)
4) 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.
5) 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.[2]
6) 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.
7) 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:
GtkStyleProperty (abstract)
+ GtkCssShorthandProperty
+ GtkCssStyleProperty
+ GtkCssCustomProperty
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.
- GtkCssShorthandProperty
http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssshorthandpropertyprivate.h
implementations:
http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssshorthandpropertyimpl.c
spec: http://www.w3.org/TR/2003/WD-css3-syntax-20030813/#shorthand
A shorthand is not a real property. It's just a nice quick way to
specify stuff in the CSS. You can query its subproperties. And parse
them. Parsing will always return a GValueArray with a value for every
subproperty. The code will then unpack those into the real properties
for further use.
Oh, and we accidentally exposed them in the public API as real
properties (see above for an example). Ooops.
- GtkCssStyleProperty
http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssstylepropertyprivate.h
implementations:
http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssstylepropertyimpl.c
This is where the real stuff happens. Each of these properties
represents a value that needs to be provided for a style context. What
can you do with them?
- 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.
- GtkCssCustomProperty
http://git.gnome.org/browse/gtk+/tree/gtk/gtkcsscustompropertyprivate.h
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:
http://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?id=85da4ca5bde42af0d0f61b4e7194dd1e5d86874e#n1069
http://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?id=85da4ca5bde42af0d0f61b4e7194dd1e5d86874e#n935
build_properties() will then go and do this:
1) 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.
2) 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.
3) 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.
4) 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.
5) 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
This may change the GType used for this GValue - from the "specified
GType" to the "computed GType".
6) Done
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
http://git.gnome.org/browse/gtk+/tree/gtk/gtkthemingbackground.c
http://git.gnome.org/browse/gtk+/tree/gtk/gtkborderimage.c
http://git.gnome.org/browse/gtk+/tree/gtk/gtkthemingengine.c?id=85da4ca5bde42af0d0f61b4e7194dd1e5d86874e#n1716
for background, border images and regular borders respectively.
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):
http://dev.w3.org/csswg/css3-values/
http://dev.w3.org/csswg/css3-background/
http://dev.w3.org/csswg/css3-images/
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:
http://dev.w3.org/csswg/css3-fonts/
http://dev.w3.org/csswg/css3-transitions/
http://dev.w3.org/csswg/css3-animations/
But those still require a bunch of work in GTK to be useful.
Benjamin
1: 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.
2: 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.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]