GTK 4 中的可伸缩列表

GTK 4 中最后缺少的一大块是用于新的列表和小部件的基础设施。它刚刚被合并,并包含在 3.98.5 版本中。因此,现在是时候仔细研究一下了。

历史:树状视图和列表框

自远古时代(即 GTK 2)以来,GtkTreeView 一直是 GTK 中常用的数据展示小部件。它使用模型-视图模式来保持数据与显示基础设施的分离。多年来,它增长了一个网格显示兄弟(GtkIconView)和一个选择堂兄弟(GtkComboBox),它们使用相同的基础设施(树模型和单元格渲染器)。

不幸的是,GtkTreeView 中使用单元格渲染器进行渲染的方法导致了分裂:小部件使用一组 vfuncs 和技术进行大小分配和渲染,而单元格渲染器则使用另一组。这种分裂的一个不幸后果是,很难在树状视图中进行动画(因为单元格渲染器不保留状态)。另一个后果是,GTK CSS 渲染机制的大部分进展在单元格渲染器中都不可用。

因此,我们非常希望使用小部件来显示列表中的数据。在 GTK 3 时代,我们为此引入了许多容器:GtkListBox 用于列表,GtkFlowBox 用于网格。它们不使用单元格渲染器,因此上述限制不是问题。它们甚至可以使用列表模型来保存数据。但是它们为每个数据项生成一个小部件,这严重限制了它们的可伸缩性。根据经验,GtkListBox 可以很好地处理 1000 个项目,而 GtkTreeView 可以很好地处理 100000 个项目。

在仍然使用小部件进行所有渲染的同时,克服可伸缩性限制一直是我们的长期路线图。

可伸缩性限制
小部件 可伸缩性
GtkIconView 100
GtkListBox 1 000
GtkTreeView 100 000
GtkListView 无限

新的基础设施

通过列表视图系列小部件,我们希望最终实现这一目标。目标是很好地处理无限数量的项目。如果您使用过像 Android 回收器视图之类的东西,您会认出列表视图背后的基本思想

  • 项目的数据以模型(包含对象)的形式提供
  • 仅为可查看的项目范围创建小部件
  • 可以通过将小部件绑定到不同的项目来回收小部件
  • 尽可能避免遍历模型中的所有项目,而只处理绑定到小部件的可视范围内的项目

模型

对于我们的模型-视图架构的模型方面,我们已经放弃了 GtkTreeModel,现在使用 GListModel。这有几个原因。其中一个原因是,我们希望这些项目是具有属性的对象,因此我们可以使用属性绑定。另一个原因是,GtkTreeModel 本身的可伸缩性并不高(例如,请参见此 案例中的意外二次行为)。

GTK 4 带有丰富的 GListModel 实现,从组合或修改现有模型的各种方式到过滤和排序。几个 GtkFilter 和 GtkSorter 类支持过滤和排序。选择也在模型端处理,使用 GtkSelectionModel 及其子类。

最后,还有用于处理的数据类型的具体模型:用于文件的 GtkDirectoryList,用于字体的 PangoFontMap。

传统上返回各种项目的 GList 的 API 已被更改或补充为返回 GListModel 的 API,例如 gdk_display_get_monitors()gtk_window_get_toplevels()gtk_stack_get_pages()

工厂

由于我们正在讨论按需自动创建小部件,因此将涉及工厂。GtkListItemFactory 是负责为模型中的项目创建小部件的对象。

此工厂有不同的实现。其中之一,GtkBuilderListItemFactory,使用 ui 文件作为列表项小部件的模板。这是一个典型的示例

<interface>
  <template class="GtkListItem">
    <property name="child">
      <object class="GtkLabel">
        <property name="xalign">0</property>
        <property name="wrap">1</property>
        <property name="width-chars">50</property>
        <property name="max-width-chars">50</property>
        <binding name="label">
          <lookup name="summary" type="SettingsKey">
            <lookup name="item">GtkListItem</lookup>
          </lookup>
        </binding>
      </object>
    </property>
  </template>
</interface>

完整的示例可以在 gtk4-demo 中找到。

另一个列表项工厂实现 GtkSignalListItemFactory,采用回调来 设置拆卸绑定解除绑定 来自项目的窗口小部件。

static void
setup_listitem_cb (GtkListItemFactory *factory,
                   GtkListItem        *list_item)
{
  GtkWidget *image;

  image = gtk_image_new ();
  gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE);
  gtk_list_item_set_child (list_item, image);
}

static void
bind_listitem_cb (GtkListItemFactory *factory,
                  GtkListItem *list_item)
{
  GtkWidget *image;
  GAppInfo *app_info;

  image = gtk_list_item_get_child (list_item);
  app_info = gtk_list_item_get_item (list_item);
  gtk_image_set_from_gicon (GTK_IMAGE (image),
                            g_app_info_get_icon (app_info));
}

static void
activate_cb (GtkListView  *list,
             guint         position,
             gpointer      unused)
{
  GListModel *model;
  GAppInfo *app_info;

  model = gtk_list_view_get_model (list);
  app_info = g_list_model_get_item (model, position);
  g_app_info_launch (app_info, NULL, NULL, NULL);
  g_object_unref (app_info);
}

...

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);

list = gtk_list_view_new_with_factory (factory);
g_signal_connect (list, "activate", activate_cb, NULL);

model = create_application_list ();
gtk_list_view_set_model (GTK_LIST_VIEW (list), model);
g_object_unref (model);

gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list);

完整的示例可以在 这里 找到。

表达式

要将工厂创建的小部件绑定到项目中的数据,我们需要一种灵活的机制来绑定属性。GObject 的 GBinding 机制朝着正确的方向发展,但它不够灵活,无法处理您可能需要在子对象或小部件的层次结构深处绑定属性,以及相关对象甚至在您设置绑定时可能不存在的情况。

为了处理这个问题,我们引入了 GtkExpression 作为更灵活的绑定系统,可以表达诸如

label = this->item->value

其中 _this_ 是 GtkListItem,它具有 _item_ 属性(类型为 SettingsKey),我们希望将其 _value_ 属性绑定到标签属性。在 GtkBuilder ui 文件中表达相同的内容看起来有点笨拙

<binding name="label">
  <lookup name="value" type="SettingsKey">
    <lookup name="item">GtkListItem</lookup>
  </lookup>
</binding>

新的小部件

GtkListView 是一个简单的列表,没有列或标题。使用这种列表的一个示例是 GtkFontChooser。GtkListView 开辟新天地的一个小方法是,它可以设置为水平列表以及通常的垂直方向。

GtkGridView 将小部件放置在一个可重排的网格中,很像 GtkFlowBox 或 GtkIconView。

GtkColumnView 相当于完整的 GtkTreeView,具有多列和标题,以及诸如交互式调整大小和重新排序等功能。就像 GtkTreeView 有 GtkTreeViewColumns 一样,GtkColumnView 具有 GtkColumnViewColumns 列表。每一列都有一个为每个项目生成单元格的工厂。然后将这些单元格组合成该项目的行。

示例

复杂 GTK 对话框中的许多列表(尽管并非所有列表)都已替换为新的列表小部件。例如,字体选择器现在使用 GtkListView,GTK 检查器中的大多数列表都使用 GtkColumnView。

但是 gtk4-demo 包含许多新小部件的示例。这里有一些

时钟示例显示了具有完整小部件渲染灵活性的优势。

颜色示例显示了以各种方式渲染的中型数据集。

设置示例表明,列视图在功能方面或多或少与 GtkTreeView 匹配。

总结

这篇文章介绍了新的列表小部件。还有更多我们在这里没有涉及的内容,例如树或组合框替换。了解新 apis 的一个地方是 GTK 文档中的 详细介绍

我们现在已将列表视图基础设施合并到主分支。这并不意味着它已经完成。但是我们认为它已准备好进行更广泛的使用,并且我们希望得到您对哪些有效、哪些无效以及缺少哪些内容的反馈。

并且,要明确的是,这并不意味着我们正在从 GTK 4 中删除树状视图和组合框——为时已晚,它们仍然在 GTK 内部的许多地方使用。这可能是 GTK 5 的目标。

GTK 4 中的媒体

显示动态图像变得越来越重要。GTK 4 将使 GTK 应用程序更容易显示动画;无论是程序动画、webm 文件还是直播流。

一切都是可绘制的

在查看动画之前,值得花一点时间来了解 GTK 用于可绘制内容的底层抽象。在 GTK 2 和 3 中,这主要是 GdkPixbuf:您加载一个文件,然后得到一个像素数据块(或多或少以一种格式)。如果您想对其进行动画处理,则可以使用 GdkPixbufAnimation,但公平地说,它不是一个非常成功的 API。

GTK 4 带来了一个名为 GdkPaintable 的新 API,它受到了 CSS Houdini 工作的启发。它非常灵活——任何您可以合理绘制的东西都可以是 GdkPaintable。内容可以是可调整大小的(如 svg),也可以随时间变化(如 webm)。

通常显示图像内容的部件,例如 GtkImage 或 GtkPicture,知道如何使用可绘制对象。而且,许多过去会以某种形式生成像素数据的事物现在可以用可绘制对象表示:纹理图标,甚至 部件

如果您有更专业的需求,任何可以捕获到 GtkSnapshot 中的内容都可以使用 gtk_snapshot_to_paintable() 转换为可绘制对象。如果您创建了一个想要绘制可绘制对象的自定义部件,这非常简单。只需调用 gdk_paintable_snapshot() 即可。

获取动画

正如我之前所说,可绘制对象可以随着时间的推移更改其内容。它们只需发出 ::contents-changed 信号,像 GtkPicture 这样的部件就会执行正确的操作并更新它们的显示。

那么,我们从哪里获得一个能够更改其内容的 GdkPaintable 呢?我们可以使用 GTK 4 的内置 GtkMediaFile API 从文件中加载它。这是一个高级 API,类似于 GstPlayer:您输入一个 URI,然后获得一个具有 play() 函数和 pause() 函数的对象,并且它充当一个可绘制对象。

GTK 附带了 GtkMediaFile 的两个实现,一个使用 gstreamer,另一个使用 ffmpeg。由于我们不想让这两个成为 GTK 的硬依赖项,它们是可加载的模块。

您可以打开 GTK 检查器来找出正在使用哪一个

保持控制

GtkMediaFile API 是 gtk4-widget-factory 在其首页上演示动画 GTK 徽标的工具

如您所见,它不仅仅是一张移动的图片,还有媒体控件——您可以通过使用 GtkVideo 部件免费获得这些控件。

超越基础

从文件中加载动画可能不是那么令人兴奋,所以这里有另一个 示例,它更进一步。这是一个周末小项目,它结合了 GtkVideo、libportalpipewire,演示如何在 GTK 应用程序中显示视频流。

坏消息是,我们尚未找到支持粘合代码的永久位置(GstSink、GdkPaintable 和 GtkMediaStream)。它不适合 GTK,因为如上所述,我们不想依赖 gstreamer,它也不适合 gstreamer,因为 GTK 4 尚未发布。我们肯定会在不久的将来解决这个问题,因为用几行代码将 gstreamer 管道转换为可绘制对象非常方便。

好消息是,代码的核心只有几行

fd = xdp_portal_open_pipewire_remote_for_camera (portal);
stream = gtk_gst_media_stream_new_for_pipewire_fd (fd, NULL);
gtk_video_set_media_stream (video, stream);

 

GTK 4 中的自定义部件 – 操作

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

激活所有事物

GTK 中的许多事物都可以被激活:按钮、复选框、开关、菜单项等等。通常,可以使用多种方式完成相同的任务,例如,可以通过 Control-C 快捷键和上下文菜单中的项目将选择复制到剪贴板。

在 GTK 内部,事物可以通过多种方式进行:可能会发出信号(::activate 或 ::mnemonic-activate 或键绑定信号),可能会调用回调,或者可能会激活 GAction。在 GTK 4 中,这一切都不是全新的,但我们正在朝着使用 GActions 作为连接操作的主要机制的方向发展。

操作

操作可以以各种形式出现在 GTK 应用程序中。

首先,有全局应用程序操作,添加到 GtkApplication 或 GtkApplicationWindow(两者都实现 GActionGroup 接口)。这是操作在 GTK 3 中首次出现的地方,主要目的是在会话总线上导出它们,以便与应用程序菜单一起使用。

我们也允许通过调用 gtk_widget_insert_action_group() 将操作与部件关联。以这种方式添加的操作仅当它起源于层级结构中小于部件的位置时才会被考虑激活。

在 GTK 4 中创建操作的新方法是通过 gtk_widget_class_install_action() 在 class_init 函数中声明操作,类似于使用 g_object_class_install_property() 声明属性的方式。以这种方式创建的操作可用于部件的每个实例。

以下是 GtkColorSwatch 中的一个示例

gtk_widget_class_install_action (widget_class,
                                 "color.customize", "(dddd)",
                                 customize_color);

当 color.customize 操作被激活时,将调用 customize_color 函数。如您所见,操作可以声明它们期望参数。这是使用 GVariant 语法;您需要提供四个双精度值。

一个方便的简写允许您创建有状态的操作来设置部件类的属性

gtk_widget_class_install_property_action (widget_class,
                                         "misc.toggle-visibility",
                                         "visibility");

这将声明一个名为 misc.toggle-visibility 的操作,该操作会切换布尔属性 visibility 的值。

可操作对象和菜单

仅声明操作是不够的,您还需要以某种形式将您的操作连接到 UI。对于像按钮或开关这样实现可操作接口的部件,这就像设置 action-name 属性一样容易

gtk_actionable_set_action_name (GTK_ACTIONABLE (button),
                                "misc.toggle-visibility");

当然,您也可以在 ui 文件中执行此操作。

如果您想从菜单中激活您的操作,您可能会使用从 XML 构建的菜单模型,例如这个

<menu id="menu">
  <section>
    <item>
      <attribute name="label">Show text</attribute>
      <attribute name="action">misc.toggle-visibility</attribute>
    </item>
  </section>
</menu>

在 GTK 3 中,您会连接到 ::populate-popup 信号,以将项目添加到标签或条目的上下文菜单中。在 GTK 4 中,这是通过向部件添加菜单模型来完成的

gtk_entry_set_extra_menu (entry, menu_model);

深入了解

要了解有关 GTK 4 中操作的更多信息,您可以阅读 GTK 文档中的 操作概述

GTK 4 中的自定义部件 – 输入

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

事件处理程序接管

在前面的部分中,我们已经看到了一些用一些辅助对象替换处理 GtkWidget 信号的示例。这种趋势在输入区域更为明显,在输入区域中,我们传统上需要处理许多信号:::button-press-event、::key-press-event、::touch-event 等等。所有这些信号在 GTK 4 中都已消失,相反,您应该向您的部件添加事件控制器并监听它们的信号。例如,有 GtkGestureClick、GtkEventControllerKey、GtkGestureLongPress 等等。

事件控制器可以在 ui 文件中创建,但在 init() 函数中创建更为常见

static void click_cb (GtkGestureClick *gesture,
                      int              n_press,
                      double           x,
                      double           y)
{
  GtkEventController *controller = GTK_EVENT_CONTROLLER (gesture);
  GtkWidget *widget = gtk_event_controller_get_widget (controller);

  if (x < gtk_widget_get_width (widget) / 2.0 &&
      y < gtk_widget_get_height (widget) / 2.0)
     g_print ("Red!\n");
}

...

  controller = gtk_gesture_click_new ();
  g_signal_handler_connect (controller, "pressed",
                            G_CALLBACK (click_cb), NULL);
  gtk_widget_add_controller (widget, controller);

gtk_widget_add_controller() 获取控制器的所有权,并且 GTK 会在部件最终确定时自动清理控制器,因此无需执行其他操作。

复杂的事件处理程序

前面部分中的事件处理程序示例很简单,一次只处理单个事件。手势稍微复杂一些,因为它们处理相关事件的序列,并且通常会保持状态。

更复杂的事件处理程序的示例包括 DND 和键盘快捷键等内容。我们可能会在以后的文章中介绍其中的一些。

深入了解

所有不同事件处理程序背后的统一原则是,GTK 将它从窗口系统接收的事件从部件树的根传递到目标部件,然后再返回,这种模式通常称为捕获-冒泡。

在键盘事件的情况下,目标部件是当前焦点。对于指针事件,它是指针下悬停的部件。

要了解有关 GTK 中输入处理的更多信息,请访问 GTK 文档中的 输入处理概述。

展望

我们已经完成了本系列准备的材料。如果有人感兴趣,它可能会在未来的某个时候继续进行。可能的主题包括:快捷方式、操作和激活、拖放、焦点处理或可访问性。

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 曲面的重新实现,并将 GdkSurface API 分割为单独的 GdkToplevelGdkPopup 接口(还有一个 GdkDragSurface 接口),它们反映了曲面的不同角色

  • 顶层窗口是由用户放置的主权窗口,可以最大化、全屏等。
  • 弹出窗口相对于父曲面定位,并且通常会捕获输入,例如在用于菜单时。

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

另一个主要特性是新的键盘快捷键基础设施。过去,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 的文章时,我说拖放重构已经完成。事实证明这并不完全正确,此后又进行了一轮拖放工作。这些更改是根据开发人员对拖放 API 的反馈而进行的。用户测试万岁!

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

我们还清理了拖放实现的内部结构,将拖放事件分组到事件序列中,以与处理正常的移动事件相同的方式处理它们,并引入了 GtkDropControllerMotion,这是一种事件控制器,旨在处理拖放操作期间的选项卡切换等操作。

最后,我们可以删除 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 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 元素是否具有正确的辅助功能描述和链接

有关说明和路线图的更多信息,请访问维基,我们还安排了今年夏天的额外检查点会议。

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

GTK 团队感谢 GNOME 基金会赞助场地和与会者,以及感谢 Hypra 的各位同仁参加黑客节并解释用例和辅助功能堆栈的当前状态,以及在开发方面提供帮助。

GTK 3.98

几天前,我发布了 GTK 3.98 tarball。这是向 GTK 4 迈出的又一步。它稍微落后于计划,并且没有完全包含我们希望加入的所有内容,但它更接近我们希望在 GTK 4 中发布的内容。

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

性能

GL 渲染器已经看到了一系列优化和性能改进。

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

自新年以来,一个重要的关注点是提高 CSS 机制的性能。CSS 值实现已得到优化,以尽可能避免计算值。CSS 查找现在使用 Bloom 过滤器。图标加载的 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 现在是强制性的,并且为了支持 IBus,已删除了对 xim 输入法的支持。

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 一起使用。

表情符号选择器小部件已公开。

未来展望

在 3.98 之后,我计划进行更频繁的快照,因为剩余的未完成项目正在着陆。你问这些项目是什么?

以下是我们仍然希望在 GTK 4 之前集成的内容

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