/* gtkoverlaylayout.c: Overlay layout manager * * SPDX-License-Identifier: LGPL-2.1-or-later * * Copyright 2019 Red Hat, Inc. * * 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 . */ #include "config.h" #include "gtkoverlaylayout.h" #include "gtkintl.h" #include "gtklayoutchild.h" #include "gtkoverlay.h" #include "gtkprivate.h" #include "gtkwidgetprivate.h" #include /** * GtkOverlayLayout: * * `GtkOverlayLayout` is the layout manager used by `GtkOverlay`. * * It places widgets as overlays on top of the main child. * * This is not a reusable layout manager, since it expects its widget * to be a `GtkOverlay`. It only listed here so that its layout * properties get documented. */ /** * GtkOverlayLayoutChild: * * `GtkLayoutChild` subclass for children in a `GtkOverlayLayout`. */ struct _GtkOverlayLayout { GtkLayoutManager parent_instance; }; struct _GtkOverlayLayoutChild { GtkLayoutChild parent_instance; guint measure : 1; guint clip_overlay : 1; }; enum { PROP_MEASURE = 1, PROP_CLIP_OVERLAY, N_CHILD_PROPERTIES }; static GParamSpec *child_props[N_CHILD_PROPERTIES]; G_DEFINE_TYPE (GtkOverlayLayoutChild, gtk_overlay_layout_child, GTK_TYPE_LAYOUT_CHILD) static void gtk_overlay_layout_child_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkOverlayLayoutChild *self = GTK_OVERLAY_LAYOUT_CHILD (gobject); switch (prop_id) { case PROP_MEASURE: gtk_overlay_layout_child_set_measure (self, g_value_get_boolean (value)); break; case PROP_CLIP_OVERLAY: gtk_overlay_layout_child_set_clip_overlay (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void gtk_overlay_layout_child_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { GtkOverlayLayoutChild *self = GTK_OVERLAY_LAYOUT_CHILD (gobject); switch (prop_id) { case PROP_MEASURE: g_value_set_boolean (value, self->measure); break; case PROP_CLIP_OVERLAY: g_value_set_boolean (value, self->clip_overlay); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void gtk_overlay_layout_child_class_init (GtkOverlayLayoutChildClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = gtk_overlay_layout_child_set_property; gobject_class->get_property = gtk_overlay_layout_child_get_property; /** * GtkOverlayLayoutChild:measure: (attributes org.gtk.Property.get=gtk_overlay_layout_child_get_measure org.gtk.Property.set=gtk_overlay_layout_child_set_measure) * * Whether the child size should contribute to the `GtkOverlayLayout`'s * measurement. */ child_props[PROP_MEASURE] = g_param_spec_boolean ("measure", NULL, NULL, FALSE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); /** * GtkOverlayLayoutChild:clip-overlay: (attributes org.gtk.Property.get=gtk_overlay_layout_child_get_clip_overlay org.gtk.Property.set=gtk_overlay_layout_child_set_clip_overlay) * * Whether the child should be clipped to fit the parent's size. */ child_props[PROP_CLIP_OVERLAY] = g_param_spec_boolean ("clip-overlay", NULL, NULL, FALSE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (gobject_class, N_CHILD_PROPERTIES, child_props); } static void gtk_overlay_layout_child_init (GtkOverlayLayoutChild *self) { } /** * gtk_overlay_layout_child_set_measure: (attributes org.gtk.Method.set_property=measure) * @child: a `GtkOverlayLayoutChild` * @measure: whether to measure this child * * Sets whether to measure this child. */ void gtk_overlay_layout_child_set_measure (GtkOverlayLayoutChild *child, gboolean measure) { GtkLayoutManager *layout; g_return_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child)); if (child->measure == measure) return; child->measure = measure; layout = gtk_layout_child_get_layout_manager (GTK_LAYOUT_CHILD (child)); gtk_layout_manager_layout_changed (layout); g_object_notify_by_pspec (G_OBJECT (child), child_props[PROP_MEASURE]); } /** * gtk_overlay_layout_child_get_measure: (attributes org.gtk.Method.get_property=measure) * @child: a `GtkOverlayLayoutChild` * * Retrieves whether the child is measured. * * Returns: whether the child is measured */ gboolean gtk_overlay_layout_child_get_measure (GtkOverlayLayoutChild *child) { g_return_val_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child), FALSE); return child->measure; } /** * gtk_overlay_layout_child_set_clip_overlay: (attributes org.gtk.Method.set_property=clip-overlay) * @child: a `GtkOverlayLayoutChild` * @clip_overlay: whether to clip this child * * Sets whether to clip this child. */ void gtk_overlay_layout_child_set_clip_overlay (GtkOverlayLayoutChild *child, gboolean clip_overlay) { GtkLayoutManager *layout; g_return_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child)); if (child->clip_overlay == clip_overlay) return; child->clip_overlay = clip_overlay; layout = gtk_layout_child_get_layout_manager (GTK_LAYOUT_CHILD (child)); gtk_layout_manager_layout_changed (layout); g_object_notify_by_pspec (G_OBJECT (child), child_props[PROP_CLIP_OVERLAY]); } /** * gtk_overlay_layout_child_get_clip_overlay: (attributes org.gtk.Method.get_property=clip-overlay) * @child: a `GtkOverlayLayoutChild` * * Retrieves whether the child is clipped. * * Returns: whether the child is clipped */ gboolean gtk_overlay_layout_child_get_clip_overlay (GtkOverlayLayoutChild *child) { g_return_val_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child), FALSE); return child->clip_overlay; } G_DEFINE_TYPE (GtkOverlayLayout, gtk_overlay_layout, GTK_TYPE_LAYOUT_MANAGER) static void gtk_overlay_layout_measure (GtkLayoutManager *layout_manager, GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkOverlayLayoutChild *child_info; GtkWidget *child; int min, nat; GtkWidget *main_widget; main_widget = gtk_overlay_get_child (GTK_OVERLAY (widget)); min = 0; nat = 0; for (child = _gtk_widget_get_first_child (widget); child != NULL; child = _gtk_widget_get_next_sibling (child)) { if (!gtk_widget_should_layout (child)) continue; child_info = GTK_OVERLAY_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (layout_manager, child)); if (child == main_widget || child_info->measure) { int child_min, child_nat, child_min_baseline, child_nat_baseline; gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, &child_min_baseline, &child_nat_baseline); min = MAX (min, child_min); nat = MAX (nat, child_nat); } } if (minimum != NULL) *minimum = min; if (natural != NULL) *natural = nat; } static void gtk_overlay_compute_child_allocation (GtkOverlay *overlay, GtkWidget *widget, GtkOverlayLayoutChild *child, GtkAllocation *widget_allocation) { GtkAllocation allocation; gboolean result; g_signal_emit_by_name (overlay, "get-child-position", widget, &allocation, &result); widget_allocation->x = allocation.x; widget_allocation->y = allocation.y; widget_allocation->width = allocation.width; widget_allocation->height = allocation.height; } static GtkAlign effective_align (GtkAlign align, GtkTextDirection direction) { switch (align) { case GTK_ALIGN_START: return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_END : GTK_ALIGN_START; case GTK_ALIGN_END: return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_START : GTK_ALIGN_END; case GTK_ALIGN_FILL: case GTK_ALIGN_CENTER: case GTK_ALIGN_BASELINE: default: return align; } } static void gtk_overlay_child_update_style_classes (GtkOverlay *overlay, GtkWidget *child, GtkAllocation *child_allocation) { GtkWidget *widget = GTK_WIDGET (overlay); int width, height; GtkAlign valign, halign; gboolean is_left, is_right, is_top, is_bottom; gboolean has_left, has_right, has_top, has_bottom; has_left = gtk_widget_has_css_class (child, "left"); has_right = gtk_widget_has_css_class (child, "right"); has_top = gtk_widget_has_css_class (child, "top"); has_bottom = gtk_widget_has_css_class (child, "bottom"); is_left = is_right = is_top = is_bottom = FALSE; width = gtk_widget_get_width (widget); height = gtk_widget_get_height (widget); halign = effective_align (gtk_widget_get_halign (child), gtk_widget_get_direction (child)); if (halign == GTK_ALIGN_START) is_left = (child_allocation->x == 0); else if (halign == GTK_ALIGN_END) is_right = (child_allocation->x + child_allocation->width == width); valign = gtk_widget_get_valign (child); if (valign == GTK_ALIGN_START) is_top = (child_allocation->y == 0); else if (valign == GTK_ALIGN_END) is_bottom = (child_allocation->y + child_allocation->height == height); if (has_left && !is_left) gtk_widget_remove_css_class (child, "left"); else if (!has_left && is_left) gtk_widget_add_css_class (child, "left"); if (has_right && !is_right) gtk_widget_remove_css_class (child, "right"); else if (!has_right && is_right) gtk_widget_add_css_class (child, "right"); if (has_top && !is_top) gtk_widget_remove_css_class (child, "top"); else if (!has_top && is_top) gtk_widget_add_css_class (child, "top"); if (has_bottom && !is_bottom) gtk_widget_remove_css_class (child, "bottom"); else if (!has_bottom && is_bottom) gtk_widget_add_css_class (child, "bottom"); } static void gtk_overlay_child_allocate (GtkOverlay *overlay, GtkWidget *widget, GtkOverlayLayoutChild *child) { GtkAllocation child_allocation; if (!gtk_widget_should_layout (widget)) return; gtk_overlay_compute_child_allocation (overlay, widget, child, &child_allocation); gtk_overlay_child_update_style_classes (overlay, widget, &child_allocation); gtk_widget_size_allocate (widget, &child_allocation, -1); } static void gtk_overlay_layout_allocate (GtkLayoutManager *layout_manager, GtkWidget *widget, int width, int height, int baseline) { GtkWidget *child; GtkWidget *main_widget; main_widget = gtk_overlay_get_child (GTK_OVERLAY (widget)); if (main_widget && gtk_widget_get_visible (main_widget)) gtk_widget_size_allocate (main_widget, &(GtkAllocation) { 0, 0, width, height }, -1); for (child = _gtk_widget_get_first_child (widget); child != NULL; child = _gtk_widget_get_next_sibling (child)) { if (child != main_widget) { GtkOverlayLayoutChild *child_data; child_data = GTK_OVERLAY_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (layout_manager, child)); gtk_overlay_child_allocate (GTK_OVERLAY (widget), child, child_data); } } } static void gtk_overlay_layout_class_init (GtkOverlayLayoutClass *klass) { GtkLayoutManagerClass *layout_class = GTK_LAYOUT_MANAGER_CLASS (klass); layout_class->layout_child_type = GTK_TYPE_OVERLAY_LAYOUT_CHILD; layout_class->measure = gtk_overlay_layout_measure; layout_class->allocate = gtk_overlay_layout_allocate; } static void gtk_overlay_layout_init (GtkOverlayLayout *self) { } /** * gtk_overlay_layout_new: * * Creates a new `GtkOverlayLayout` instance. * * Returns: the newly created instance */ GtkLayoutManager * gtk_overlay_layout_new (void) { return g_object_new (GTK_TYPE_OVERLAY_LAYOUT, NULL); }