GTK 3.99.2

GTK 3.99.2 版本延续了 3.99.1 的主题:API 清理、新的和完善的演示、更好的文档。您可以在这里查看详细信息。

关于文档主题的一个小提示是,我们依赖于一些未发布的 gtk-doc 功能。因此,我们现在在 gtk 发布 tarball 中包含了 gtk-doc 作为子项目。如果您是发行商,请不要惊讶,构建 GTK 现在会安装 gtk-doc 工具。

这个快照中的重大消息是我们正在努力更多地展示新的基于 GL 的渲染堆栈的功能。

预热:Shadertoy

gtk4-demo 现在包含一个 Shadertoy 演示。

该演示使用 GtkGLArea 小部件运行与 shadertoy.com 上找到的 GLSL 代码片段兼容的代码片段。如果您将示例粘贴到此演示的编辑器中,那里找到的许多示例都将起作用。

这很有趣,但有点局限性。GLSL 被限制在其“沙箱”中,即 GtkGLArea 小部件,该小部件使用 GL API 来编译和使用着色器。

着色器作为一等对象

这不是我们第一次尝试制作类似 shadertoy 的东西。当我们第一次研究它时,我们认为我们将创建一个应用程序可以使用的着色器抽象。当发现使其在不同的渲染器和后端上工作需要我们编写自己的着色器编译器时,我们就把它放在了一边——工作量太大了。

但是在我们 shadertoy 成功之后,我们重新审视了着色器作为一等对象的想法,目标更加适度:我们使用 GLSL,并且不尝试使着色器在 OpenGL 渲染器之外的任何东西上工作。

在 3.99.2 中,我们现在有了

有了这些组件,我们制作了一个演示,展示了着色器的各种用途。它可能有点超载,并且一些效果有点过分,但它说明了重点:您可以在您的窗口小部件中使用着色器。

 

我们尚未做的是添加内置着色器支持的小部件。该演示展示了一些可能的候选对象

一个着色器可绘制对象。如您所回忆,GdkPaintable 是一个非常灵活的接口,适用于任何可以“绘制”的东西。着色器当然符合条件。gtk-demo 中的 GskShaderPaintable 使用没有输入纹理的着色器来仅生成像素,然后我们将其添加到 GtkPicture 小部件以使其出现在小部件树中。

一个着色器容器。这是一个非常简单的 容器,可以使用着色器在子小部件的顶部绘制效果。它适用于采用单个输入纹理(用于子小部件)的着色器。

一个着色器堆栈。这是一个类似堆栈的容器,显示多个子小部件中的一个,并在可见子小部件更改时使用着色器进行过渡。它适用于期望两个输入纹理(用于旧的和新的活动子项)的着色器。

值得庆幸的是,在 GTK 4 中制作自定义小部件比过去容易得多,因此渲染节点 API 应该足以让您开始进行一些有趣的实验。您当然可以采用 gtk4-demo 代码作为起点。

您可以调试它

除了小部件外,着色器支持已完全集成。GTK 检查器可以像处理任何其他渲染节点一样处理着色器节点,您可以序列化它们,例如在 gtk4-node-editor 中加载生成的文件

如果您需要查看 GTK 发送到着色器编译器的输入,设置环境变量

GDK_DEBUG=shaders

可能会有所帮助。

下一步是什么?

在这次 GL 冒险之后,我们现在将专注于实现更多新的可访问性基础架构。

GtkColumnView

在我最近关于 GTK 4 中列表视图模型的系列文章中,我留下了一个未完成的内容,那就是对 GtkColumnView 的详细介绍。这将很容易成为该系列中最复杂的部分。我们正在进入 GtkTreeView 的中心地带——任何旨在取代其大部分功能的东西都将是一个复杂的庞然大物。

概述

正如我们对 GtkListView 所做的那样,我们将从一个高层次的概述和一个图片开始。

如果您回顾 listview 图片,您会记得我们使用列表项工厂为我们需要显示的模型的每个项目创建一个小部件。

在列视图中,我们需要每个项目的多个小部件——每列一个。我们这样做的方式是为每一列提供自己的列表项工厂。每当我们需要显示一个新项目时,我们将每列工厂中的小部件组合成一个新项目的行。

在内部,列视图实际上使用列表视图来保存行。这很好,因为我在上一篇文章中解释的所有关于项目重用以及如何使用列表项工厂的内容都同样适用。

当然,有些事情不同的。例如,列视图组织大小分配,以便所有行中的小部件对齐以形成适当的列。

注意:就像 GtkListView 一样,列视图仅为当前在视图中的模型部分创建小部件,因此它共享垂直可伸缩性。在水平方向上并非如此——即使它们在左右视图之外,每一行都完全填充了每列的小部件。因此,如果您添加大量列,事情就会变慢。

标题和其他复杂情况

列对象还包含其他数据,例如标题。列视图使用这些数据来显示每列的标题。如果列视图被标记为可重新排序,您可以通过拖放标题小部件来重新排列列。如果列被标记为可调整大小,您可以拖动两列之间的边框来调整它们的大小。

如果您注意了,您现在可能会想知道这种调整大小如何与行中的单元格可以是任意小部件这一事实相结合,而这些小部件期望至少有可用于绘制其内容的最小大小。答案是我们正在使用 GTK 4 渲染机制的另一个新功能:小部件可以控制如何处理其边界之外(由子小部件)的绘制,使用

 gtk_widget_set_overflow (cell, GTK_OVERFLOW_HIDDEN)

排序、选择以及对 treeview 对等性的追求

由于我们希望在功能上与 GtkTreeview 匹配,因此我们尚未完成。用户喜欢在树视图中做的另一件事是单击标题,按该列对内容进行排序。GtkColumnView 标题也允许这样做。

您可能还记得上一篇文章,排序是通过将您的数据包装在 GtkSortListModel 中,并为其提供一个合适的排序器对象来完成的。由于我们希望根据您单击的列标题具有不同的排序顺序,因此我们为每一列提供自己的排序器,您可以使用

gtk_column_view_column_set_sorter (column, sorter)

但是我们如何从您刚刚单击的列中获取正确的排序器,并将其附加到排序模型?请记住,排序模型不会是我们传递给列视图的最外层模型,因为它始终是一个选择模型,因此列视图不能仅仅自行在排序列表模型上切换排序器。

我们提出的解决方案是使列视图提供一个排序器,该排序器在内部使用列排序器,使用

gtk_column_view_get_sorter (view)

您可以在设置模型时将此排序器一次性提供给您的排序模型,然后在用户单击列标题以激活不同的列排序器时,事情会自动更新。

这听起来很复杂,但它的工作效果出奇地好。这种方法的一个好处是,我们实际上可以一次按多列排序——因为我们拥有所有列排序器,并且我们知道您上次单击了哪个排序器。

相比之下,选择处理很容易。它的工作方式与在 GtkListView 中相同。

总结

GtkColumnView 是一个复杂的小部件,但我希望这一系列文章能让您更容易开始使用它。

关于列表模型

在上一个帖子中,我承诺深入研究列表模型以及 GTK 4 在此领域提供的功能。让我们首先看一下 GListModel 接口

struct _GListModelInterface
{
  GTypeInterface g_iface;

  GType    (* get_item_type) (GListModel *list);
  guint    (* get_n_items)   (GListModel *list);
  gpointer (* get_item)      (GListModel *list,
                              guint       position);
};

实现接口的一个重要部分是您需要发出
在需要时发出 ::items-changed 信号,使用 GLib 为此目的提供的辅助函数
GLib为此目的提供了辅助函数

void g_list_model_items_changed (GListModel *list,
                                 guint       position,
                                 guint       removed,
                                 guint       added)

关于此接口的一些注意事项

  • 非常简单;这使得它易于实现
  • 该 API 基于位置,并且仅处理列表中成员身份的更改 - 跟踪项目本身的更改取决于您

列表模型动物园

GTK 提供大量列表模型实现。仔细观察,它们分为几个不同的组。

列表模型构建套件

第一组可以称为列表模型构建套件:通过修改或组合您已有的模型来构建新模型的模型。

此组中的第一个模型 GtkSliceListModel,获取现有模型的切片,由偏移量和大小给出,并创建一个仅包含这些项的新模型。如果您想在分页视图中呈现一个大列表,这将非常有用——前进和后退按钮将简单地按大小增加或减少偏移量。切片模型还可以用于通过随着时间的推移使切片变大来增量填充列表。GTK 在某些地方正在使用这种技术。

此组中的下一个模型 GtkFlattenListModel,采用多个列表模型并将它们组合成一个。由于这一切都与列表模型有关,因此要组合的模型以列表模型的形式传递给扁平模型。当您需要组合来自多个来源的数据时,这非常有用,例如 GTK 在打印对话框中对纸张尺寸的处理方式。

Paper size list in print dialog
扁平化列表

请注意,原始模型继续存在于扁平模型之后,并且它们的更新将按预期由扁平列表模型传播。

有时,您的数据在列表模型中,但其形式不太正确。在这种情况下,您可以使用 GtkMapListModel 将原始模型中的每个项目替换为不同的项目。

具体模型

GTK 及其依赖项包含许多用于我们自己处理的数据类型的具体模型。

这里的第一个示例是 Pango 对象,这些对象为其数据实现了列表模型接口:PangoFontMap 是 PangoFontFamily 对象的列表模型,而 PangoFontFamily 是 PangoFontFace 对象的列表模型。字体选择器正在使用这些模型。

font chooser dialog
Pango 列表模型

下一个示例是 GtkDirectoryListGtkBookmarkList 对象,它们将在文件选择器中用于表示目录内容和书签。关于这些有趣的一个细节是,它们都需要进行 IO 操作才能填充其内容,并且它们以异步方式进行,以避免长时间阻止 UI。

此组中的最后一个模型略微不那么具体:GtkStringList 是围绕所有常用的字符串数组的简单列表模型包装器。这种列表模型经常使用的一个例子是 GtkDropDown。这非常常见,以至于 GtkDropDown 具有一个方便的构造函数,该构造函数接受一个字符串数组并为您创建 GtkStringList

GtkWidget *
    gtk_drop_down_new_from_strings (const char * const * strings)

选择

下一组模型使用新接口扩展 GListModel:GtkSelectionModel。对于底层模型中的每个项目,GtkSelectionModel 都会维护该项目是否被选中的信息。

我们不会详细讨论该接口,因为您不太可能需要自己实现它,但最重要的几点是

gboolean gtk_selection_model_is_selected (GtkSelectionModel *model)
                                          guint              pos)
GtkBitset *
       gtk_selection_model_get_selection (GtkSelectionModel *model)

因此,您可以以位集的形式获取单个项目的选择信息或整体选择信息。当然,还有一个 ::selection-changed 信号,其工作方式与 GListModel 的 ::items-changed 信号非常相似。

GTK 有三个 GtkSelectionModel 实现:GtkSingleSelectionGtkMultiSelectionGtkNoSelection,它们在可以同时选择的项目数量上有所不同(1 个、多个或 0 个)。

GtkGridView 颜色演示显示了带有橡皮筋的多项选择

 

当您使用 GTK 的新列表小部件时,很可能会遇到选择模型,因为它们都希望其模型为选择模型。

重要的

我要提到的最后一组模型是执行您在列表中期望的典型操作的模型:过滤和排序。模型是 GtkFilterListModel GtkSortListModel。两者都使用辅助对象来实现其操作:GtkFilter 和 GtkSorter。两者都有子类来处理常见情况:对字符串或数字进行排序和过滤,或使用回调。

在 GTK 3.99 的准备阶段,我们在这两个模型上花费了大量精力,并使它们能够增量地完成其工作,以避免在使用大型模型时长时间阻止 UI。

GtkListView 单词演示显示了对 500,000 个单词的列表进行交互式过滤

剩余

GTK 中还有一些列表模型实现不完全适合上述任何组,例如 GtkTreeListModelGtkSelectionFilterModelGtkShortcutController。今天我会跳过这些。

模型无处不在

我将以返回列表模型的 GTK API 的简短列表结束

  • gdk_display_get_monitors
  • gtk_widget_observe_children
  • gtk_widget_observe_controllers
  • gtk_constraint_layout_observe_constraints
  • gtk_constraint_layout_observe_guides
  • gtk_file_chooser_get_files
  • gtk_drop_down_get_model
  • gtk_list_view_get_model
  • gtk_grid_view_get_model
  • gtk_column_view_get_model
  • gtk_column_view_get_columns
  • gtk_window_get_toplevels
  • gtk_assistant_get_pages
  • gtk_stack_get_pages
  • gtk_notebook_get_pages

总之,列表模型在 GTK 4 中无处不在。它们灵活且有趣,您应该使用它们!

GtkListView 入门

一些 GTK4 的早期采用者指出,新的列表小部件并非最容易学习的。特别是,GtkExpression 和 GtkBuilderListItemFactory 很难理解。这并不奇怪——一个完整的列表小部件,带有列、选择和排序以及树结构等,是一个复杂的野兽。

但是,让我们看看是否可以逐一解开这些东西,并使其更易于理解。

概述

让我们从相关组件及其交互的高级视图开始:模型、列表项工厂和列表视图。

它们是创建列表视图时发生的三件事

view = gtk_list_view_new (model, factory);

我们使用的模型是 GListModels。这些总是包含 GObject,因此您必须以对象的形式提供数据。这是与 GtkTreeview 的第一个显著区别,后者使用直接包含基本类型的 GtkTreeModels。

对于某些简单的情况,GTK 提供了现成的模型,例如 GtkStringList。但通常,您必须制作自己的模型。值得庆幸的是,GListModel 是一个比 GtkTreeModel 简单得多的接口,因此这并不太难。

列表项工厂的职责是生成一个行小部件,并在列表视图需要时将其连接到模型中的项目。

列表视图将创建比填充其可见区域所需的多一些行,以更好地估计滚动条的大小,并为了在您决定滚动视图时有一些“缓冲”。

一旦您进行滚动,我们不一定需要要求工厂制作更多行——我们可以回收正在另一端滚动出视图的行。

值得庆幸的是,所有这些都在幕后自动发生。您要做的就是提供一个列表项工厂。

创建项目

GTK 提供了两种不同的创建项目的方法。您可以手动使用 GtkSignalListItemFactory,也可以使用 GtkBuilderListItemFactory 从 ui 文件实例化行小部件。

手动方法更容易理解,所以让我们先看看这个。

factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", setup_listitem_cb, NULL);
g_signal_connect (factory, "bind", bind_listitem_cb, NULL);

当工厂需要创建新的行小部件时会发出“setup”信号,当需要将行小部件连接到模型中的项目时会发出“bind”信号。

这两个信号都以 GtkListItem 作为参数,这是一个包装对象,可让您获取模型项(使用 gtk_list_item_get_item())并允许您传递新的行小部件(使用 gtk_list_item_set_child())。

static void
setup_listitem_cb (GtkListItemFactory *factory,
                   GtkListItem        *list_item)
{
  GtkWidget *label = gtk_label_new ("");
  gtk_list_item_set_child (list_item, label);
}

通常,您的行会比单个标签更复杂。您可以根据需要创建复杂的小部件并将它们分组在容器中。

static void
bind_listitem_cb (GtkListItemFactory *factory,
                  GtkListItem        *list_item)
{
  GtkWidget *label;
  MyObject *obj;

  label = gtk_list_item_get_child (list_item);
  obj = gtk_list_item_get_item (list_item);
  gtk_label_set_label (GTK_LABEL (label),
                       my_object_get_string (obj));
}

如果您的“bind”处理程序连接到项目上的信号或执行其他需要清理的操作,则可以使用“unbind”信号来执行该清理操作。“setup”信号有一个类似的对应项,称为“teardown”。

构建器方式

我们的“setup”处理程序基本上是创建小型小部件层次结构的配方。GTK 有一种更声明式的方法来执行此操作:GtkBuilder ui 文件。这就是 GtkBuilderListItemFactory 的工作方式:您给它一个 ui 文件,它会在需要创建行时实例化该 ui 文件。

ui = "<interface><template class="GtkListItem">...";
bytes = g_bytes_new_static (ui, strlen (ui));
gtk_builder_list_item_factory_new_from_bytes (scope, bytes);

您现在可能会想:等一下,您正在为我模型中的数千个项目中的每个项目解析一个 xml 文件,这不是很昂贵吗?

对此问题有两个答案

  • 我们并不是真的为每个条目都解析 XML;我们只解析一次,存储回调序列,然后在之后重放它。
  • GtkBuilder 最耗时的部分实际上不是 XML 解析,而是对象的创建;回收行有助于解决这个问题。

很容易理解 UI 文件如何替换“setup”处理程序,但是“bind”呢?在上面的例子中,“bind”回调函数获取条目的属性(MyObject:string 属性),并使用它们的值来设置小部件的属性(GtkLabel:label 属性)。换句话说,“bind”处理程序正在执行属性绑定。为了简单起见,我们在这里只创建了一个一次性绑定,但是我们也可以使用 g_object_bind_property() 来创建一个持久绑定。

GtkBuilder UI 文件可以设置对象之间的属性绑定,但存在一个问题:模型项在 UI 文件中不是“存在的”,它只是在稍后的“bind”时间才与行小部件相关联。

这就是 GtkExpression 发挥作用的地方。GtkExpression 的核心是一种描述对象之间绑定的方式,而这些对象不一定已经存在。在我们的例子中,我们想要实现的是

label->label = list_item->item->string

不幸的是,当它作为我们 UI 文件的一部分转换为 XML 时,会变得稍微笨拙一些

<interface>
  <template class="GtkListItem">
    <property name="child">
      <object class="GtkLabel">
        <binding name="label">
          <lookup name="string">
            <lookup name="item">GtkListItem</lookup>
          </lookup>
        </binding>
      </object>
    </property>
  </template>
</interface>

请记住,UI 模板中的类名 (GtkListItem) 用作指向正在实例化的对象的 “this” 指针。

因此,<lookup name="item">GtkListItem</lookup> 的意思是:创建的列表项的 “item” 属性的值。 <lookup name="string"> 的意思是:该对象的 “string” 属性。 <binding name="label"> 表示将小部件的 “label” 属性设置为该属性的值。

由于表达式的工作方式,当列表项工厂将列表项的 “item” 属性设置为新值时,所有这些都将被重新评估,这正是我们使行小部件回收工作所需的。

专家级迷惑

当您嵌套事物时,GtkBuilderListItemFactory 和 GtkExpression 可能会变得非常令人困惑——列表项工厂可以在 UI 文件本身中构建,并且它们可以将自己的 UI 文件作为属性赋予,因此您最终会得到如下结构:

<object class="GtkListView">
  <property name="factory">
    <object class="GtkBuilderListItemFactory">
      <property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
  <property name="child">
  ...
]]>
      </property>
    </object>
  </property>
...

即使对于 GTK 专家来说,这也很令人困惑。

我的建议是,在刚开始使用 GtkListView 时避免这种情况——您不必在 UI 文件中创建列表项工厂,并且可以将它的 UI 模板指定为资源,而不是直接嵌入它。

深入了解

我们今天在这里描述的所有内容也适用于网格视图,只需进行少量调整。

到目前为止,我们专注于视图方面。关于模型也有很多要说的。

然后是列视图,它值得单独写一篇文章。

GTK 3.99.1

自从我们发布 GTK 3.99 以来已经一个月了,是时候发布另一个快照了。这是:https://download.gnome.org/sources/gtk/3.99/gtk-3.99.1.tar.xz

这个快照的重点是完善和完成。

未完成的事项

我们已经解决了 API 中的许多未完成的事项。

最明显的改变可能是按钮类层次结构的简化。GtkCheckButton 不再从 GtkToggleButton 派生,它们现在是两个独立的小部件,并且它们都可以被分组以互斥(也称为“单选组”)。在这个新的设置中,GtkRadioButton 实际上不再需要了,因此被删除了。

我们的新列表小部件(GtkListView 和 GtkGridView)的 API 也进行了一些小的调整。我们现在明确要求模型是 GtkSelectionModel 类型,以明确小部件处理选择。并且我们已经摆脱了额外的 “with_factory” 构造函数,而只是在新构造函数 new() 中使用可为空的工厂参数,保留了

GtkWidget * gtk_list_view_new (GtkSelectionModel  *model,
                               GtkListItemFactory *factory);

作为更多的 API 清理工作,我们删除了所有 CSS 样式类的定义 - 我们的小部件支持哪些样式类在其文档中定义,并且这些额外的定义实际上定义不明确或无用。

我们的主题现在正在使 GtkFrame 小部件绘制的框架的角变圆。这需要我们让框架裁剪其子项——这不是一个真正的 API 更改,而是一个值得提及的行为更改。

更多演示

在过去的一个月中,我们在 gtk4-demo 上投入了大量精力。

我们已经现代化了源代码高亮显示。我们现在使用 highlight 命令行实用程序。除其他外,这使我们可以对 XML 和 CSS 进行语法高亮显示,并支持深色主题。

Highlighting XML in a dark theme
高亮显示

演示列表具有更好的过滤和更好的外观。新外观是 Adwaita 现在支持的几种预定义的列表样式之一:富列表,导航侧边栏和数据表。

 

Rich List list style
富列表
Navigation Sidebar list style
导航侧边栏
Data Table list style
数据表

我们从 gtk4-demo 中删除了一些过时的演示,并改进了许多现有的演示。这是我们的拖放演示现在的样子

Drag-and-Drop demo
拖放演示

还添加了许多新的演示。这是新的布局管理器和转换的演示

性能和其他错误

许多错误已被修复;感谢我们热情的测试人员和错误报告人员。

我们最近最终追踪到的一个长期存在的问题导致我们的 GL 渲染器在存在非平凡的投影变换的情况下剪切错误。现在已经纠正了这个问题(结果可以在上面的转换演示中看到)。

作为之前提到的高亮显示改进的一部分,gtk_text_view_buffer_insert_markup() 的速度快得多。此改进的发生仅仅是因为 highlight 实用程序可以生成 Pango 标记。向实现它的人致敬!

我们解决的另一个性能问题是在具有许多字体的系统上加载字体选择器对话框的时间。我们现在正在逐步填充字体列表。除了此更改之外,调查还导致了 fontconfig 和 Pango 中的性能改进,这将使这些库的任何用户受益。

我已经可以移植了吗?

答案是:是的!

现在是查看 GTK4、开始移植您的应用程序并向我们提供有关我们的 API(新的和旧的)反馈的好时机。我们也渴望看到您在以意想不到的方式使用 GTK4 方面的想法 - 我们上面展示的一些演示或许可以给您一些启发。

下一步是什么?

我们正在寻求尽快为我们的新辅助功能接口加入 at-spi 后端;它应该包含在下一个 GTK 快照中。