GTK 4 中的自定义小部件 – 布局

(这是关于 GTK 4 中自定义小部件的系列文章的第三部分。第一部分第二部分)。

推荐使用小部件

正如我们之前所说,“一切皆为小部件”。例如,我们建议您使用 GtkLabel 而不是手动渲染 pango 布局,或使用 GtkImage 而不是手动加载和渲染 pixbuf。使用现成的小部件可确保您获得所有预期的行为,例如选择处理、上下文菜单或高 DPI 支持。而且它比自己完成所有事情要容易得多。

委托布局

snapshot() 和 measure() 函数的默认实现会自动处理子小部件。自定义小部件的主要责任是根据需要排列子小部件。在 GTK 3 中,这将通过实现 size_allocate() 函数来完成。您仍然可以这样做。但是在 GTK 4 中,更方便的选择是使用布局管理器。GTK 附带了许多预定义的布局管理器,例如 GtkBoxLayout、GtkCenterLayout、GtkGridLayout,仅举几例。

布局管理器可以通过多种方式进行设置,最简单的方法是在您的 class_init 函数中设置布局管理器类型

gtk_widget_class_set_layout_manager_type (widget_class, 
                                          GTK_TYPE_GRID_LAYOUT);

然后,GTK 将自动实例化并使用此类型的布局管理器。

布局管理器将您的子小部件包装在它们自己的“布局子”对象中,这些对象可以具有影响布局的属性。这是子属性的替代品。就像子属性一样,您可以在 ui 文件中设置这些“布局属性”

<child>
  <object class="GtkLabel">
    <property name="label">Image:</property>
    <layout>
      <property name="left-attach">0</property>
    </layout>
  </object>
</child>

添加子项

使用模板是将子项添加到小部件的最方便方法。在 GTK 4 中,这适用于任何小部件,而不仅仅是容器。如果出于某种原因,您需要手动创建子小部件,则最好在您的 init() 函数中完成

void
demo_init (DemoWidget *demo)
{
  demo->label = gtk_label_new ("Image:");
  gtk_widget_set_parent (demo->label, GTK_WIDGET (demo));
}

这样做时,重要的是设置正确的父子关系,使您的子小部件成为整个小部件层次结构的一部分。并且这种设置需要在您的 dispose() 函数中撤销

void
demo_dispose (GObject *object)
{
  DemoWidget *demo = DEMO_WIDGET (object);

  g_clear_pointer (&demo->label, gtk_widget_unparent);

  GTK_WIDGET_CLASS (demo_widget_parent_class)->dispose (object);
}

新的可能性

布局管理器很好地将布局任务与小部件机制的其余部分隔离,这使得尝试新的布局更容易。

例如,GTK 4 包括 GtkConstraintLayout,它使用约束求解器,根据小部件大小和位置的一组约束来创建布局。

要了解有关 GTK 4 中约束的更多信息,请阅读 GtkConstraintLayout 的文档。

展望

在下一篇文章中,我们将探讨 GTK 4 中的小部件如何处理输入。

GTK 4 中的自定义小部件 – 绘图

(这是关于 GTK 4 中自定义小部件的系列文章的第二部分。第一部分)。

老式的绘图方式

在研究小部件如何进行自己的绘图之前,值得指出的是,如果您只需要一些独立的 cairo 绘图,GtkDrawingArea 仍然是一个有效的选择。

GTK 3 和 GTK 4 之间的唯一区别是您调用 gtk_drawing_area_set_draw_func() 来提供您的绘图函数,而不是将信号处理程序连接到 ::draw 信号。其他一切都相同:GTK 为您提供了一个 cairo 上下文,您可以直接在其上进行绘制。

void
draw_func (GtkDrawingArea *da,
           cairo_t        *cr,
           int             width,
           int             height,
           gpointer        data)
{
  GdkRGBA red, green, yellow, blue;
  double w, h;

  w = width / 2.0;
  h = height / 2.0;

  gdk_rgba_parse (&red, "red");
  gdk_rgba_parse (&green, "green");
  gdk_rgba_parse (&yellow, "yellow");
  gdk_rgba_parse (&blue, "blue");

  gdk_cairo_set_source_rgba (cr, &red);
  cairo_rectangle (cr, 0, 0, w, h);
  cairo_fill (cr);

  gdk_cairo_set_source_rgba (cr, &green);
  cairo_rectangle (cr, w, 0, w, h);
  cairo_fill (cr);

  gdk_cairo_set_source_rgba (cr, &yellow);
  cairo_rectangle (cr, 0, h, w, h);
  cairo_fill (cr);

  gdk_cairo_set_source_rgba (cr, &blue);
  cairo_rectangle (cr, w, h, w, h);
  cairo_fill (cr);
}

...

gtk_drawing_area_set_draw_func (area, draw, NULL, NULL);

渲染模型

GTK 3 和 GTK 4 之间的主要区别之一是我们现在以 GL/Vulkan 而不是 cairo 为目标。作为此切换的一部分,我们已从即时模式渲染模型转移到保留模式。在 GTK 3 中,我们使用 cairo 命令渲染到表面上。在 GTK 4 中,我们创建一个包含渲染节点的场景图,这些渲染节点可以传递给渲染器,或以其他方式处理,或保存到文件中。

在小部件 API 中,此更改反映在以下两者之间的差异中

gboolean (* draw) (GtkWidget *widget, cairo_t *cr)

 void (* snapshot) (GtkWidget *widget, GtkSnapshot *snapshot)

GtkSnapshot 是一个辅助对象,它将您的绘图命令转换为渲染节点并将它们添加到场景图中。

小部件的 CSS 样式信息描述了如何渲染其背景、边框等等。GTK 将此转换为一系列函数调用,这些函数调用会在小部件内容的渲染节点之前和之后将合适的渲染节点添加到场景图中。因此,您的小部件会自动符合 CSS 绘图模型,无需额外的工作。

为内容提供渲染节点是小部件 snapshot() 实现的责任。GtkSnapshot 具有方便的 API,使其易于使用。例如,使用 gtk_snapshot_append_texture() 来渲染纹理。使用 gtk_snapshot_append_layout() 来渲染文本。如果您想使用自定义 cairo 绘图,gtk_snapshot_append_cairo() 可让您这样做。

绘图小部件

要实现一个进行一些自定义绘图的小部件,您需要实现 snapshot() 函数,该函数为您的绘图创建渲染节点

void
demo_snapshot (GtkWidget *widget, GtkSnapshot *snapshot)
{
  GdkRGBA red, green, yellow, blue;
  float w, h;

  gdk_rgba_parse (&red, "red");
  gdk_rgba_parse (&green, "green");
  gdk_rgba_parse (&yellow, "yellow");
  gdk_rgba_parse (&blue, "blue");

  w = gtk_widget_get_width (widget) / 2.0;
  h = gtk_widget_get_height (widget) / 2.0;

  gtk_snapshot_append_color (snapshot, &red,
                             &GRAPHENE_RECT_INIT(0, 0, w, h));
  gtk_snapshot_append_color (snapshot, &green,
                             &GRAPHENE_RECT_INIT(w, 0, w, h));
  gtk_snapshot_append_color (snapshot, &yellow,
                             &GRAPHENE_RECT_INIT(0, h, w, h));
  gtk_snapshot_append_color (snapshot, &blue,
                             &GRAPHENE_RECT_INIT(w, h, w, h));
}

...

widget_class->snapshot = demo_snapshot;

此示例生成四个颜色节点

如果您的绘图需要特定大小,您也应该实现 measure() 函数

void
demo_measure (GtkWidget      *widget,
              GtkOrientation  orientation,
              int             for_size,
              int            *minimum_size,
              int            *natural_size,
              int            *minimum_baseline,
              int            *natural_baseline)
{
  *minimum_size = 100;
  *natural_size = 200;
}

...

widget_class->measure = demo_measure;

GTK 会保留您的 snapshot() 函数生成的渲染节点,并在您通过调用 gdk_widget_queue_draw() 告诉它您的小部件需要再次绘制之前重复使用它们。

更深入地了解

如果您有兴趣阅读更多关于此主题的内容,GTK 文档概述了 GTK 绘图模型

展望

在下一篇文章中,我们将探讨 GTK 4 中的小部件如何处理子小部件。

GTK 4 中的自定义小部件 – 简介

随着 GTK 4 越来越接近完成,现在是时候概述一下 GTK 4 中自定义小部件的外观了。

这系列文章将探讨编写小部件的主要方面,以及它们与 GTK 3 相比的变化。这些文章将提供一个高层次的概述;有关将应用程序移植到 GTK 4 的详细清单,请查看 迁移指南

简介

我们的 API 更改的总体方向是强调委托而不是子类化。这样做的动机之一是使编写自己的小部件更容易且不易出错。因此,您将看到更多辅助对象接管核心小部件类的功能方面。许多小部件现在是最终类 - 期望直接从 GtkWidget 派生。

我们的 API 的另一个总体趋势是“一切皆为小部件”。在 GTK 3 中,我们缓慢地将复杂的小部件分解为它们的组成部分,首先是 CSS 节点,然后是小工具。例如,在 GTK 4 中,GtkScale 的槽和滑块是完全形成的子小部件,它们可以保持自己的状态并像任何其他小部件一样接收输入。

GTK 4 过渡中的一个大输家是 GtkContainer 基类。它变得不那么重要了。现在任何小部件都可以有子小部件。子属性已被布局子及其属性取代。所有焦点处理已从 GtkContainer 移动到 GtkWidget。

另一个大输家是 GtkWindow。在 GTK 3 中,所有“弹出窗口”(条目完成、菜单、工具提示等)都在下面使用 GtkWindow。在 GTK 4 中,它们中的大多数已转换为弹出窗口,并且 GtkPopover 实现已从 GtkWindow 中解开。此外,许多特定于顶层的功能已分解为单独的接口,称为 GtkRoot 和 GtkNative。

展望

在下一篇文章中,我们将探讨 GTK 4 中的小部件如何使用渲染节点进行自己的绘图。

GTK 3.98.2

当我们发布 3.98.0 版本时,我们承诺会更频繁地发布快照,因为剩余的 GTK 4 功能正在陆续加入。几周后,3.98.1 和 3.98.2 快照已经悄然发布了。

那么,有哪些新功能呢?

功能

虽然还有一些工作要做,但一些重要的功能已经加入。

首先,我们已经完成了将 GtkPopovers 重实现为 xdg-popup surface 的工作,并将 GdkSurface API 拆分为单独的 GdkToplevelGdkPopup 接口(还有一个 GdkDragSurface 接口),它们反映了 surface 的不同角色。

  • Toplevel 是由用户放置并可以最大化、全屏等操作的独立窗口。
  • Popups 是相对于父 surface 定位的,并且通常会捕获输入,例如在用于菜单时。

在 GTK 中,popover 失去了它们的 :relative-to 属性,因为它们现在像任何其他小部件一样是常规层次结构的一部分,而 GtkWindow 则失去了其 :window-type 属性,因为所有 GTK_WINDOW_POPUP 实例都已转换为 popover,窗口仅用于正确的 toplevel。

另一个主要功能是新的键盘快捷键基础设施。过去,GTK 有大量的 API 来实现按键绑定、助记符和加速键。在 GTK 4 中,所有这些都由事件控制器处理。GtkShortcutController 比典型的事件控制器稍微复杂一些,因为它使用统一的 API 处理所有不同类型的快捷键。

值得庆幸的是,大部分复杂性都被隐藏了。对于小部件实现者来说,重要的 API 是 gtk_widget_class_add_shortcut() 的变体,用于添加按键绑定。对于应用程序,助记符和全局加速键(使用 gtk_application_set_accels_for_action())的工作方式与以前相同。此外,还可以在 ui 文件中创建快捷键控制器和快捷键。

一组较小的功能以一些 GtkTextTag 属性的形式加入,这些属性公开了新的 pango 功能,例如上划线、空格的可见渲染和对断字的控制。现在可以通过标签在 GtkTextView 中控制这些功能。在条目中,可以通过直接添加 pango 属性来控制它们。

完成

当我写关于 3.98 的文章时,我说拖放重构已经完成。结果证明这不太正确,此后又进行了一轮 DND 工作。这些更改是根据开发人员对拖放 API 的反馈进行的。用户测试万岁!

我们引入了单独的 GtkDropTarget 和 GtkDropTargetAsync 事件控制器,前者被简化以避免所有异步 API,这使得处理本地情况非常容易。

我们还清理了 DND 实现的内部结构,将 DND 事件分组到事件序列中,以与处理正常运动事件相同的方式处理它们,并引入了 GtkDropControllerMotion,这是一个事件控制器,旨在处理诸如在 DND 操作期间切换标签之类的事件。

最后,我们可以删除 X11 样式的属性和选择 API 的残余:GtkSelectionData 和 GdkAtom 已被删除。

清理和修复

与往常一样,发生了很多较小的清理和修复。

最大的清理工作发生在文件选择器中,其中一些几乎没有用处的 API(额外的窗口小部件、覆盖确认、:local-only、GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER 等)已被删除。为了弥补这一点,原生文件选择器的门户实现现在支持选择文件夹。

另一个大的清理工作是 GdkEvent 现在是一个不可变的 boxed 类型。这主要是一个内部清理;对应用程序级 API 的影响很小,因为事件控制器在很大程度上取代了直接事件处理。

一个新的此类事件控制器是 GdkEventControllerFocus,它是从按键事件控制器中分离出来的,仅提供焦点处理。

GtkMenuButton 在从 GtkButton 子类转换为普通小部件时失去了使用助记符的能力。此功能已恢复,具有 :use-underline 属性。

包含在 GTK 中的 HighContrast 和 HighContrastInverse 主题现在源自 Adwaita,从而大大减少了维护负担并提高了质量。现在在 gtk4-widget-factory 中尝试这些主题更容易了,因为我们添加了一个样式菜单。

新的 HighContrast 主题也已向后移植到 GTK 3。

未来展望

我们将继续发布快照,并希望获得更多关于上述新 API 和功能的开发人员反馈。

以下是我们希望在 GTK 4 之前集成的东西

  • 行回收列表和网格视图
  • 改进的辅助功能基础设施
  • 动画 API

如果你想关注 GTK 4 的工作,请转到 这里

构建和测试 GTK

… 或者:GTK 开发人员如何检查他们在工具包上的工作.

自从 GNOME 集体迁移到 GitLab 以来,GTK 一直在利用该平台提供的功能——尤其是在其持续集成管道方面。

在过去,检查我们对工具包的更改是否正确的唯一方法是等待 Continuous 构建机器人通知我们主开发分支上的任何中断。虽然这比没有好,但它不允许我们在任何事情的开发阶段(从功能到错误修复,从文档改进到添加新测试)中预防中断。

如今,GitLab 中可用的 CI 管道在每个分支和合并请求上运行,远早于更改到达其他人使用的公共开发分支。

主题分支和合并请求

当针对 GTK 4 主开发分支开发主题分支时,我们运行一个 CI 管道,该管道首先对分支中应用的更改进行简单的编码样式检查。样式检查使用 clang-format,它通常足以满足 GTK 编码样式;编码样式有一些“特殊”的注意事项,clang-format 可能会引发误报和漏报。因此,样式检查允许失败,但强烈建议贡献者和审阅者在失败时检查日志。

一旦通过样式检查,我们就运行构建阶段,目前包含三个单独的作业

  • 使用 Fedora 容器的 Linux 调试构建
  • Windows 上的 MSYS2 构建
  • Linux 发布构建

Linux 调试构建非常标准。

MSYS2 构建会捕获 Windows 上 GNU 工具链的任何问题。

发布构建是必要的,以确保我们不依赖于我们在开发期间使用的调试代码的副作用。

所有这些作业都运行 GTK 测试套件。

我们发布测试报告,既作为 JUnit 文件,利用 GitLab 的支持;又作为 HTML 报告,存储为管道工件。这使我们更容易检查哪些失败了,哪些成功了。

理想情况下,我们希望添加更多环境

  • 基于其他主流发行版的 Linux 构建
  • 使用 MSVC 工具链的 Windows 构建
  • 一旦 GDK 后端修复,就可以进行 macOS 构建

在构建和测试作业通过后,我们进入分析阶段。我们在 GTK 的代码库上运行 Clang 静态分析工具并生成报告。在不久的将来,我们还可以运行诸如 UBSan 和 ASan 之类的清理工具;用于我们的解析器(例如 GtkBuilder 和 CSS)的模糊测试工具;或者验证我们的 UI 定义是否包含适当的辅助功能描述符的工具。

与测试一样,我们将分析报告作为 GitLab 工件发布以供审核。

一旦分析阶段通过,我们就构建 API 参考,并检查结果,以确保新添加的符号已正确记录在案。

最后,我们有手动 CI 作业来为 GTK 演示应用程序、窗口小部件工厂和图标浏览器构建 Flatpak 包。这使设计人员可以立即测试 Adwaita 的更改或新添加的小部件,而无需在其系统上从头开始构建 GTK。

主线开发分支

一旦主题分支/合并请求的 CI 管道通过,我们就可以将更改合并到主开发分支中,并有一定程度的信心认为代码是正确的并且可以完成我们想要的工作。

主开发分支运行与前面描述的相同的管道,只是 Flatpak 作业不再是手动的了,因此始终可以在本地测试 GTK 的当前最前沿版本。此外,文档 在线 发布,因此始终保持最新。

The GTK CI pipeline

GTK 3 怎么样?

在 GTK 3 分支中,我们有一个更简单的管道,它运行以下作业

  • 在 Linux 和 Windows/MSYS2 上进行完整的 Meson 调试构建,适用于 Fedora 和 Debian 当前稳定版本上的静态和共享库工件
  • 在 Linux 上进行完整的 Meson 发布构建,这也生成 API 参考
  • 在 Linux 和 Windows/MSYS2 上进行 Autotools 构建
  • 在 Linux 上进行可选的 Autotools distcheck 构建

只要 GTK 3 支持 Autotools,Autotools 作业就会存在。理想情况下,我们希望利用 Meson 构建为 macOS 和 Windows/MSVC 添加其他作业。

The GTK3 CI pipeline

一旦 GTK 4 CI 管道达到一定的功能和稳定性水平,我们将把它向后移植到 GTK 3,这样我们就可以更加确信当前稳定分支不会退步。


有关更多信息,您可以查看 GTK 存储库

GTK Hackfest 2020 — 路线图和辅助功能

在 1 月 28 日至 1 月 31 日期间,GTK 团队在布鲁塞尔举办了第三次 hackfest

黑客马拉松的主要议题包括:

  • GTK4 下一个开发快照的时间表
  • 阻碍 GTK 4.0 发布的功能缺失
  • 工具包中当前的可访问性支持状态

前两个议题占据了黑客马拉松前两天的大部分时间;您可以阅读GTK 3.98 发布公告,了解自 3.96 版本发布以来的 300 天内我们一直在做的事情。缺失的功能包括:

  • 键盘快捷键的事件控制器
  • Wayland 上可移动的弹出窗口
  • 行回收列表和网格视图
  • 动画 API

所有这些都在主题分支中进行开发。键盘快捷键分支最近已重新基于,目前正在进行文档编写和清理;可移动弹出窗口也经过了几次迭代后正在审查中。最后两个剩余分支相当大,需要更多迭代才能使其正确——其中动画 API 目前主要是一个原型。

黑客马拉松的最后一个议题是最大的,也是一个早就应该进行的讨论。

GTK 的可访问性支持由 Sun 可访问性团队在 GTK 2.0 版本中添加;它依赖于 ATK(可访问性工具包)提供的抽象数据类型,这些数据类型然后在 GTK 类(如 GtkWidgetAccessibleGtkEntryAccessible)中具体实现。每个小部件都有一个与之关联的“可访问”对象,该对象由 GTK 自动创建,或者在子类化 GTK 小部件时由应用程序代码提供。非小部件类型也可以有关联的可访问对象——最值得注意的是用于树视图和组合框的单元格渲染器集。在这一切之下,是 AT-SPI,这是一个协议,供 AT(辅助技术,如屏幕阅读器)使用以消费应用程序提供的数据。通常,AT 将使用像 libatspi 这样的库来处理协议本身。

现有堆栈的主要问题是:

  • 由于 ATK 的存在,导致了大量的间接性;任何新功能或错误修复都需要在 ATK 内部定义,然后实现到 GTK 和 libatspi 中
  • ATK 是在非常不同的环境中编写的,虽然它已经看到了一些弃用,但它在其假设(如全局坐标空间)和设计中显示出它的年代感
  • AT 要求和 GUI 测试要求之间存在一定的重叠,最终导致 API 设计中的摩擦
  • 自从 Sun 可访问性团队解散以来,该堆栈已经失修;目前的大部分工作仍然主要发生在 AT 领域(如 Orca)和 Web 浏览器中
  • 整个堆栈是在 CORBA 盛行时编写的,然后及时移植到 GNOME3 中的 DBus;但是,该协议效率不高,需要大量的往返来移动少量数据,而不是进行批量操作和通知

最后一点也是为什么我们需要一个单独的可访问性总线的原因,以避免垃圾邮件发送会话总线,并在启用可访问性支持后使所有事情变慢。单独的总线意味着我们需要在任何沙箱中戳一个额外的孔,并且仍然让连接到可访问性总线的一切潜在地窥探每个应用程序中发生的事情。

最后,GTK 仅支持 Linux 上的可访问性;它不支持 macOS 或 Windows,这意味着用 GTK 编写并移植到其他平台的应用程序在这些平台上对 AT 不可访问。当我们在 API 中公开 ATK 时,在其他平台上添加对可访问性功能的支持将需要桥接 ATK,从而增加复杂性。

由于我们希望重新设计和更新 GTK4 中的可访问性功能,我们需要了解现有可访问性堆栈用户的需求,以及我们需要定位哪种类型的用例。为此,我们请 Hypra 帮助我们,这是一家致力于开发基于自由开源软件的可访问解决方案的公司。

Hypra 开发人员熟悉 GNOME,并且一直在研究 Linux 可访问性堆栈。他们的客户涵盖了广泛的可访问性用户,因此他们最能描述日常实际使用的 AT 类型。

从工具包到合成器,堆栈的不同层必须提供各种工具和功能;应用程序开发人员还必须能够访问为 AT 提供适当支持所需的工具,因为他们比工具包更清楚他们的应用程序应该是什么样子以及如何运行。

在两天的时间里,我们确定了一个前进的计划:

  • 从堆栈中删除 ATK,让 GTK 直接与 AT-SPI 协议通信;这类似于 Qt 从工具包方面所做的事情,并且可以更轻松地扩展和验证最终的协议更改
  • 清理 AT-SPI 协议本身,在需要时更新它,以便更有效地使用 DBus
  • 删除全局可访问性总线,让 AT 与每个应用程序协商点对点连接
  • 让 AT 请求合成器收集全局状态(如键盘快捷键),而不是与应用程序通信,然后应用程序将不得不请求窗口系统(如果可能),或者在不可能时返回无效数据
  • 将 GUI 测试与可访问性分离
  • 为应用程序开发人员编写小部件和应用程序创作指南,并提供可以作为构建和 CI 流程一部分的验证工具,以检查 UI 元素是否具有正确的可访问描述和链接

有关笔记和路线图的更多信息可在 wiki 上找到,我们已经安排了今年夏天的额外检查点会议。

有很多工作要做,但我们现在对这种重新设计的范围和可交付成果有了更清晰的认识。如果您想帮助事情更快地发生,请随时加入我们的工作;您也可以向 GNOME 基金会捐款。

GTK 团队感谢 GNOME 基金会赞助场地和与会者,并感谢 Hypra 的同事加入黑客马拉松,解释用例和当前的可访问性堆栈状态,以及在开发方面提供帮助。

GTK 3.98

几天前,我发布了 GTK 3.98 的 tarball。这是朝着 GTK 4 迈出的又一步。它比计划晚了一点,并没有完全包含我们希望包含的所有内容,但它更接近我们希望在 GTK 4 中发布的内容。

自 3.96 快照以来已经过去了将近 9 个月,因此有很多新东西值得关注。太多了,无法全部涵盖,但这里有一些亮点:

性能

GL 渲染器已经看到了稳定的优化和性能改进。

在去年的西海岸黑客马拉松之后,通过缓存可见范围的渲染节点,GtkTextView 的滚动性能得到了极大的提高。在同一次黑客马拉松上,文本插入符的闪烁被更改为平滑动画,这与性能无关,但看起来很酷。

自新年以来,重点主要放在提高 CSS 机制的性能上。CSS 值实现已得到优化,以尽可能避免计算值。CSS 查找现在使用布隆过滤器。图标加载的 IO 已移动到线程。

最近的大部分工作是通过性能黑客马拉松后添加的 sysprof 分析支持实现的,并且最近已得到增强以报告更多信息。要使用它,只需在环境中启动一个带有 GTK_TRACE=1 的 GTK 应用程序,然后使用 sysprof 加载生成的 syscap 文件。

DND

DND 重构已完成。DND 的 GTK API 已转换为事件控制器:GtkDragSourceGtkDropTarget。已为 DND 和剪贴板添加了通过文件传输门户进行文件传输的支持。有关数据传输的底层新基础结构已在之前详细介绍。

GDK

GDK 向 Wayland 概念的迁移正在继续。此清理尚未 100% 完成。

子表面已被移除。GDK 现在仅支持顶层和弹出表面。客户端窗口实现也已删除。全局位置和相关的 API(如 gdk_surface_move())不再可用。

抓取不再作为 API 公开。作为替代,可以将弹出表面配置为在外部单击时隐藏。

构建 X11 后端时,XI2 现在是强制性的,并且已删除对 xim 输入法的支持,转而支持 IBus。

Wayland 后端不再依赖 libwayland-cursor 来加载光标主题,而是按需加载单个光标。

GTK 移除

许多类已被明确设置为不可子类化,并且通过使小部件尽可能直接从 GtkWidget 派生,简化了小部件层次结构。

GtkMenu、GtkMenuBar、GtkToolbar 和相关类已被删除。它们正在被 GMenu 和基于弹出窗口的变体所取代。弹出菜单现在可以执行传统的嵌套菜单,并且还可以显示快捷键。

上下文菜单不再使用 ::populate-popup 信号创建,而是使用菜单模型和操作。使用诸如 gtk_widget_class_install_action() 之类的 API 可以更轻松地创建这些操作,以便在 class_init 中创建它们。

GtkGestureMultiPress 已重命名为 GtkGestureClick,以使其更清楚此事件控制器的用途。

GTK 添加

我们不仅仅删除了一些东西。还添加了一些新东西。

为具有自己表面的小部件引入了 GtkNative 接口。它已从 GtkRoot 接口中分离出来,该接口专门用于没有父级的顶层小部件。

添加了基于约束的布局管理器。很高兴看到人们尝试一下。如果您这样做,请给我们反馈。

GtkTextView 和其他文本小部件获得了一个简单的撤消堆栈,可以与 Ctrl-Z 一起使用。

Emoji 选择器小部件已公开。

未来展望

在 3.98 版本之后,我计划进行更频繁的快照,因为剩余的未完成项正在陆续完成。您可能会问,这些未完成项是什么?

以下是我们在 GTK 4 之前仍然希望集成的项目:

– 键盘快捷键的事件控制器
– 可移动的弹出窗口
– 行回收列表和网格视图
– 改进的辅助功能基础设施
– 动画 API

 

 

GTK4 中的数据传输

桌面应用程序之间用户发起的数据传输的传统方法是剪贴板或拖放。GTK+ 从一开始就支持这些方法,但直到 GTK3,我们用于这种数据传输的 API 都是对相应的 X11 API 的粗略复制:选择、属性和原子。这并不奇怪,因为整个 GDK API 都是以 X11 为模型构建的。不幸的是,该实现包含诸如增量传输和字符串格式转换之类的恐怖之处。

对于 GTK4,我们将把这些东西抛在身后,因为我们正在将 GDK 中的东西移动到更接近 Wayland API 的位置。数据传输是迫切需要现代化的领域之一。值得庆幸的是,它目前几乎已经完成,因此值得看看发生了哪些变化,以及未来事情将如何运作。

概念

如果您的应用程序想要发送的数据不是字符串,那么它可能是一些对象,例如 GFile、GdkTexture 或 GdkRGBA。接收端的应用程序可能不使用 GTK 或 GLib,因此不会知道这些类型。即使它知道,也没有办法将对象从一个进程完整地传递到另一个进程。

在核心层面上,数据传输的工作方式是:从源应用程序发送一个文件描述符,目标应用程序从中读取字节流。剪贴板和拖放的协议使用 mime 类型(例如 text/uri-list、image/png 或 application/x-color)来标识字节流的格式。

发送对象涉及协商双方都支持的数据格式,将源侧的对象序列化为该格式的字节流,传输数据,并在目标侧反序列化对象。

本地情况

在继续讨论具体的 API 之前,值得停下来考虑一下另一种常见的数据传输情况:在单个应用程序内部。很常见的情况是,使用相同的剪贴板和拖放机制将数据从应用程序的一侧发送到另一侧。由于在这种情况下我们没有跨越进程边界,因此我们可以避免字节流以及相关的序列化和反序列化开销,只需传递对该对象的引用即可传输数据。

API

我们在上一节中提到的对象由 GType(例如 G_TYPE_FILE 或 GDK_TYPE_TEXTURE)描述,而如前所述,Wayland 和 X11 协议中的数据交换格式由 mime 类型描述。

我们引入的第一个处理这些类型的 API 是 GdkContentFormats 对象。它可以包含格式列表,这些格式可能是 GType 或 mime 类型。我们使用 GdkContentFormats 对象来描述应用程序可以提供数据的格式,以及应用程序可以接收数据的格式。

您可能想知道为什么我们在同一个对象中混合了 GType 和 mime 类型。答案是,我们希望使用相同的 API 处理跨进程和本地情况。虽然我们需要匹配跨进程情况的 mime 类型,但我们需要本地情况的 GType。

转换

我们仍然需要一种方法来关联 GType 和 mime 类型,以便我们可以将它们相互转换。这由 GdkContentSerializer 和 GdkContentDeserializer API 处理。这些本质上是转换函数的注册表:GdkContentSerializer 知道如何将 GType 转换为 mime 类型,而 GdkContentDeserializer 处理相反的方向。

GDK 为常见类型预定义了转换,但该系统可以使用 gdk_content_register_serializer 和 gdk_content_register_deserializer 进行扩展。

内容

现在我们知道如何描述格式并在它们之间进行转换,但是要将所有这些组合在一起,我们仍然需要一个方便的 API,它在一侧获取一个对象,并在另一侧提供一个字节流。为此,我们添加了 GdkContentProvider API。

GdkContentProvider 允许您将一个对象与输入侧的格式描述组合在一起,并在输出侧提供一个异步写入器 API,该 API 可以连接到我们想要通过其发送数据的文件描述符。

典型的 content provider 是这样创建的

gdk_content_provider_new_for_value (gvalue)
gdk_content_provider_new_for_bytes (gbytes, mimetype)

它接受的 GValue 包含对象及其类型的信息,因此如果我们将对象作为 GBytes(本质上只是一段内存)提供,则不需要额外的类型信息,我们需要单独提供类型信息。

剪贴板

GTK3 有一个 GtkClipboard 对象,它为复制/粘贴操作提供实现。在 GTK 中拥有此对象并不理想,因为它需要在 GTK 支持的平台上具有不同的实现。因此,GTK4 将该对象移动到 GDK,并因此将其重命名为 GdkClipboard。它也已移植到上述新的数据传输 API。要在 GTK4 中将数据放入剪贴板,您可以使用其中一个“set” API

gdk_clipboard_set_content()
gdk_clipboard_set_value()
gdk_clipboard_set_text()

最终,所有这些函数都会将 GdkContentProvider 与剪贴板关联起来。

要在 GTK4 中从剪贴板读取数据,您可以使用其中一个异步“read”API

gdk_clipboard_read_async()
gdk_clipboard_read_value_async()
gdk_clipboard_read_text_async()

拖放

GTK3 的拖放 API 涉及监听 GtkWidget 上的多个信号,并为拖动源和目标调用一些特殊的设置函数。它很灵活,但通常被认为是令人困惑的,我们在此处不做详细描述。

在 GTK4 中,拖放 API 已围绕内容提供程序和事件控制器的概念进行了重新组织。要启动拖放操作,您需要创建一个 GtkDragSource 事件控制器,该控制器对拖动手势做出反应(您也可以通过调用 gdk_drag_begin 自己启动“一次性”拖放操作),并为其提供要传输的数据的 GdkContentProvider。要接收拖放操作,您需要创建一个 GtkDropTarget 事件控制器,并在其发出 ::drop-done 信号时调用一个异步读取方法。

gdk_drop_read_value_async()
gdk_drop_read_text_async()

Guadec 的 GTK BoF

像每年一样,我们在塞萨洛尼基的 Guadec 举办了 GTK BoF。今年,我们获得了很好的出席率 — 每个人都对 GTK4 的计划感兴趣。

但由于 Emmanuele 早上很忙,我们从一些其他主题开始了讨论。

GLib

我们从房间里收集了一些关于有用的 GLib 添加的建议。

  • OS 信息的 API(基本上是 /etc/os-release 中的数据)。这似乎没有争议;Robert 将实现它
  • 有序的 map。这在很多地方通过将哈希表与列表或数组组合在一起进行临时实现。似乎大家一致认为,如果有人做提出 API 的工作,在 GLib 中提供它将是值得的

对有序 map 的讨论也涉及到了通用容器接口;Philipp 描述了如何实现这一点,详见此处

仍然在容器主题上,Alex 描述了一个关于传输注释的问题。我们讨论了各种想法,但可能没有完美的解决方案。

Matthias 指出 Pango 中仍然有一些 Unicode 数据。我们简要讨论了如果有一个通用的、可映射的 Unicode 数据二进制格式,以便每个人都可以共享它,这将是多么美好。除此之外,将最后一点数据移动到 GLib 是没有争议的。

暗黑模式

由于“暗黑模式”BoF 加入了我们,我们接下来切换到讨论暗黑模式。在第二天的供应商主题BoF 中对此主题进行了更多讨论;GTK 的讨论侧重于如何实现暗黑模式的技术细节。

有多种选择

  • 向主题索引文件添加额外的元数据以将主题标记为深色
  • 添加一个“暗主题名称”设置,并将深色和浅色主题视为独立的
  • 保留现有约定,将“-dark”附加到主题名称以查找主题的深色变体

保留现有约定的务实解决方案似乎得到了房间的支持。Matthias 开始探索一些应用程序支持 API 此处

GTK

最终,我们转而讨论 GTK4 的状态和进展。高级总结是,GTK4 仍需要完成一系列功能

  • 一个可扩展的列表视图,用于回收行小部件。这包括更广泛地切换到在更多地方使用列表模型。为了使其完整,它还应包括使用该技术的网格视图。Benjamin 正在研究此内容
  • 用于动画的基础设施和API。这类似于 CSS 中动画的工作方式,部分工作不仅是将我们现有的 CSS 动画支持移植过来,还要将堆栈切换动画、揭示器、进度条和微调器移植到新的框架中。Emmanuele 正在进行这项工作。
  • 完成菜单/弹出窗口的重构。有些人试用了新的弹出窗口菜单栏。反馈意见是,我们可能应该恢复嵌套子菜单,至少对于菜单栏是这样,并推进删除菜单,因为菜单的一些特殊之处(例如保持向上的三角形、滚动)很难保持工作(或保持良好工作状态)。Matthias 将在 Guadec 之后重新开始这项工作。
  • 快捷键 - 用事件控制器替换助记符、加速键和按键绑定。有一个相当完整的分支,其中包含代码和 API,一些人已经为此工作;欢迎帮助审查和测试。
  • 新的拖放 API 需要完成。

好消息是,这个列表相当短,并且大多数条目旁边都有名字。坏消息是,每个条目都需要相当多的工作量。因此,在我们合并所有这些条目之前,承诺 4.0 版本的紧凑时间表不是一个好主意。因此,以下是暂定的,但(我们希望)在某种程度上是现实的

  • 今年年底之前再发布一个 GTK 3.9x 快照
  • 大约在 2020 年春季 GNOME 3.36 发布的同时,发布一个功能完整的 3.99 版本
  • 大约在 2020 年秋季 GNOME 3.38 发布的同时,发布 4.0 版本

不可避免地,我们也讨论了其他一些希望拥有的东西。这些都不在 GTK4 的路线图上;但是如果有人出现来完成这项工作,它们就会发生

  • 一个“小部件存储库”或“hig”库,以避免 GTK 中充斥着过于具体或实验性的小部件
  • 一个“UI 设计器”小部件。这也可以放在一个单独的库中
  • 更好地支持拆分标题栏和状态转换

我们还讨论了 GTK 本身之外的一些问题,这些问题会阻止应用程序移植到 GTK4。这包括常用的库,例如 GtkSourceView、vte 和 webkitgtk,它们都需要 GTK4 移植,然后依赖于它们的应用程序才能被移植。其中一些工作已经在进行中;但欢迎在这方面提供任何帮助!

GTK4 移植的另一个潜在障碍是平台支持。GL 渲染器在 Linux 上运行良好;Vulkan 渲染器需要一些修复。在 Windows 上,我们目前使用 cairo 回退,这对于 4.0 可能足够好。或者,我们可以合并现有工作以使用带有 ANGLE 的 GL 渲染器。在 OS X 上情况不太乐观,我们没有可用的后端;如果您想在这方面帮助我们,第一步仍然是使 GDK 后端适应 GDK 中的更改。

黑客时间

下午,房间从讨论转向了黑客,人们的笔记本电脑上可以看到各种与 GTK 相关的工作正在进行中:加快 GtkBuilder 模板加载的工作、嵌套弹出菜单、一个半成品的 GtkSourceView 移植。

您有望很快在 GTK master 中看到这些(和其他)内容。

约束布局

什么是约束

最基本的,约束是两个值之间的关系。这种关系
可以描述为一个线性方程

target.attribute = source.attribute × multiplier + constant

例如,这个

可以描述为

blue.start = red.end × 1.0 + 8.0

  • 约束将要设置的目标“蓝色”的属性“start”;这是等式的左侧
  • 等式左右两侧之间的关系,在这种情况下是相等;关系也可以是大于或等于
    小于或等于
  • 约束将要读取的“红色”的属性“end”;这是等式的右侧
  • 应用于源的属性的乘数“1.0”
  • 常量“8.0”,一个添加到属性的偏移量

约束布局是一系列像上面这样的方程式,描述了 UI 各个部分之间的所有关系。

重要的是要注意,关系不是赋值,而是相等(或不等):等式的两边将以满足约束的方式求解;这意味着可以重新排列约束列表;例如,上面的示例可以重写为

red.end = blue.start × 1.0 - 8.0

一般来说,为了方便和可读性,您应该按照阅读顺序安排约束,从前导边缘到尾随边缘,从上到下。您还应该支持整数作为乘数,并支持正数作为常量。

求解布局

线性方程组可以有一个解、多个解,甚至根本没有解。此外,出于性能原因,您并不真的希望每次都重新计算所有解。

早在 1998 年,Greg J. Badros 和 Alan Borning 就发布了用于求解线性算术约束的 Cassowary 算法,以及其在 C++、Smalltalk 和 Java 中的实现。Cassowary 算法尝试通过找到其最优解来求解线性方程组;此外,它是增量式求解的,这使其对于用户界面非常有用。

在过去的十年中,各种平台和工具包开始提供基于约束的布局管理器,其中大多数使用了 Cassowary 算法。第一个是 Apple 的 AutoLayout,于 2011 年发布;2016 年,Google 在 Android SDK 中添加了 ConstraintLayout。

2016 年,Endless 在一个名为 Emeus 的库中为 GTK 3 实现了约束布局。从那项工作开始,GTK 4 现在有一个 GtkConstraintLayout 布局管理器可供应用程序和小部件开发人员使用。

实现约束求解器的机制对于 GTK 是私有的,但公共 API 提供了一个可以分配给您的 GtkWidget 类的布局管理器,以及一个不可变的 GtkConstraint 对象,该对象描述了您希望添加到布局的每个约束,将两个小部件绑定在一起。

引导约束

约束使用小部件作为源和目标,但在某些情况下,您希望将小部件属性绑定到屏幕上不实际绘制任何内容的矩形区域。您可以向布局添加一个虚拟小部件,然后将其不透明度设置为 0 以避免渲染它,但这会给场景增加不必要的开销。相反,GTK 提供了 GtkConstraintGuide,该对象的唯一工作是为布局做出贡献

An example of the guide UI element

在上面的示例中,只有标记为“子项 1”和“子项 2”的小部件将可见,而引导将是一个空的空间。

引导具有最小、自然(或首选)和最大尺寸。所有这些都是约束,这意味着您不仅可以将引导用作对齐的助手,还可以将它们用作布局中可以增长和收缩的灵活空间。

在布局中描述约束

可以以编程方式添加约束,但像 GTK 中的许多其他东西一样,为了方便起见,它们也可以在 GtkBuilder UI 文件中描述。如果将 GtkConstraintLayout 添加到 UI 文件中,则可以在特殊的“<constraints>”元素内列出约束和引导

  <object class="GtkConstraintLayout">
    <constraints>
      <constraint target="button1" target-attribute="width"
                     relation="eq"
                     source="button2" source-attribute="width" />
      <constraint target="button2" target-attribute="start"
                     relation="eq"
                     source="button1" source-attribute="end"
                     constant="12" />
      <constraint target="button1" target-attribute="start"
                     relation="eq"
                     source="super" source-attribute="start"
                     constant="12" />
      <constraint target="button2" target-attribute="end"
                     relation="eq"
                     source="super" source-attribute="end"
                     constant="-12"/>
    </constraints>
  </object>

您还可以使用“<guide>”自定义元素描述引导

  <constraints>
    <guide min-width="100" max-width="500" />
  </constraints>

可视化格式语言

除了 XML 之外,约束还可以使用称为“可视化格式语言”的紧凑语法来描述。VFL 描述是面向行和列的:您使用与您正在实现的布局在视觉上相似的行来描述布局中的每一行和每一列,例如

|-[findButton]-[findEntry(<=250)]-[findNext][findPrev]-|

描述一个水平布局,其中 findButton 小部件与布局管理器的前导边缘之间有一些默认空间,然后是相同的默认空间;然后是 findEntry 小部件,该小部件的宽度最多为 250 像素。在 findEntry 小部件之后,我们再次有一些默认空间,然后是两个小部件 findNextfindPrev,它们彼此紧挨着;最后,这两个小部件与布局管理器的尾随边缘之间有默认的空间量。

使用 VFL 表示法,GtkConstraintLayout 将创建所有必需的约束,而无需手动描述所有约束。

重要的是要注意,VFL 不能描述所有可能的约束;在某些情况下,您需要使用 GtkConstraint 的 API 创建它们。

约束布局的局限性

约束布局非常灵活,因为它们可以实现任何布局策略。这种灵活性是有代价的

  • 您的布局可能有太多解,这会使其含糊不清且不稳定;如果您的布局非常复杂,这可能会有问题
  • 您的布局可能没有任何解。这通常发生在您没有使用足够约束时;一个经验法则是每个目标每个维度使用至少两个约束,因为所有小部件都应该具有定义的位置和大小
  • 相同的布局可以通过不同的约束序列来描述;在某些情况下,几乎不可能说哪种方法更好,这意味着你必须进行实验,尤其是在涉及到动态添加或删除 UI 元素,或者允许用户交互(如拖动 UI 元素)的布局时。

此外,在更大规模的情况下,本地的、临时的布局管理器可能比基于约束的管理器性能更高;如果你有一个可以增长到未知行数的列表框,除非你预先测量了性能影响,否则你不应该用约束布局替换它。

演示

当然,由于我们添加了这个新的 API,我们还在 GTK 演示应用程序中添加了一些演示。

A constraints demo
约束演示窗口,作为 GTK 演示应用程序的一部分。

以及一个完整的约束编辑器演示

The GTK constraints editor demo
GTK 约束编辑器演示应用程序的屏幕截图,左侧边栏显示 UI 元素、辅助线和约束列表,右侧窗口显示结果

更多信息