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 的文章时,我说拖放重构已经完成。事实证明这不太正确,并且此后又进行了一轮 DND 工作。这些更改是根据开发人员对拖放 API 的反馈进行的。用户测试万岁!

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

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

最后,我们可以删除 X11 风格的属性和选择 API 的残余; GtkSelectionData 和 GdkAtom 已消失。

清理和修复

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

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

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

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

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

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

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

未来

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

以下是我们仍希望在 GTK 4 之前整合的内容

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

如果您想关注 GTK 4 的工作,请点击此处