GTK+ 3 更新

计划

当我们开始向 GTK+ 4 开发时,我们制定了一个计划,其中指出 GTK+ 3.22 将是 GTK+ 3 的最终稳定分支。我们已经坚持这个计划一段时间了。

它对我们来说相当有效 - GTK+ 3 停止了剧烈的变化,这很受欢迎,而且我们终于看到应用程序正在从 GTK+ 2 迁移。

现实

但是,GTK+ 4 的成熟需要时间(更多内容将在另一篇文章中介绍),并且一些很棒的新功能(例如字体变体支持或 Emoji 完成)在 master 分支中未被使用。我们还收到了一些移植应用程序对关键 API 的请求。

因此,我们决定最好改变方向,通过在 9 月份发布 GTK+ 3.24 版本,允许在 GTK+ 3.x 中添加有限的新功能和 API。

现在 git 中有一个 gtk-3-24 分支。GTK+ 3.x 的维护已转移到该分支,我们将不再进行任何 3.22.x 版本的发布。

亮点

此新分支的第一个版本是 GTK+ 3.23.0,可以在这里找到

https://download.gnome.org/sources/gtk+/3.23/gtk+-3.23.0.tar.xz

此版本的亮点包括新的字体选择器功能,

  • 允许设置 OpenType 字体功能
  • 显示 OpenType 字体功能的示例
  • 允许选择 OpenType 字体变体
  • 支持选择的细节级别

新的 Emoji 功能,

  • 支持 Emoji 的完成弹出窗口
  • 删除 Ctrl-Shift-e 快捷方式

gdk_window_move_to_rect 作为公共 API,

以及在 FreeBSD 上使用匿名共享内存的 Wayland 后端。

数字命理

再做一个 3.x 循环的副作用是,我们将把 GTK+ 3.24 作为最终的 GTK+ 3,这与 GTK+ 2.24 是最终的 GTK+ 2 形成了一个令人愉快的平行。

纹理和可绘制对象

在 GTK4 中,我们一直在尝试为图像数据找到更好的解决方案。在 GTK3 中,我们用于此目的的对象是 pixbufCairo surfaces。但是它们不再符合要求,所以现在我们有了 GdkTextureGdkPaintable

GdkTexture

GdkTextureGdkPixbuf 的替代品。为什么它更好?
首先,它简单得多。API 看起来像这样

int gdk_texture_get_width (GdkTexture *texture);
int gdk_texture_get_height (GdkTexture *texture);

void gdk_texture_download (GdkTexture *texture,
                           guchar     *data,
                           gsize       stride);

所以它是一个二维像素数组,如果需要,您可以下载像素。它也被保证是不可变的,所以像素永远不会改变。存在许多构造函数可以从文件、资源、数据或 pixbuf 创建纹理。

但是纹理和 pixbuf 之间最大的区别在于它们不暴露用于存储像素的内存。事实上,在调用 gdk_texture_download() 之前,该数据甚至不需要存在。
这在 GL 纹理中使用。例如,GtkGLArea 小部件使用此方法来传递数据。预计 GStreamer 也会以 GL 纹理的形式传递视频。

GdkPaintable

但是有时,您会拥有比不可变的一堆像素更复杂的东西。例如,您可能有一个动画 GIF 或可缩放的 SVG。这就是 GdkPaintable 的用武之地。
抽象地说,GdkPaintable 是一个接口,用于知道如何在任何尺寸下渲染自身的对象。受 CSS 图像 的启发,它们可以选择性地提供 GTK 小部件可以用来放置它们的固有尺寸信息。
因此,GdkPaintable 接口的核心是使可绘制对象自身渲染的函数和提供尺寸信息的 3 个函数

void gdk_paintable_snapshot (GdkPaintable *paintable,
                             GdkSnapshot  *snapshot,
                             double        width,
                             double        height);

int gdk_paintable_get_intrinsic_width (GdkPaintable *paintable);
int gdk_paintable_get_intrinsic_height (GdkPaintable *paintable);
double gdk_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable);

最重要的是,当其内容或大小发生变化时,可绘制对象可以发出“invalidate-contents”和“invalidate-size”信号。

为了更具体地说明这一点,让我们以可缩放的 SVG 为例:可绘制对象的实现将返回无固有大小(这些尺寸函数的返回值为 0 可实现此目的),并且无论何时绘制它,都将在给定的尺寸下以像素精确的方式绘制自身。
或者以动画 GIF 为例:它将提供其像素大小作为其固有大小,并绘制缩放到给定大小的动画当前帧。并且每当应显示动画的下一帧时,它将发出“invalidate-size”信号。
最后但并非最不重要的一点是,GdkTexture 实现了此接口。

我们目前正在更改 GTK3 中接受 GdkPixbuf 的所有代码,使其现在接受 GdkPaintable。当然,GtkImage 小部件已经更改,拖放图标或 GtkAboutDialog 也已更改。存在实验性补丁,允许应用程序向 GTK CSS 引擎提供可绘制对象。

如果您现在将所有这些信息放在一起,了解 GStreamer 可能提供由 GL 图像支持的纹理并创建可以上传到 CSS 的动画可绘制对象,您或许可以了解它的发展方向

GTK+ 4 中的输入法

GTK 对可加载模块的支持可以追溯到很久以前,这就是为什么 GTK 有很多代码来处理 GTypeModules 和搜索路径等。后来,Alex 重新审视了 GVfs 的这个主题,并提出了扩展点和 GIO 模块的概念,这些模块实现了它们。这是一个好得多的框架,GTK 4 是我们切换到使用它的绝佳机会。

GTK+ 4 中的更改

因此,我最近花了一些时间在 GTK 中的模块支持上。这里的主要变化如下

  • 我们不再支持通用的可加载模块。此功能的少数剩余用户之一是 libcanberra,我们将考虑直接在 GTK+ 中实现“事件声音”功能,而不是依赖于模块。如果您依赖于加载 GTK+ 模块,请与我们讨论实现您正在做的事情的其他方法。
  • 打印后端现在使用名为“gtk-print-backend”的扩展点定义,该扩展点需要 GtkPrintBackend 类型。现有的打印后端已转换为实现此扩展点的 GIO 模块。由于我们从未支持过树外打印后端,因此这不应影响其他人。
  • 输入法也使用名为“gtk-im-module”的扩展点定义,该扩展点需要 GtkIMContext 类型。我们已经删除了所有非平台 IM 模块,并将平台 IM 模块移动到 GTK+ 中,同时还实现了扩展点。

调整现有输入法

由于我们仍然支持树外 IM 模块,我想使用这篇文章的其余部分来快速概述 GTK+ 4 的树外 IM 模块的外观。

将传统的基于 GTypeModule 的 IM 模块转换为新的扩展点需要几个步骤。以下示例代码取自 Broadway 输入法。

使用 G_DEFINE_DYNAMIC_TYPE

我们将从模块加载一个类型,而 G_DEFINE_DYNAMIC_TYPE 是定义此类类型的正确方法

G_DEFINE_DYNAMIC_TYPE (GtkIMContextBroadway,
                       gtk_im_context_broadway,
                       GTK_TYPE_IM_CONTEXT)

请注意,此宏定义了一个 gtk_im_context_broadway_register_type() 函数,我们将在下一步中使用该函数。

请注意,动态类型除了更常见的 class_init 之外,还需要一个 class_finalize 函数,该函数可以是微不足道的

static void
gtk_im_context_broadway_class_finalize
               (GtkIMContextBroadwayClass *class)
{
}

实现 GIO 模块 API

为了用作 GIOModule,模块必须实现三个函数:g_io_module_load()、g_io_module_unload() 和 g_io_module_query()(严格来说,最后一个是可选的,但无论如何我们都会在此处实现它)。

void
g_io_module_load (GIOModule *module)
{
  g_type_module_use (G_TYPE_MODULE (module));
  gtk_im_context_broadway_register_type  
                        (G_TYPE_MODULE (module));
  g_io_extension_point_implement
             (GTK_IM_MODULE_EXTENSION_POINT_NAME,
              GTK_TYPE_IM_CONTEXT_BROADWAY,
              "broadway",
              10);
 }
void
g_io_module_unload (GIOModule *module)
{
}
char **
g_io_module_query (void)
{
  char *eps[] = {
    GTK_IM_MODULE_EXTENSION_POINT_NAME,
    NULL
  };
  return g_strdupv (eps);
}

正确安装您的模块

GTK+ 仍然会在 $LIBDIR/gtk-4.0/immodules/ 中查找要加载的输入法,但是 GIO 只会查找名称以“lib”开头的共享对象,因此请确保您遵循该约定。

调试

就这样!

现在 GTK+ 4 应该加载您的输入法,如果您使用 GTK_DEBUG=modules 运行 GTK+ 4 应用程序,您应该会在调试输出中看到您的模块。

 

GTK+ 3.92

昨天,我们发布了 GTK+ 3.92.1,重庆市。由于距离上次 3.91 版本发布已经有一段时间了,因此这里简要介绍一下主要更改。

此版本是我们迈向 GTK+ 4 的又一个里程碑。虽然还有很多工作要做,但此版本可以初步了解我们希望在 GTK+ 4 中实现的一些目标。

GSK

自上次发布以来,大部分工作都投入到了 GSK 中。Vulkan 渲染器现在几乎完成,就避免 cairo 回退而言。唯一缺少的部分是模糊阴影(诚然,这是一个重要的部分)。

自 3.91.2 版本以来,一个主要的进步是我们不再对所有文本使用 Cairo 回退。相反,文本(在标签和条目中,遗憾的是,文本视图中还不行)会被转换为文本节点。每个文本节点包含一个 PangoGlyphString 和一个 PangoFont。Vulkan 渲染器使用字形缓存来避免为每一帧重新渲染字形。

Vulkan 渲染器的内部逻辑已经过重新设计,使用纹理而不是 Cairo 表面作为中间结果,从而避免更多 Cairo 回退。

Vulkan 渲染器中获得支持的其他节点类型包括模糊、重复节点、混合模式和交叉淡入淡出。在某些情况下,我们使用的着色器是非常简单的实现。如果能帮助改进它们,我们将不胜感激!

作为我们使用渲染节点的第一个例子,我们为 GtkOverlay 实现了模糊底层功能。其工作原理是将覆盖层的“主子项”捕获为渲染节点,然后多次重用它,并进行正确的裁剪,有时还会添加模糊节点。

检查器

为了帮助您探索 GSK,检查器现在显示 Vulkan 信息,并且记录器显示关于渲染节点的更多信息。

输入

在输入方面,事件获得了访问器,我们不再直接访问它们的字段。这是一个中间步骤,清理事件仍在进行中。我们已将事件的传统小部件信号(例如 ::key-press-event)移动到事件控制器,并且 GTK+ 中的大多数小部件已完全停止使用它们。

构建系统

我们已切换为专门使用 Meson 来构建 GTK+,并且 3.92.1 版本是第一个使用 Meson 的 dist 支持完成的版本。为了发布该版本,我们还必须将文档、测试套件和已安装的测试移植到使用 Meson。

仍然存在一些粗糙的边缘(我们并非 100% 正确地获取所有依赖项),但总的来说,Meson 对我们来说效果很好。

其余部分

当然,每个人都喜欢 Emoji,并且 GTK+ 3.22 中已有的相同的彩色 Emoji 支持在此版本中也可用。除此之外,CSS 中的字体支持通过对 CSS3 font-variant 属性的支持得到了一定的改进。

当然,这依赖于具有相应功能的字体。

尝试一下

使用 GTK+ 3.92.1,您应该可以轻松地亲自尝试其中的一些功能。

如果您一直想参与 GTK+ 开发但从未找到合适的机会,那么现在是参与其中的好时机!

滚动入门

几年前,我写了一篇关于 GTK+ 3 中滚动的文章。现在是时候再次回顾一下了!

常见情况

当时描述的更改的基本思想仍然相同。我们期望触摸板(或指点杆)滚动是最常见的滚动形式之一,并且滚动条充当其狭窄的指示器。

如您所见,我们更改了光标以指示滚动,并且您可以在所有方向自由滚动。它也是动态的。

经典滚动

当然,仍然可以单击并拖动滑块进行经典滚动。

“经典”滚动的另一个方面是,您可以单击滑块外的槽,然后将位置扭曲到您单击的位置,或者以页面大小的增量跳转。

默认情况下,主点击扭曲,而 Shift+主点击按页面跳转。我们刚刚添加了鼠标中键单击作为 Shift+主点击的替代方案,因为这是许多人习惯的常见替代方案。主要出于历史原因,GTK+ 有一个设置,gtk-primary-button-warps-slider,它会切换主点击和 Shift+主点击在此方面的作用。

典型的键盘快捷键(Page Up、Page Down、Home、End)让您可以使用键盘控制滚动。

平滑滚动

GTK+ 中的滚动还有更多您可能不知道的功能。我们很久以前引入的一个功能是“缩放”或“微调”模式,该模式会减慢滚动速度,以实现像素级的精确定位。

要触发此模式,您可以长按或在滑块中按 Shift+单击。如您在视频中所见,一旦您将指针移动到滚动条下方或上方,它将保持相同的放松速度滚动,直到您松开。

作为该主题的变体,最近我们添加了平滑滚动的变速变体。

要触发它,您可以在滑块外的槽中进行辅助点击。滚动开始后,您可以通过将指针移近或远离滚动条来控制速度。一旦您发现它,它就非常容易让人上瘾!

自定义位置

作为最后一个功能,应用程序可以添加一个上下文菜单,该菜单由在滑块上进行辅助点击触发,并使其滚动到重要的位置。

就是这样,继续滚动吧!

GTK+ 4 的进展

上周在曼彻斯特举行的 GUADEC 大会上,GTK+ 维护者和感兴趣的人们在非常规会议期间举行了工作会议。

Georges 已经在他的博客文章中很好地总结了结果,您应该阅读它(哪怕只是为了看看聚集在一起的 GTK+ 人员的一些照片)。

GTK+ 3

我们确实简要讨论了 GTK+ 3。我们的印象是,大多数人都在享受 GTK+ 3.22 带来的稳定性,并且不急于跳到新的、不太稳定的工具包版本。

普遍的共识是,我们应该在 GTK+ 3 中保持对 API 添加的相当严格的立场,但在有足够好处时允许新功能。出现的这方面的示例是 Wayland 的客户端与服务器端协商协议支持或彩色 Emoji 支持。

GTK+ 4

大部分时间都用于讨论我们想要或需要为 GTK+ 4 完成的所有事情。我们非常清楚谁将负责这些项目中的每一个,但我们没有为完成它们确定非常详细的时间表。

最后,我们收集了我们认为是阻碍因素的项目列表

  • 基于约束的布局
  • 支持在 ui 文件中定义状态和转换
  • 设计器支持
  • 将键盘处理转换为事件控制器
  • 非回退文本渲染
  • 完成的 GL 渲染器
  • GDK 中对子曲面的干净支持
  • GDK 中不再有根窗口
  • 事件清理

其中一些要点值得更详细的讨论。

基于约束的布局、状态和设计器支持

使用约束进行布局是一个灵活的系统,并且在其他平台上已获得成功。更重要的是,它更接近于大多数人考虑在屏幕或纸上布置事物的方式,并且它有望提供一种通用的语言,供 GTK+ 应用程序的设计师和开发人员进行交流。

Emmanuele 和其他人一直在开发的 Emeus 小部件正在使用约束来查找单个容器的子小部件的位置和大小。

将其集成到 GTK+ 中的计划更具雄心:我们设想每个顶级都有一个约束求解器,窗口内的所有容器都将它们的约束添加到该求解器。这将需要 GTK+ 中的当前容器以约束的形式表达它们的布局算法,这在大多数情况下应该不会太难,并且可以逐步完成。

状态以及它们之间的转换是 Christian Hergert 在 libdazzle 中原型设计的东西。这里的想法不仅是要定义一个复杂的小部件(例如对话框),还要在 ui 文件中定义其主要状态以及它们之间的转换应如何工作。这将使我们拥有一个 UI 设计器工具,该工具不仅用于在画布上排列小部件,而且还用于故事板和设计转换。当然,说起来容易做起来难……

键盘处理

Christian 花了一些时间来描述他为 gnome 构建器编写的快捷键引擎,该引擎目前存在于 libdazzle 中。它具有一些有趣的功能,例如捕获冒泡事件处理、和弦(即,多键序列,例如 Ctrl-C Ctrl-X)、与操作的紧密集成以及自动生成键盘快捷键帮助的能力。

该领域的计划是从 Christian 的引擎中提取最佳功能,并将它们转换为一个或多个 GtkEventController。完成此工作后,我们将转换所有小部件以使用事件控制器而不是按键信号处理程序。

GtkBindingSet 也将由事件控制器替换。

文本渲染

GSK 的 Vulkan 渲染器已经基本完成。它可以使用着色器高效地渲染 CSS 引擎产生的大部分内容。最大的例外是文本:目前文本的处理方式是使用 cairo 将其渲染到表面,然后将表面上传到纹理,然后在渲染节点中使用该纹理。每一帧都是如此。

这里需要做的是将所需的字形上传到一个更大的纹理中,该纹理我们将其保留为图集,然后创建引用该图集的文本渲染节点。

由于文本是用户界面中非常重要的组成部分,因此在为 Vulkan 实现适当的文本渲染之前,我们不能真正声称我们已经验证了渲染节点方法。

GL 渲染器

Benjamin 完成了大部分工作,使 Vulkan 渲染器几乎达到了完整状态。在他进行这项工作时,GL 渲染器已经落后了——它没有 Vulkan 中使用的着色器。

这里需要做的是抽象出公共部分,并将其余部分从 Vulkan 向后移植到其 GL 等效项。一个不太令人愉快的事情是,我们最终可能需要多个 GL 渲染器变体,用于传统的 GL 和 GLES 平台。但至少在最初,我们可能可以使用一个现代的 GL 渲染器。

字体和文本

一个单独的会议专门讨论了我们文本渲染堆栈中的新功能。这里的主题是可变字体和彩色表情符号。不幸的是,我错过了大部分讨论,但结果的总结是:

  • 对于在 pango 和 fontconfig 中支持可变字体需要做的事情,Behdad 有一个粗略的计划。这涉及到 PangoFontDescription 中用于指定轴值的新语法以及 PangoFontFamily 中用于获取有关可用轴的信息的新 API。
  • 在 GUADEC 期间,Behdad 合并了 cairo、fontconfig 和 pango 中对彩色表情符号的支持,我开始在 GTK+ 中进行一些简单的表情符号输入。现在,GTK+ 3 和 master 中也都已经实现了。

其他

我们涉及了太多其他主题,无法在此处一一总结。其中之一是可访问性的状态,但这是另一个时间的主题。

容器的秘密:尺寸分配,第 6 部分

基线

我们正在进入 GTK+ 尺寸分配的另一个更神秘的领域。基线将小部件从简单的具有宽度和高度的模型转换为小部件可以以更有趣的方式垂直对齐的模型。这方面最重要的地方是文本。当您沿着一行文本移动时,读者的眼睛对单词上下移动非常敏感。基线是为了避免这种情况。

 

由于这是关于相对于彼此垂直对齐子项,因此仅当容器处于水平方向时,基线才相关。

测量上方和下方

由于子项现在可以具有“强制”对齐方式,因此简单地取子项高度的最大值不再足够。对齐可能会导致子项在顶部或底部“伸出”,从而需要更大的整体高度。为了处理这个问题,我们分别测量“基线上方”的部分和“基线下方”的部分,并分别使它们最大化。

for (i = 0; i < 3; i++) {
  gtk_widget_measure (child[i],
                      orientation,
                      sizes[i].minimum_size,
                      &child_min, &child_nat,
                      &child_min_baseline, &child_nat_baseline);

   below_min = MAX (below_min, child_min - child_min_baseline);
   above_min = MAX (above_min, child_min_baseline);
   below_nat = MAX (below_nat, child_nat - child_nat_baseline);
   above_nat = MAX (above_nat, child_nat_baseline);
}

total_min = above_min + below_min;
total_nat = above_nat + below_nat;

此代码省略了一些细节,例如处理不返回基线的子项。

在基线上分配

在分配方面,有两种情况:我们要么得到一个必须对齐子项的基线,要么必须自己确定基线。在后一种情况下,我们需要做的与我们之前测量时所做的基本相同:分别确定下方和上方的大小,并使用它们来找到我们的基线。

for (i = 0; i < 3; i++) {
  if (gtk_widget_get_valign (child[i]) != GTK_ALIGN_BASELINE)
    continue;

  gtk_widget_measure (child[i],
                      GTK_ORIENTATION_VERTICAL,
                      child_size[i],
                      &child_min, &child_nat,
                      &child_min_baseline, &child_nat_baseline);

  below_min = MAX (below_min, child_min - child_min_baseline);
  below_nat = MAX (below_nat, child_nat - child_nat_baseline);
  above_min = MAX (above_min, child_min_baseline);
  above_nat = MAX (above_nat, child_nat_baseline);
}

在确定基线时,我们又有一个选择要做。当可用空间大于最小值时,我们将基线放置在尽可能高的位置、尽可能低的位置还是中间的某个位置?GtkBox 有一个 ::baseline-position 属性,可以将此选择留给用户,我们也在这里执行相同的操作。

switch (baseline_position) {
  case GTK_BASELINE_POSITION_TOP:
    baseline = above_min;
    break;
  case GTK_BASELINE_POSITION_CENTER:
    baseline = above_min + (height - (above_min + below_min)) / 2;
    break;
  case GTK_BASELINE_POSITION_BOTTOM:
    baseline = height - below_min;
    break;
}
展开,基线位置:中心
压缩,基线位置:顶部
压缩,基线位置:中心
压缩,基线位置:底部

总结

这结束了我们对 GTK+ 尺寸分配机制的探索之旅。希望你喜欢它。

参考文献

  1. 容器的秘密:尺寸分配
  2. 容器的秘密:尺寸分配,第 2 部分
  3. 容器的秘密:尺寸分配,第 3 部分
  4. 容器的秘密:尺寸分配,第 4 部分
  5. 容器的秘密:尺寸分配,第 5 部分
  6. 带有这些更改的代码

容器的秘密:尺寸分配,第 5 部分

方向

GTK+ 中的许多小部件可以水平或垂直定向。从分隔符到工具栏的任何内容都实现了 GtkOrientable 接口,以允许通过设置 ::orientation 属性在运行时更改此方向。因此,显然,GtkCenterBox 也应该遵循这种模式。

我不会详细解释如何添加接口和实现属性。对我们来说有趣的部分是我们如何在尺寸分配期间使用 orientation 属性。

值得庆幸的是,我们的许多机制已经使用单维度编写,并且可以应用于高度,就像宽度一样。剩下的工作是遍历所有函数,并确保我们在执行任何依赖于方向的操作时都考虑到了方向。例如,我们引入了一个小的帮助器来查询适当的 expand 属性。

static gboolean
get_expand (GtkWidget *widget,
            GtkOrientation orientation)
{
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    return gtk_widget_get_hexpand (widget);
  else
    return gtk_widget_get_vexpand (widget);
}

需要记住的一件事是,我们在这里实现的一些功能仅适用于水平方向,例如从右到左翻转或基线。

更改 measure() 函数以避免硬编码水平方向

if (orientation == self->orientation)
  gtk_center_box_measure_orientation (widget, orientation, for_size,
                                      minimum, natural,
                                      min_baseline, nat_baseline);
else
  gtk_center_box_measure_opposite (widget, orientation, for_size,
                                   minimum, natural,
                                   min_baseline, nat_baseline);

size_allocate() 函数调用 distribute() 来分配宽度或高度,具体取决于方向

if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
  size = width;
  for_size = height;
} else {
  size = height;
  for_size = width;
}
distribute (self, for_size, size, sizes);

在进行这些简单但乏味的更改后,我们可以垂直定向中心框

参考文献

  1. 容器的秘密:尺寸分配
  2. 容器的秘密:尺寸分配,第 2 部分
  3. 容器的秘密:尺寸分配,第 3 部分
  4. 容器的秘密:尺寸分配,第 4 部分
  5. 带有这些更改的代码

容器的秘密:尺寸分配,第 4 部分

高度换宽度

这是我们进入 GTK+ 尺寸分配更深层部分的地方。高度换宽度意味着小部件没有单一的最小尺寸,但它可能会为了获得更大的高度而适应更小的宽度。大多数小部件不是这样的。此行为的典型示例是可以将其文本包装在多行中的标签

  

高度换宽度使尺寸分配的成本更高,因此容器必须通过设置请求模式来显式启用它。通常,容器应查看其子项并使用大多数子项首选的请求模式。为了简单起见,我们在此处对高度换宽度进行硬编码

static GtkSizeRequestMode
gtk_center_box_get_request_mode (GtkWidget *widget)
{
  return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}

两种方式都测量

编写可以处理高度换宽度的 measure() 函数的惯用方法是将其分解为两种情况:一种是沿着布局方向进行测量,另一种是沿着相反方向进行测量。

if (orientation == GTK_ORIENTATION_HORIZONTAL)
  measure_orientation (widget, for_size,
                       orientation,
                       minimum, natural,
                       minimum_baseline, natural_baseline);
else
  measure_opposite (widget, for_size,
                    orientation,
                    minimum, natural,
                    minimum_baseline, natural_baseline);

沿着方向进行测量就像我们一直以来的 measure() 函数一样:我们得到一个高度,因此我们询问所有子项对于该高度需要多少宽度,然后我们将答案相加。

沿着相反方向进行测量意味着回答问题:给定此宽度,您需要多少高度?我们要问子项相同的问题,但是我们应该给每个子项提供什么宽度?我们不能只是将全部宽度传递给每个子项,因为我们不希望它们重叠。

分配

为了解决这个问题,我们需要在子项之间分配可用宽度。这正是我们的 size_allocate() 函数正在执行的操作,因此我们需要将 size_allocate() 的内部部分分解为一个单独的函数。

毫不奇怪,我们将把新函数命名为 distribute()。

static void
distribute (GtkCenterBox *self,
            int for_size,
            int size,
            GtkRequestedSize *sizes)
{
   /* Do whatever size_allocate() used to do
    * to determine sizes
    */

  sizes[0].minimum_size = start_size;
  sizes[1].minimum_size = center_size;
  sizes[2].minimum_size = end_size;
}

既然我们知道如何获取子元素的候选宽度,我们就可以完成反方向的测量函数。和之前一样,我们最终返回子元素所需高度的最大值,因为我们的布局是水平的。

请注意,在这种情况下,方向是 GTK_ORIENTATION_VERTICAL,因此 gtk_widget_measure() 调用返回的 min 和 nat 值是高度。

distribute (self, -1, width, sizes);

gtk_widget_measure (start_widget,
                    orientation,
                    sizes[0].minimum_size,
                    &start_min, &start_nat,
                    &min_baseline, &nat_baseline);

gtk_widget_measure (center_widget,
                    orientation,
                    sizes[1].minimum_size,
                    &center_min, &center_nat,
                    &min_baseline, &nat_baseline);

gtk_widget_measure (end_widget,
                    orientation,
                    sizes[2].minimum_size,
                    &end_min, &end_nat,
                    &min_baseline, &nat_baseline);

*minimum = MAX (start_min, center_min, end_min);
*natural = MAX (start_nat, center_nat, end_nat);

既然我们已经将 size_allocate() 的大部分工作分解到 distribute() 函数中,我们可以直接从那里调用它,然后执行剩余的必要工作来为子元素分配位置(因为 distribute 已经给了我们大小)。

已展开
略小于自然大小
更小
以及更小
以及更小

参考文献

  1. 容器的秘密:尺寸分配
  2. 容器的秘密:尺寸分配,第 2 部分
  3. 容器的秘密:尺寸分配,第 3 部分
  4. 这些更改的代码
  5. 关于宽度适应高度几何管理的文档

容器的秘密:尺寸分配,第 3 部分

 扩展子元素

我们对功能齐全的尺寸分配的下一个探索点是 ::expand 属性。实际上有两个属性,::hexpand 和 ::vexpand,它们具有在部件层次结构中向上传播的有趣行为。但这并不是我们今天要讨论的内容,我们只是想在中心框部件的子元素设置了 ::hexpand 标志时,给它们提供所有可用空间。

再一次,measure() 的实现可以保持原样。再一次,我们需要决定如果多个子元素设置了 expand 标志,应该先扩展哪个子元素。GtkBox 尝试平等对待所有子元素,并在所有正在扩展的子元素之间均匀分配可用的额外空间。另一方面,我们更喜欢中心子元素,因为它最重要。

但是我们该如何处理呢?经过一些实验,我发现如果因为中心子元素不适合而必须将其推到左边或右边,那么使其变得更大就没有意义了。因此,只有在不是这种情况时,我们才会尊重 expand 标志。

center_expand = gtk_widget_get_hexpand (center);

if (left_size > center_x)
  center_x = left_size;
else if (width - right_size < center_pos + center_size)
  center_x = width - center_width - right_size;
else if (center_expand) {
  center_width = width - 2 * MAX (left_size, right_size);
  center_x = (width / 2) - (center_width / 2);
}

完成此操作后,如果外部子元素正在扩展,则可能仍有一些剩余空间可以给它们。

if (left_expand)
  left_size = center_pos - left_pos;
if (right_expand)
  right_size = pos + width - (center_pos + center_width);
没有正在扩展的子元素
中心子元素正在扩展
末尾子元素正在扩展
中心和末尾子元素正在扩展

参考文献

  1. 容器的秘密:尺寸分配
  2. 容器的秘密:尺寸分配,第 2 部分
  3. 这些更改的代码