Hook GtkLayoutManager into GtkWidget

We delegate the size request mode, the measuring, and the allocation of
a widget through a GtkLayoutManager instance, if one has been attached
to the widget; otherwise, we fall back to the widget's own implementation.
This commit is contained in:
Emmanuele Bassi 2018-12-12 17:20:28 +00:00
parent 24754c3259
commit 1b8595b5f2
8 changed files with 255 additions and 64 deletions

View File

@ -4590,6 +4590,8 @@ gtk_widget_get_first_child
gtk_widget_get_last_child
gtk_widget_insert_before
gtk_widget_insert_after
gtk_widget_set_layout_manager
gtk_widget_get_layout_manager
<SUBSECTION>
gtk_widget_get_path

View File

@ -22,12 +22,23 @@
* @Title: GtkLayoutManager
* @Short_description: Base class for layout manager
*
* ...
* Layout managers are delegate classes that handle the preferred size
* and the allocation of a container widget.
*
* You typically subclass #GtkLayoutManager if you want to implement a
* layout policy for the children of a widget, without necessarily
* implementing the @GtkWidgetClass.measure() and @GtkWidgetClass.size_allocate()
* virtual functions directly.
*
* Each #GtkWidget can only have a #GtkLayoutManager instance associated to it
* at any given time; it is possible, though, to replace the layout manager
* instance using gtk_widget_set_layout_manager().
*/
#include "config.h"
#include "gtklayoutmanager.h"
#include "gtklayoutmanagerprivate.h"
#include "gtkwidget.h"
#ifdef G_ENABLE_DEBUG
#define LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED(m,method) G_STMT_START { \
@ -108,7 +119,7 @@ gtk_layout_manager_init (GtkLayoutManager *self)
* @layout_manager: a #GtkLayoutManager
* @widget: (nullable): a #GtkWidget
*
* ...
* Sets a back pointer from @widget to @layout_manager.
*/
void
gtk_layout_manager_set_widget (GtkLayoutManager *layout_manager,
@ -116,22 +127,44 @@ gtk_layout_manager_set_widget (GtkLayoutManager *layout_manager,
{
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (layout_manager);
if (widget != NULL && priv->widget != NULL)
{
g_critical ("The layout manager %p of type %s is already in use "
"by widget %p '%s', and cannot be used by widget %p '%s'",
layout_manager, G_OBJECT_TYPE_NAME (layout_manager),
priv->widget, gtk_widget_get_name (priv->widget),
widget, gtk_widget_get_name (widget));
return;
}
priv->widget = widget;
}
/**
* gtk_layout_manager_measure:
* @manager:
* @widget:
* @orientation:
* @for_size:
* @minimum: (out):
* @natural: (out):
* @minimum_baseline: (out):
* @natural_baseline: (out):
* @manager: a #GtkLayoutManager
* @widget: the #GtkWidget using @manager
* @orientation: the orientation to measure
* @for_size: Size for the opposite of @orientation; for instance, if
* the @orientation is %GTK_ORIENTATION_HORIZONTAL, this is the height
* of the widget; if the @orientation is %GTK_ORIENTATION_VERTICAL, this
* is the width of the widget. This allows to measure the height for the
* given width, and the width for the given height. Use -1 if the size
* is not known
* @minimum: (out) (optional): the minimum size for the given size and
* orientation
* @natural: (out) (optional): the natural, or preferred size for the
* given size and orientation
* @minimum_baseline: (out) (optional): the baseline position for the
* minimum size
* @natural_baseline: (out) (optional): the baseline position for the
* natural size
*
* ...
* Measures the size of the @widget using @manager, for the
* given @orientation and size.
*
* See [GtkWidget's geometry management section][geometry-management] for
* more details.
*/
void
gtk_layout_manager_measure (GtkLayoutManager *manager,
@ -158,13 +191,15 @@ gtk_layout_manager_measure (GtkLayoutManager *manager,
/**
* gtk_layout_manager_allocate:
* @manager:
* @widget:
* @width:
* @height:
* @baseline:
* @manager: a #GtkLayoutManager
* @widget: the #GtkWidget using @manager
* @width: the new width of the @widget
* @height: the new height of the @widget
* @baseline: the baseline position of the @widget
*
* ...
* This function assigns the given @width, @height, and @baseline to
* a @widget, and computes the position and sizes of the children of
* the @widget using the layout management policy of @manager.
*/
void
gtk_layout_manager_allocate (GtkLayoutManager *manager,
@ -185,12 +220,12 @@ gtk_layout_manager_allocate (GtkLayoutManager *manager,
/**
* gtk_layout_manager_get_request_mode:
* @manager:
* @widget:
* @manager: a #GtkLayoutManager
* @widget: the #GtkWidget using @manager
*
* ...
* Retrieves the request mode of @manager.
*
* Returns: ...
* Returns: a #GtkSizeRequestMode
*/
GtkSizeRequestMode
gtk_layout_manager_get_request_mode (GtkLayoutManager *manager,
@ -206,17 +241,40 @@ gtk_layout_manager_get_request_mode (GtkLayoutManager *manager,
return klass->get_request_mode (manager, widget);
}
/**
* gtk_layout_manager_get_widget:
* @manager: a #GtkLayoutManager
*
* Retrieves the #GtkWidget using the given #GtkLayoutManager.
*
* Returns: (transfer none) (nullable): a #GtkWidget
*/
GtkWidget *
gtk_layout_manager_get_widget (GtkLayoutManager *manager)
{
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (manager);
g_return_val_if_fail (GTK_IS_LAYOUT_MANAGER (manager), NULL);
return priv->widget;
}
/**
* gtk_layout_manager_layout_changed:
* @manager: a #GtkLayoutManager
*
* ...
* Queues a resize on the #GtkWidget using @manager, if any.
*
* This function should be called by subclasses of #GtkLayoutManager in
* response to changes to their layout management policies.
*/
void
gtk_layout_manager_layout_changed (GtkLayoutManager *manager)
{
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (manager);
g_return_if_fail (GTK_IS_LAYOUT_MANAGER (manager));
if (priv->widget != NULL)
gtk_widget_queue_resize (priv->widget);
}

View File

@ -18,7 +18,8 @@
*/
#pragma once
#include <gtk/gtkcontainer.h>
#include <gtk/gtktypes.h>
#include <gtk/gtkwidget.h>
G_BEGIN_DECLS
@ -88,6 +89,9 @@ GDK_AVAILABLE_IN_ALL
GtkSizeRequestMode gtk_layout_manager_get_request_mode (GtkLayoutManager *manager,
GtkWidget *widget);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_layout_manager_get_widget (GtkLayoutManager *manager);
GDK_AVAILABLE_IN_ALL
void gtk_layout_manager_layout_changed (GtkLayoutManager *manager);

View File

@ -31,6 +31,7 @@
#include "gtkwidgetprivate.h"
#include "gtkcssnodeprivate.h"
#include "gtkcssnumbervalueprivate.h"
#include "gtklayoutmanagerprivate.h"
#ifdef G_ENABLE_CONSISTENCY_CHECKS
@ -195,45 +196,94 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget,
css_min_for_size = get_number_ceil (style, GTK_CSS_PROPERTY_MIN_WIDTH);
}
if (for_size < 0)
GtkLayoutManager *layout_manager = gtk_widget_get_layout_manager (widget);
if (layout_manager != NULL)
{
push_recursion_check (widget, orientation);
widget_class->measure (widget, orientation, -1,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
if (for_size < 0)
{
push_recursion_check (widget, orientation);
gtk_layout_manager_measure (layout_manager, widget,
orientation, -1,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
}
else
{
int adjusted_for_size;
int minimum_for_size = 0;
int natural_for_size = 0;
int dummy = 0;
/* Pull the minimum for_size from the cache as it's needed to adjust
* the proposed 'for_size' */
gtk_layout_manager_measure (layout_manager, widget,
OPPOSITE_ORIENTATION (orientation), -1,
&minimum_for_size, &natural_for_size,
NULL, NULL);
if (for_size < MAX (minimum_for_size, css_min_for_size))
for_size = MAX (minimum_for_size, css_min_for_size);
adjusted_for_size = for_size;
gtk_widget_adjust_size_allocation (widget, OPPOSITE_ORIENTATION (orientation),
&for_size, &natural_for_size,
&dummy, &adjusted_for_size);
adjusted_for_size -= css_extra_for_size;
if (adjusted_for_size < 0)
adjusted_for_size = MAX (minimum_for_size, css_min_for_size);
push_recursion_check (widget, orientation);
gtk_layout_manager_measure (layout_manager, widget,
orientation,
adjusted_for_size,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
}
}
else
{
int adjusted_for_size;
int minimum_for_size = 0;
int natural_for_size = 0;
int dummy = 0;
if (for_size < 0)
{
push_recursion_check (widget, orientation);
widget_class->measure (widget, orientation, -1,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
}
else
{
int adjusted_for_size;
int minimum_for_size = 0;
int natural_for_size = 0;
int dummy = 0;
/* Pull the minimum for_size from the cache as it's needed to adjust
* the proposed 'for_size' */
gtk_widget_measure (widget, OPPOSITE_ORIENTATION (orientation), -1,
&minimum_for_size, &natural_for_size, NULL, NULL);
/* Pull the minimum for_size from the cache as it's needed to adjust
* the proposed 'for_size' */
gtk_widget_measure (widget, OPPOSITE_ORIENTATION (orientation), -1,
&minimum_for_size, &natural_for_size, NULL, NULL);
/* TODO: Warn if the given for_size is too small? */
if (for_size < MAX (minimum_for_size, css_min_for_size))
for_size = MAX (minimum_for_size, css_min_for_size);
/* TODO: Warn if the given for_size is too small? */
if (for_size < MAX (minimum_for_size, css_min_for_size))
for_size = MAX (minimum_for_size, css_min_for_size);
adjusted_for_size = for_size;
gtk_widget_adjust_size_allocation (widget, OPPOSITE_ORIENTATION (orientation),
&for_size, &natural_for_size,
&dummy, &adjusted_for_size);
adjusted_for_size = for_size;
gtk_widget_adjust_size_allocation (widget, OPPOSITE_ORIENTATION (orientation),
&for_size, &natural_for_size,
&dummy, &adjusted_for_size);
adjusted_for_size -= css_extra_for_size;
push_recursion_check (widget, orientation);
widget_class->measure (widget,
orientation,
adjusted_for_size,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
adjusted_for_size -= css_extra_for_size;
push_recursion_check (widget, orientation);
widget_class->measure (widget,
orientation,
adjusted_for_size,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
}
}
min_size = MAX (0, MAX (reported_min_size, css_min_size)) + css_extra_size;
@ -512,7 +562,13 @@ gtk_widget_get_request_mode (GtkWidget *widget)
if (G_UNLIKELY (!cache->request_mode_valid))
{
cache->request_mode = GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget);
GtkLayoutManager *layout_manager = gtk_widget_get_layout_manager (widget);
if (layout_manager != NULL)
cache->request_mode = gtk_layout_manager_get_request_mode (layout_manager, widget);
else
cache->request_mode = GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget);
cache->request_mode_valid = TRUE;
}

View File

@ -38,6 +38,7 @@ typedef struct _GtkBuilder GtkBuilder;
typedef struct _GtkClipboard GtkClipboard;
typedef struct _GtkEventController GtkEventController;
typedef struct _GtkGesture GtkGesture;
typedef struct _GtkLayoutManager GtkLayoutManager;
typedef struct _GtkRequisition GtkRequisition;
typedef struct _GtkRoot GtkRoot;
typedef struct _GtkSelectionData GtkSelectionData;

View File

@ -48,6 +48,7 @@
#include "gtkgesturesingle.h"
#include "gtkgestureswipe.h"
#include "gtkintl.h"
#include "gtklayoutmanagerprivate.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
#include "gtkmenu.h"
@ -4346,16 +4347,26 @@ gtk_widget_allocate (GtkWidget *widget,
priv->height = adjusted.height;
priv->baseline = baseline;
if (g_signal_has_handler_pending (widget, widget_signals[SIZE_ALLOCATE], 0, FALSE))
g_signal_emit (widget, widget_signals[SIZE_ALLOCATE], 0,
priv->width,
priv->height,
baseline);
if (priv->layout_manager != NULL)
{
gtk_layout_manager_allocate (priv->layout_manager, widget,
priv->width,
priv->priv->height,
baseline);
}
else
GTK_WIDGET_GET_CLASS (widget)->size_allocate (widget,
priv->width,
priv->height,
baseline);
{
if (g_signal_has_handler_pending (widget, widget_signals[SIZE_ALLOCATE], 0, FALSE))
g_signal_emit (widget, widget_signals[SIZE_ALLOCATE], 0,
priv->width,
priv->height,
baseline);
else
GTK_WIDGET_GET_CLASS (widget)->size_allocate (widget,
priv->width,
priv->height,
baseline);
}
/* Size allocation is god... after consulting god, no further requests or allocations are needed */
#ifdef G_ENABLE_DEBUG
@ -8041,6 +8052,9 @@ gtk_widget_dispose (GObject *object)
while (priv->paintables)
gtk_widget_paintable_set_widget (priv->paintables->data, NULL);
gtk_widget_set_layout_manager (widget, NULL);
g_clear_object (&priv->layout_manager);
priv->visible = FALSE;
if (_gtk_widget_get_realized (widget))
gtk_widget_unrealize (widget);
@ -13361,3 +13375,50 @@ gtk_widget_get_height (GtkWidget *widget)
return priv->height;
}
/**
* gtk_widget_set_layout_manager:
* @widget: a #GtkWidget
* @layout_manager: (nullable) (transfer full): a #GtkLayoutManager
*
* Sets the layout manager delegate instance that provides an implementation
* for measuring and allocating the children of @widget.
*
* The @widget acquires a reference to the given @layout_manager.
*/
void
gtk_widget_set_layout_manager (GtkWidget *widget,
GtkLayoutManager *layout_manager)
{
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (layout_manager == NULL || GTK_IS_LAYOUT_MANAGER (layout_manager));
g_return_if_fail (layout_manager == NULL || gtk_layout_manager_get_widget (layout_manager) == NULL);
if (g_set_object (&priv->layout_manager, layout_manager))
{
if (priv->layout_manager != NULL)
gtk_layout_manager_set_widget (priv->layout_manager, widget);
gtk_widget_queue_resize (widget);
}
}
/**
* gtk_widget_get_layout_manager:
* @widget: a #GtkWidget
*
* Retrieves the layout manager set using gtk_widget_set_layout_manager().
*
* Returns: (transfer none) (nullable): a #GtkLayoutManager
*/
GtkLayoutManager *
gtk_widget_get_layout_manager (GtkWidget *widget)
{
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
return priv->layout_manager;
}

View File

@ -424,6 +424,12 @@ void gtk_widget_get_preferred_size (GtkWidget *w
GtkRequisition *minimum_size,
GtkRequisition *natural_size);
GDK_AVAILABLE_IN_ALL
void gtk_widget_set_layout_manager (GtkWidget *widget,
GtkLayoutManager *layout_manager);
GDK_AVAILABLE_IN_ALL
GtkLayoutManager * gtk_widget_get_layout_manager (GtkWidget *widget);
GDK_AVAILABLE_IN_ALL
void gtk_widget_add_accelerator (GtkWidget *widget,
const gchar *accel_signal,

View File

@ -160,6 +160,9 @@ struct _GtkWidgetPrivate
/* The render node we draw or %NULL if not yet created.*/
GskRenderNode *render_node;
/* The layout manager, or %NULL */
GtkLayoutManager *layout_manager;
GSList *paintables;
/* The widget's surface or its parent surface if it does