In GUADEC 2019 we had a vendor themes BoF which got expanded to the application developers’ need to brand their app with color. We agreed on the need of a recoloring API for apps and vendors to take advantage of.
This week we had the Design Tools Hackfest 2020, virtualized because of COVID-19, where we discussed that recoloring API. We came up with something I think is interesting enough to discuss more widely.
The Need
First let’s define the need:
- vendors want to inject their branding in applications, this is typically done by shipping a different theme which creates a whole new set of problems;
- application developers want to use their branding or inject colors in parts of the application’s UI;
- users want to make the system more their own by setting the system’s colors.
Common patterns in vendor themes are to change the accent color from the default blue to something better matching their brand like orange or green, another is to have a light theme but a dark titlebar as do Ubuntu and Pop!_OS.
Application developers want to convey information via color, e.g. Web uses a light blue titlebar to convey you are browsing in private mode, and King’s Cross uses a red titlebar to convey you are in super user mode and a purple one to convey you are not on the host’s shell (e.g. in a container or in SSH).
Application developers want to inject their branding via color, e.g. Ciano has a cyan titlebar and Tootle has a desaturated blue one. I could easily see GNOME Twitch use a purple titlebar too.
Application developers color the UI for the material meaning they convey, e.g. Notes gives a color to the notes; when viewing a specific one, almost all the window takes the note’s color, I could imagine the whole window take the note’s color.
Users want to set the system’s accent color, to make the device they use feel more like their own; to do so Windows lets its user set an accent color that will be used in the applications as the titlebar color and accent color.
How to Get There
We need to define a set of color variables and what they should be used for, like selected foreground color or unfocused border color, and these color variables should be made public and defined as the coloring API. Adwaita already has a list of color variables which could serve as a base to forge that API.
To add some variety, each of these color variables would come in a specific variant; during the hackfest we defined three variants so far: regular, titlebar and alternate which would correspond to CSS classes you can use to automatically color elements. regular would be the default color used in the whole UI, titlebar would correspond to the titlebar style class that GTK already gives by default to window titlebars, and the alternate style class would let the application developer apply a third style wherever they want in the UI.
Applications would be able to redefine the colors for all their windows via CSS, like this:
@define-color titlebar_bg_color #f00;
For that to work, themes would only be allowed to use these public color variables in CSS. Given SASS hardcodes its variables’ values, its functions’ results, and the selected conditional branches in the compiled CSS, you can’t affect the style by overriding the public color variables in your application.
In order to change the style by redefining public color variables, you have to make the color computation logic appear directly in the generated CSS, which is fortunately doable using some tricks (see the code example below). Same limitations of not being able to use the full power of SASS can pretty easily be mitigated by offering similar color functions in GTK, but color-dependant conditional branching would have to be abandonned.
// Here is how you can call the alpha() GTK CSS color function from SASS.
@function gtkalpha($color, $alpha) {
@return unquote("alpha(#{$color}, #{$alpha})");
}
// Because CSS doesn't have conditional branching, this bit from Adwaita
// would have to be abandonned, unless we create a very specific color
// function in GTK for each and every case.
@if lightness($c)>95% { @return white; }
@else if lightness($c)>90% { @return transparentize(white, 0.2); }
@else if lightness($c)>80% { @return transparentize(white, 0.5); }
@else if lightness($c)>50% { @return transparentize(white, 0.8); }
@else if lightness($c)>40% { @return transparentize(white, 0.9); }
@else { @return transparentize(white, 0.98); }
Application developers too shouldn’t deviate from this API as their source of colors; unless of course there is a very valid reason to opt-out.
This color API would be a contract between the GTK developers, theme developers, and application developers that they all should follow for colors to be tweakable.
Implementing Variants in Adwaita
The bulk of Adwaita is implemented in a file named _common.scss
, which is then imported by the files implementing its different variants (light and dark). In it, the label color would be set that way:
label { color: $fg_color; }
$fg_color
is a color hardcoded in Adwaita and then exported as @theme_fg_color
, if we assume it’s pure white, the following CSS would be generated:
label { color: #fff; }
To implement the color API we have two problems: Adwaita should use the overrideable color variabless from the color API in its CSS instead of hardcoded colors, and it should offer the different color variants.
Adwaita implements one of its theme variants that way:
$variant: 'light';
@import 'colors';
@import 'drawing';
@import 'common';
@import 'colors-public';
As you can see, _common.scss
in imported via @import 'common';
, and variables can be set before importing the file and used in it, like $variant: 'light';
. We could import _common.scss
for each of the variants of the color API, tweaking a few variables for each import:
$variant: 'light';
@import 'colors';
@import 'drawing';
// The default color variant
$color_selector: '&';
$fg_color: unquote('@theme_fg_color');
@import 'common';
// The titlebar color variant
$color_variant: 'titlebar';
$color_selector: '&.#{$color_variant}, .#{$color_variant} &';
$fg_color: unquote('@titlebar_fg_color');
@import 'common';
// The alternate color variant
$color_variant: 'alternate';
$color_selector: '&.#{$color_variant}, .#{$color_variant} &';
$fg_color: unquote('@alternate_fg_color');
@import 'common';
@import 'colors-public';
Then in _common.scss
, every time we use a public color we would do this:
label { #{$api_selector} { color: $fg_color; } }
The following CSS would then be generated:
label { color: @theme_fg_color; }
label.titlebar, .titlebar label { color: @titlebar_fg_color; }
label.alternate, .alternate label { color: @alternate_fg_color; }
By default, Adwaita would use the same color values for all color variants.
Tada~ 🎉️, all labels in a titlebar would have the overrideable titlebar color and all labels marked to use the alternate color would do so.
Settings and Priorities
So far I detailed how such a coloring API could be implemented by themes and taken advantage of by apps, but I didn’t explain how we could support vendor theming or user customization. While I don’t think this should be supported and implemented, and I think this API should only be between the GTK team and application developers, I’ll explain how I think we can extend it to let the vendors and the users set the colors.
GSettings seems like a good candidate to offer that API to vendors and users: GTK could offer to set the color variables from the API via GSetting, that means vendors could override them by a simple GSettings override, and users could override them to set their prefered colors over the default and vendor ones.
GTK allows different sources to provide styling, and each style provider is given a priority: GTK offers the fallback, theme, settings, application, and user priorities, from the lowest to the highest. If we follow these priorities and GTK loads the colors from the settings with the settings priority, it means we offer what I consider the perfect priority order, from the lowest to the highest:
- theme provided colors (theme priority);
- vendor provided colors (settings priority);
- user provided colors (settings priority, overriding the vendor’s default);
- application provided colors (application priority).