/* * Copyright (C) 2018 Purism SPC * * SPDX-License-Identifier: LGPL-2.1+ */ #include "bluetooth-hdy-column.h" #include #include /* * SECTION:bluetooth-hdy-column * @short_description: A container letting its child grow up to a given width. * @Title: BluetoothHdyColumn * * The #BluetoothHdyColumn widget limits the size of the widget it contains to a * given maximum width. The expansion of the child from its minimum to its * maximum size is eased out for a smooth transition. * * If the child requires more than the requested maximum width, it will be * allocated the minimum width it can fit in instead. */ #define BLUETOOTH_EASE_OUT_TAN_CUBIC 3 enum { PROP_0, PROP_MAXIMUM_WIDTH, PROP_LINEAR_GROWTH_WIDTH, LAST_PROP, }; struct _BluetoothHdyColumn { GtkBin parent_instance; gint maximum_width; gint linear_growth_width; }; static GParamSpec *props[LAST_PROP]; G_DEFINE_TYPE (BluetoothHdyColumn, bluetooth_hdy_column, GTK_TYPE_BIN) static gdouble ease_out_cubic (gdouble progress) { gdouble tmp = progress - 1; return tmp * tmp * tmp + 1; } static void bluetooth_hdy_column_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BluetoothHdyColumn *self = BLUETOOTH_HDY_COLUMN (object); switch (prop_id) { case PROP_MAXIMUM_WIDTH: g_value_set_int (value, bluetooth_hdy_column_get_maximum_width (self)); break; case PROP_LINEAR_GROWTH_WIDTH: g_value_set_int (value, bluetooth_hdy_column_get_linear_growth_width (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bluetooth_hdy_column_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BluetoothHdyColumn *self = BLUETOOTH_HDY_COLUMN (object); switch (prop_id) { case PROP_MAXIMUM_WIDTH: bluetooth_hdy_column_set_maximum_width (self, g_value_get_int (value)); break; case PROP_LINEAR_GROWTH_WIDTH: bluetooth_hdy_column_set_linear_growth_width (self, g_value_get_int (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static gint get_child_width (BluetoothHdyColumn *self, gint width) { GtkBin *bin = GTK_BIN (self); GtkWidget *child; gint minimum_width = 0, maximum_width; gdouble amplitude, threshold, progress; child = gtk_bin_get_child (bin); if (child == NULL) return 0; if (gtk_widget_get_visible (child)) gtk_widget_get_preferred_width (child, &minimum_width, NULL); /* Sanitize the minimum width to use for computations. */ minimum_width = MIN (MAX (minimum_width, self->linear_growth_width), self->maximum_width); if (width <= minimum_width) return width; /* Sanitize the maximum width to use for computations. */ maximum_width = MAX (minimum_width, self->maximum_width); amplitude = maximum_width - minimum_width; threshold = (BLUETOOTH_EASE_OUT_TAN_CUBIC * amplitude + (gdouble) minimum_width); if (width >= threshold) return maximum_width; progress = (width - minimum_width) / (threshold - minimum_width); return ease_out_cubic (progress) * amplitude + minimum_width; } /* This private method is prefixed by the call name because it will be a virtual * method in GTK+ 4. */ static void bluetooth_hdy_column_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkBin *bin = GTK_BIN (widget); GtkWidget *child; if (minimum) *minimum = 0; if (natural) *natural = 0; if (minimum_baseline) *minimum_baseline = -1; if (natural_baseline) *natural_baseline = -1; child = gtk_bin_get_child (bin); if (!(child && gtk_widget_get_visible (child))) return; if (orientation == GTK_ORIENTATION_HORIZONTAL) gtk_widget_get_preferred_width (child, minimum, natural); else { gint child_width = get_child_width (BLUETOOTH_HDY_COLUMN (widget), for_size); gtk_widget_get_preferred_height_and_baseline_for_width (child, child_width, minimum, natural, minimum_baseline, natural_baseline); } } static void bluetooth_hdy_column_get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural) { bluetooth_hdy_column_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, minimum, natural, NULL, NULL); } static void bluetooth_hdy_column_get_preferred_height_and_baseline_for_width (GtkWidget *widget, gint width, gint *minimum, gint *natural, gint *minimum_baseline, gint *natural_baseline) { bluetooth_hdy_column_measure (widget, GTK_ORIENTATION_VERTICAL, width, minimum, natural, minimum_baseline, natural_baseline); } static void bluetooth_hdy_column_get_preferred_height (GtkWidget *widget, gint *minimum, gint *natural) { bluetooth_hdy_column_measure (widget, GTK_ORIENTATION_VERTICAL, -1, minimum, natural, NULL, NULL); } static void bluetooth_hdy_column_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { BluetoothHdyColumn *self = BLUETOOTH_HDY_COLUMN (widget); GtkBin *bin = GTK_BIN (widget); GtkAllocation child_allocation; gint baseline; GtkWidget *child; gtk_widget_set_allocation (widget, allocation); child = gtk_bin_get_child (bin); if (child == NULL) return; child_allocation.width = get_child_width (self, allocation->width); child_allocation.height = allocation->height; if (!gtk_widget_get_has_window (widget)) { /* This allways center the child vertically. */ child_allocation.x = allocation->x + (allocation->width - child_allocation.width) / 2; child_allocation.y = allocation->y; } else { child_allocation.x = 0; child_allocation.y = 0; } baseline = gtk_widget_get_allocated_baseline (widget); gtk_widget_size_allocate_with_baseline (child, &child_allocation, baseline); } static void bluetooth_hdy_column_class_init (BluetoothHdyColumnClass *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 = bluetooth_hdy_column_get_property; object_class->set_property = bluetooth_hdy_column_set_property; widget_class->get_preferred_width = bluetooth_hdy_column_get_preferred_width; widget_class->get_preferred_height = bluetooth_hdy_column_get_preferred_height; widget_class->get_preferred_height_and_baseline_for_width = bluetooth_hdy_column_get_preferred_height_and_baseline_for_width; widget_class->size_allocate = bluetooth_hdy_column_size_allocate; gtk_container_class_handle_border_width (container_class); /** * BluetoothHdyColumn:maximum_width: * * The maximum width to allocate to the child. */ props[PROP_MAXIMUM_WIDTH] = g_param_spec_int ("maximum-width", _("Maximum width"), _("The maximum width allocated to the child"), 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * BluetoothHdyColumn:linear_growth_width: * * The width up to which the child will be allocated all the width. */ props[PROP_LINEAR_GROWTH_WIDTH] = g_param_spec_int ("linear-growth-width", _("Linear growth width"), _("The width up to which the child will be allocated all the width"), 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); gtk_widget_class_set_css_name (widget_class, "bluetoothcolumn"); } static void bluetooth_hdy_column_init (BluetoothHdyColumn *self) { } /** * bluetooth_hdy_column_new: * * Creates a new #BluetoothHdyColumn. * * Returns: a new #BluetoothHdyColumn */ BluetoothHdyColumn * bluetooth_hdy_column_new (void) { return g_object_new (BLUETOOTH_TYPE_HDY_COLUMN, NULL); } /** * bluetooth_hdy_column_get_maximum_width: * @self: a #BluetoothHdyColumn * * Gets the maximum width to allocate to the contained child. * * Returns: the maximum width to allocate to the contained child. */ gint bluetooth_hdy_column_get_maximum_width (BluetoothHdyColumn *self) { g_return_val_if_fail (BLUETOOTH_IS_HDY_COLUMN (self), 0); return self->maximum_width; } /** * bluetooth_hdy_column_set_maximum_width: * @self: a #BluetoothHdyColumn * @maximum_width: the maximum width * * Sets the maximum width to allocate to the contained child. */ void bluetooth_hdy_column_set_maximum_width (BluetoothHdyColumn *self, gint maximum_width) { g_return_if_fail (BLUETOOTH_IS_HDY_COLUMN (self)); self->maximum_width = maximum_width; } /** * bluetooth_hdy_column_get_linear_growth_width: * @self: a #BluetoothHdyColumn * * Gets the width up to which the child will be allocated all the available * width and starting from which it will be allocated a portion of the available * width. In bith cases the allocated width won't exceed the declared maximum. * * Returns: the width up to which the child will be allocated all the available * width. */ gint bluetooth_hdy_column_get_linear_growth_width (BluetoothHdyColumn *self) { g_return_val_if_fail (BLUETOOTH_IS_HDY_COLUMN (self), 0); return self->linear_growth_width; } /** * bluetooth_hdy_column_set_linear_growth_width: * @self: a #BluetoothHdyColumn * @linear_growth_width: the linear growth width * * Sets the width up to which the child will be allocated all the available * width and starting from which it will be allocated a portion of the available * width. In bith cases the allocated width won't exceed the declared maximum. * */ void bluetooth_hdy_column_set_linear_growth_width (BluetoothHdyColumn *self, gint linear_growth_width) { g_return_if_fail (BLUETOOTH_IS_HDY_COLUMN (self)); self->linear_growth_width = linear_growth_width; gtk_widget_queue_resize (GTK_WIDGET (self)); }