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 中的小部件如何处理子小部件。