boxlayout: Add a baseline child property
authorMatthias Clasen <mclasen@redhat.com>
Thu, 27 Apr 2023 12:57:09 +0000 (14:57 +0200)
committerMatthias Clasen <mclasen@redhat.com>
Sat, 29 Apr 2023 17:28:55 +0000 (13:28 -0400)
In horizontal layout, we line up the baselines of all children to find
how much space we need above and below the box baseline.

In vertical layout, we need to pick one child to inherit the baseline
from, which is what the new GtkBoxLayout:baseline-child property is
about. It is the equivalent of GtkGridLayout:baseline-row.

gtk/gtkbox.c
gtk/gtkbox.h
gtk/gtkboxlayout.c
gtk/gtkboxlayout.h

index a1ae76fa8f0187a930b61fd581e89788d8d1ff0d..52630d280043604a135d547b4d04f52f6e255b56 100644 (file)
@@ -74,6 +74,7 @@ enum {
   PROP_0,
   PROP_SPACING,
   PROP_HOMOGENEOUS,
+  PROP_BASELINE_CHILD,
   PROP_BASELINE_POSITION,
 
   /* orientable */
@@ -125,6 +126,9 @@ gtk_box_set_property (GObject      *object,
     case PROP_SPACING:
       gtk_box_set_spacing (box, g_value_get_int (value));
       break;
+    case PROP_BASELINE_CHILD:
+      gtk_box_set_baseline_child (box, g_value_get_int (value));
+      break;
     case PROP_BASELINE_POSITION:
       gtk_box_set_baseline_position (box, g_value_get_enum (value));
       break;
@@ -154,6 +158,9 @@ gtk_box_get_property (GObject    *object,
     case PROP_SPACING:
       g_value_set_int (value, gtk_box_layout_get_spacing (box_layout));
       break;
+    case PROP_BASELINE_CHILD:
+      g_value_set_int (value, gtk_box_layout_get_baseline_child (box_layout));
+      break;
     case PROP_BASELINE_POSITION:
       g_value_set_enum (value, gtk_box_layout_get_baseline_position (box_layout));
       break;
@@ -270,6 +277,18 @@ gtk_box_class_init (GtkBoxClass *class)
                           FALSE,
                           GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
 
+  /**
+   * GtkBox:baseline-child: (attributes org.gtk.Property.get=gtk_box_get_baseline_child org.gtk.Property.set=gtk_box_set_baseline_child)
+   *
+   * The child that determines the baseline, in vertical orientation.
+   *
+   * Since: 4.12
+   */
+  props[PROP_BASELINE_CHILD] =
+    g_param_spec_int ("baseline-child", NULL, NULL,
+                      -1, G_MAXINT, -1,
+                      GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+
   /**
    * GtkBox:baseline-position: (attributes org.gtk.Property.get=gtk_box_get_baseline_position org.gtk.Property.set=gtk_box_set_baseline_position)
    *
@@ -425,6 +444,57 @@ gtk_box_get_spacing (GtkBox *box)
   return gtk_box_layout_get_spacing (GTK_BOX_LAYOUT (box_layout));
 }
 
+/**
+ * gtk_box_set_baseline_child: (attributes org.gtk.Method.set_property=baseline-child)
+ * @box: a `GtkBox`
+ * @child: a child, or -1
+ *
+ * Sets the baseline child of a box.
+ *
+ * This affects only vertical boxes.
+ *
+ * Since: 4.12
+ */
+void
+gtk_box_set_baseline_child (GtkBox *box,
+                            int     child)
+{
+  GtkBoxLayout *box_layout;
+
+  g_return_if_fail (GTK_IS_BOX (box));
+  g_return_if_fail (child >= -1);
+
+  box_layout = GTK_BOX_LAYOUT (gtk_widget_get_layout_manager (GTK_WIDGET (box)));
+  if (child == gtk_box_layout_get_baseline_child (box_layout))
+    return;
+
+  gtk_box_layout_set_baseline_child  (box_layout, child);
+  g_object_notify_by_pspec (G_OBJECT (box), props[PROP_BASELINE_CHILD]);
+  gtk_widget_queue_resize (GTK_WIDGET (box));
+}
+
+/**
+ * gtk_box_get_baseline_child: (attributes org.gtk.Method.get_property=baseline-child)
+ * @box: a `GtkBox`
+ *
+ * Gets the value set by gtk_box_set_baseline_child().
+ *
+ * Returns: the baseline child
+ *
+ * Since: 4.12
+ */
+int
+gtk_box_get_baseline_child (GtkBox *box)
+{
+  GtkLayoutManager *box_layout;
+
+  g_return_val_if_fail (GTK_IS_BOX (box), -1);
+
+  box_layout = gtk_widget_get_layout_manager (GTK_WIDGET (box));
+
+  return gtk_box_layout_get_baseline_child (GTK_BOX_LAYOUT (box_layout));
+}
+
 /**
  * gtk_box_set_baseline_position: (attributes org.gtk.Method.set_property=baseline-position)
  * @box: a `GtkBox`
index c0d1da489033684ba7c27f30c771349d622c6918..7bd92b9e7fcaef21765196ef63644d62d8a2eb65 100644 (file)
@@ -86,6 +86,12 @@ void        gtk_box_set_baseline_position (GtkBox             *box,
 GDK_AVAILABLE_IN_ALL
 GtkBaselinePosition gtk_box_get_baseline_position (GtkBox         *box);
 
+GDK_AVAILABLE_IN_4_12
+void        gtk_box_set_baseline_child (GtkBox         *box,
+                                        int             child);
+GDK_AVAILABLE_IN_4_12
+int         gtk_box_get_baseline_child (GtkBox         *box);
+
 GDK_AVAILABLE_IN_ALL
 void        gtk_box_append             (GtkBox         *box,
                                         GtkWidget      *child);
index b629e9e7837463212da64d5b719a5bff1dcda407..0b5c22a22a02eebeb57a057527f9e3be174b6e45 100644 (file)
@@ -55,6 +55,7 @@ struct _GtkBoxLayout
   guint spacing;
   GtkOrientation orientation;
   GtkBaselinePosition baseline_position;
+  int baseline_child;
 };
 
 G_DEFINE_TYPE_WITH_CODE (GtkBoxLayout, gtk_box_layout, GTK_TYPE_LAYOUT_MANAGER,
@@ -63,6 +64,7 @@ G_DEFINE_TYPE_WITH_CODE (GtkBoxLayout, gtk_box_layout, GTK_TYPE_LAYOUT_MANAGER,
 enum {
   PROP_HOMOGENEOUS = 1,
   PROP_SPACING,
+  PROP_BASELINE_CHILD,
   PROP_BASELINE_POSITION,
 
   /* From GtkOrientable */
@@ -112,6 +114,10 @@ gtk_box_layout_set_property (GObject      *gobject,
       gtk_box_layout_set_spacing (self, g_value_get_int (value));
       break;
 
+    case PROP_BASELINE_CHILD:
+      gtk_box_layout_set_baseline_child (self, g_value_get_int (value));
+      break;
+
     case PROP_BASELINE_POSITION:
       gtk_box_layout_set_baseline_position (self, g_value_get_enum (value));
       break;
@@ -144,6 +150,10 @@ gtk_box_layout_get_property (GObject    *gobject,
       g_value_set_int (value, box_layout->spacing);
       break;
 
+    case PROP_BASELINE_CHILD:
+      g_value_set_int (value, box_layout->baseline_child);
+      break;
+
     case PROP_BASELINE_POSITION:
       g_value_set_enum (value, box_layout->baseline_position);
       break;
@@ -204,20 +214,28 @@ gtk_box_layout_compute_size (GtkBoxLayout *self,
                              GtkWidget    *widget,
                              int           for_size,
                              int          *minimum,
-                             int          *natural)
+                             int          *natural,
+                             int          *minimum_baseline,
+                             int          *natural_baseline)
 {
   GtkWidget *child;
   int n_visible_children = 0;
   int required_min = 0, required_nat = 0;
   int largest_min = 0, largest_nat = 0;
   int spacing = get_spacing (self, gtk_widget_get_css_node (widget));
+  int child_above_min = 0, child_above_nat = 0;
+  int above_min = 0, above_nat = 0;
+  gboolean have_baseline = FALSE;
+  int pos;
 
-  for (child = gtk_widget_get_first_child (widget);
+  for (child = gtk_widget_get_first_child (widget), pos = 0;
        child != NULL;
-       child = gtk_widget_get_next_sibling (child))
+       child = gtk_widget_get_next_sibling (child), pos++)
     {
       int child_min = 0;
       int child_nat = 0;
+      int child_min_baseline = -1;
+      int child_nat_baseline = -1;
 
       if (!gtk_widget_should_layout (child))
         continue;
@@ -225,7 +243,7 @@ gtk_box_layout_compute_size (GtkBoxLayout *self,
       gtk_widget_measure (child, self->orientation,
                           for_size,
                           &child_min, &child_nat,
-                          NULL, NULL);
+                          &child_min_baseline, &child_nat_baseline);
 
       largest_min = MAX (largest_min, child_min);
       largest_nat = MAX (largest_nat, child_nat);
@@ -233,6 +251,29 @@ gtk_box_layout_compute_size (GtkBoxLayout *self,
       required_min += child_min;
       required_nat += child_nat;
 
+      if (self->orientation == GTK_ORIENTATION_VERTICAL)
+        {
+          if (pos < self->baseline_child)
+            {
+              above_min += child_min;
+              above_nat += child_nat;
+            }
+          else if (pos == self->baseline_child)
+            {
+              have_baseline = TRUE;
+              if (child_min_baseline > -1)
+                {
+                  child_above_min = child_min_baseline;
+                  child_above_nat = child_nat_baseline;
+                }
+              else
+                {
+                  child_above_min = child_min;
+                  child_above_nat = child_nat;
+                }
+            }
+        }
+
       n_visible_children += 1;
     }
 
@@ -242,14 +283,31 @@ gtk_box_layout_compute_size (GtkBoxLayout *self,
         {
           required_min = largest_min * n_visible_children;
           required_nat = largest_nat * n_visible_children;
+
+          above_min = largest_min * MAX (self->baseline_child, 0);
+          above_nat = largest_nat * MAX (self->baseline_child, 0);
         }
 
       required_min += (n_visible_children - 1) * spacing;
       required_nat += (n_visible_children - 1) * spacing;
+
+      above_min += MAX (self->baseline_child, 0) * spacing;
+      above_nat += MAX (self->baseline_child, 0) * spacing;
     }
 
   *minimum = required_min;
   *natural = required_nat;
+
+  if (have_baseline)
+    {
+      *minimum_baseline = above_min + child_above_min;
+      *natural_baseline = above_nat + child_above_nat;
+    }
+  else
+    {
+      *minimum_baseline = -1;
+      *natural_baseline = -1;
+    }
 }
 
 static void
@@ -264,6 +322,7 @@ gtk_box_layout_compute_opposite_size (GtkBoxLayout *self,
   int largest_min = 0, largest_nat = 0;
   int largest_min_above = -1, largest_min_below = -1;
   int largest_nat_above = -1, largest_nat_below = -1;
+  gboolean have_baseline = FALSE;
 
   for (child = gtk_widget_get_first_child (widget);
        child != NULL;
@@ -290,6 +349,7 @@ gtk_box_layout_compute_opposite_size (GtkBoxLayout *self,
         {
           if (child_min_baseline > -1)
             {
+              have_baseline = TRUE;
               largest_min_above = MAX (largest_min_above, child_min_baseline);
               largest_min_below = MAX (largest_min_below, child_min - child_min_baseline);
               largest_nat_above = MAX (largest_nat_above, child_nat_baseline);
@@ -307,8 +367,16 @@ gtk_box_layout_compute_opposite_size (GtkBoxLayout *self,
   *minimum = largest_min;
   *natural = largest_nat;
 
-  *min_baseline = largest_min_above;
-  *nat_baseline = largest_nat_above;
+  if (have_baseline)
+    {
+      *min_baseline = largest_min_above;
+      *nat_baseline = largest_nat_above;
+    }
+  else
+    {
+      *min_baseline = -1;
+      *nat_baseline = -1;
+    }
 }
 
 /* if widgets haven't reached their min opposite size at this
@@ -469,13 +537,21 @@ gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self,
                               &child_minimum, &child_natural,
                               &child_minimum_baseline, &child_natural_baseline);
 
-          if (child_minimum_baseline >= 0)
+          if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
             {
-              have_baseline = TRUE;
-              computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
-              computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
-              computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
-              computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
+              if (child_minimum_baseline > -1)
+                {
+                  have_baseline = TRUE;
+                  computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
+                  computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
+                  computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
+                  computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
+                }
+              else
+                {
+                  computed_minimum = MAX (computed_minimum, child_minimum);
+                  computed_natural = MAX (computed_natural, child_natural);
+                }
             }
           else
             {
@@ -585,13 +661,21 @@ gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self,
                               &child_minimum, &child_natural,
                               &child_minimum_baseline, &child_natural_baseline);
 
-          if (child_minimum_baseline >= 0)
+          if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
             {
-              have_baseline = TRUE;
-              computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
-              computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
-              computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
-              computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
+              if (child_minimum_baseline > -1)
+                {
+                  have_baseline = TRUE;
+                  computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
+                  computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
+                  computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
+                  computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
+                }
+              else
+                {
+                  computed_minimum = MAX (computed_minimum, child_minimum);
+                  computed_natural = MAX (computed_natural, child_natural);
+                }
             }
           else
             {
@@ -603,24 +687,27 @@ gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self,
 
   if (have_baseline)
     {
-      computed_minimum = MAX (computed_minimum, computed_minimum_below + computed_minimum_above);
-      computed_natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
-      switch (self->baseline_position)
+      if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
         {
-        case GTK_BASELINE_POSITION_TOP:
-          computed_minimum_baseline = computed_minimum_above;
-          computed_natural_baseline = computed_natural_above;
-          break;
-        case GTK_BASELINE_POSITION_CENTER:
-          computed_minimum_baseline = computed_minimum_above + MAX((computed_minimum - (computed_minimum_above + computed_minimum_below)) / 2, 0);
-          computed_natural_baseline = computed_natural_above + MAX((computed_natural - (computed_natural_above + computed_natural_below)) / 2, 0);
-          break;
-        case GTK_BASELINE_POSITION_BOTTOM:
-          computed_minimum_baseline = computed_minimum - computed_minimum_below;
-          computed_natural_baseline = computed_natural - computed_natural_below;
-          break;
-        default:
-          break;
+          computed_minimum = MAX (computed_minimum, computed_minimum_below + computed_minimum_above);
+          computed_natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
+          switch (self->baseline_position)
+            {
+            case GTK_BASELINE_POSITION_TOP:
+              computed_minimum_baseline = computed_minimum_above;
+              computed_natural_baseline = computed_natural_above;
+            break;
+            case GTK_BASELINE_POSITION_CENTER:
+              computed_minimum_baseline = computed_minimum_above + MAX((computed_minimum - (computed_minimum_above + computed_minimum_below)) / 2, 0);
+              computed_natural_baseline = computed_natural_above + MAX((computed_natural - (computed_natural_above + computed_natural_below)) / 2, 0);
+              break;
+            case GTK_BASELINE_POSITION_BOTTOM:
+              computed_minimum_baseline = computed_minimum - computed_minimum_below;
+              computed_natural_baseline = computed_natural - computed_natural_below;
+              break;
+            default:
+              break;
+            }
         }
     }
 
@@ -660,7 +747,8 @@ gtk_box_layout_measure (GtkLayoutManager *layout_manager,
   else
     {
       gtk_box_layout_compute_size (self, widget, for_size,
-                                   minimum, natural);
+                                   minimum, natural,
+                                   min_baseline, nat_baseline);
     }
 }
 
@@ -735,7 +823,6 @@ gtk_box_layout_allocate (GtkLayoutManager *layout_manager,
       /* We still need to run the above loop to populate the minimum sizes for
        * children that aren't going to fill.
        */
-
       size_given_to_child = extra_space / nvis_children;
       n_extra_widgets = extra_space % nvis_children;
     }
@@ -935,6 +1022,24 @@ gtk_box_layout_class_init (GtkBoxLayoutClass *klass)
                       GTK_PARAM_READWRITE |
                       G_PARAM_EXPLICIT_NOTIFY);
 
+  /**
+   * GtkBoxLayout:baseline-child: (attributes org.gtk.Property.get=gtk_box_layout_get_baseline_child org.gtk.Property.set=gtk_box_layout_set_baseline_child)
+   *
+   * The child that determines the baseline of the box
+   * in vertical layout.
+   *
+   * If the child does baseline positioning, then its baseline
+   * is lined up with the baseline of the box. If it doesn't, then
+   * the bottom edge of the child is used.
+   *
+   * Since: 4.12
+   */
+  box_layout_props[PROP_BASELINE_CHILD] =
+    g_param_spec_int ("baseline-child", NULL, NULL,
+                      -1, G_MAXINT, -1,
+                      GTK_PARAM_READWRITE |
+                      G_PARAM_EXPLICIT_NOTIFY);
+
   /**
    * GtkBoxLayout:baseline-position: (attributes org.gtk.Property.get=gtk_box_layout_get_baseline_position org.gtk.Property.set=gtk_box_layout_set_baseline_position)
    *
@@ -962,6 +1067,7 @@ gtk_box_layout_init (GtkBoxLayout *self)
   self->spacing = 0;
   self->orientation = GTK_ORIENTATION_HORIZONTAL;
   self->baseline_position = GTK_BASELINE_POSITION_CENTER;
+  self->baseline_child = -1;
 }
 
 /**
@@ -1102,3 +1208,49 @@ gtk_box_layout_get_baseline_position (GtkBoxLayout *box_layout)
 
   return box_layout->baseline_position;
 }
+
+/**
+ * gtk_box_layout_set_baseline_child: (attributes org.gtk.Method.set_property=baseline-child)
+ * @box_layout: a `GtkBoxLayout`
+ * @child: the child position, or -1
+ *
+ * Sets the index of the child that determines the baseline
+ * in vertical layout.
+ *
+ * Since: 4.12
+ */
+void
+gtk_box_layout_set_baseline_child (GtkBoxLayout *box_layout,
+                                   int           child)
+{
+  g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
+  g_return_if_fail (child >= -1);
+
+  if (box_layout->baseline_child == child)
+    return;
+
+  box_layout->baseline_child = child;
+
+  g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_BASELINE_CHILD]);
+
+  gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
+}
+
+/**
+ * gtk_box_layout_get_baseline_child:
+ * @box_layout: a `GtkBoxLayout`
+ *
+ * Gets the value set by gtk_box_layout_set_baseline_child().
+ *
+ * Returns: the index of the child that determines the baseline
+ *     in vertical layout, or -1
+ *
+ * Since: 4.12
+ */
+int
+gtk_box_layout_get_baseline_child (GtkBoxLayout *box_layout)
+{
+  g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), -1);
+
+  return box_layout->baseline_child;
+}
index 8d394219b73b007ffd658828086ddb9cebe12cff..02cb2d461bd03b8bf2dd16d64fedbd96782b6c1a 100644 (file)
@@ -51,4 +51,10 @@ void                    gtk_box_layout_set_baseline_position    (GtkBoxLayout
 GDK_AVAILABLE_IN_ALL
 GtkBaselinePosition     gtk_box_layout_get_baseline_position    (GtkBoxLayout        *box_layout);
 
+GDK_AVAILABLE_IN_4_12
+void                    gtk_box_layout_set_baseline_child       (GtkBoxLayout        *box_layout,
+                                                                 int                  child);
+GDK_AVAILABLE_IN_4_12
+int                     gtk_box_layout_get_baseline_child       (GtkBoxLayout        *box_layout);
+
 G_END_DECLS