今天,来自 GNOME 设计团队的 Jakub Steiner 将会谈论 Adwaita,GTK+ 的默认主题;设计师可以用来设置 GTK+ 样式的工具;以及工具包如何改变以允许更好的设计工作流程。
Adwaita 是 GTK+ 面向用户的外观。过去 GTK+ 没有外观;工具包没有明确定义的外观。就像 FOSS 世界中的许多事物一样,它是自带的。有一个Raleigh,一个回退皮肤,只有在主题或系统设置出现问题时才会显示。而且你真的不想看到它。

CSS
随着 GTK+ 3.0 的发布,一项大胆的新努力开始了。这项努力是为了让视觉设计师负责视觉设计,使用他们理解的工具。不再求助于主题引擎来绘制独特的控件,而是选择了一种在网络上使用的样式引擎。“一切皆盒子”的 CSS 模型很好地应用于 GTK+。这花费了大量的努力,主要由 Benjamin Otte 承担,多年来他设法给了我们我们梦想的东西:一个类似 CSS 的盒子模型,允许我们使用 padding、margins、borders 和一些巧妙的功能(如最小宽度)来间隔元素/控件。在选择器方面,我们不是在处理从一个版本到另一个版本都会发生变化的直接嵌套的 widget 结构,而是给出了一个抽象的、类似 HTML 的DOM结构,带有节点和类。节点也始终保持状态,并且更容易动画化。
SCSS
在 GTK+ 中,有很多控件看起来像按钮,但不是按钮。每个程序员都有惰性,这是一件好事。设计师也没什么不同。有一个缩写词来表示它是多么积极,DRY —— 不要重复自己。所以在旧的 Adwaita 中,当我们设计一些看起来都相同的东西的外观时,我们只有一个属性块和大量的选择器——该外观的目标。按钮、下拉菜单,等等。不需要太多输入,但修改起来令人抓狂。
SASS 通过提供一次定义通用绘图过程的方法来解救,但在结构良好的样式表中重用它。您将能够绘制“像按钮一样”的东西,但不将其定义为按钮。您仍然可以在下拉菜单部分中找到组织良好的下拉菜单。SASS 将这些宏称为mixin,您将在 src/gtk/theme/Adwaita/_drawing.scss
中找到我们的绘图宏。
/* Switch Slider being a button */
slider {
/* ... */
@include button(normal, $edge: $shadow_color);
}
检查器
对设计师工作流程的巨大改进是引入了检查器。检查器是一个非常宝贵的工具,可以交互式地测试新样式或弄清楚为什么某个特定的选择器不起作用。它提供了一些强大的工具
- 小部件选择器。您可以交互式地指向一个小部件,以了解其属性或它在小部件树堆栈中的位置。自 3.20 起,您还可以了解其 CSS 节点,了解它可以进入的各种状态,了解已分配给它的所有类。它还可以告诉您在样式表中定义该设置属性的位置。这可以帮助您弄清楚为什么您的选择器不起作用。某种程度上。如果可以看到所有匹配的选择器,即使是被那些具有优先级的选择器覆盖的选择器,那就太好了。
- 交互式 CSS 样式表。您可以编写 CSS 规则并实时应用它。这不仅有助于弄清楚正确的选择器,还有助于直接使用 GTK+ 而不是使用 Inkscape 等工具来尝试绘图。能够快速迭代并尝试一些事情会带来更好的设计。
如果这一切听起来与现代浏览器提供的非常相似,那不是什么巧合。
检查器中的 CSS 节点
未来的改进
在修改 Adwaita 方面,一个主要因素是图形资源使得我们不够灵活。我们仍然有一些事情必须使用图像资源。这些实际上是在一个大型资源表 SVG 中,我们有一些脚本来分割多个大小的图像(用于 HiDPI)。添加或更改特定位仍然很麻烦。
为了让它不那么无聊,这是一个小的网络演示,说明我们如何可以避免使用图像资源来绘制 GtkScale 滑块,以及使用简单的 CSS 框来代替
<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>
本质上,滑块是一个带有 3 个圆角的盒子,旋转了 45 度。我们只需要盒子。
/* 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);
}