(这是关于 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 中的小部件如何处理子小部件。