Adwaita

今天,来自 GNOME 设计团队的 Jakub Steiner 将会谈论 Adwaita,GTK+ 的默认主题;设计师可以用来设置 GTK+ 样式的工具;以及工具包如何改变以允许更好的设计工作流程。

Adwaita 是 GTK+ 的用户界面。过去,GTK+ 没有一个固定的界面;工具包没有适当定义的样式。像 FOSS 世界中的许多事物一样,它是自带的。有一个Raleigh,一个备用皮肤,只有在主题设置或系统设置出现问题时才会出现。你真的不想看到那个。

Adwaita

CSS

随着 GTK+ 3.0 的发布,一项大胆的新努力开始了。这项努力旨在让视觉设计师使用他们理解的工具来负责视觉设计。不再求助于主题引擎来绘制独特的控件,而是选择了在网络上使用的样式引擎。“一切都是一个盒子”的 CSS 模型非常适用于 GTK+。这花费了大量的精力,主要由 Benjamin Otte 承担,多年来他设法为我们提供了我们梦想的东西:一个类似 CSS 的盒子模型,允许我们使用内边距、外边距、边框和巧妙的功能(如最小宽度)来间隔元素/控件。在选择器方面,我们不再处理从一个版本到另一个版本变化的直接嵌套小部件结构,而是得到了一个抽象的、类似 HTML 的DOM结构,带有节点和类。节点也始终如一地携带状态,并且更容易进行动画。

SCSS

在 GTK+ 中,有很多看起来像按钮但不是按钮的控件。每个程序员都很懒,这是一件好事。设计师也一样。这非常积极,以至于有一个缩写词,DRY——不要重复自己。因此,在旧的 Adwaita 中,当我们设计一些看起来相同的东西的外观时,我们只有一个属性块和大量的选择器——该外观的目标。按钮、下拉列表等等。不需要太多输入,但更改起来很麻烦。

SASS 通过提供一种定义通用绘图过程一次,但在结构良好的样式表中重用它的方法来挽救局面。您将能够绘制“像按钮一样”的东西,但不会将其定义为按钮。您仍然可以在下拉部分中找到语义组织良好的下拉列表。SASS 将这些宏称为混合,您可以在 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 Nodes in the Inspector
检查器中的 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);
}

本周 GTK+ – 6

在过去的一周里,GTK+ 进行了 20 次提交,增加了 1852 行代码,删除了 1234 行代码。

计划和状态
  • 由于 GTK+ 黑客节 持续了大部分时间,因此 Git 存储库中没有发生太多事情。
  • 黑客节的每一天都有其在 wiki 上的记录:1234
  • 路线图已清理并更新。
值得注意的更改
  • Ray Strode 在 GDK Wayland 后端上工作,以确保它可以在没有 memfd 支持的情况下使用较旧的 Linux 内核构建和使用。
  • Philip Chimento 为 GtkStyleContext 层级结构添加了包含多个上下文的功能;这是允许样式上下文在小部件的子项中正确级联的第一步。
修复的错误
  • 错误 766341 不要依赖 memfd,因为它需要相当新的内核
  • 错误 767766 配置错误地检测到 CUPS 2.X
  • 错误 767795 当 GtkShortcutsShortcut 的“加速器”属性设置为“less”时发出警告
  • 错误 751409 gtk_style_context_add_provider() 不会传播到子项
  • 错误 767705 GtkActionHelper:将消息更改为警告
  • 错误 767468 在显示后,树视图单元格渲染器上的弹出窗口会立即隐藏
参与其中

有兴趣参与 GTK+ 的工作吗?请查看 新手错误 列表,并加入 irc.gnome.org 上的 IRC 频道 #gtk+。

在 GTK+ 中绘图

GTK+ 如何绘制窗口内容是一个相当复杂的话题;它涉及从 GtkWidgetGdkWindow、再到 Cairo、再到当前使用的窗口系统的深入研究。即使对于从应用程序开发角度熟悉 GTK+ API 的人来说,这项任务也可能看起来有些令人生畏,因此我决定快速介绍一下 GTK+ 如何绘制,从窗口小部件到窗口、再到表面、再到本机窗口资源。

它是如何开始的

GTK+ 始终会进行绘制,因为有东西要求它这样做。此请求可能来自窗口系统——例如,因为窗口管理器将您的应用程序窗口呈现给用户,或者因为用户调整了其大小——但更常见的情况是它来自更新其内容的小部件。例如,一个进度条从 50% 变为 60%;或者一个标签,更改其文本;或者一个旋转器,进行新的迭代。此请求会使小部件的后备 GdkWindow 失效——通常它是包含该小部件的顶层 GtkWindowGdkWindow。每个失效都带有要失效的窗口区域(“损坏”),以便当我们实际开始绘制时,我们知道需要更新窗口的哪些部分,并且我们可以避免在损坏区域之外进行绘制。

争分夺秒

第一次失效将启动“帧时钟”;这个时钟是一个对象,它跟踪帧内的每个阶段,例如绘制窗口、布局小部件或处理事件队列。这使得 GTK+ 可以与窗口系统合成器等事物同步,并避免执行用户看不到的不必要的工作——例如,当你的显示器只能以 60Hz 运行时,却以每秒 1000 帧的速度绘制某些内容。

一旦时钟到达“绘制”阶段,我们将处理窗口上的所有计划更新;这将导致发出一个 GDK_EXPOSE 事件。GDK_EXPOSE 事件包含需要更新的 GdkWindow,以及所有失效区域的并集。重要的是要注意,一般来说,只有顶层窗口才会收到 GDK_EXPOSE 事件;但由于历史原因,一些小部件可能会应用特定的事件掩码,也会导致 GDK_EXPOSE 事件传递给它们。你不应该编写依赖于此的代码,如果你有从旧版本的 GTK+ 2.x 移植过来的遗留代码,你应该认真考虑从事件掩码中删除 GDK_EXPOSURE_MASK

渲染

GTK+ 从 GDK_EXPOSE 事件中取出窗口和失效区域,并确定它们属于哪个顶层小部件。一旦找到,GTK+ 将开始实际的渲染过程。首先,GTK+ 将要求 GdkWindow 创建一个缓冲区,用于绘制窗口的内容;缓冲区将被裁剪到需要绘制的区域,并使用窗口的背景颜色清除。GDK 将创建一个“绘图上下文”——一个瞬态对象,用于跟踪 OpenGL 和 Cairo 绘图等内容。然后,GTK+ 将要求小部件使用 Cairo 上下文绘制自身。对于叶子小部件,这意味着在该上下文中绘制自身;对于容器小部件,这还意味着递归遍历其所有子项。在此过程结束时,GTK+ 将通过告诉 GDK 获取包含所有渲染小部件的缓冲区,并使用它来替换窗口的当前内容来结束帧。GDK 随后将要求窗口系统在更合适的时候向用户呈现窗口。

更改历史

上面概述的过程有各种注意事项,并且 GDK 内部处理窗口失效和验证的代码相当复杂;它也有一段很长的历史,这意味着它的 API 中散落着过去时代的遗迹。

例如,在 GTK+ 3.0 之前,你应该自己处理 “expose” 事件,并使用 gdk_cairo_create() 创建 Cairo 上下文以在小部件上绘制;这早已变得没有必要,因为 GtkWidget::draw 虚函数已经为我们提供了用于绘制的 Cairo 上下文。但是,gdk_cairo_create() 函数已在 GTK+ 3.22 中被弃用,不应在新编写的代码中使用;如果你需要 Cairo 上下文,你应该创建一个类似的 Cairo 表面,在其上调用 cairo_create(),然后使用该表面作为 GTK+ 在绘制小部件时提供给你的 Cairo 上下文的源。另一方面,如果你使用 gdk_cairo_create() 在响应 GDK_EXPOSE 事件时在顶层的本机 GdkWindow 上绘制,那么你应该改用新添加的 gdk_window_begin_draw_frame()gdk_window_end_draw_frame()GdkDrawingContext API。

塑造未来

多年来,GTK+ 中的绘图代码的内部结构已逐步更新,以应对诸如新的窗口系统以及其他渲染 API之类的东西。可以肯定的是,它们将再次改变,尤其是在提高渲染性能方面。许多看起来可能很随意的更改实际上是减少每一帧在工具包内部花费的时间,并为应用程序逻辑留出更多时间的垫脚石。

本周 GTK+ – 5

在过去的一周中,GTK+ 已有 35 次提交,增加了 3140 行代码,删除了 2353 行代码。

计划和状态
  • GTK+ 黑客松今天开始;主题包括:CSS、布局管理、沙盒门户和 GDK。
  • Matthias Clasen 和 Alex Larsson 正在开发用于在沙盒应用程序中打开文件的“门户”的初始实现
值得注意的更改
  • Emmanuele Bassi 合并了他的分支,用于简化 GdkWindow 绘图入口点;此分支引入了一些新函数和一个 GdkDrawingContext 类,同时弃用了旧的 gdk_window_begin_paint* 系列函数;gdk_window_end_paint(); gdk_cairo_create(); 和 gtk_widget_send_expose()
  • Tristan Van Berkom 致力于修复在 GtkScrolledWindow 中引入新的内容大小调整属性后出现的问题,并确保大小调整请求的一致性。
修复的错误
  • 错误 767312 除非调用了 gtk_widget_get_style_context(),否则 gtk_widget_path_append_for_widget() 会遗漏类
  • 错误 79229 具有大量数字且值位置设置为 GTK_POS_TOP/BOTTOM 的 GtkScale 绘制不正确
  • 错误 118959 GtkScale 值‘-0’
  • 错误 710471 使 gtk_scrolled_window_remove() 更加智能
  • 错误 767310 高对比度主题在默认按钮中不显示焦点矩形
  • 错误 766860 Wayland 中平铺(捕捉、半最大化)的窗口不是 GDK_WINDOW_STATE_TILED
  • 错误 766675 向 GdkWindow 添加适当的帧绘制 API
参与其中

有兴趣参与 GTK+ 的工作吗?请查看 新手错误 列表,并加入 irc.gnome.org 上的 IRC 频道 #gtk+。

本周 GTK+ – 4

在过去的一周中,GTK+ 已有 55 次提交,增加了 2378 行代码,删除了 1493 行代码。

计划和状态
  • Carlos Soriano 一直在各种 主题 分支中对新的路径栏小部件进行进一步的实验。
  • Emmanuele Bassi 一直在开发一个新的 API,用于简化 GtkWidget 内的绘图入口点,为 GSK 内部新的渲染 API 做准备。
值得注意的更改
  • Timm Bäder 为 GtkStack 添加了一个可访问的表示,以便在辅助工具中仅显示当前可见的子项。
  • Matthias Clasen 添加了一个警告,以防我们在没有分配的情况下发出 GtkWidget::draw 调用;这不应该发生,并且警告允许跟踪行为不佳的小部件。
  • Lapo Calamandrei 修复了 Adwaita 中有关菜单项、信息栏和可选择标签的插入符号颜色的问题。
  • Matthias Clasen 弃用了 GtkSizeGroup:ignore-hidden 属性,并将其记录为已损坏;不可见小部件的大小调整实际上是不可能的,因为它们无法访问窗口系统和样式资源。建议改用 GtkStack 来为隐藏的小部件保留空间。
  • 添加了一个新的 GDK 设备源类型 GDK_SOURCE_TRACKPOINT,以表示 Trackpoint/指点杆设备;此新设备类型可用于在小部件中实现特定于设备的行为。
  • CSS 文本样式属性现在可以用于 GtkScaleGtkProgressbar 小部件的值和标记。
  • Georges Basile Stavracas Neto 在 GtkScrolledWindow 中实现了 max-content-widthmax-content-height 属性;Tristan Van Berkom 修复了现有 min-content-widthmin-content-height 属性的长期存在的大小调整问题。
修复的错误
  • 错误 745622 GtkInfoBar 中未突出显示选定的文本
  • 错误 767058 GtkInfoBar:右键单击/上下文菜单全白
  • 错误 767052 Wayland:图标化模态对话框会使应用程序无法使用
  • 错误 767100 为指点杆添加一个输入源类型
  • 错误 767108 分隔符在 GtkPopover 中放置不正确
  • 错误 767093 wayland:提供有关滚动设备的信息
  • 错误 753202 更改单击滚动的光标
  • 错误 767165 更新 GDK_GRAB_FAILED 可用性的文档
  • 错误 742281 GtkScrolledWindow 应具有 max-content-height 和 max-content-width 属性
  • 错误 674215 更新工具提示的回归
  • 错误 556254 在“对象”测试中测试类型为 GObject 的属性
  • 错误 708148 gtk_tree_view_get_path_at_pos 错误地识别了初始像素的列
  • 缺陷 765595 当焦点离开时,模态弹出窗口不会关闭
  • 缺陷 766569 为 GTK_SCROLL_NATURAL 子项提供更好的尺寸请求
  • 缺陷 767238 修复最小内容宽度/最小内容高度中长期存在的回归问题
参与其中

有兴趣参与 GTK+ 的工作吗?请查看 新手错误 列表,并加入 irc.gnome.org 上的 IRC 频道 #gtk+。

本周 GTK+ – 3

在过去的一周里,GTK+ 进行了 26 次提交,增加了 5081 行代码,删除了 3618 行代码。

计划和状态
值得注意的更改
  • 文档方面进行了一些改进,特别是在 CSS 参考和 GTK+ 2.x → 3.x 迁移指南方面。
  • Matthias Clasen 在 API 参考中添加了一个部分,用于映射 README 文件中的发行说明;此部分将用作从 3.x 迁移到 GTK+ 未来主要版本的迁移指南的起点。
  • gtk-builder-tool 实用程序允许验证、简化、预览或检查 UI 描述文件,现在在简化设置为默认值的属性时,会保留 GtkDialog:border-width 属性。
修复的错误
  • 缺陷 759037 GtkInfoBar:文档未更新背景颜色和消息类型
  • 缺陷 747206 gtktextview:关于如何获取两个段落之间的行距的说明
  • 缺陷 766643 当取消映射具有待处理配置事件的窗口时,窗口冻结
  • 缺陷 766122 重复使用的文件选择器在显示时一半时间显示 $pwd
  • 缺陷 766878 placesview:不要将图标名称标记为可翻译
  • 缺陷 764203 “文本视图边框”节点的默认背景颜色
参与其中

有兴趣参与 GTK+ 的工作吗?请查看 新手错误 列表,并加入 irc.gnome.org 上的 IRC 频道 #gtk+。

本周 GTK+ – 2

在过去的一周里,GTK+ 进行了 90 次提交,增加了 8502 行代码,删除了 7914 行代码。

计划和状态
  • Matthias Clasen 使用他的 原型分支 更新了 GTK+ 路线图 中关于 “选项卡条” GtkStack 控制器部件的条目。
值得注意的更改
  • Matthias Clasen 和 Lapo Calamandrei 为 GtkScale 提交了一些 CSS 更改,这些更改是为了覆盖所有侧面上标记的所有可能情况。此外,GtkScale 在显示值时使用的 *value* 节点在文档中缺失,现在已添加。
  • Debarshi Ray 修复了在 libvte 上工作时发现的 GtkScrolledWindowGTK_POLICY_NEVER 的一些分配问题。
  • Matthias Clasen 为 GTK 支持的 CSS 光标名称列表添加了两个新的光标名称 context-menuno-drop;这些光标由 Adwaita 提供。
  • Matthew Waters 修复了 GDK 以使用线程安全的 Wayland 调度 API;这在多个线程轮询 Wayland 文件描述符的情况下尤其有用。
  • Olivier Fourdan 致力于允许 Wayland 后端在询问 GDK 窗口显示在哪个监视器上时尝试返回合理的值;这仍然是一项进行中的工作,可能需要协议扩展来避免可能导致误报的猜测。
  • Ondrej Holy 致力于 GIO、GVFS 和 GTK+,以确保在文件选择对话框的侧边栏中将冷插拔驱动器正确检测为可移动驱动器。
  • Javier Jardón 最终更新了 GTK+ 以使用上游 gettext,而不是 GLib 修改的宏和构建文件。
修复的错误
参与其中

有兴趣参与 GTK+ 的工作吗?请查看 新手错误 列表,并加入 irc.gnome.org 上的 IRC 频道 #gtk+。

本周 GTK+ – 1

2016 年 5 月 9 日至 2016 年 5 月 15 日当周 GTK+ 的情况

在过去的一周里,GTK+ 进行了 51 次提交,增加了 5375 行代码,删除了 4970 行代码。

计划和状态

当我们浏览 路线图 的项目时,3.22 开发周期正在加速。

值得注意的更改
  • Olivier Fourdan 修复了 GtkMenu 中的滚动事件处理,以确保它在 X11 和 Wayland 上关于平滑和离散滚动时的行为一致
  • 现在,“连接到服务器”帮助弹出窗口中的可用协议列表使用 GVFS 中支持的方案列表进行填充,这要归功于 Georges Basile Stavracas Neto
  • Benjamin Otte 推送了一个提交,该提交允许 GtkWidget 在未实现的部件上发出 style-updated 信号,而不是延迟到实现时发出;原始行为是避免在构造期间进行太多无效的旧优化结果,但是样式系统多年来有所改进。
  • Timm Bäder 在各种部件(如 GtkListBoxGtkStackGtkToolbar)上推送了各种清理提交。
修复的错误
  • 缺陷 766166 gtk.css 中的键绑定被忽略
  • 缺陷 766207 修复在 C99 之前编译器上的构建
  • 缺陷 765939 [Wayland] 在 GtkMenu 中使用触摸板滚动非常慢
  • 缺陷 756570 gtkplacesview 不再提供关于地址格式的指导
  • 缺陷 766120 刻度 draw_value() 对齐方式从中心/右 (H/V) 更改为左侧,导致显着的视觉倒退
  • 缺陷 766233 当服务器不支持 XI2 时崩溃
  • 缺陷 766175 引号的翻译可能会被 GTK 侧边栏误解
  • 缺陷 765700 GtkPaned 的使用导致 “代码如何知道要分配的大小?”
  • Bug 682080 Gtk:ERROR:gtktoolbar.c:2271:logical_to_physical: 断言失败: (logical == 0)
  • Bug 766458 widget: 修复 GtkLabelAccessible NULL 链接。
参与其中

有兴趣参与 GTK+ 的工作吗?请查看 新手错误 列表,并加入 irc.gnome.org 上的 IRC 频道 #gtk+。

本周 GTK – 0

为了改善 GTK 团队与 GNOME 平台其他部分以及应用程序开发人员之间的沟通,我们将开始撰写每周状态报告,介绍 GTK 和 GNOME 核心平台其他部分发生的事情。

这些报告的灵感主要来自 Servo 团队撰写的报告。

您可以在每周一的这个博客上看到一篇帖子;我们将关注值得注意的更改;新的贡献者;新功能或弃用;以及计划的活动。

如果您有疑问,请像往常一样将其发送到 GTK 开发邮件列表

玩得开心!

你好,世界!(重演)

大家好,欢迎回到 GTK+ 开发博客。

在 3.0 版本发布后,我们已经忽略这个博客一段时间了,但我们认为有一个地方可以谈论 GTK+(以及 GNOME 核心平台的其他部分),它是如何变化的以及它的发展方向是很重要的。

展望未来,我们不会将此博客用于公告——但如果您想要公告,您可以简单地订阅 gnome-announcement 邮件列表。相反,我们将利用这个空间来展示 GTK+ 中正在发生的事情;讨论路线图;突出一些新功能,也许还有一些不为人所知的旧功能。

请继续关注,像往常一样:玩得开心!