今天,来自 GNOME 设计团队的 Jakub Steiner 将会谈论 Adwaita,GTK+ 的默认主题;设计师可以用来设计 GTK+ 样式的工具;以及工具包如何改变以允许更好的设计工作流程。
Adwaita 是 GTK+ 面向用户的外观。过去,GTK+ 没有外观;该工具包没有明确定义的外观。就像 FOSS 世界中的许多事物一样,它是自带的。有一个Raleigh,一个后备皮肤,只有在主题或系统设置出现问题时才会出现。你真的不想看到它。
CSS
随着 GTK+ 3.0 的发布,一项大胆的新努力开始了。这项努力旨在让视觉设计师使用他们理解的工具来负责视觉设计。我们没有使用主题引擎来绘制独特的控件,而是选择了在 Web 上使用的样式引擎。“一切皆为盒子”的 CSS 模型非常适用于 GTK+。这花费了大量的精力,主要由 Benjamin Otte 承担,多年来,他设法实现了我们梦想的:类似 CSS 的盒子模型,允许我们使用内边距、外边距、边框和诸如最小宽度之类的巧妙功能来分隔元素/控件。在选择器方面,我们不再处理从一个版本到另一个版本不断变化的直接嵌套小部件结构,而是获得了一个抽象的、类似 HTML 的DOM结构,带有节点和类。节点还始终保持状态,并且更容易进行动画处理。
SCSS
在 GTK+ 中,有很多看起来像按钮但不是按钮的控件。每个程序员都有偷懒的一面,这是一件好事。设计师也不例外。它如此积极,以至于有一个缩写词来表示它,DRY——不要重复自己。所以在旧的 Adwaita 中,当我们设计一些看起来相同的东西的外观时,我们只有一个属性块和大量的选择器——该外观的目标。按钮、下拉菜单,等等。不需要太多输入,但要改变却很疯狂。
SASS 通过提供一次定义通用绘图过程,但在结构良好的样式表中重用它的方法来帮助我们。你将能够绘制“像按钮一样”的东西,但不会将其定义为按钮。你仍然会在下拉菜单部分找到一个语义组织良好的下拉菜单。SASS 将这些宏称为mixins,你可以在 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 这样的工具。能够快速迭代和尝试可以带来更好的设计。
如果这一切听起来与现代浏览器提供的非常相似,那并非巧合。

未来的改进
在更改 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); }