GTK4 中的数据传输

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

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

概念

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

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

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

本地情况

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

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,该 API 在一侧获取对象,并在另一侧提供字节流。为此,我们添加了 GdkContentProvider API。

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

典型的 content providers 是这样创建的

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 添加功能的建议。

  • 用于操作系统信息的 API(基本上是 /etc/os-release 中的数据)。这似乎没有争议;Robert 将要实现它
  • 一个有序的 map。这在许多地方通过将哈希表与列表或数组相结合来临时实现。似乎有人认为,如果有人进行 API 提议工作,那么在 GLib 中提供它将是值得的

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

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

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

暗黑模式

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

有多种选择

  • 向主题索引文件添加额外的元数据,以将主题标记为暗色
  • 添加“dark-theme-name”设置,并将暗色和浅色主题视为独立的
  • 保留将“-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 版本
  • 一个 4.0 版本大约在 2020 年秋季与 GNOME 3.38 同时发布

不可避免地,我们也讨论了其他一些很棒的想法。这些都不在 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 主分支中看到这些(以及其他)。

约束布局

什么是约束

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

target.attribute = source.attribute × multiplier + constant

例如,这个

可以描述为

blue.start = red.end × 1.0 + 8.0

或者

  • 将由约束设置的目标“蓝色”的属性“开始”;这是等式的左侧
  • 等式左侧和右侧之间的关系,在本例中为相等;关系也可以是大于或等于
    小于或等于
  • 将由约束读取的“红色”的属性“结束”;这是等式的右侧
  • 应用于源属性的乘数“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 算法。第一个是 2011 年的 Apple 的 AutoLayout;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 元素、引导和约束的列表,右侧显示结果

更多信息

GTK 3.96.0

本周,我们发布了 GTK 3.96.0。同样,距离上次发布已经有一段时间了,所以值得总结一下此版本中的新功能。这里的内容实在太多了,无法全部涵盖,因此本文仅重点介绍最重要的更改。

此版本是我们迈向 GTK 4 的另一个里程碑。虽然仍有一些未完成的事情,但此版本更接近我们希望通过 GTK 4 实现的目标。

GSK

GSK 修复了许多错误,并新增了许多测试,这些测试使用新的调试工具 gtk4-node-editor 更容易进行。它可以加载和显示序列化的渲染节点树,例如从 GTK 检查器保存的树,并比较不同渲染器的输出。

3D 变换支持已提升到我们可以进行如下面的立方体旋转动画过渡的程度。

GDK

向 Wayland 风格的 API 发展的趋势仍在继续,更多仅限 X11 的 API 被移至 X11 后端或直接删除。子曲面和全局坐标的使用已大大减少,但这项工作尚未完成。

拖放的重构也已继续,引入了 GdkDragGdkDrop 对象。此重构的 GTK 部分仍未完成。

事件已被简化,现在仅用于输入。其他事件已被 GdkSurface 上的信号和属性替换。具体而言,公开事件已被 ::render 信号替换,配置事件已被 ::size-changed 信号替换。映射事件已被 :mapped 属性替换,gdk_event_handler_set() 已被 ::event 信号替换。

Wayland 后端已获得对 GtkSettings 的设置门户支持,并使用 text-input-unstable-v3 协议进行输入法支持。

GTK

窗口小部件

自定义窗口小部件的一个重大更改是引入了 GtkLayoutManager,这是一个新的委托对象,它接管大小分配。布局管理器可以选择使用布局子项来保存布局属性。这取代了 GTK 容器(如 GtkBoxGtkGrid)中与布局相关的子属性。

有许多布局管理器可用

  • GtkBinLayout,用于简单的单子容器
  • GtkBoxLayout,用于线性排列的子项
  • GtkGridLayout,用于在网格中排列的子项
  • GtkFixedLayout,用于自由定位和变换的子项
  • GtkCustomLayout,作为一种将传统 measuresize_allocate 虚函数转换为布局管理器的快捷方法

未来将出现更多布局管理器实现。最重要的是,基于约束的布局管理器正在开发中。

GtkAssistantGtkStackGtkNotebook 公开
其子项的可访问页面对象。页面对象
也通过列表模型公开。这些容器的非布局相关子属性已转换为这些页面对象上的常规属性。

由于所有现有的子属性都已转换为常规属性、移动到布局属性或移动到此类页面对象,因此已从 GtkContainer 中删除了对子属性的支持。

核心 GtkEntry 功能已移至新的 GtkText 窗口小部件,该窗口小部件还实现了扩展的 GtkEditable 接口。GTK 中所有现有的条目子类都已转换为包装 GtkText 窗口小部件的 GtkEditable 实现。这还包括一个新的 GtkPasswordEntry

其他更改

GTK 窗口小部件可以使用投影线性
变换来变换其子项。此功能在 CSS 中可用,并且
作为 GskTransform 参数传递给 gtk_widget_allocateGtkFixed
第一个公开此功能的容器。有关更多示例,
请参阅 GtkRevealer 的摆动过渡、旋转过渡
GtkStack 或 gtk4-demo 中的固定布局示例。

引入了许多列表模型,用于内部使用
和作为公共 API:GtkMapListModelGtkSliceListModelGtkSortListModelGtkSelectionModelGtkSingleSelection。当我们引入基于列表模型的 GtkListView 时,这些将被更广泛地使用。

GtkBuilder 可以内联指定对象值属性,而不是通过 ID 引用它们,并且 gtk4-builder-tool 的 simplify 命令已获得一个选项,可以自动将 GTK 3 UI 定义文件转换为 GTK 4。

即将推出

有关 GTK 4 仍在开发中的更多信息,请在 Discourse、IRC 上找到我们,或查看此处

GTK 4 中的布局管理器

自 GTK 最初开始,容器和布局策略一直是其设计的核心。如果您希望窗口小部件根据特定策略布局其子项,则必须实现 GtkContainer 以处理子窗口小部件的添加、删除和迭代,然后必须实现 GtkWidget 中的大小协商虚函数以测量、定位和调整每个子项的大小。

GTK 4 开发周期的一个主要主题是将更多功能委托给辅助对象,而不是将其编码到 GTK 提供的基类中。例如,我们将事件处理从 GtkWidget 描述的信号处理程序移至事件控制器,并将渲染委托给 GtkSnapshot 对象。该方向上的另一步是将布局机制从 GtkWidget 本身分离到一个辅助类型 GtkLayoutManager

布局管理器

布局管理器是负责测量和调整窗口小部件及其子项大小的对象。每个 GtkWidget 都拥有一个 GtkLayoutManager,并使用它来代替 measure()allocate() 虚函数,这些虚函数即将被删除。更改的要点:不是子类化 GtkWidget 来实现其布局策略,而是子类化 GtkLayoutManager,然后将布局管理器分配给窗口小部件。

就像旧的 GtkWidget 代码一样,您需要重写一个虚函数来测量布局,称为 measure(),它取代了 GTK 3 的 get_preferred_* 系列虚函数

static void
layout_measure (GtkLayoutManager *layout_manager,
                GtkWidget        *widget,
                GtkOrientation    orientation,
                int               for_size,
                int              *minimum,
                int              *natural,
                int              *minimum_baseline,
                int              *natural_baseline)

测量后,您需要将大小分配给布局;这发生在 allocate() 虚函数中,该函数取代了之前 GTK 主要版本的古老的 size_allocate() 虚函数

static void
layout_allocate (GtkLayoutManager *layout_manager,
                 GtkWidget        *widget,
                 int               width,
                 int               height,
                 int               baseline)

在更深奥的一方面,您还可以重写 get_request_mode() 虚函数,该函数允许您声明布局管理器是否请求固定大小,或者其大小之一是否取决于相反的大小,例如高度对宽度或宽度对高度

static GtkSizeRequestMode
layout_get_request_mode (GtkLayoutManager *layout_manager,
                         GtkWidget        *widget)

您可能会注意到,每个虚函数都会传递布局管理器实例,以及使用布局管理器的窗口小部件。

当然,这对 GTK 窗口小部件的工作方式的各个方面都有更大的影响,最明显的是,现在可以将所有布局代码的复杂性限制在自己的对象中,通常不可派生,而窗口小部件可以保持可派生并变得更简单。

这项工作的另一个特点是,如果您想更改容器的布局策略,则可以在运行时更改布局管理器;您还可以拥有每个窗口小部件的布局策略,而无需增加窗口小部件代码的复杂性。

最后,布局管理器允许我们摆脱 GTK 的特殊情况之一,即:容器子属性。

子属性

GtkContainer 的内部,存在一个本质上是 GObject 属性相关代码的副本,其唯一的工作是为从 GtkContainer 派生的类型实现“子”属性。这些容器/子属性仅在子项父级化为特定容器类时才存在,并且用于多种原因,但通常用于控制布局选项,如框和类框容器中的打包方向;GtkFixed 中的固定位置;或笔记本选项卡小部件的展开/填充规则。

子属性很难使用,因为它们需要临时的 API,而不是常用的 GObject API,因此需要在 GtkBuilder、gtk-doc 和语言绑定中进行特殊处理。子属性也附加到容器的实际直接子项,因此如果一个部件插入一个子项——例如,像 GtkScrolledWindowGtkListBox 这样做——那么您需要保留对子项的引用,以便更改应用于您自己的部件的布局。

在 GTK 的主分支中,我们已经删除了大部分子属性——要么通过简单地删除它们(当有实际的部件 API 实现相同的功能时),要么通过创建辅助的 GObject 类型并将子属性移动到这些类型。最终目标是在 GTK 4 发布时删除所有子属性以及 GtkContainer 中的相关 API。对于与布局相关的属性,GtkLayoutManager 提供了自己的 API,以便在将子项添加到使用布局管理器的部件或从该部件中删除子项时,分别自动创建和销毁对象。创建的对象是可自省的,并且在文档或绑定方面不需要特殊处理。

您首先从 GtkLayoutChild 类派生您自己的类型,并像为任何其他 GObject 类型一样添加属性。然后,您重写 GtkLayoutManagercreate_layout_child() 虚函数

static GtkLayoutChild *
create_layout_child (GtkLayoutManager *manager,
                     GtkWidget *container,
                     GtkWidget *child)
{
  // The simplest implementation
  return g_object_new (your_layout_child_get_type (),
                       "layout-manager", manager,
                       "child-widget", child,
                       "some-property", some_property_initial_state,
                       NULL);
}

之后,只要一个部件仍然是使用布局管理器的容器的子项,您就可以访问您的布局子对象;如果子项从其父项中删除,或者容器更改了布局管理器,则布局子项会自动收集。

新的布局管理器

当然,仅在 GTK 中拥有 GtkLayoutManager 类对我们没有任何好处。GTK 4 为应用程序和部件开发人员引入了各种布局管理器

  • GtkBinLayout 实现了 GtkBin 的布局策略,并增加了一个特性,即它支持多个子项堆叠在一起,类似于 GtkOverlay 的工作方式。您可以使用每个部件的对齐和扩展属性来控制它们在分配区域内的位置,并且 GtkBinLayout 将始终请求分配其最大子项所需的尽可能多的空间。
  • GtkBoxLayoutGtkBox 实现的布局策略的直接移植;GtkBox 本身已被移植为在内部使用 GtkBoxLayout
  • GtkFixedLayoutGtkFixedGtkLayout 的固定布局定位策略的移植,并增加了让您定义通用转换而不是每个子项的纯 2D 平移的功能;GtkFixed 已被修改为使用 GtkFixedLayout 并使用 2D 平移——并且 GtkLayout 已被合并到 GtkFixed 中,因为它唯一突出的特点是实现了 GtkScrollable 接口。
  • GtkCustomLayout 是一个方便的布局管理器,它采用曾经是 GtkWidget 虚函数重写的函数,并且主要用作在将现有部件移植到布局管理器未来时的桥梁。

我们仍在实现 GtkGridLayout 并使 GtkGrid 在内部使用它,遵循与 GtkBoxLayoutGtkBox 相同的模式。GTK 内的其他部件将在后续过程中获得它们自己的布局管理器,但在此期间,它们可以使用 GtkCustomLayout

最后一步是实现一个基于约束的布局管理器,这将使我们能够创建复杂的、响应式的用户界面,而无需将部件打包到嵌套的层次结构中。基于约束的布局值得单独写一篇博客文章,敬请期待!

GTK 4 中的条目

最近在 GTK 主分支中完成的较大重构之一是重做条目层次结构。这篇文章总结了发生了哪些变化,以及为什么我们认为事情会变得更好。

GTK 3 中的条目

让我们先看看 GTK 3 中的情况。

GtkEntry 是这里的基础类。它实现了 GtkEditable 接口。GtkSpinButtonGtkEntry 的子类。多年来,添加了更多内容。GtkEntry 获得了对条目完成、嵌入图标和显示进度的支持。我们添加了另一个子类 GtkSearchEntry

这种方法的一些问题立刻显现出来。gtkentry.c 超过 11100 行代码。不仅很难在这个庞大的代码库中添加更多功能,而且很难对其进行子类化——这是创建您自己的条目的唯一方法,因为所有单行文本编辑功能都在 GtkEntry 内部。

GtkEditable 接口非常古老——它在 GTK 2 之前就已经存在了。不幸的是,它作为一个接口并没有真正成功——GtkEntry 是唯一实现,并且它以一种令人困惑的方式在内部使用接口函数。

GTK 4 中的条目

现在让我们看看 GTK 主分支中的情况。

我们做的第一件事是将 GtkEntry 的核心文本编辑功能移动到一个名为 GtkText 的新部件中。这基本上是一个减去所有额外功能(如图标、完成和进度)的条目。

通过向 GtkEditable 接口添加一些更常见的功能(如 width-chars 和 max-width-chars),并使 GtkText 实现它,我们使 GtkEditable 接口更有用。我们还添加了辅助 API,以方便将 GtkEditable 实现委托给另一个对象。

现在,‘复杂’的条目部件(GtkEntryGtkSpinButtonGtkSearchEntry)都是组合部件,其中包含一个 GtkText 子项,并将其 GtkEditable 实现委托给这个子项。

最后,我们添加了一个新的 GtkPasswordEntry 部件,它接管了 GtkEntry 过去拥有的相应功能,例如显示大写锁定警告

或让用户查看内容。

为什么这样更好?

这种重构的主要目标之一是使在 GTK 之外创建自定义条目部件更容易。

过去,这需要对 GtkEntry 进行子类化,并浏览一个复杂的 vfuncs 迷宫才能进行重写。现在,您只需添加一个 GtkText 部件,将其 GtkEditable 实现委托给它,就可以轻松地获得一个功能正常的条目部件。

并且您可以灵活地在 GtkText 组件周围添加花哨的东西。例如,我们已经在 gtk4-demo 中添加了一个标记条目,现在可以很容易地在 GTK 之外实现。

从 GTK 3 移植时会影响您吗?

在将代码移植到这种新的条目样式时,需要注意一些可能的陷阱。

GtkSearchEntryGtkSpinButton 不再派生自 GtkEntry。如果您看到关于从这些类之一强制转换为 GtkEntry 的运行时警告,您很可能需要切换到使用 GtkEditable API。

GtkEntry 和其他复杂的条目部件不再可聚焦——焦点会转移到包含的 GtkText 上。但是 gtk_widget_grab_focus() 仍然有效,并将焦点移动到正确的位置。您不太可能受到此影响。

大写锁定警告功能已从 GtkEntry 中删除。如果您使用 visibility==FALSEGtkEntry 来显示密码,您应该只切换到 GtkPasswordEntry

如果您使用 GtkEntry 进行基本的编辑功能,并且不需要任何额外的条目功能,您应该考虑使用 GtkText 代替。

GTK+ 3.94

今天,我们发布了 GTK+ 3.94.0。同样,自从上次发布以来已经有一段时间了,因此值得总结一下此版本中的新功能。这里的内容太多了,无法全部涵盖,因此本文仅重点介绍最重要的更改。

此版本是我们走向 GTK+ 4 的另一个里程碑。虽然还有一些未完成的事情,但此版本更接近我们希望通过 GTK+ 4 实现的目标。

GSK

Broadway 后端现在有一个 GskRenderer,因此 Broadway 的未来看起来好多了。

我们引入了一种新的渲染节点类型 GskOffsetNode,它是一个简化的 GskTransformNode,它负责在我们上下移动渲染节点树时转换内容。通过此更改,我们现在能够为多个帧缓存部件的渲染节点,并在必要时重新定位它们。

我们还引入了 GskDebugNodes,它接管了节点名称,并让我们简化了用于创建渲染节点的 GTK+ api。

当回退到 cairo 进行渲染时,我们现在使用记录表面而不是图像表面,因此我们可以以不同的比例重放渲染。

一个重要的新操作是 gsk_render_node_diff,用于比较两个渲染节点树(有关此内容的更多信息,请参见下文)。

GDK

遵循使 GDK api 与 Wayland 而不是 X 对齐的总体趋势,GdkWindow 被重命名为 GdkSurface

GdkTexture api 已经过改进,具有新的 GdkMemoryTextureGdkGLTexture 子类,并且引入了一个强大的新抽象 GdkPaintable

GdkPaintable 表示一个可以在任何大小的任何地方绘制的对象,而无需任何类型的布局。这受到其他地方类似概念的启发,例如 ClutterContent、HTML/CSS Paint Sources 或 SVG Paint Servers。为了展示这个概念的强大功能,在 gtk4-demo 中添加了一些新的演示。

DND 代码继续进行重大重构。它现在使用 3.93 版本中引入的用于剪贴板处理的相同内容提供程序基础结构,并且对于 DND 操作的源和目标端都有单独的对象。这里还会进行更多更改。

GTK

小部件

GTK+ 已获得显示视频的支持,包含 GtkVideo 和 GtkMediaControls 小部件,并且还有一个新的 GtkPicture 小部件,用于将图像查看从 GtkImage 中分离出来(GtkImage 实际上是关于图标的)。

GtkFontChooser 允许调整 OpenType 功能和字体变体,并且用于彩色 Emoji 输入的 Ctrl-Shift-e 已被完成取代,可以通过 GtkEntry::enable-emoji-completion 属性启用。

输入

GtkWidget 中的特定于事件的信号继续消失。目前只剩下 ::event,但它也会消失。相反,我们正在使用事件控制器,并且添加了几个新的事件控制器来涵盖所有需要的事件

  • GtkEventControllerMotion
  • GtkEventControllerKey
  • GtkGestureStylus

为了使过渡更容易,现在可以在 ui 文件中创建事件控制器。

Wayland 有自己的平台输入法,基于 Wayland 文本协议。

绘图

::draw 信号已被删除,所有小部件都必须实现 ::snapshot。它们现在可以创建自己的 GtkSnapshot 实例进行中间渲染。裁剪不再在 GTK+ 级别应用 – 小部件可以自由地在其分配区域之外绘制,如果需要的话。

小部件失效已更改,它现在通过丢弃失效小部件的缓存渲染节点并重新创建渲染节点树的缺失部分来工作。

为了找到需要重绘的区域,GTK+ 对上一帧和当前帧的渲染节点树进行差异比较,并应用一些启发式方法来防止矩形数量增长过大。

GTK+ 检查器允许您跟踪失效,您可以在此处看到其运行情况

其他更改

GTK+ 不再支持通用可加载模块。输入法、打印后端和媒体后端已转换为 GIOModules 和扩展点。

平台 im 模块(即 Windows、Wayland、Broadway im 上下文)始终包含在内,并且默认情况下会在其平台上启用。

GDK 中的 Vulkan 支持现在可以使用 GDK_VULKAN_DEVICE 环境变量指定的特定设备。使用 GDK_VULKAN_DEVICE=list 查看所有可用设备。

试用一下

使用 GTK+ 3.94.0,应该可以开始移植应用程序了。文档包含一个初始移植指南。

纹理和可绘制对象

在 GTK4 中,我们一直在尝试为图像数据找到更好的解决方案。在 GTK3 中,我们用于此目的的对象是pixbufsCairo 曲面。但它们不再适合,所以现在我们有了 GdkTextureGdkPaintable

GdkTexture

GdkTextureGdkPixbuf 的替代品。为什么它更好?
首先,它简单得多。API 如下所示

int gdk_texture_get_width (GdkTexture *texture);
int gdk_texture_get_height (GdkTexture *texture);

void gdk_texture_download (GdkTexture *texture,
                           guchar     *data,
                           gsize       stride);

因此,它是一个 2D 像素数组,如果需要,您可以下载像素。它也保证是不可变的,因此像素永远不会改变。存在许多构造函数来从文件、资源、数据或 pixbuf 创建纹理。

但是纹理和 pixbuf 之间的最大区别在于它们不暴露它们用来存储像素的内存。事实上,在调用 gdk_texture_download() 之前,该数据甚至不需要存在。
这用于 GL 纹理。GtkGLArea 小部件例如使用此方法来传递数据。GStreamer 也希望以 GL 纹理的形式传递视频。

GdkPaintable

但有时,您拥有的东西比不可变的一堆像素更复杂。例如,您可能有一个动画 GIF 或一个可缩放的 SVG。这就是 GdkPaintable 的用武之地。
抽象地说,GdkPaintable 是一个接口,用于知道如何在任何大小下渲染自身的对象。受CSS 图像的启发,它们可以选择提供 GTK 小部件可用来放置它们的固有尺寸信息。
因此,GdkPaintable 接口的核心是使可绘制对象自行渲染的函数以及提供尺寸信息的 3 个函数

void gdk_paintable_snapshot (GdkPaintable *paintable,
                             GdkSnapshot  *snapshot,
                             double        width,
                             double        height);

int gdk_paintable_get_intrinsic_width (GdkPaintable *paintable);
int gdk_paintable_get_intrinsic_height (GdkPaintable *paintable);
double gdk_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable);

除此之外,当可绘制对象的内容或大小发生变化时,它可以发出“invalidate-contents”和“invalidate-size”信号。

为了使这更具体,让我们以可缩放的 SVG 为例:可绘制对象的实现将返回固有尺寸(这些尺寸函数的返回值 0 可以实现这一点),并且无论何时绘制,它都会在给定大小下以像素精确地绘制自身。
或者以动画 GIF 为例:它会将其像素大小作为固有大小提供,并将动画的当前帧缩放到给定大小。并且每当应该显示动画的下一帧时,它都会发出“invalidate-size”信号。
最后但并非最不重要的是,GdkTexture 实现了此接口。

我们目前正在更改所有在 GTK3 中接受 GdkPixbuf 的代码,以现在接受 GdkPaintableGtkImage 小部件当然已经更改,拖放图标或 GtkAboutDialog 也已更改。存在实验性补丁,允许应用程序向 GTK CSS 引擎提供可绘制对象。

如果您现在将所有这些关于 GStreamer 可能提供由 GL 图像支持的纹理并创建执行动画的可绘制对象,然后可以将其上传到 CSS 的信息放在一起,您可能会看到它的走向……

GTK+ 4 中的输入法

GTK 对可加载模块的支持可以追溯到最早的时候,这就是为什么 GTK 有大量代码来处理 GTypeModules 和搜索路径等。很久以后,Alex 为 GVfs 重新审视了这个主题,并提出了扩展点和 GIO 模块的概念,它们实现了这些扩展点。这是一个更好的框架,GTK 4 是我们切换到使用它的绝佳机会。

GTK+ 4 中的更改

因此,我最近花了一些时间在 GTK 中的模块支持上。这里的主要变化如下

  • 我们不再支持通用可加载模块。此功能的少数剩余用户之一是 libcanberra,我们将考虑直接在 GTK+ 中实现“事件声音”功能,而不是依赖模块来实现。如果您依赖加载 GTK+ 模块,请来与我们讨论实现您正在执行的操作的其他方法。
  • 打印后端现在使用名为“gtk-print-backend”的扩展点定义,该扩展点需要 GtkPrintBackend 类型。现有的打印后端已转换为实现此扩展点的 GIO 模块。由于我们从未支持树外的打印后端,因此这不应影响其他任何人。
  • 输入法也使用名为“gtk-im-module”的扩展点定义,该扩展点需要 GtkIMContext 类型。我们已经删除了所有非平台 IM 模块,并将平台 IM 模块移入 GTK+ 本身,同时还实现了扩展点。

调整现有的输入法

由于我们仍然支持树外 IM 模块,我想利用本文的剩余部分来简要概述 GTK+ 4 的树外 IM 模块的外观。

将基于传统 GTypeModule 的 IM 模块转换为新的扩展点需要几个步骤。下面的示例代码取自 Broadway 输入法。

使用 G_DEFINE_DYNAMIC_TYPE

我们将从模块加载一个类型,G_DEFINE_DYNAMIC_TYPE 是定义此类类型的正确方法

G_DEFINE_DYNAMIC_TYPE (GtkIMContextBroadway,
                       gtk_im_context_broadway,
                       GTK_TYPE_IM_CONTEXT)

请注意,此宏定义了一个 gtk_im_context_broadway_register_type() 函数,我们将在下一步中使用它。

请注意,动态类型除了更常见的 class_init 之外,还应该具有 class_finalize 函数,该函数可以是微不足道的

static void
gtk_im_context_broadway_class_finalize
               (GtkIMContextBroadwayClass *class)
{
}

实现 GIO 模块 API

为了可以作为 GIOModule 使用,模块必须实现三个函数:g_io_module_load()、g_io_module_unload() 和 g_io_module_query()(严格来说,最后一个是可选的,但我们仍然会在这里实现它)。

void
g_io_module_load (GIOModule *module)
{
  g_type_module_use (G_TYPE_MODULE (module));
  gtk_im_context_broadway_register_type  
                        (G_TYPE_MODULE (module));
  g_io_extension_point_implement
             (GTK_IM_MODULE_EXTENSION_POINT_NAME,
              GTK_TYPE_IM_CONTEXT_BROADWAY,
              "broadway",
              10);
 }
void
g_io_module_unload (GIOModule *module)
{
}
char **
g_io_module_query (void)
{
  char *eps[] = {
    GTK_IM_MODULE_EXTENSION_POINT_NAME,
    NULL
  };
  return g_strdupv (eps);
}

正确安装您的模块

GTK+ 仍然会在 $LIBDIR/gtk-4.0/immodules/ 中查找要加载的输入法,但 GIO 仅查看名称以“lib”开头的共享对象,因此请确保遵循该约定。

调试

就这样!

现在 GTK+ 4 应该加载您的输入法,如果您使用 GTK_DEBUG=modules 运行 GTK+ 4 应用程序,您应该会在调试输出中看到您的模块。

 

GTK+ 3.92

昨天,我们发布了 GTK+ 3.92.1,重庆市。由于距离上次 3.91 版本发布已经有一段时间了,这里简要介绍一下主要变化。

此版本是我们迈向 GTK+ 4 的又一个里程碑。虽然还有很多需要做,但此版本允许我们初步了解我们希望在 GTK+ 4 中实现的一些目标。

GSK

自上次发布以来,大部分工作都集中在 GSK 上。 Vulkan 渲染器现在已接近完成,至少在避免 cairo 回退方面是如此。唯一缺少的部分是模糊阴影(诚然,这是一个重要的部分)。

自 3.91.2 版本以来的一大进步是,我们不再对所有文本使用 cairo 回退。相反,文本(在标签和条目中,遗憾的是尚未在文本视图中)会被转换为文本节点。每个文本节点都包含一个 PangoGlyphString 和一个 PangoFont。Vulkan 渲染器使用字形缓存来避免为每一帧重新渲染字形。

Vulkan 渲染器的内部逻辑已得到改进,使用纹理而不是 cairo 表面作为中间结果,从而避免了更多的 cairo 回退。

Vulkan 渲染器中获得支持的其他节点类型包括模糊、重复节点、混合模式和交叉淡入淡出。在某些情况下,我们使用的着色器是非常原始的实现。欢迎大家帮助改进它们!

作为我们可以使用渲染节点的一个首例,我们为 GtkOverlay 实现了模糊背景功能。其工作原理是将叠加层的“主子项”捕获为一个渲染节点,然后多次重用它,使用正确的剪切,有时还使用模糊节点。

检查器

为了帮助您探索 GSK,检查器现在会显示 Vulkan 信息,记录器会显示有关渲染节点的更多信息。

输入

在输入方面,事件已获得访问器,我们不再直接访问它们的字段。这是一个中间步骤,清理事件仍然是一项正在进行的工作。我们已将传统的用于事件的窗口小部件信号(例如 ::key-press-event)移动到一个事件控制器,并且 GTK+ 内的大多数窗口小部件已完全停止使用它们。

构建系统

我们已切换为专门使用 Meson 来构建 GTK+,并且 3.92.1 版本是第一个使用 Meson 的 dist 支持完成的版本。为了发布该版本,我们还必须将文档、测试套件和已安装的测试移植到使用 Meson。

仍然存在一些不足之处(我们并非 100% 正确地获取所有依赖项),但总的来说,Meson 对我们来说效果很好。

其余部分

当然,每个人都喜欢 Emoji,并且 GTK+ 3.22 中引入的相同颜色 Emoji 支持也在此版本中可用。除此之外,CSS 中的字体支持有所改进,支持 CSS3 font-variant 属性。

当然,这依赖于具有相应功能的字体。

试用一下

使用 GTK+ 3.92.1,应该很容易自己尝试这些功能。

如果您一直想参与 GTK+ 开发,但从未找到合适的机会,那么现在是参与的好时机!