/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * GtkAspectFrame: Ensure that the child window has a specified aspect ratio * or, if obey_child, has the same aspect ratio as its requested size * * Copyright Owen Taylor 4/9/97 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ /* * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ /** * GtkAspectFrame: * * `GtkAspectFrame` preserves the aspect ratio of its child. * * The frame can respect the aspect ratio of the child widget, * or use its own aspect ratio. * * # CSS nodes * * `GtkAspectFrame` uses a CSS node with name `frame`. * * # Accessibility * * Until GTK 4.10, `GtkAspectFrame` used the `GTK_ACCESSIBLE_ROLE_GROUP` role. * * Starting from GTK 4.12, `GtkAspectFrame` uses the `GTK_ACCESSIBLE_ROLE_GENERIC` role. */ #include "config.h" #include "gtkaspectframe.h" #include "gtksizerequest.h" #include "gtkbuildable.h" #include "gtkwidgetprivate.h" #include "gtkprivate.h" typedef struct _GtkAspectFrameClass GtkAspectFrameClass; struct _GtkAspectFrame { GtkWidget parent_instance; GtkWidget *child; gboolean obey_child; float xalign; float yalign; float ratio; }; struct _GtkAspectFrameClass { GtkWidgetClass parent_class; }; enum { PROP_0, PROP_XALIGN, PROP_YALIGN, PROP_RATIO, PROP_OBEY_CHILD, PROP_CHILD }; static void gtk_aspect_frame_dispose (GObject *object); static void gtk_aspect_frame_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gtk_aspect_frame_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gtk_aspect_frame_size_allocate (GtkWidget *widget, int width, int height, int baseline); static void gtk_aspect_frame_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline); static void gtk_aspect_frame_compute_expand (GtkWidget *widget, gboolean *hexpand, gboolean *vexpand); static GtkSizeRequestMode gtk_aspect_frame_get_request_mode (GtkWidget *widget); static void gtk_aspect_frame_buildable_init (GtkBuildableIface *iface); #define MAX_RATIO 10000.0 #define MIN_RATIO 0.0001 G_DEFINE_TYPE_WITH_CODE (GtkAspectFrame, gtk_aspect_frame, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_aspect_frame_buildable_init)) static void gtk_aspect_frame_class_init (GtkAspectFrameClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); gobject_class->dispose = gtk_aspect_frame_dispose; gobject_class->set_property = gtk_aspect_frame_set_property; gobject_class->get_property = gtk_aspect_frame_get_property; widget_class->measure = gtk_aspect_frame_measure; widget_class->size_allocate = gtk_aspect_frame_size_allocate; widget_class->compute_expand = gtk_aspect_frame_compute_expand; widget_class->get_request_mode = gtk_aspect_frame_get_request_mode; /** * GtkAspectFrame:xalign: (attributes org.gtk.Property.get=gtk_aspect_frame_get_xalign org.gtk.Property.set=gtk_aspect_frame_set_xalign) * * The horizontal alignment of the child. */ g_object_class_install_property (gobject_class, PROP_XALIGN, g_param_spec_float ("xalign", NULL, NULL, 0.0, 1.0, 0.5, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); /** * GtkAspectFrame:yalign: (attributes org.gtk.Property.get=gtk_aspect_frame_get_yalign org.gtk.Property.set=gtk_aspect_frame_set_yalign) * * The vertical alignment of the child. */ g_object_class_install_property (gobject_class, PROP_YALIGN, g_param_spec_float ("yalign", NULL, NULL, 0.0, 1.0, 0.5, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); /** * GtkAspectFrame:ratio: (attributes org.gtk.Property.get=gtk_aspect_frame_get_ratio org.gtk.Property.set=gtk_aspect_frame_set_ratio) * * The aspect ratio to be used by the `GtkAspectFrame`. * * This property is only used if * [property@Gtk.AspectFrame:obey-child] is set to %FALSE. */ g_object_class_install_property (gobject_class, PROP_RATIO, g_param_spec_float ("ratio", NULL, NULL, MIN_RATIO, MAX_RATIO, 1.0, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); /** * GtkAspectFrame:obey-child: (attributes org.gtk.Property.get=gtk_aspect_frame_get_obey_child org.gtk.Property.set=gtk_aspect_frame_set_obey_child) * * Whether the `GtkAspectFrame` should use the aspect ratio of its child. */ g_object_class_install_property (gobject_class, PROP_OBEY_CHILD, g_param_spec_boolean ("obey-child", NULL, NULL, TRUE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); /** * GtkAspectFrame:child: (attributes org.gtk.Property.get=gtk_aspect_frame_get_child org.gtk.Property.set=gtk_aspect_frame_set_child) * * The child widget. */ g_object_class_install_property (gobject_class, PROP_CHILD, g_param_spec_object ("child", NULL, NULL, GTK_TYPE_WIDGET, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), I_("aspectframe")); gtk_widget_class_set_accessible_role (GTK_WIDGET_CLASS (class), GTK_ACCESSIBLE_ROLE_GENERIC); } static void gtk_aspect_frame_init (GtkAspectFrame *self) { self->xalign = 0.5; self->yalign = 0.5; self->ratio = 1.0; self->obey_child = TRUE; } static void gtk_aspect_frame_dispose (GObject *object) { GtkAspectFrame *self = GTK_ASPECT_FRAME (object); g_clear_pointer (&self->child, gtk_widget_unparent); G_OBJECT_CLASS (gtk_aspect_frame_parent_class)->dispose (object); } static void gtk_aspect_frame_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkAspectFrame *self = GTK_ASPECT_FRAME (object); switch (prop_id) { /* g_object_notify is handled by the _frame_set function */ case PROP_XALIGN: gtk_aspect_frame_set_xalign (self, g_value_get_float (value)); break; case PROP_YALIGN: gtk_aspect_frame_set_yalign (self, g_value_get_float (value)); break; case PROP_RATIO: gtk_aspect_frame_set_ratio (self, g_value_get_float (value)); break; case PROP_OBEY_CHILD: gtk_aspect_frame_set_obey_child (self, g_value_get_boolean (value)); break; case PROP_CHILD: gtk_aspect_frame_set_child (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_aspect_frame_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkAspectFrame *self = GTK_ASPECT_FRAME (object); switch (prop_id) { case PROP_XALIGN: g_value_set_float (value, self->xalign); break; case PROP_YALIGN: g_value_set_float (value, self->yalign); break; case PROP_RATIO: g_value_set_float (value, self->ratio); break; case PROP_OBEY_CHILD: g_value_set_boolean (value, self->obey_child); break; case PROP_CHILD: g_value_set_object (value, gtk_aspect_frame_get_child (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GtkBuildableIface *parent_buildable_iface; static void gtk_aspect_frame_buildable_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const char *type) { if (GTK_IS_WIDGET (child)) gtk_aspect_frame_set_child (GTK_ASPECT_FRAME (buildable), GTK_WIDGET (child)); else parent_buildable_iface->add_child (buildable, builder, child, type); } static void gtk_aspect_frame_buildable_init (GtkBuildableIface *iface) { parent_buildable_iface = g_type_interface_peek_parent (iface); iface->add_child = gtk_aspect_frame_buildable_add_child; } /** * gtk_aspect_frame_new: * @xalign: Horizontal alignment of the child within the parent. * Ranges from 0.0 (left aligned) to 1.0 (right aligned) * @yalign: Vertical alignment of the child within the parent. * Ranges from 0.0 (top aligned) to 1.0 (bottom aligned) * @ratio: The desired aspect ratio. * @obey_child: If %TRUE, @ratio is ignored, and the aspect * ratio is taken from the requistion of the child. * * Create a new `GtkAspectFrame`. * * Returns: the new `GtkAspectFrame`. */ GtkWidget * gtk_aspect_frame_new (float xalign, float yalign, float ratio, gboolean obey_child) { GtkAspectFrame *self; self = g_object_new (GTK_TYPE_ASPECT_FRAME, NULL); self->xalign = CLAMP (xalign, 0.0, 1.0); self->yalign = CLAMP (yalign, 0.0, 1.0); self->ratio = CLAMP (ratio, MIN_RATIO, MAX_RATIO); self->obey_child = obey_child != FALSE; return GTK_WIDGET (self); } /** * gtk_aspect_frame_set_xalign: (attributes org.gtk.Method.set_property=xalign) * @self: a `GtkAspectFrame` * @xalign: horizontal alignment, from 0.0 (left aligned) to 1.0 (right aligned) * * Sets the horizontal alignment of the child within the allocation * of the `GtkAspectFrame`. */ void gtk_aspect_frame_set_xalign (GtkAspectFrame *self, float xalign) { g_return_if_fail (GTK_IS_ASPECT_FRAME (self)); xalign = CLAMP (xalign, 0.0, 1.0); if (self->xalign == xalign) return; self->xalign = xalign; g_object_notify (G_OBJECT (self), "xalign"); gtk_widget_queue_resize (GTK_WIDGET (self)); } /** * gtk_aspect_frame_get_xalign: (attributes org.gtk.Method.get_property=xalign) * @self: a `GtkAspectFrame` * * Returns the horizontal alignment of the child within the * allocation of the `GtkAspectFrame`. * * Returns: the horizontal alignment */ float gtk_aspect_frame_get_xalign (GtkAspectFrame *self) { g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), 0.5); return self->xalign; } /** * gtk_aspect_frame_set_yalign: (attributes org.gtk.Method.set_property=yalign) * @self: a `GtkAspectFrame` * @yalign: horizontal alignment, from 0.0 (top aligned) to 1.0 (bottom aligned) * * Sets the vertical alignment of the child within the allocation * of the `GtkAspectFrame`. */ void gtk_aspect_frame_set_yalign (GtkAspectFrame *self, float yalign) { g_return_if_fail (GTK_IS_ASPECT_FRAME (self)); yalign = CLAMP (yalign, 0.0, 1.0); if (self->yalign == yalign) return; self->yalign = yalign; g_object_notify (G_OBJECT (self), "yalign"); gtk_widget_queue_resize (GTK_WIDGET (self)); } /** * gtk_aspect_frame_get_yalign: (attributes org.gtk.Method.get_property=yalign) * @self: a `GtkAspectFrame` * * Returns the vertical alignment of the child within the * allocation of the `GtkAspectFrame`. * * Returns: the vertical alignment */ float gtk_aspect_frame_get_yalign (GtkAspectFrame *self) { g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), 0.5); return self->xalign; } /** * gtk_aspect_frame_set_ratio: (attributes org.gtk.Method.set_property=ratio) * @self: a `GtkAspectFrame` * @ratio: aspect ratio of the child * * Sets the desired aspect ratio of the child. */ void gtk_aspect_frame_set_ratio (GtkAspectFrame *self, float ratio) { g_return_if_fail (GTK_IS_ASPECT_FRAME (self)); ratio = CLAMP (ratio, MIN_RATIO, MAX_RATIO); if (self->ratio == ratio) return; self->ratio = ratio; g_object_notify (G_OBJECT (self), "ratio"); gtk_widget_queue_resize (GTK_WIDGET (self)); } /** * gtk_aspect_frame_get_ratio: (attributes org.gtk.Method.get_property=ratio) * @self: a `GtkAspectFrame` * * Returns the desired aspect ratio of the child. * * Returns: the desired aspect ratio */ float gtk_aspect_frame_get_ratio (GtkAspectFrame *self) { g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), 1.0); return self->ratio; } /** * gtk_aspect_frame_set_obey_child: (attributes org.gtk.Method.set_propery=obey-child) * @self: a `GtkAspectFrame` * @obey_child: If %TRUE, @ratio is ignored, and the aspect * ratio is taken from the requisition of the child. * * Sets whether the aspect ratio of the child's size * request should override the set aspect ratio of * the `GtkAspectFrame`. */ void gtk_aspect_frame_set_obey_child (GtkAspectFrame *self, gboolean obey_child) { g_return_if_fail (GTK_IS_ASPECT_FRAME (self)); if (self->obey_child == obey_child) return; self->obey_child = obey_child; g_object_notify (G_OBJECT (self), "obey-child"); gtk_widget_queue_resize (GTK_WIDGET (self)); } /** * gtk_aspect_frame_get_obey_child: (attributes org.gtk.Method.get_property=obey-child) * @self: a `GtkAspectFrame` * * Returns whether the child's size request should override * the set aspect ratio of the `GtkAspectFrame`. * * Returns: whether to obey the child's size request */ gboolean gtk_aspect_frame_get_obey_child (GtkAspectFrame *self) { g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), TRUE); return self->obey_child; } static void get_full_allocation (GtkAspectFrame *self, GtkAllocation *child_allocation) { child_allocation->x = 0; child_allocation->y = 0; child_allocation->width = gtk_widget_get_width (GTK_WIDGET (self)); child_allocation->height = gtk_widget_get_height (GTK_WIDGET (self)); } static void compute_child_allocation (GtkAspectFrame *self, GtkAllocation *child_allocation) { double ratio; if (self->child && gtk_widget_get_visible (self->child)) { GtkAllocation full_allocation; if (self->obey_child) { GtkRequisition child_requisition; gtk_widget_get_preferred_size (self->child, &child_requisition, NULL); if (child_requisition.height != 0) { ratio = ((double) child_requisition.width / child_requisition.height); if (ratio < MIN_RATIO) ratio = MIN_RATIO; } else if (child_requisition.width != 0) ratio = MAX_RATIO; else ratio = 1.0; } else ratio = self->ratio; get_full_allocation (self, &full_allocation); if (ratio * full_allocation.height > full_allocation.width) { child_allocation->width = full_allocation.width; child_allocation->height = full_allocation.width / ratio + 0.5; } else { child_allocation->width = ratio * full_allocation.height + 0.5; child_allocation->height = full_allocation.height; } child_allocation->x = full_allocation.x + self->xalign * (full_allocation.width - child_allocation->width); child_allocation->y = full_allocation.y + self->yalign * (full_allocation.height - child_allocation->height); } else get_full_allocation (self, child_allocation); } static void gtk_aspect_frame_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkAspectFrame *self = GTK_ASPECT_FRAME (widget); if (self->child && gtk_widget_get_visible (self->child)) { int child_min, child_nat; gtk_widget_measure (self->child, orientation, for_size, &child_min, &child_nat, NULL, NULL); *minimum = child_min; *natural = child_nat; } else { *minimum = 0; *natural = 0; } } static void gtk_aspect_frame_size_allocate (GtkWidget *widget, int width, int height, int baseline) { GtkAspectFrame *self = GTK_ASPECT_FRAME (widget); GtkAllocation new_allocation; compute_child_allocation (self, &new_allocation); if (self->child && gtk_widget_get_visible (self->child)) gtk_widget_size_allocate (self->child, &new_allocation, -1); } static void gtk_aspect_frame_compute_expand (GtkWidget *widget, gboolean *hexpand, gboolean *vexpand) { GtkAspectFrame *self = GTK_ASPECT_FRAME (widget); if (self->child) { *hexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_HORIZONTAL); *vexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_VERTICAL); } else { *hexpand = FALSE; *vexpand = FALSE; } } static GtkSizeRequestMode gtk_aspect_frame_get_request_mode (GtkWidget *widget) { GtkAspectFrame *self = GTK_ASPECT_FRAME (widget); if (self->child) return gtk_widget_get_request_mode (self->child); else return GTK_SIZE_REQUEST_CONSTANT_SIZE; } /** * gtk_aspect_frame_set_child: (attributes org.gtk.Method.set_property=child) * @self: a `GtkAspectFrame` * @child: (nullable): the child widget * * Sets the child widget of @self. */ void gtk_aspect_frame_set_child (GtkAspectFrame *self, GtkWidget *child) { g_return_if_fail (GTK_IS_ASPECT_FRAME (self)); g_return_if_fail (child == NULL || self->child == child || gtk_widget_get_parent (child) == NULL); if (self->child == child) return; g_clear_pointer (&self->child, gtk_widget_unparent); if (child) { self->child = child; gtk_widget_set_parent (child, GTK_WIDGET (self)); } g_object_notify (G_OBJECT (self), "child"); } /** * gtk_aspect_frame_get_child: (attributes org.gtk.Method.get_property=child) * @self: a `GtkAspectFrame` * * Gets the child widget of @self. * * Returns: (nullable) (transfer none): the child widget of @self */ GtkWidget * gtk_aspect_frame_get_child (GtkAspectFrame *self) { g_return_val_if_fail (GTK_IS_ASPECT_FRAME (self), NULL); return self->child; }