From 852cbb62b8b98bb3345604ce1dcf4186613f74e8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 5 Mar 2013 14:54:03 +0100 Subject: [PATCH] Initial support for baselines This modifies the size machinery in order to allow baseline support. We add a new widget vfunc get_preferred_height_and_baseline_for_width which queries the normal height_for_width (or non-for-width if width is -1) and additionally returns optional (-1 means "no baseline") baselines for the minimal and natural heights. We also add a new gtk_widget_size_allocate_with_baseline() which baseline-aware containers can use to allocate children with a specific baseline, either one inherited from the parent, or one introduced due to requested baseline alignment in the container itself. size_allocate_with_baseline() works just like a normal size allocation, except the baseline gets recorded so that the child can access it via gtk_widget_get_allocated_baseline() when it aligns itself. There are also adjust_baseline_request/allocation similar to the allocation adjustment, and we extend the size request cache to also store the baselines. --- gtk/gtkcontainer.c | 50 +++++ gtk/gtksizerequest.c | 306 +++++++++++++++++++++++-------- gtk/gtksizerequestcache.c | 18 +- gtk/gtksizerequestcacheprivate.h | 10 +- gtk/gtkwidget.c | 203 ++++++++++++++++++-- gtk/gtkwidget.h | 35 +++- gtk/gtkwidgetprivate.h | 4 +- 7 files changed, 523 insertions(+), 103 deletions(-) diff --git a/gtk/gtkcontainer.c b/gtk/gtkcontainer.c index ce16fe4df4..09bfffabd1 100644 --- a/gtk/gtkcontainer.c +++ b/gtk/gtkcontainer.c @@ -309,12 +309,17 @@ static void gtk_container_adjust_size_request (GtkWidget *widget, GtkOrientation orientation, gint *minimum_size, gint *natural_size); +static void gtk_container_adjust_baseline_request (GtkWidget *widget, + gint *minimum_baseline, + gint *natural_baseline); static void gtk_container_adjust_size_allocation (GtkWidget *widget, GtkOrientation orientation, gint *minimum_size, gint *natural_size, gint *allocated_pos, gint *allocated_size); +static void gtk_container_adjust_baseline_allocation (GtkWidget *widget, + gint *baseline); static GtkSizeRequestMode gtk_container_get_request_mode (GtkWidget *widget); static gchar* gtk_container_child_default_composite_name (GtkContainer *container, @@ -444,7 +449,9 @@ gtk_container_class_init (GtkContainerClass *class) widget_class->focus = gtk_container_focus; widget_class->adjust_size_request = gtk_container_adjust_size_request; + widget_class->adjust_baseline_request = gtk_container_adjust_baseline_request; widget_class->adjust_size_allocation = gtk_container_adjust_size_allocation; + widget_class->adjust_baseline_allocation = gtk_container_adjust_baseline_allocation; widget_class->get_request_mode = gtk_container_get_request_mode; class->add = gtk_container_add_unimplemented; @@ -1917,6 +1924,28 @@ gtk_container_adjust_size_request (GtkWidget *widget, minimum_size, natural_size); } +static void +gtk_container_adjust_baseline_request (GtkWidget *widget, + gint *minimum_baseline, + gint *natural_baseline) +{ + GtkContainer *container; + + container = GTK_CONTAINER (widget); + + if (GTK_CONTAINER_GET_CLASS (widget)->_handle_border_width) + { + int border_width; + + border_width = container->priv->border_width; + + *minimum_baseline += border_width; + *natural_baseline += border_width; + } + + parent_class->adjust_baseline_request (widget, minimum_baseline, natural_baseline); +} + static void gtk_container_adjust_size_allocation (GtkWidget *widget, GtkOrientation orientation, @@ -1952,6 +1981,27 @@ gtk_container_adjust_size_allocation (GtkWidget *widget, allocated_size); } +static void +gtk_container_adjust_baseline_allocation (GtkWidget *widget, + gint *baseline) +{ + GtkContainer *container; + int border_width; + + container = GTK_CONTAINER (widget); + + if (GTK_CONTAINER_GET_CLASS (widget)->_handle_border_width) + { + border_width = container->priv->border_width; + + if (*baseline >= 0) + *baseline -= border_width; + } + + parent_class->adjust_baseline_allocation (widget, baseline); +} + + typedef struct { gint hfw; gint wfh; diff --git a/gtk/gtksizerequest.c b/gtk/gtksizerequest.c index 36d24f2f0c..4e47d88420 100644 --- a/gtk/gtksizerequest.c +++ b/gtk/gtksizerequest.c @@ -98,11 +98,16 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget, GtkOrientation orientation, gint for_size, gint *minimum_size, - gint *natural_size) + gint *natural_size, + gint *minimum_baseline, + gint *natural_baseline) { SizeRequestCache *cache; + GtkWidgetClass *widget_class; gint min_size = 0; gint nat_size = 0; + gint min_baseline = -1; + gint nat_baseline = -1; gboolean found_in_cache; if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_CONSTANT_SIZE) @@ -113,7 +118,11 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget, orientation, for_size, &min_size, - &nat_size); + &nat_size, + &min_baseline, + &nat_baseline); + + widget_class = GTK_WIDGET_GET_CLASS (widget); if (!found_in_cache) { @@ -128,7 +137,7 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget, if (for_size < 0) { push_recursion_check (widget, orientation, for_size); - GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_size, &nat_size); + widget_class->get_preferred_width (widget, &min_size, &nat_size); pop_recursion_check (widget, orientation); } else @@ -142,17 +151,17 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget, gtk_widget_get_preferred_height (widget, &minimum_height, &natural_height); /* convert for_size to unadjusted height (for_size is a proposed allocation) */ - GTK_WIDGET_GET_CLASS (widget)->adjust_size_allocation (widget, - GTK_ORIENTATION_VERTICAL, - &minimum_height, - &natural_height, - &ignored_position, - &adjusted_for_size); + widget_class->adjust_size_allocation (widget, + GTK_ORIENTATION_VERTICAL, + &minimum_height, + &natural_height, + &ignored_position, + &adjusted_for_size); push_recursion_check (widget, orientation, for_size); - GTK_WIDGET_GET_CLASS (widget)->get_preferred_width_for_height (widget, - MAX (adjusted_for_size, minimum_height), - &min_size, &nat_size); + widget_class->get_preferred_width_for_height (widget, + MAX (adjusted_for_size, minimum_height), + &min_size, &nat_size); pop_recursion_check (widget, orientation); } } @@ -161,7 +170,9 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget, if (for_size < 0) { push_recursion_check (widget, orientation, for_size); - GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_size, &nat_size); + widget_class->get_preferred_height_and_baseline_for_width (widget, -1, + &min_size, &nat_size, + &min_baseline, &nat_baseline); pop_recursion_check (widget, orientation); } else @@ -175,17 +186,17 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget, gtk_widget_get_preferred_width (widget, &minimum_width, &natural_width); /* convert for_size to unadjusted width (for_size is a proposed allocation) */ - GTK_WIDGET_GET_CLASS (widget)->adjust_size_allocation (widget, - GTK_ORIENTATION_HORIZONTAL, - &minimum_width, - &natural_width, - &ignored_position, - &adjusted_for_size); + widget_class->adjust_size_allocation (widget, + GTK_ORIENTATION_HORIZONTAL, + &minimum_width, + &natural_width, + &ignored_position, + &adjusted_for_size); push_recursion_check (widget, orientation, for_size); - GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, - MAX (adjusted_for_size, minimum_width), - &min_size, &nat_size); + widget_class->get_preferred_height_and_baseline_for_width (widget, MAX (adjusted_for_size, minimum_width), + &min_size, &nat_size, + &min_baseline, &nat_baseline); pop_recursion_check (widget, orientation); } } @@ -198,10 +209,10 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget, adjusted_min = min_size; adjusted_natural = nat_size; - GTK_WIDGET_GET_CLASS (widget)->adjust_size_request (widget, - orientation, - &adjusted_min, - &adjusted_natural); + widget_class->adjust_size_request (widget, + orientation, + &adjusted_min, + &adjusted_natural); if (adjusted_min < min_size || adjusted_natural < nat_size) @@ -229,11 +240,42 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget, nat_size = adjusted_natural; } + if (min_baseline != -1 || nat_baseline != -1) + { + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + g_warning ("%s %p reported a horizontal baseline", + G_OBJECT_TYPE_NAME (widget), widget); + min_baseline = -1; + nat_baseline = -1; + } + else if (min_baseline == -1 || nat_baseline == -1) + { + g_warning ("%s %p reported baseline for only one of min/natural (min: %d, natural: %d)", + G_OBJECT_TYPE_NAME (widget), widget, + min_baseline, nat_baseline); + min_baseline = -1; + nat_baseline = -1; + } + else if (gtk_widget_get_valign_with_baseline (widget) != GTK_ALIGN_BASELINE) + { + /* Ignore requested baseline for non-aligned widgets */ + min_baseline = -1; + nat_baseline = -1; + } + else + widget_class->adjust_baseline_request (widget, + &min_baseline, + &nat_baseline); + } + _gtk_size_request_cache_commit (cache, orientation, for_size, min_size, - nat_size); + nat_size, + min_baseline, + nat_baseline); } if (minimum_size) @@ -242,15 +284,26 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget, if (natural_size) *natural_size = nat_size; + if (minimum_baseline) + *minimum_baseline = min_baseline; + + if (natural_baseline) + *natural_baseline = nat_baseline; + g_assert (min_size <= nat_size); GTK_NOTE (SIZE_REQUEST, - g_print ("[%p] %s\t%s: %d is minimum %d and natural: %d (hit cache: %s)\n", + g_print ("[%p] %s\t%s: %d is minimum %d and natural: %d", widget, G_OBJECT_TYPE_NAME (widget), orientation == GTK_ORIENTATION_HORIZONTAL ? "width for height" : "height for width" , - for_size, min_size, nat_size, - found_in_cache ? "yes" : "no")); + for_size, min_size, nat_size); + if (min_baseline != -1 || nat_baseline != -1) + g_print (", baseline %d/%d", + min_baseline, nat_baseline); + g_print (" (hit cache: %s)\n", + found_in_cache ? "yes" : "no") + ); } /* This is the main function that checks for a cached size and @@ -263,7 +316,9 @@ _gtk_widget_compute_size_for_orientation (GtkWidget *widget, GtkOrientation orientation, gint for_size, gint *minimum, - gint *natural) + gint *natural, + gint *minimum_baseline, + gint *natural_baseline) { GHashTable *widgets; GHashTableIter iter; @@ -276,12 +331,17 @@ _gtk_widget_compute_size_for_orientation (GtkWidget *widget, *minimum = 0; if (natural) *natural = 0; + if (minimum_baseline) + *minimum_baseline = -1; + if (natural_baseline) + *natural_baseline = -1; return; } if (G_LIKELY (!_gtk_widget_get_sizegroups (widget))) { - gtk_widget_query_size_for_orientation (widget, orientation, for_size, minimum, natural); + gtk_widget_query_size_for_orientation (widget, orientation, for_size, minimum, natural, + minimum_baseline, natural_baseline); return; } @@ -295,7 +355,7 @@ _gtk_widget_compute_size_for_orientation (GtkWidget *widget, GtkWidget *tmp_widget = key; gint min_dimension, nat_dimension; - gtk_widget_query_size_for_orientation (tmp_widget, orientation, for_size, &min_dimension, &nat_dimension); + gtk_widget_query_size_for_orientation (tmp_widget, orientation, for_size, &min_dimension, &nat_dimension, NULL, NULL); min_result = MAX (min_result, min_dimension); nat_result = MAX (nat_result, nat_dimension); @@ -305,6 +365,13 @@ _gtk_widget_compute_size_for_orientation (GtkWidget *widget, g_hash_table_destroy (widgets); + /* Baselines make no sense with sizegroups really */ + if (minimum_baseline) + *minimum_baseline = -1; + + if (natural_baseline) + *natural_baseline = -1; + if (minimum) *minimum = min_result; @@ -377,7 +444,8 @@ gtk_widget_get_preferred_width (GtkWidget *widget, GTK_ORIENTATION_HORIZONTAL, -1, minimum_width, - natural_width); + natural_width, + NULL, NULL); } @@ -411,7 +479,8 @@ gtk_widget_get_preferred_height (GtkWidget *widget, GTK_ORIENTATION_VERTICAL, -1, minimum_height, - natural_height); + natural_height, + NULL, NULL); } @@ -448,7 +517,8 @@ gtk_widget_get_preferred_width_for_height (GtkWidget *widget, GTK_ORIENTATION_HORIZONTAL, height, minimum_width, - natural_width); + natural_width, + NULL, NULL); } /** @@ -483,7 +553,122 @@ gtk_widget_get_preferred_height_for_width (GtkWidget *widget, GTK_ORIENTATION_VERTICAL, width, minimum_height, - natural_height); + natural_height, + NULL, NULL); +} + +/** + * gtk_widget_get_preferred_height_and_baseline_for_width: + * @widget: a #GtkWidget instance + * @width: the width which is available for allocation, or -1 if none + * @minimum_height: (out) (allow-none): location for storing the minimum height, or %NULL + * @natural_height: (out) (allow-none): location for storing the natural height, or %NULL + * @minimum_baseline: (out) (allow-none): location for storing the baseline for the minimum height, or %NULL + * @natural_baseline: (out) (allow-none): location for storing the baseline for the natural height, or %NULL + * + * Retrieves a widget's minimum and natural height and the corresponding baselines if it would be given + * the specified @width, or the default height if @width is -1. The baselines may be -1 which means + * that no baseline is requested for this widget. + * + * The returned request will be modified by the + * GtkWidgetClass::adjust_size_request and GtkWidgetClass::adjust_baseline_request virtual methods + * and by any #GtkSizeGroups that have been applied. That is, the returned request + * is the one that should be used for layout, not necessarily the one + * returned by the widget itself. + * + * Since: 3.10 + */ +void +gtk_widget_get_preferred_height_and_baseline_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height, + gint *minimum_baseline, + gint *natural_baseline) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (minimum_height != NULL || natural_height != NULL); + g_return_if_fail (width >= -1); + + _gtk_widget_compute_size_for_orientation (widget, + GTK_ORIENTATION_VERTICAL, + width, + minimum_height, + natural_height, + minimum_baseline, + natural_baseline); +} + +/** + * gtk_widget_get_preferred_size_and_baseline: + * @widget: a #GtkWidget instance + * @minimum_size: (out) (allow-none): location for storing the minimum size, or %NULL + * @natural_size: (out) (allow-none): location for storing the natural size, or %NULL + * + * Retrieves the minimum and natural size and the corresponding baselines of a widget, taking + * into account the widget's preference for height-for-width management. The baselines may + * be -1 which means that no baseline is requested for this widget. + * + * This is used to retrieve a suitable size by container widgets which do + * not impose any restrictions on the child placement. It can be used + * to deduce toplevel window and menu sizes as well as child widgets in + * free-form containers such as GtkLayout. + * + * Handle with care. Note that the natural height of a height-for-width + * widget will generally be a smaller size than the minimum height, since the required + * height for the natural width is generally smaller than the required height for + * the minimum width. + * + * Since: 3.10 + */ +void +gtk_widget_get_preferred_size_and_baseline (GtkWidget *widget, + GtkRequisition *minimum_size, + GtkRequisition *natural_size, + gint *minimum_baseline, + gint *natural_baseline) +{ + gint min_width, nat_width; + gint min_height, nat_height; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) + { + gtk_widget_get_preferred_width (widget, &min_width, &nat_width); + + if (minimum_size) + { + minimum_size->width = min_width; + gtk_widget_get_preferred_height_and_baseline_for_width (widget, min_width, + &minimum_size->height, NULL, minimum_baseline, NULL); + } + + if (natural_size) + { + natural_size->width = nat_width; + gtk_widget_get_preferred_height_and_baseline_for_width (widget, nat_width, + NULL, &natural_size->height, NULL, natural_baseline); + } + } + else /* GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT or CONSTANT_SIZE */ + { + gtk_widget_get_preferred_height_and_baseline_for_width (widget, -1, &min_height, &nat_height, minimum_baseline, natural_baseline); + + if (minimum_size) + { + minimum_size->height = min_height; + gtk_widget_get_preferred_width_for_height (widget, min_height, + &minimum_size->width, NULL); + } + + if (natural_size) + { + natural_size->height = nat_height; + gtk_widget_get_preferred_width_for_height (widget, nat_height, + NULL, &natural_size->width); + } + } } /** @@ -505,6 +690,9 @@ gtk_widget_get_preferred_height_for_width (GtkWidget *widget, * height for the natural width is generally smaller than the required height for * the minimum width. * + * Use gtk_widget_get_preferred_size_and_baseline() if you want to support + * baseline alignment. + * * Since: 3.0 */ void @@ -512,50 +700,10 @@ gtk_widget_get_preferred_size (GtkWidget *widget, GtkRequisition *minimum_size, GtkRequisition *natural_size) { - gint min_width, nat_width; - gint min_height, nat_height; - - g_return_if_fail (GTK_IS_WIDGET (widget)); - - if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) - { - gtk_widget_get_preferred_width (widget, &min_width, &nat_width); - - if (minimum_size) - { - minimum_size->width = min_width; - gtk_widget_get_preferred_height_for_width (widget, min_width, - &minimum_size->height, NULL); - } - - if (natural_size) - { - natural_size->width = nat_width; - gtk_widget_get_preferred_height_for_width (widget, nat_width, - NULL, &natural_size->height); - } - } - else /* GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT or CONSTANT_SIZE */ - { - gtk_widget_get_preferred_height (widget, &min_height, &nat_height); - - if (minimum_size) - { - minimum_size->height = min_height; - gtk_widget_get_preferred_width_for_height (widget, min_height, - &minimum_size->width, NULL); - } - - if (natural_size) - { - natural_size->height = nat_height; - gtk_widget_get_preferred_width_for_height (widget, nat_height, - NULL, &natural_size->width); - } - } + gtk_widget_get_preferred_size_and_baseline (widget, minimum_size, natural_size, + NULL, NULL); } - static gint compare_gap (gconstpointer p1, gconstpointer p2, diff --git a/gtk/gtksizerequestcache.c b/gtk/gtksizerequestcache.c index 102e2ddd99..e6107c7cb1 100644 --- a/gtk/gtksizerequestcache.c +++ b/gtk/gtksizerequestcache.c @@ -67,7 +67,9 @@ _gtk_size_request_cache_commit (SizeRequestCache *cache, GtkOrientation orientation, gint for_size, gint minimum_size, - gint natural_size) + gint natural_size, + gint minimum_baseline, + gint natural_baseline) { SizeRequest **cached_sizes; SizeRequest *cached_size; @@ -78,6 +80,8 @@ _gtk_size_request_cache_commit (SizeRequestCache *cache, { cache->cached_size[orientation].minimum_size = minimum_size; cache->cached_size[orientation].natural_size = natural_size; + cache->cached_size[orientation].minimum_baseline = minimum_baseline; + cache->cached_size[orientation].natural_baseline = natural_baseline; cache->flags[orientation].cached_size_valid = TRUE; return; } @@ -92,7 +96,9 @@ _gtk_size_request_cache_commit (SizeRequestCache *cache, for (i = 0; i < n_sizes; i++) { if (cached_sizes[i]->cached_size.minimum_size == minimum_size && - cached_sizes[i]->cached_size.natural_size == natural_size) + cached_sizes[i]->cached_size.natural_size == natural_size && + cached_sizes[i]->cached_size.minimum_baseline == minimum_baseline && + cached_sizes[i]->cached_size.natural_baseline == natural_baseline) { cached_sizes[i]->lower_for_size = MIN (cached_sizes[i]->lower_for_size, for_size); cached_sizes[i]->upper_for_size = MAX (cached_sizes[i]->upper_for_size, for_size); @@ -125,6 +131,8 @@ _gtk_size_request_cache_commit (SizeRequestCache *cache, cached_size->upper_for_size = for_size; cached_size->cached_size.minimum_size = minimum_size; cached_size->cached_size.natural_size = natural_size; + cached_size->cached_size.minimum_baseline = minimum_baseline; + cached_size->cached_size.natural_baseline = natural_baseline; } /* looks for a cached size request for this for_size. @@ -137,7 +145,9 @@ _gtk_size_request_cache_lookup (SizeRequestCache *cache, GtkOrientation orientation, gint for_size, gint *minimum, - gint *natural) + gint *natural, + gint *minimum_baseline, + gint *natural_baseline) { CachedSize *result = NULL; @@ -168,6 +178,8 @@ _gtk_size_request_cache_lookup (SizeRequestCache *cache, { *minimum = result->minimum_size; *natural = result->natural_size; + *minimum_baseline = result->minimum_baseline; + *natural_baseline = result->natural_baseline; return TRUE; } else diff --git a/gtk/gtksizerequestcacheprivate.h b/gtk/gtksizerequestcacheprivate.h index ac851dde74..e458428021 100644 --- a/gtk/gtksizerequestcacheprivate.h +++ b/gtk/gtksizerequestcacheprivate.h @@ -41,6 +41,8 @@ G_BEGIN_DECLS typedef struct { gint minimum_size; gint natural_size; + gint minimum_baseline; + gint natural_baseline; } CachedSize; typedef struct @@ -72,12 +74,16 @@ void _gtk_size_request_cache_commit (SizeRequestCach GtkOrientation orientation, gint for_size, gint minimum_size, - gint natural_size); + gint natural_size, + gint minimum_baseline, + gint natural_baseline); gboolean _gtk_size_request_cache_lookup (SizeRequestCache *cache, GtkOrientation orientation, gint for_size, gint *minimum, - gint *natural); + gint *natural, + gint *minimum_baseline, + gint *natural_baseline); G_END_DECLS diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index d1c9095bc1..bbfdefad4b 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -95,6 +95,7 @@ * #GtkWidgetClass.get_preferred_height() * #GtkWidgetClass.get_preferred_height_for_width() * #GtkWidgetClass.get_preferred_width_for_height() + * #GtkWidgetClass.get_preferred_height_and_baseline_for_width() * * * There are some important things to keep in mind when implementing @@ -222,6 +223,26 @@ * container, you must use the wrapper APIs. * Otherwise, you would not properly consider widget margins, * #GtkSizeGroup, and so forth. + * + * Since 3.10 Gtk+ also supports baseline vertical alignment of widgets. This + * means that widgets are positioned such that the typographical baseline of + * widgets in the same row are aligned. This happens if a widget supports baselines, + * has a vertical alignment of %GTK_ALIGN_BASELINE, and is inside a container + * that supports baselines and has a natural "row" that it aligns to the baseline, + * or a baseline assigned to it by the grandparent. + * + * Baseline alignment support for a widget is done by the #GtkWidgetClass.get_preferred_height_and_baseline_for_width() + * virtual function. It allows you to report a baseline in combination with the + * minimum and natural height. If there is no baseline you can return -1 to indicate + * this. The default implementation of this virtual function calls into the + * #GtkWidgetClass.get_preferred_height() and #GtkWidgetClass.get_preferred_height_for_width(), + * so if baselines are not supported it doesn't need to be implemented. + * + * If a widget ends up baseline aligned it will be allocated all the space in the parent + * as if it was %GTK_ALIGN_FILL, but the selected baseline can be found via gtk_widget_get_allocated_baseline(). + * If this has a value other than -1 you need to align the widget such that the baseline + * appears at the position. + * * * * @@ -478,6 +499,7 @@ struct _GtkWidgetPrivate /* The widget's allocated size */ GtkAllocation allocation; + gint allocated_baseline; /* The widget's requested sizes */ SizeRequestCache requests; @@ -777,6 +799,12 @@ static void gtk_widget_real_get_width (GtkWidget static void gtk_widget_real_get_height (GtkWidget *widget, gint *minimum_size, gint *natural_size); +static void gtk_widget_real_get_preferred_height_and_baseline_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height, + gint *minimum_baseline, + gint *natural_baseline); static void gtk_widget_queue_tooltip_query (GtkWidget *widget); @@ -785,12 +813,17 @@ static void gtk_widget_real_adjust_size_request (GtkWidget GtkOrientation orientation, gint *minimum_size, gint *natural_size); +static void gtk_widget_real_adjust_baseline_request (GtkWidget *widget, + gint *minimum_baseline, + gint *natural_baseline); static void gtk_widget_real_adjust_size_allocation (GtkWidget *widget, GtkOrientation orientation, gint *minimum_size, gint *natural_size, gint *allocated_pos, gint *allocated_size); +static void gtk_widget_real_adjust_baseline_allocation (GtkWidget *widget, + gint *baseline); /* --- functions dealing with template data structures --- */ static AutomaticChildClass *automatic_child_class_new (const gchar *name, @@ -1091,6 +1124,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) klass->get_preferred_height = gtk_widget_real_get_height; klass->get_preferred_width_for_height = gtk_widget_real_get_width_for_height; klass->get_preferred_height_for_width = gtk_widget_real_get_height_for_width; + klass->get_preferred_height_and_baseline_for_width = gtk_widget_real_get_preferred_height_and_baseline_for_width; klass->state_changed = NULL; klass->state_flags_changed = gtk_widget_real_state_flags_changed; klass->parent_set = NULL; @@ -1150,7 +1184,9 @@ gtk_widget_class_init (GtkWidgetClass *klass) klass->get_accessible = gtk_widget_real_get_accessible; klass->adjust_size_request = gtk_widget_real_adjust_size_request; + klass->adjust_baseline_request = gtk_widget_real_adjust_baseline_request; klass->adjust_size_allocation = gtk_widget_real_adjust_size_allocation; + klass->adjust_baseline_allocation = gtk_widget_real_adjust_baseline_allocation; g_object_class_install_property (gobject_class, PROP_NAME, @@ -5306,22 +5342,31 @@ gtk_widget_invalidate_widget_windows (GtkWidget *widget, } /** - * gtk_widget_size_allocate: + * gtk_widget_size_allocate_with_baseline: * @widget: a #GtkWidget * @allocation: position and size to be allocated to @widget + * @baseline: The baseline of the child, or -1 * - * This function is only used by #GtkContainer subclasses, to assign a size - * and position to their child widgets. + * This function is only used by #GtkContainer subclasses, to assign a size, + * position and (optionally) baseline to their child widgets. * - * In this function, the allocation may be adjusted. It will be forced - * to a 1x1 minimum size, and the adjust_size_allocation virtual - * method on the child will be used to adjust the allocation. Standard - * adjustments include removing the widget's margins, and applying the - * widget's #GtkWidget:halign and #GtkWidget:valign properties. + * In this function, the allocation and baseline may be adjusted. It + * will be forced to a 1x1 minimum size, and the + * adjust_size_allocation virtual and adjust_baseline_allocation + * methods on the child will be used to adjust the allocation and + * baseline. Standard adjustments include removing the widget's + * margins, and applying the widget's #GtkWidget:halign and + * #GtkWidget:valign properties. + * + * If the child widget does not have a valign of %GTK_ALIGN_BASELINE the + * baseline argument is ignored and -1 is used instead. + * + * Since: 3.10 **/ void -gtk_widget_size_allocate (GtkWidget *widget, - GtkAllocation *allocation) +gtk_widget_size_allocate_with_baseline (GtkWidget *widget, + GtkAllocation *allocation, + gint baseline) { GtkWidgetPrivate *priv; GdkRectangle real_allocation; @@ -5329,9 +5374,11 @@ gtk_widget_size_allocate (GtkWidget *widget, GdkRectangle adjusted_allocation; gboolean alloc_needed; gboolean size_changed; + gboolean baseline_changed; gboolean position_changed; gint natural_width, natural_height, dummy; gint min_width, min_height; + gint old_baseline; priv = widget->priv; @@ -5358,17 +5405,27 @@ gtk_widget_size_allocate (GtkWidget *widget, } name = g_type_name (G_OBJECT_TYPE (G_OBJECT (widget))); - g_print ("gtk_widget_size_allocate: %*s%s %d %d\n", + g_print ("gtk_widget_size_allocate: %*s%s %d %d", 2 * depth, " ", name, allocation->width, allocation->height); + if (baseline != -1) + g_print (" baseline: %d", baseline); + g_print ("\n"); } #endif /* G_ENABLE_DEBUG */ + /* Never pass a baseline to a child unless it requested it. + This means containers don't have to manually check for this. */ + if (baseline != -1 && + gtk_widget_get_valign_with_baseline (widget) != GTK_ALIGN_BASELINE) + baseline = -1; + alloc_needed = priv->alloc_needed; /* Preserve request/allocate ordering */ priv->alloc_needed = FALSE; old_allocation = priv->allocation; + old_baseline = priv->allocated_baseline; real_allocation = *allocation; adjusted_allocation = real_allocation; @@ -5418,6 +5475,9 @@ gtk_widget_size_allocate (GtkWidget *widget, &natural_height, &adjusted_allocation.y, &adjusted_allocation.height); + if (baseline >= 0) + GTK_WIDGET_GET_CLASS (widget)->adjust_baseline_allocation (widget, + &baseline); if (adjusted_allocation.x < real_allocation.x || adjusted_allocation.y < real_allocation.y || @@ -5447,14 +5507,16 @@ gtk_widget_size_allocate (GtkWidget *widget, real_allocation.width = MAX (real_allocation.width, 1); real_allocation.height = MAX (real_allocation.height, 1); + baseline_changed = old_baseline != baseline; size_changed = (old_allocation.width != real_allocation.width || old_allocation.height != real_allocation.height); position_changed = (old_allocation.x != real_allocation.x || old_allocation.y != real_allocation.y); - if (!alloc_needed && !size_changed && !position_changed) + if (!alloc_needed && !size_changed && !position_changed && !baseline_changed) goto out; + priv->allocated_baseline = baseline; g_signal_emit (widget, widget_signals[SIZE_ALLOCATE], 0, &real_allocation); /* Size allocation is god... after consulting god, no further requests or allocations are needed */ @@ -5473,7 +5535,7 @@ gtk_widget_size_allocate (GtkWidget *widget, cairo_region_destroy (invalidate); } - if (size_changed) + if (size_changed || baseline_changed) { if (priv->redraw_on_alloc) { @@ -5488,7 +5550,7 @@ gtk_widget_size_allocate (GtkWidget *widget, } } - if ((size_changed || position_changed) && priv->parent && + if ((size_changed || position_changed || baseline_changed) && priv->parent && gtk_widget_get_realized (priv->parent) && _gtk_container_get_reallocate_redraws (GTK_CONTAINER (priv->parent))) { cairo_region_t *invalidate = cairo_region_create_rectangle (&priv->parent->priv->allocation); @@ -5500,6 +5562,31 @@ out: gtk_widget_pop_verify_invariants (widget); } + +/** + * gtk_widget_size_allocate: + * @widget: a #GtkWidget + * @allocation: position and size to be allocated to @widget + * + * This function is only used by #GtkContainer subclasses, to assign a size + * and position to their child widgets. + * + * In this function, the allocation may be adjusted. It will be forced + * to a 1x1 minimum size, and the adjust_size_allocation virtual + * method on the child will be used to adjust the allocation. Standard + * adjustments include removing the widget's margins, and applying the + * widget's #GtkWidget:halign and #GtkWidget:valign properties. + * + * For baseline support in containers you need to use gtk_widget_size_allocate_with_baseline() + * instead. + **/ +void +gtk_widget_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + gtk_widget_size_allocate_with_baseline (widget, allocation, -1); +} + /** * gtk_widget_common_ancestor: * @widget_a: a #GtkWidget @@ -5792,6 +5879,18 @@ gtk_widget_real_adjust_size_allocation (GtkWidget *widget, } } +static void +gtk_widget_real_adjust_baseline_allocation (GtkWidget *widget, + gint *baseline) +{ + const GtkWidgetAuxInfo *aux_info; + + aux_info = _gtk_widget_get_aux_info_or_defaults (widget); + + if (baseline >= 0) + *baseline -= aux_info->margin.top; +} + static gboolean gtk_widget_real_can_activate_accel (GtkWidget *widget, guint signal_id) @@ -11294,6 +11393,28 @@ gtk_widget_real_adjust_size_request (GtkWidget *widget, } } +static void +gtk_widget_real_adjust_baseline_request (GtkWidget *widget, + gint *minimum_baseline, + gint *natural_baseline) +{ + const GtkWidgetAuxInfo *aux_info; + + aux_info =_gtk_widget_get_aux_info_or_defaults (widget); + + if (aux_info->height >= 0) + { + /* No baseline support for explicitly set height */ + *minimum_baseline = -1; + *natural_baseline = -1; + } + else + { + *minimum_baseline += aux_info->margin.top; + *natural_baseline += aux_info->margin.top; + } +} + /** * _gtk_widget_peek_request_cache: * @@ -13478,6 +13599,25 @@ gtk_widget_real_get_width_for_height (GtkWidget *widget, GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, minimum_width, natural_width); } +static void +gtk_widget_real_get_preferred_height_and_baseline_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height, + gint *minimum_baseline, + gint *natural_baseline) +{ + if (width == -1) + GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, minimum_height, natural_height); + else + GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, width, minimum_height, natural_height); + + if (minimum_baseline) + *minimum_baseline = -1; + if (natural_baseline) + *natural_baseline = -1; +} + /** * gtk_widget_get_halign: * @widget: a #GtkWidget @@ -13519,6 +13659,15 @@ gtk_widget_set_halign (GtkWidget *widget, g_object_notify (G_OBJECT (widget), "halign"); } +/** + * gtk_widget_get_valign_with_baseline: + * @widget: a #GtkWidget + * + * Gets the value of the #GtkWidget:valign property, including + * %GTK_ALIGN_BASELINE. + * + * Returns: the vertical alignment of @widget + */ GtkAlign gtk_widget_get_valign_with_baseline (GtkWidget *widget) { @@ -13532,7 +13681,12 @@ gtk_widget_get_valign_with_baseline (GtkWidget *widget) * * Gets the value of the #GtkWidget:valign property. * - * Returns: the vertical alignment of @widget + * For backwards compatibility reasons this method will never return + * %GTK_ALIGN_BASELINE, but instead it will convert it to + * %GTK_ALIGN_FILL. If your widget want to support baseline aligned + * children it must use gtk_widget_get_valign_with_baseline(). + * + * Returns: the vertical alignment of @widget, ignoring baseline alignment */ GtkAlign gtk_widget_get_valign (GtkWidget *widget) @@ -14319,6 +14473,25 @@ gtk_widget_get_allocated_height (GtkWidget *widget) return widget->priv->allocation.height; } +/** + * gtk_widget_get_allocated_baseline: + * @widget: the widget to query + * + * Returns the baseline that has currently been allocated to @widget. + * This function is intended to be used when implementing handlers + * for the #GtkWidget::draw function, and when allocating child + * widgets in #GtkWidget::size_allocate. + * + * Returns: the baseline of the @widget, or -1 if none + **/ +int +gtk_widget_get_allocated_baseline (GtkWidget *widget) +{ + g_return_val_if_fail (GTK_IS_WIDGET (widget), 0); + + return widget->priv->allocated_baseline; +} + /** * gtk_widget_get_requisition: * @widget: a #GtkWidget diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index 97e9f7afdc..58bcea6355 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -433,14 +433,23 @@ struct _GtkWidgetClass gboolean (* touch_event) (GtkWidget *widget, GdkEventTouch *event); + void (* get_preferred_height_and_baseline_for_width) (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height, + gint *minimum_baseline, + gint *natural_baseline); + void (* adjust_baseline_request)(GtkWidget *widget, + gint *minimum_baseline, + gint *natural_baseline); + void (* adjust_baseline_allocation) (GtkWidget *widget, + gint *baseline); + /*< private >*/ GtkWidgetClassPrivate *priv; /* Padding for future expansion */ - void (*_gtk_reserved2) (void); - void (*_gtk_reserved3) (void); - void (*_gtk_reserved4) (void); void (*_gtk_reserved5) (void); void (*_gtk_reserved6) (void); void (*_gtk_reserved7) (void); @@ -498,6 +507,10 @@ void gtk_widget_size_request (GtkWidget *widget, GtkRequisition *requisition); void gtk_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation); +GDK_AVAILABLE_IN_3_10 +void gtk_widget_size_allocate_with_baseline (GtkWidget *widget, + GtkAllocation *allocation, + gint baseline); GtkSizeRequestMode gtk_widget_get_request_mode (GtkWidget *widget); void gtk_widget_get_preferred_width (GtkWidget *widget, @@ -514,9 +527,22 @@ void gtk_widget_get_preferred_width_for_height (GtkWidget *w gint height, gint *minimum_width, gint *natural_width); +GDK_AVAILABLE_IN_3_10 +void gtk_widget_get_preferred_height_and_baseline_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height, + gint *minimum_baseline, + gint *natural_baseline); void gtk_widget_get_preferred_size (GtkWidget *widget, GtkRequisition *minimum_size, GtkRequisition *natural_size); +GDK_AVAILABLE_IN_3_10 +void gtk_widget_get_preferred_size_and_baseline (GtkWidget *widget, + GtkRequisition *minimum_size, + GtkRequisition *natural_size, + gint *minimum_baseline, + gint *natural_baseline); GDK_DEPRECATED_IN_3_0_FOR(gtk_widget_get_preferred_size) void gtk_widget_get_child_requisition (GtkWidget *widget, @@ -662,6 +688,8 @@ void gtk_widget_unregister_window (GtkWidget *widget, int gtk_widget_get_allocated_width (GtkWidget *widget); int gtk_widget_get_allocated_height (GtkWidget *widget); +GDK_AVAILABLE_IN_3_10 +int gtk_widget_get_allocated_baseline (GtkWidget *widget); void gtk_widget_get_allocation (GtkWidget *widget, GtkAllocation *allocation); @@ -760,6 +788,7 @@ GtkAlign gtk_widget_get_halign (GtkWidget *widget); void gtk_widget_set_halign (GtkWidget *widget, GtkAlign align); GtkAlign gtk_widget_get_valign (GtkWidget *widget); +GDK_AVAILABLE_IN_3_10 GtkAlign gtk_widget_get_valign_with_baseline (GtkWidget *widget); void gtk_widget_set_valign (GtkWidget *widget, GtkAlign align); diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h index 84cad6429f..79278134eb 100644 --- a/gtk/gtkwidgetprivate.h +++ b/gtk/gtkwidgetprivate.h @@ -69,7 +69,9 @@ void _gtk_widget_compute_size_for_orientation (GtkWidget *widget, GtkOrientation orientation, gint for_size, gint *minimum_size, - gint *natural_size); + gint *natural_size, + gint *minimum_baseline, + gint *natural_baseline); void _gtk_widget_get_preferred_size_for_size (GtkWidget *widget, GtkOrientation orientation, gint size,