summaryrefslogtreecommitdiffstats
path: root/subprojects/libhandy/src/hdy-squeezer.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--subprojects/libhandy/src/hdy-squeezer.c1576
1 files changed, 1576 insertions, 0 deletions
diff --git a/subprojects/libhandy/src/hdy-squeezer.c b/subprojects/libhandy/src/hdy-squeezer.c
new file mode 100644
index 0000000..1995661
--- /dev/null
+++ b/subprojects/libhandy/src/hdy-squeezer.c
@@ -0,0 +1,1576 @@
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ * Copyright (C) 2019 Purism SPC
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ * Author: Adrien Plazas <adrien.plazas@puri.sm>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+/*
+ * Forked from the GTK+ 3.24.2 GtkStack widget initially written by Alexander
+ * Larsson, and heavily modified for libhandy by Adrien Plazas on behalf of
+ * Purism SPC 2019.
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "hdy-squeezer.h"
+
+#include "gtkprogresstrackerprivate.h"
+#include "hdy-animation-private.h"
+#include "hdy-cairo-private.h"
+#include "hdy-css-private.h"
+
+/**
+ * SECTION:hdy-squeezer
+ * @short_description: A best fit container.
+ * @Title: HdySqueezer
+ *
+ * The HdySqueezer widget is a container which only shows the first of its
+ * children that fits in the available size. It is convenient to offer different
+ * widgets to represent the same data with different levels of detail, making
+ * the widget seem to squeeze itself to fit in the available space.
+ *
+ * Transitions between children can be animated as fades. This can be controlled
+ * with hdy_squeezer_set_transition_type().
+ *
+ * # CSS nodes
+ *
+ * #HdySqueezer has a single CSS node with name squeezer.
+ */
+
+/**
+ * HdySqueezerTransitionType:
+ * @HDY_SQUEEZER_TRANSITION_TYPE_NONE: No transition
+ * @HDY_SQUEEZER_TRANSITION_TYPE_CROSSFADE: A cross-fade
+ *
+ * These enumeration values describe the possible transitions between children
+ * in a #HdySqueezer widget.
+ */
+
+enum {
+ PROP_0,
+ PROP_HOMOGENEOUS,
+ PROP_VISIBLE_CHILD,
+ PROP_TRANSITION_DURATION,
+ PROP_TRANSITION_TYPE,
+ PROP_TRANSITION_RUNNING,
+ PROP_INTERPOLATE_SIZE,
+ PROP_XALIGN,
+ PROP_YALIGN,
+
+ /* Overridden properties */
+ PROP_ORIENTATION,
+
+ LAST_PROP = PROP_YALIGN + 1,
+};
+
+enum {
+ CHILD_PROP_0,
+ CHILD_PROP_ENABLED,
+
+ LAST_CHILD_PROP,
+};
+
+typedef struct {
+ GtkWidget *widget;
+ gboolean enabled;
+ GtkWidget *last_focus;
+} HdySqueezerChildInfo;
+
+struct _HdySqueezer
+{
+ GtkContainer parent_instance;
+
+ GList *children;
+
+ GdkWindow* bin_window;
+ GdkWindow* view_window;
+
+ HdySqueezerChildInfo *visible_child;
+
+ gboolean homogeneous;
+
+ HdySqueezerTransitionType transition_type;
+ guint transition_duration;
+
+ HdySqueezerChildInfo *last_visible_child;
+ cairo_surface_t *last_visible_surface;
+ GtkAllocation last_visible_surface_allocation;
+ guint tick_id;
+ GtkProgressTracker tracker;
+ gboolean first_frame_skipped;
+
+ gint last_visible_widget_width;
+ gint last_visible_widget_height;
+
+ HdySqueezerTransitionType active_transition_type;
+
+ gboolean interpolate_size;
+
+ gfloat xalign;
+ gfloat yalign;
+
+ GtkOrientation orientation;
+};
+
+static GParamSpec *props[LAST_PROP];
+static GParamSpec *child_props[LAST_CHILD_PROP];
+
+G_DEFINE_TYPE_WITH_CODE (HdySqueezer, hdy_squeezer, GTK_TYPE_CONTAINER,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
+
+static GtkOrientation
+get_orientation (HdySqueezer *self)
+{
+ return self->orientation;
+}
+
+static void
+set_orientation (HdySqueezer *self,
+ GtkOrientation orientation)
+{
+ if (self->orientation == orientation)
+ return;
+
+ self->orientation = orientation;
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ g_object_notify (G_OBJECT (self), "orientation");
+}
+
+static HdySqueezerChildInfo *
+find_child_info_for_widget (HdySqueezer *self,
+ GtkWidget *child)
+{
+ HdySqueezerChildInfo *info;
+ GList *l;
+
+ for (l = self->children; l != NULL; l = l->next) {
+ info = l->data;
+ if (info->widget == child)
+ return info;
+ }
+
+ return NULL;
+}
+
+static void
+hdy_squeezer_progress_updated (HdySqueezer *self)
+{
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+
+ if (!self->homogeneous)
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+
+ if (gtk_progress_tracker_get_state (&self->tracker) == GTK_PROGRESS_STATE_AFTER) {
+ if (self->last_visible_surface != NULL) {
+ cairo_surface_destroy (self->last_visible_surface);
+ self->last_visible_surface = NULL;
+ }
+
+ if (self->last_visible_child != NULL) {
+ gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
+ self->last_visible_child = NULL;
+ }
+ }
+}
+
+static gboolean
+hdy_squeezer_transition_cb (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ HdySqueezer *self = HDY_SQUEEZER (widget);
+
+ if (self->first_frame_skipped) {
+ gtk_progress_tracker_advance_frame (&self->tracker,
+ gdk_frame_clock_get_frame_time (frame_clock));
+ } else {
+ self->first_frame_skipped = TRUE;
+ }
+
+ /* Finish the animation early if the widget isn't mapped anymore. */
+ if (!gtk_widget_get_mapped (widget))
+ gtk_progress_tracker_finish (&self->tracker);
+
+ hdy_squeezer_progress_updated (HDY_SQUEEZER (widget));
+
+ if (gtk_progress_tracker_get_state (&self->tracker) == GTK_PROGRESS_STATE_AFTER) {
+ self->tick_id = 0;
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+hdy_squeezer_schedule_ticks (HdySqueezer *self)
+{
+ if (self->tick_id == 0) {
+ self->tick_id =
+ gtk_widget_add_tick_callback (GTK_WIDGET (self), hdy_squeezer_transition_cb, self, NULL);
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
+ }
+}
+
+static void
+hdy_squeezer_unschedule_ticks (HdySqueezer *self)
+{
+ if (self->tick_id != 0) {
+ gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_id);
+ self->tick_id = 0;
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
+ }
+}
+
+static void
+hdy_squeezer_start_transition (HdySqueezer *self,
+ HdySqueezerTransitionType transition_type,
+ guint transition_duration)
+{
+ GtkWidget *widget = GTK_WIDGET (self);
+
+ if (gtk_widget_get_mapped (widget) &&
+ hdy_get_enable_animations (widget) &&
+ transition_type != HDY_SQUEEZER_TRANSITION_TYPE_NONE &&
+ transition_duration != 0 &&
+ self->last_visible_child != NULL) {
+ self->active_transition_type = transition_type;
+ self->first_frame_skipped = FALSE;
+ hdy_squeezer_schedule_ticks (self);
+ gtk_progress_tracker_start (&self->tracker,
+ self->transition_duration * 1000,
+ 0,
+ 1.0);
+ } else {
+ hdy_squeezer_unschedule_ticks (self);
+ self->active_transition_type = HDY_SQUEEZER_TRANSITION_TYPE_NONE;
+ gtk_progress_tracker_finish (&self->tracker);
+ }
+
+ hdy_squeezer_progress_updated (HDY_SQUEEZER (widget));
+}
+
+static void
+set_visible_child (HdySqueezer *self,
+ HdySqueezerChildInfo *child_info,
+ HdySqueezerTransitionType transition_type,
+ guint transition_duration)
+{
+ HdySqueezerChildInfo *info;
+ GtkWidget *widget = GTK_WIDGET (self);
+ GList *l;
+ GtkWidget *toplevel;
+ GtkWidget *focus;
+ gboolean contains_focus = FALSE;
+
+ /* If we are being destroyed, do not bother with transitions and
+ * notifications.
+ */
+ if (gtk_widget_in_destruction (widget))
+ return;
+
+ /* If none, pick the first visible. */
+ if (child_info == NULL) {
+ for (l = self->children; l != NULL; l = l->next) {
+ info = l->data;
+ if (gtk_widget_get_visible (info->widget)) {
+ child_info = info;
+ break;
+ }
+ }
+ }
+
+ if (child_info == self->visible_child)
+ return;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (GTK_IS_WINDOW (toplevel)) {
+ focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
+ if (focus &&
+ self->visible_child &&
+ self->visible_child->widget &&
+ gtk_widget_is_ancestor (focus, self->visible_child->widget)) {
+ contains_focus = TRUE;
+
+ if (self->visible_child->last_focus)
+ g_object_remove_weak_pointer (G_OBJECT (self->visible_child->last_focus),
+ (gpointer *)&self->visible_child->last_focus);
+ self->visible_child->last_focus = focus;
+ g_object_add_weak_pointer (G_OBJECT (self->visible_child->last_focus),
+ (gpointer *)&self->visible_child->last_focus);
+ }
+ }
+
+ if (self->last_visible_child != NULL)
+ gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
+ self->last_visible_child = NULL;
+
+ if (self->last_visible_surface != NULL)
+ cairo_surface_destroy (self->last_visible_surface);
+ self->last_visible_surface = NULL;
+
+ if (self->visible_child && self->visible_child->widget) {
+ if (gtk_widget_is_visible (widget)) {
+ GtkAllocation allocation;
+
+ self->last_visible_child = self->visible_child;
+ gtk_widget_get_allocated_size (self->last_visible_child->widget, &allocation, NULL);
+ self->last_visible_widget_width = allocation.width;
+ self->last_visible_widget_height = allocation.height;
+ } else {
+ gtk_widget_set_child_visible (self->visible_child->widget, FALSE);
+ }
+ }
+
+ self->visible_child = child_info;
+
+ if (child_info) {
+ gtk_widget_set_child_visible (child_info->widget, TRUE);
+
+ if (contains_focus) {
+ if (child_info->last_focus)
+ gtk_widget_grab_focus (child_info->last_focus);
+ else
+ gtk_widget_child_focus (child_info->widget, GTK_DIR_TAB_FORWARD);
+ }
+ }
+
+ if (self->homogeneous)
+ gtk_widget_queue_allocate (widget);
+ else
+ gtk_widget_queue_resize (widget);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD]);
+
+ hdy_squeezer_start_transition (self, transition_type, transition_duration);
+}
+
+static void
+stack_child_visibility_notify_cb (GObject *obj,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ HdySqueezer *self = HDY_SQUEEZER (user_data);
+ GtkWidget *child = GTK_WIDGET (obj);
+ HdySqueezerChildInfo *child_info;
+
+ child_info = find_child_info_for_widget (self, child);
+
+ if (self->visible_child == NULL &&
+ gtk_widget_get_visible (child))
+ set_visible_child (self, child_info, self->transition_type, self->transition_duration);
+ else if (self->visible_child == child_info &&
+ !gtk_widget_get_visible (child))
+ set_visible_child (self, NULL, self->transition_type, self->transition_duration);
+
+ if (child_info == self->last_visible_child) {
+ gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
+ self->last_visible_child = NULL;
+ }
+}
+
+static void
+hdy_squeezer_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ HdySqueezer *self = HDY_SQUEEZER (container);
+ HdySqueezerChildInfo *child_info;
+
+ g_return_if_fail (child != NULL);
+
+ child_info = g_slice_new (HdySqueezerChildInfo);
+ child_info->widget = child;
+ child_info->enabled = TRUE;
+ child_info->last_focus = NULL;
+
+ self->children = g_list_append (self->children, child_info);
+
+ gtk_widget_set_child_visible (child, FALSE);
+ gtk_widget_set_parent_window (child, self->bin_window);
+ gtk_widget_set_parent (child, GTK_WIDGET (self));
+
+ if (self->bin_window != NULL) {
+ gdk_window_set_events (self->bin_window,
+ gdk_window_get_events (self->bin_window) |
+ gtk_widget_get_events (child));
+ }
+
+ g_signal_connect (child, "notify::visible",
+ G_CALLBACK (stack_child_visibility_notify_cb), self);
+
+ if (self->visible_child == NULL &&
+ gtk_widget_get_visible (child))
+ set_visible_child (self, child_info, self->transition_type, self->transition_duration);
+
+ if (self->visible_child == child_info)
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+hdy_squeezer_remove (GtkContainer *container,
+ GtkWidget *child)
+{
+ HdySqueezer *self = HDY_SQUEEZER (container);
+ HdySqueezerChildInfo *child_info;
+ gboolean was_visible;
+
+ child_info = find_child_info_for_widget (self, child);
+ if (child_info == NULL)
+ return;
+
+ self->children = g_list_remove (self->children, child_info);
+
+ g_signal_handlers_disconnect_by_func (child,
+ stack_child_visibility_notify_cb,
+ self);
+
+ was_visible = gtk_widget_get_visible (child);
+
+ child_info->widget = NULL;
+
+ if (self->visible_child == child_info)
+ set_visible_child (self, NULL, self->transition_type, self->transition_duration);
+
+ if (self->last_visible_child == child_info)
+ self->last_visible_child = NULL;
+
+ gtk_widget_unparent (child);
+
+ if (child_info->last_focus)
+ g_object_remove_weak_pointer (G_OBJECT (child_info->last_focus),
+ (gpointer *)&child_info->last_focus);
+
+ g_slice_free (HdySqueezerChildInfo, child_info);
+
+ if (self->homogeneous && was_visible)
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+hdy_squeezer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ HdySqueezer *self = HDY_SQUEEZER (object);
+
+ switch (property_id) {
+ case PROP_HOMOGENEOUS:
+ g_value_set_boolean (value, hdy_squeezer_get_homogeneous (self));
+ break;
+ case PROP_VISIBLE_CHILD:
+ g_value_set_object (value, hdy_squeezer_get_visible_child (self));
+ break;
+ case PROP_TRANSITION_DURATION:
+ g_value_set_uint (value, hdy_squeezer_get_transition_duration (self));
+ break;
+ case PROP_TRANSITION_TYPE:
+ g_value_set_enum (value, hdy_squeezer_get_transition_type (self));
+ break;
+ case PROP_TRANSITION_RUNNING:
+ g_value_set_boolean (value, hdy_squeezer_get_transition_running (self));
+ break;
+ case PROP_INTERPOLATE_SIZE:
+ g_value_set_boolean (value, hdy_squeezer_get_interpolate_size (self));
+ break;
+ case PROP_XALIGN:
+ g_value_set_float (value, hdy_squeezer_get_xalign (self));
+ break;
+ case PROP_YALIGN:
+ g_value_set_float (value, hdy_squeezer_get_yalign (self));
+ break;
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, get_orientation (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+hdy_squeezer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ HdySqueezer *self = HDY_SQUEEZER (object);
+
+ switch (property_id) {
+ case PROP_HOMOGENEOUS:
+ hdy_squeezer_set_homogeneous (self, g_value_get_boolean (value));
+ break;
+ case PROP_TRANSITION_DURATION:
+ hdy_squeezer_set_transition_duration (self, g_value_get_uint (value));
+ break;
+ case PROP_TRANSITION_TYPE:
+ hdy_squeezer_set_transition_type (self, g_value_get_enum (value));
+ break;
+ case PROP_INTERPOLATE_SIZE:
+ hdy_squeezer_set_interpolate_size (self, g_value_get_boolean (value));
+ break;
+ case PROP_XALIGN:
+ hdy_squeezer_set_xalign (self, g_value_get_float (value));
+ break;
+ case PROP_YALIGN:
+ hdy_squeezer_set_yalign (self, g_value_get_float (value));
+ break;
+ case PROP_ORIENTATION:
+ set_orientation (self, g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+hdy_squeezer_realize (GtkWidget *widget)
+{
+ HdySqueezer *self = HDY_SQUEEZER (widget);
+ GtkAllocation allocation;
+ GdkWindowAttr attributes = { 0 };
+ GdkWindowAttributesType attributes_mask;
+ HdySqueezerChildInfo *info;
+ GList *l;
+
+ gtk_widget_set_realized (widget, TRUE);
+ gtk_widget_set_window (widget, g_object_ref (gtk_widget_get_parent_window (widget)));
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.event_mask =
+ gtk_widget_get_events (widget);
+ attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL;
+
+ self->view_window =
+ gdk_window_new (gtk_widget_get_window (GTK_WIDGET (self)),
+ &attributes, attributes_mask);
+ gtk_widget_register_window (widget, self->view_window);
+
+ attributes.x = 0;
+ attributes.y = 0;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+
+ for (l = self->children; l != NULL; l = l->next) {
+ info = l->data;
+ attributes.event_mask |= gtk_widget_get_events (info->widget);
+ }
+
+ self->bin_window =
+ gdk_window_new (self->view_window, &attributes, attributes_mask);
+ gtk_widget_register_window (widget, self->bin_window);
+
+ for (l = self->children; l != NULL; l = l->next) {
+ info = l->data;
+
+ gtk_widget_set_parent_window (info->widget, self->bin_window);
+ }
+
+ gdk_window_show (self->bin_window);
+}
+
+static void
+hdy_squeezer_unrealize (GtkWidget *widget)
+{
+ HdySqueezer *self = HDY_SQUEEZER (widget);
+
+ gtk_widget_unregister_window (widget, self->bin_window);
+ gdk_window_destroy (self->bin_window);
+ self->bin_window = NULL;
+ gtk_widget_unregister_window (widget, self->view_window);
+ gdk_window_destroy (self->view_window);
+ self->view_window = NULL;
+
+ GTK_WIDGET_CLASS (hdy_squeezer_parent_class)->unrealize (widget);
+}
+
+static void
+hdy_squeezer_map (GtkWidget *widget)
+{
+ HdySqueezer *self = HDY_SQUEEZER (widget);
+
+ GTK_WIDGET_CLASS (hdy_squeezer_parent_class)->map (widget);
+
+ gdk_window_show (self->view_window);
+}
+
+static void
+hdy_squeezer_unmap (GtkWidget *widget)
+{
+ HdySqueezer *self = HDY_SQUEEZER (widget);
+
+ gdk_window_hide (self->view_window);
+
+ GTK_WIDGET_CLASS (hdy_squeezer_parent_class)->unmap (widget);
+}
+
+static void
+hdy_squeezer_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ HdySqueezer *self = HDY_SQUEEZER (container);
+ HdySqueezerChildInfo *child_info;
+ GList *l;
+
+ l = self->children;
+ while (l) {
+ child_info = l->data;
+ l = l->next;
+
+ (* callback) (child_info->widget, callback_data);
+ }
+}
+
+static void
+hdy_squeezer_compute_expand (GtkWidget *widget,
+ gboolean *hexpand_p,
+ gboolean *vexpand_p)
+{
+ HdySqueezer *self = HDY_SQUEEZER (widget);
+ gboolean hexpand, vexpand;
+ HdySqueezerChildInfo *child_info;
+ GtkWidget *child;
+ GList *l;
+
+ hexpand = FALSE;
+ vexpand = FALSE;
+ for (l = self->children; l != NULL; l = l->next) {
+ child_info = l->data;
+ child = child_info->widget;
+
+ if (!hexpand &&
+ gtk_widget_compute_expand (child, GTK_ORIENTATION_HORIZONTAL))
+ hexpand = TRUE;
+
+ if (!vexpand &&
+ gtk_widget_compute_expand (child, GTK_ORIENTATION_VERTICAL))
+ vexpand = TRUE;
+
+ if (hexpand && vexpand)
+ break;
+ }
+
+ *hexpand_p = hexpand;
+ *vexpand_p = vexpand;
+}
+
+static void
+hdy_squeezer_draw_crossfade (GtkWidget *widget,
+ cairo_t *cr)
+{
+ HdySqueezer *self = HDY_SQUEEZER (widget);
+ gdouble progress = gtk_progress_tracker_get_progress (&self->tracker, FALSE);
+
+ cairo_push_group (cr);
+ gtk_container_propagate_draw (GTK_CONTAINER (self),
+ self->visible_child->widget,
+ cr);
+ cairo_save (cr);
+
+ /* Multiply alpha by progress. */
+ cairo_set_source_rgba (cr, 1, 1, 1, progress);
+ cairo_set_operator (cr, CAIRO_OPERATOR_DEST_IN);
+ cairo_paint (cr);
+
+ if (self->last_visible_surface != NULL) {
+ gint width_diff = gtk_widget_get_allocated_width (widget) - self->last_visible_surface_allocation.width;
+ gint height_diff = gtk_widget_get_allocated_height (widget) - self->last_visible_surface_allocation.height;
+
+ cairo_set_source_surface (cr, self->last_visible_surface,
+ width_diff * self->xalign,
+ height_diff * self->yalign);
+ cairo_set_operator (cr, CAIRO_OPERATOR_ADD);
+ cairo_paint_with_alpha (cr, MAX (1.0 - progress, 0));
+ }
+
+ cairo_restore (cr);
+
+ cairo_pop_group_to_source (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_paint (cr);
+}
+
+static gboolean
+hdy_squeezer_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ HdySqueezer *self = HDY_SQUEEZER (widget);
+
+ if (gtk_cairo_should_draw_window (cr, self->view_window)) {
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (widget);
+ gtk_render_background (context,
+ cr,
+ 0, 0,
+ gtk_widget_get_allocated_width (widget),
+ gtk_widget_get_allocated_height (widget));
+ }
+
+ if (self->visible_child) {
+ if (gtk_progress_tracker_get_state (&self->tracker) != GTK_PROGRESS_STATE_AFTER) {
+ if (self->last_visible_surface == NULL &&
+ self->last_visible_child != NULL) {
+ g_autoptr (cairo_t) pattern_cr = NULL;
+
+ gtk_widget_get_allocation (self->last_visible_child->widget,
+ &self->last_visible_surface_allocation);
+ self->last_visible_surface =
+ gdk_window_create_similar_surface (gtk_widget_get_window (widget),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ self->last_visible_surface_allocation.width,
+ self->last_visible_surface_allocation.height);
+ pattern_cr = cairo_create (self->last_visible_surface);
+ /* We don't use propagate_draw here, because we don't want to apply the
+ * bin_window offset.
+ */
+ gtk_widget_draw (self->last_visible_child->widget, pattern_cr);
+ }
+
+ cairo_rectangle (cr,
+ 0, 0,
+ gtk_widget_get_allocated_width (widget),
+ gtk_widget_get_allocated_height (widget));
+ cairo_clip (cr);
+
+ switch (self->active_transition_type) {
+ case HDY_SQUEEZER_TRANSITION_TYPE_CROSSFADE:
+ if (gtk_cairo_should_draw_window (cr, self->bin_window))
+ hdy_squeezer_draw_crossfade (widget, cr);
+ break;
+ case HDY_SQUEEZER_TRANSITION_TYPE_NONE:
+ default:
+ g_assert_not_reached ();
+ }
+
+ } else if (gtk_cairo_should_draw_window (cr, self->bin_window))
+ gtk_container_propagate_draw (GTK_CONTAINER (self),
+ self->visible_child->widget,
+ cr);
+ }
+
+ return FALSE;
+}
+
+static void
+hdy_squeezer_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ HdySqueezer *self = HDY_SQUEEZER (widget);
+ HdySqueezerChildInfo *child_info = NULL;
+ GtkWidget *child = NULL;
+ gint child_min;
+ GList *l;
+ GtkAllocation child_allocation;
+
+ hdy_css_size_allocate (widget, allocation);
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ for (l = self->children; l != NULL; l = l->next) {
+ child_info = l->data;
+ child = child_info->widget;
+
+ if (!gtk_widget_get_visible (child))
+ continue;
+
+ if (!child_info->enabled)
+ continue;
+
+ if (self->orientation == GTK_ORIENTATION_VERTICAL) {
+ if (gtk_widget_get_request_mode (child) != GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+ gtk_widget_get_preferred_height (child, &child_min, NULL);
+ else
+ gtk_widget_get_preferred_height_for_width (child, allocation->width, &child_min, NULL);
+
+ if (child_min <= allocation->height)
+ break;
+ } else {
+ if (gtk_widget_get_request_mode (child) != GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT)
+ gtk_widget_get_preferred_width (child, &child_min, NULL);
+ else
+ gtk_widget_get_preferred_width_for_height (child, allocation->height, &child_min, NULL);
+
+ if (child_min <= allocation->width)
+ break;
+ }
+ }
+
+ set_visible_child (self, child_info,
+ self->transition_type,
+ self->transition_duration);
+
+ child_allocation.x = 0;
+ child_allocation.y = 0;
+
+ if (gtk_widget_get_realized (widget)) {
+ gdk_window_move_resize (self->view_window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+ gdk_window_move_resize (self->bin_window,
+ 0, 0,
+ allocation->width, allocation->height);
+ }
+
+ if (self->last_visible_child != NULL) {
+ int min, nat;
+ gtk_widget_get_preferred_width (self->last_visible_child->widget, &min, &nat);
+ child_allocation.width = MAX (min, allocation->width);
+ gtk_widget_get_preferred_height_for_width (self->last_visible_child->widget,
+ child_allocation.width,
+ &min, &nat);
+ child_allocation.height = MAX (min, allocation->height);
+
+ gtk_widget_size_allocate (self->last_visible_child->widget, &child_allocation);
+ }
+
+ child_allocation.width = allocation->width;
+ child_allocation.height = allocation->height;
+
+ if (self->visible_child) {
+ int min, nat;
+ GtkAlign valign;
+
+ gtk_widget_get_preferred_height_for_width (self->visible_child->widget,
+ allocation->width,
+ &min, &nat);
+ if (self->interpolate_size) {
+ valign = gtk_widget_get_valign (self->visible_child->widget);
+ child_allocation.height = MAX (nat, allocation->height);
+ if (valign == GTK_ALIGN_END &&
+ child_allocation.height > allocation->height)
+ child_allocation.y -= nat - allocation->height;
+ else if (valign == GTK_ALIGN_CENTER &&
+ child_allocation.height > allocation->height)
+ child_allocation.y -= (nat - allocation->height) / 2;
+ }
+
+ gtk_widget_size_allocate (self->visible_child->widget, &child_allocation);
+ }
+}
+
+/* This private method is prefixed by the class name because it will be a
+ * virtual method in GTK 4.
+ */
+static void
+hdy_squeezer_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ HdySqueezer *self = HDY_SQUEEZER (widget);
+ HdySqueezerChildInfo *child_info;
+ GtkWidget *child;
+ gint child_min, child_nat;
+ GList *l;
+
+ *minimum = 0;
+ *natural = 0;
+
+ for (l = self->children; l != NULL; l = l->next) {
+ child_info = l->data;
+ child = child_info->widget;
+
+ if (self->orientation != orientation && !self->homogeneous &&
+ self->visible_child != child_info)
+ continue;
+
+ if (!gtk_widget_get_visible (child))
+ continue;
+
+ /* Disabled children are taken into account when measuring the widget, to
+ * keep its size request and allocation consistent. This avoids the
+ * appearant size and position of a child to changes suddenly when a larger
+ * child gets enabled/disabled.
+ */
+
+ if (orientation == GTK_ORIENTATION_VERTICAL) {
+ if (for_size < 0)
+ gtk_widget_get_preferred_height (child, &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_height_for_width (child, for_size, &child_min, &child_nat);
+ } else {
+ if (for_size < 0)
+ gtk_widget_get_preferred_width (child, &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_width_for_height (child, for_size, &child_min, &child_nat);
+ }
+
+ if (self->orientation == orientation)
+ *minimum = *minimum == 0 ? child_min : MIN (*minimum, child_min);
+ else
+ *minimum = MAX (*minimum, child_min);
+ *natural = MAX (*natural, child_nat);
+ }
+
+ if (self->orientation != orientation && !self->homogeneous &&
+ self->interpolate_size &&
+ self->last_visible_child != NULL) {
+ gdouble t = gtk_progress_tracker_get_ease_out_cubic (&self->tracker, FALSE);
+ if (orientation == GTK_ORIENTATION_VERTICAL) {
+ *minimum = hdy_lerp (self->last_visible_widget_height, *minimum, t);
+ *natural = hdy_lerp (self->last_visible_widget_height, *natural, t);
+ } else {
+ *minimum = hdy_lerp (self->last_visible_widget_width, *minimum, t);
+ *natural = hdy_lerp (self->last_visible_widget_width, *natural, t);
+ }
+ }
+
+ hdy_css_measure (widget, orientation, minimum, natural);
+}
+
+static void
+hdy_squeezer_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ hdy_squeezer_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
+ minimum, natural, NULL, NULL);
+}
+
+static void
+hdy_squeezer_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum,
+ gint *natural)
+{
+ hdy_squeezer_measure (widget, GTK_ORIENTATION_HORIZONTAL, height,
+ minimum, natural, NULL, NULL);
+}
+
+static void
+hdy_squeezer_get_preferred_height (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ hdy_squeezer_measure (widget, GTK_ORIENTATION_VERTICAL, -1,
+ minimum, natural, NULL, NULL);
+}
+
+static void
+hdy_squeezer_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum,
+ gint *natural)
+{
+ hdy_squeezer_measure (widget, GTK_ORIENTATION_VERTICAL, width,
+ minimum, natural, NULL, NULL);
+}
+
+static void
+hdy_squeezer_get_child_property (GtkContainer *container,
+ GtkWidget *widget,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ HdySqueezer *self = HDY_SQUEEZER (container);
+ HdySqueezerChildInfo *child_info;
+
+ child_info = find_child_info_for_widget (self, widget);
+ if (child_info == NULL) {
+ g_param_value_set_default (pspec, value);
+
+ return;
+ }
+
+ switch (property_id) {
+ case CHILD_PROP_ENABLED:
+ g_value_set_boolean (value, hdy_squeezer_get_child_enabled (self, widget));
+ break;
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
+ break;
+ }
+}
+
+static void
+hdy_squeezer_set_child_property (GtkContainer *container,
+ GtkWidget *widget,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ HdySqueezer *self = HDY_SQUEEZER (container);
+ HdySqueezerChildInfo *child_info;
+
+ child_info = find_child_info_for_widget (self, widget);
+ if (child_info == NULL)
+ return;
+
+ switch (property_id) {
+ case CHILD_PROP_ENABLED:
+ hdy_squeezer_set_child_enabled (self, widget, g_value_get_boolean (value));
+ break;
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
+ break;
+ }
+}
+
+static void
+hdy_squeezer_dispose (GObject *object)
+{
+ HdySqueezer *self = HDY_SQUEEZER (object);
+
+ self->visible_child = NULL;
+
+ G_OBJECT_CLASS (hdy_squeezer_parent_class)->dispose (object);
+}
+
+static void
+hdy_squeezer_finalize (GObject *object)
+{
+ HdySqueezer *self = HDY_SQUEEZER (object);
+
+ hdy_squeezer_unschedule_ticks (self);
+
+ if (self->last_visible_surface != NULL)
+ cairo_surface_destroy (self->last_visible_surface);
+
+ G_OBJECT_CLASS (hdy_squeezer_parent_class)->finalize (object);
+}
+
+static void
+hdy_squeezer_class_init (HdySqueezerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ object_class->get_property = hdy_squeezer_get_property;
+ object_class->set_property = hdy_squeezer_set_property;
+ object_class->dispose = hdy_squeezer_dispose;
+ object_class->finalize = hdy_squeezer_finalize;
+
+ widget_class->size_allocate = hdy_squeezer_size_allocate;
+ widget_class->draw = hdy_squeezer_draw;
+ widget_class->realize = hdy_squeezer_realize;
+ widget_class->unrealize = hdy_squeezer_unrealize;
+ widget_class->map = hdy_squeezer_map;
+ widget_class->unmap = hdy_squeezer_unmap;
+ widget_class->get_preferred_height = hdy_squeezer_get_preferred_height;
+ widget_class->get_preferred_height_for_width = hdy_squeezer_get_preferred_height_for_width;
+ widget_class->get_preferred_width = hdy_squeezer_get_preferred_width;
+ widget_class->get_preferred_width_for_height = hdy_squeezer_get_preferred_width_for_height;
+ widget_class->compute_expand = hdy_squeezer_compute_expand;
+
+ container_class->add = hdy_squeezer_add;
+ container_class->remove = hdy_squeezer_remove;
+ container_class->forall = hdy_squeezer_forall;
+ container_class->set_child_property = hdy_squeezer_set_child_property;
+ container_class->get_child_property = hdy_squeezer_get_child_property;
+ gtk_container_class_handle_border_width (container_class);
+
+ g_object_class_override_property (object_class,
+ PROP_ORIENTATION,
+ "orientation");
+
+ props[PROP_HOMOGENEOUS] =
+ g_param_spec_boolean ("homogeneous",
+ _("Homogeneous"),
+ _("Homogeneous sizing"),
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_VISIBLE_CHILD] =
+ g_param_spec_object ("visible-child",
+ _("Visible child"),
+ _("The widget currently visible in the squeezer"),
+ GTK_TYPE_WIDGET,
+ G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_TRANSITION_DURATION] =
+ g_param_spec_uint ("transition-duration",
+ _("Transition duration"),
+ _("The animation duration, in milliseconds"),
+ 0, G_MAXUINT, 200,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_TRANSITION_TYPE] =
+ g_param_spec_enum ("transition-type",
+ _("Transition type"),
+ _("The type of animation used to transition"),
+ HDY_TYPE_SQUEEZER_TRANSITION_TYPE,
+ HDY_SQUEEZER_TRANSITION_TYPE_NONE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_TRANSITION_RUNNING] =
+ g_param_spec_boolean ("transition-running",
+ _("Transition running"),
+ _("Whether or not the transition is currently running"),
+ FALSE,
+ G_PARAM_READABLE);
+
+ props[PROP_INTERPOLATE_SIZE] =
+ g_param_spec_boolean ("interpolate-size",
+ _("Interpolate size"),
+ _("Whether or not the size should smoothly change when changing between differently sized children"),
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * HdySqueezer:xalign:
+ *
+ * The xalign property determines the horizontal aligment of the children
+ * inside the squeezer's size allocation.
+ * Compare this to #GtkWidget:halign, which determines how the squeezer's size
+ * allocation is positioned in the space available for the squeezer.
+ * The range goes from 0 (start) to 1 (end).
+ *
+ * This will affect the position of children too wide to fit in the squeezer
+ * as they are fading out.
+ *
+ * Since: 1.0
+ */
+ props[PROP_XALIGN] =
+ g_param_spec_float ("xalign",
+ _("X align"),
+ _("The horizontal alignment, from 0 (start) to 1 (end)"),
+ 0.0, 1.0,
+ 0.5,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * HdySqueezer:yalign:
+ *
+ * The yalign property determines the vertical aligment of the children inside
+ * the squeezer's size allocation.
+ * Compare this to #GtkWidget:valign, which determines how the squeezer's size
+ * allocation is positioned in the space available for the squeezer.
+ * The range goes from 0 (top) to 1 (bottom).
+ *
+ * This will affect the position of children too tall to fit in the squeezer
+ * as they are fading out.
+ *
+ * Since: 1.0
+ */
+ props[PROP_YALIGN] =
+ g_param_spec_float ("yalign",
+ _("Y align"),
+ _("The vertical alignment, from 0 (top) to 1 (bottom)"),
+ 0.0, 1.0,
+ 0.5,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+
+ child_props[CHILD_PROP_ENABLED] =
+ g_param_spec_boolean ("enabled",
+ _("Enabled"),
+ _("Whether the child can be picked or should be ignored when looking for the child fitting the available size best"),
+ TRUE,
+ G_PARAM_READWRITE);
+
+ gtk_container_class_install_child_properties (container_class, LAST_CHILD_PROP, child_props);
+
+ gtk_widget_class_set_css_name (widget_class, "squeezer");
+}
+
+static void
+hdy_squeezer_init (HdySqueezer *self)
+{
+
+ gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+
+ self->homogeneous = TRUE;
+ self->transition_duration = 200;
+ self->transition_type = HDY_SQUEEZER_TRANSITION_TYPE_NONE;
+ self->xalign = 0.5;
+ self->yalign = 0.5;
+}
+
+/**
+ * hdy_squeezer_new:
+ *
+ * Creates a new #HdySqueezer container.
+ *
+ * Returns: a new #HdySqueezer
+ */
+GtkWidget *
+hdy_squeezer_new (void)
+{
+ return g_object_new (HDY_TYPE_SQUEEZER, NULL);
+}
+
+/**
+ * hdy_squeezer_get_homogeneous:
+ * @self: a #HdySqueezer
+ *
+ * Gets whether @self is homogeneous.
+ *
+ * See hdy_squeezer_set_homogeneous().
+ *
+ * Returns: %TRUE if @self is homogeneous, %FALSE is not
+ *
+ * Since: 0.0.10
+ */
+gboolean
+hdy_squeezer_get_homogeneous (HdySqueezer *self)
+{
+ g_return_val_if_fail (HDY_IS_SQUEEZER (self), FALSE);
+
+ return self->homogeneous;
+}
+
+/**
+ * hdy_squeezer_set_homogeneous:
+ * @self: a #HdySqueezer
+ * @homogeneous: %TRUE to make @self homogeneous
+ *
+ * Sets @self to be homogeneous or not. If it is homogeneous, @self will request
+ * the same size for all its children for its opposite orientation, e.g. if
+ * @self is oriented horizontally and is homogeneous, it will request the same
+ * height for all its children. If it isn't, @self may change size when a
+ * different child becomes visible.
+ *
+ * Since: 0.0.10
+ */
+void
+hdy_squeezer_set_homogeneous (HdySqueezer *self,
+ gboolean homogeneous)
+{
+ g_return_if_fail (HDY_IS_SQUEEZER (self));
+
+ homogeneous = !!homogeneous;
+
+ if (self->homogeneous == homogeneous)
+ return;
+
+ self->homogeneous = homogeneous;
+
+ if (gtk_widget_get_visible (GTK_WIDGET(self)))
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_HOMOGENEOUS]);
+}
+
+/**
+ * hdy_squeezer_get_transition_duration:
+ * @self: a #HdySqueezer
+ *
+ * Gets the amount of time (in milliseconds) that transitions between children
+ * in @self will take.
+ *
+ * Returns: the transition duration
+ */
+guint
+hdy_squeezer_get_transition_duration (HdySqueezer *self)
+{
+ g_return_val_if_fail (HDY_IS_SQUEEZER (self), 0);
+
+ return self->transition_duration;
+}
+
+/**
+ * hdy_squeezer_set_transition_duration:
+ * @self: a #HdySqueezer
+ * @duration: the new duration, in milliseconds
+ *
+ * Sets the duration that transitions between children in @self will take.
+ */
+void
+hdy_squeezer_set_transition_duration (HdySqueezer *self,
+ guint duration)
+{
+ g_return_if_fail (HDY_IS_SQUEEZER (self));
+
+ if (self->transition_duration == duration)
+ return;
+
+ self->transition_duration = duration;
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_DURATION]);
+}
+
+/**
+ * hdy_squeezer_get_transition_type:
+ * @self: a #HdySqueezer
+ *
+ * Gets the type of animation that will be used for transitions between children
+ * in @self.
+ *
+ * Returns: the current transition type of @self
+ */
+HdySqueezerTransitionType
+hdy_squeezer_get_transition_type (HdySqueezer *self)
+{
+ g_return_val_if_fail (HDY_IS_SQUEEZER (self), HDY_SQUEEZER_TRANSITION_TYPE_NONE);
+
+ return self->transition_type;
+}
+
+/**
+ * hdy_squeezer_set_transition_type:
+ * @self: a #HdySqueezer
+ * @transition: the new transition type
+ *
+ * Sets the type of animation that will be used for transitions between children
+ * in @self. Available types include various kinds of fades and slides.
+ *
+ * The transition type can be changed without problems at runtime, so it is
+ * possible to change the animation based on the child that is about to become
+ * current.
+ */
+void
+hdy_squeezer_set_transition_type (HdySqueezer *self,
+ HdySqueezerTransitionType transition)
+{
+ g_return_if_fail (HDY_IS_SQUEEZER (self));
+
+ if (self->transition_type == transition)
+ return;
+
+ self->transition_type = transition;
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_TYPE]);
+}
+
+/**
+ * hdy_squeezer_get_transition_running:
+ * @self: a #HdySqueezer
+ *
+ * Gets whether @self is currently in a transition from one child to another.
+ *
+ * Returns: %TRUE if the transition is currently running, %FALSE otherwise.
+ */
+gboolean
+hdy_squeezer_get_transition_running (HdySqueezer *self)
+{
+ g_return_val_if_fail (HDY_IS_SQUEEZER (self), FALSE);
+
+ return (self->tick_id != 0);
+}
+
+/**
+ * hdy_squeezer_get_interpolate_size:
+ * @self: A #HdySqueezer
+ *
+ * Gets whether @self should interpolate its size on visible child change.
+ *
+ * See hdy_squeezer_set_interpolate_size().
+ *
+ * Returns: %TRUE if @self interpolates its size on visible child change, %FALSE if not
+ *
+ * Since: 0.0.10
+ */
+gboolean
+hdy_squeezer_get_interpolate_size (HdySqueezer *self)
+{
+ g_return_val_if_fail (HDY_IS_SQUEEZER (self), FALSE);
+
+ return self->interpolate_size;
+}
+
+/**
+ * hdy_squeezer_set_interpolate_size:
+ * @self: A #HdySqueezer
+ * @interpolate_size: %TRUE to interpolate the size
+ *
+ * Sets whether or not @self will interpolate the size of its opposing
+ * orientation when changing the visible child. If %TRUE, @self will interpolate
+ * its size between the one of the previous visible child and the one of the new
+ * visible child, according to the set transition duration and the orientation,
+ * e.g. if @self is horizontal, it will interpolate the its height.
+ *
+ * Since: 0.0.10
+ */
+void
+hdy_squeezer_set_interpolate_size (HdySqueezer *self,
+ gboolean interpolate_size)
+{
+ g_return_if_fail (HDY_IS_SQUEEZER (self));
+
+ interpolate_size = !!interpolate_size;
+
+ if (self->interpolate_size == interpolate_size)
+ return;
+
+ self->interpolate_size = interpolate_size;
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INTERPOLATE_SIZE]);
+}
+
+/**
+ * hdy_squeezer_get_visible_child:
+ * @self: a #HdySqueezer
+ *
+ * Gets the currently visible child of @self, or %NULL if there are no visible
+ * children.
+ *
+ * Returns: (transfer none) (nullable): the visible child of the #HdySqueezer
+ */
+GtkWidget *
+hdy_squeezer_get_visible_child (HdySqueezer *self)
+{
+ g_return_val_if_fail (HDY_IS_SQUEEZER (self), NULL);
+
+ return self->visible_child ? self->visible_child->widget : NULL;
+}
+
+/**
+ * hdy_squeezer_get_child_enabled:
+ * @self: a #HdySqueezer
+ * @child: a child of @self
+ *
+ * Gets whether @child is enabled.
+ *
+ * See hdy_squeezer_set_child_enabled().
+ *
+ * Returns: %TRUE if @child is enabled, %FALSE otherwise.
+ */
+gboolean
+hdy_squeezer_get_child_enabled (HdySqueezer *self,
+ GtkWidget *child)
+{
+ HdySqueezerChildInfo *child_info;
+
+ g_return_val_if_fail (HDY_IS_SQUEEZER (self), FALSE);
+ g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
+
+ child_info = find_child_info_for_widget (self, child);
+
+ g_return_val_if_fail (child_info != NULL, FALSE);
+
+ return child_info->enabled;
+}
+
+/**
+ * hdy_squeezer_set_child_enabled:
+ * @self: a #HdySqueezer
+ * @child: a child of @self
+ * @enabled: %TRUE to enable the child, %FALSE to disable it
+ *
+ * Make @self enable or disable @child. If a child is disabled, it will be
+ * ignored when looking for the child fitting the available size best. This
+ * allows to programmatically and prematurely hide a child of @self even if it
+ * fits in the available space.
+ *
+ * This can be used e.g. to ensure a certain child is hidden below a certain
+ * window width, or any other constraint you find suitable.
+ */
+void
+hdy_squeezer_set_child_enabled (HdySqueezer *self,
+ GtkWidget *child,
+ gboolean enabled)
+{
+ HdySqueezerChildInfo *child_info;
+
+ g_return_if_fail (HDY_IS_SQUEEZER (self));
+ g_return_if_fail (GTK_IS_WIDGET (child));
+
+ child_info = find_child_info_for_widget (self, child);
+
+ g_return_if_fail (child_info != NULL);
+
+ enabled = !!enabled;
+
+ if (child_info->enabled == enabled)
+ return;
+
+ child_info->enabled = enabled;
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+/**
+ * hdy_squeezer_get_xalign:
+ * @self: a #HdySqueezer
+ *
+ * Gets the #HdySqueezer:xalign property for @self.
+ *
+ * Returns: the xalign property
+ *
+ * Since: 1.0
+ */
+gfloat
+hdy_squeezer_get_xalign (HdySqueezer *self)
+{
+ g_return_val_if_fail (HDY_IS_SQUEEZER (self), 0.5);
+
+ return self->xalign;
+}
+
+/**
+ * hdy_squeezer_set_xalign:
+ * @self: a #HdySqueezer
+ * @xalign: the new xalign value, between 0 and 1
+ *
+ * Sets the #HdySqueezer:xalign property for @self.
+ *
+ * Since: 1.0
+ */
+void
+hdy_squeezer_set_xalign (HdySqueezer *self,
+ gfloat xalign)
+{
+ g_return_if_fail (HDY_IS_SQUEEZER (self));
+
+ xalign = CLAMP (xalign, 0.0, 1.0);
+
+ if (self->xalign == xalign)
+ return;
+
+ self->xalign = xalign;
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_XALIGN]);
+}
+
+/**
+ * hdy_squeezer_get_yalign:
+ * @self: a #HdySqueezer
+ *
+ * Gets the #HdySqueezer:yalign property for @self.
+ *
+ * Returns: the yalign property
+ *
+ * Since: 1.0
+ */
+gfloat
+hdy_squeezer_get_yalign (HdySqueezer *self)
+{
+ g_return_val_if_fail (HDY_IS_SQUEEZER (self), 0.5);
+
+ return self->yalign;
+}
+
+/**
+ * hdy_squeezer_set_yalign:
+ * @self: a #HdySqueezer
+ * @yalign: the new yalign value, between 0 and 1
+ *
+ * Sets the #HdySqueezer:yalign property for @self.
+ *
+ * Since: 1.0
+ */
+void
+hdy_squeezer_set_yalign (HdySqueezer *self,
+ gfloat yalign)
+{
+ g_return_if_fail (HDY_IS_SQUEEZER (self));
+
+ yalign = CLAMP (yalign, 0.0, 1.0);
+
+ if (self->yalign == yalign)
+ return;
+
+ self->yalign = yalign;
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_YALIGN]);
+}