1/* gtkoverlaylayout.c: Overlay layout manager
2 *
3 * SPDX-License-Identifier: LGPL-2.1-or-later
4 *
5 * Copyright 2019 Red Hat, Inc.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "config.h"
22
23#include "gtkoverlaylayout.h"
24
25#include "gtkintl.h"
26#include "gtklayoutchild.h"
27#include "gtkoverlay.h"
28#include "gtkprivate.h"
29#include "gtkwidgetprivate.h"
30
31#include <graphene-gobject.h>
32
33
34/**
35 * GtkOverlayLayout:
36 *
37 * `GtkOverlayLayout` is the layout manager used by `GtkOverlay`.
38 *
39 * It places widgets as overlays on top of the main child.
40 *
41 * This is not a reusable layout manager, since it expects its widget
42 * to be a `GtkOverlay`. It only listed here so that its layout
43 * properties get documented.
44 */
45
46/**
47 * GtkOverlayLayoutChild:
48 *
49 * `GtkLayoutChild` subclass for children in a `GtkOverlayLayout`.
50 */
51
52struct _GtkOverlayLayout
53{
54 GtkLayoutManager parent_instance;
55};
56
57struct _GtkOverlayLayoutChild
58{
59 GtkLayoutChild parent_instance;
60
61 guint measure : 1;
62 guint clip_overlay : 1;
63};
64
65enum
66{
67 PROP_MEASURE = 1,
68 PROP_CLIP_OVERLAY,
69
70 N_CHILD_PROPERTIES
71};
72
73static GParamSpec *child_props[N_CHILD_PROPERTIES];
74
75G_DEFINE_TYPE (GtkOverlayLayoutChild, gtk_overlay_layout_child, GTK_TYPE_LAYOUT_CHILD)
76
77static void
78gtk_overlay_layout_child_set_property (GObject *gobject,
79 guint prop_id,
80 const GValue *value,
81 GParamSpec *pspec)
82{
83 GtkOverlayLayoutChild *self = GTK_OVERLAY_LAYOUT_CHILD (ptr: gobject);
84
85 switch (prop_id)
86 {
87 case PROP_MEASURE:
88 gtk_overlay_layout_child_set_measure (child: self, measure: g_value_get_boolean (value));
89 break;
90
91 case PROP_CLIP_OVERLAY:
92 gtk_overlay_layout_child_set_clip_overlay (child: self, clip_overlay: g_value_get_boolean (value));
93 break;
94
95 default:
96 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
97 break;
98 }
99}
100
101static void
102gtk_overlay_layout_child_get_property (GObject *gobject,
103 guint prop_id,
104 GValue *value,
105 GParamSpec *pspec)
106{
107 GtkOverlayLayoutChild *self = GTK_OVERLAY_LAYOUT_CHILD (ptr: gobject);
108
109 switch (prop_id)
110 {
111 case PROP_MEASURE:
112 g_value_set_boolean (value, v_boolean: self->measure);
113 break;
114
115 case PROP_CLIP_OVERLAY:
116 g_value_set_boolean (value, v_boolean: self->clip_overlay);
117 break;
118
119 default:
120 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
121 break;
122 }
123}
124
125static void
126gtk_overlay_layout_child_class_init (GtkOverlayLayoutChildClass *klass)
127{
128 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
129
130 gobject_class->set_property = gtk_overlay_layout_child_set_property;
131 gobject_class->get_property = gtk_overlay_layout_child_get_property;
132
133 /**
134 * GtkOverlayLayoutChild:measure: (attributes org.gtk.Property.get=gtk_overlay_layout_child_get_measure org.gtk.Property.set=gtk_overlay_layout_child_set_measure)
135 *
136 * Whether the child size should contribute to the `GtkOverlayLayout`'s
137 * measurement.
138 */
139 child_props[PROP_MEASURE] =
140 g_param_spec_boolean (name: "measure",
141 P_("Measure"),
142 P_("Include in size measurement"),
143 FALSE,
144 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
145
146 /**
147 * 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)
148 *
149 * Whether the child should be clipped to fit the parent's size.
150 */
151 child_props[PROP_CLIP_OVERLAY] =
152 g_param_spec_boolean (name: "clip-overlay",
153 P_("Clip Overlay"),
154 P_("Clip the overlay child widget so as to fit the parent"),
155 FALSE,
156 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
157
158 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_CHILD_PROPERTIES, pspecs: child_props);
159}
160
161static void
162gtk_overlay_layout_child_init (GtkOverlayLayoutChild *self)
163{
164}
165
166/**
167 * gtk_overlay_layout_child_set_measure: (attributes org.gtk.Method.set_property=measure)
168 * @child: a `GtkOverlayLayoutChild`
169 * @measure: whether to measure this child
170 *
171 * Sets whether to measure this child.
172 */
173void
174gtk_overlay_layout_child_set_measure (GtkOverlayLayoutChild *child,
175 gboolean measure)
176{
177 GtkLayoutManager *layout;
178
179 g_return_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child));
180
181 if (child->measure == measure)
182 return;
183
184 child->measure = measure;
185
186 layout = gtk_layout_child_get_layout_manager (layout_child: GTK_LAYOUT_CHILD (ptr: child));
187 gtk_layout_manager_layout_changed (manager: layout);
188
189 g_object_notify_by_pspec (G_OBJECT (child), pspec: child_props[PROP_MEASURE]);
190}
191
192/**
193 * gtk_overlay_layout_child_get_measure: (attributes org.gtk.Method.get_property=measure)
194 * @child: a `GtkOverlayLayoutChild`
195 *
196 * Retrieves whether the child is measured.
197 *
198 * Returns: whether the child is measured
199 */
200gboolean
201gtk_overlay_layout_child_get_measure (GtkOverlayLayoutChild *child)
202{
203 g_return_val_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child), FALSE);
204
205 return child->measure;
206}
207
208/**
209 * gtk_overlay_layout_child_set_clip_overlay: (attributes org.gtk.Method.set_property=clip-overlay)
210 * @child: a `GtkOverlayLayoutChild`
211 * @clip_overlay: whether to clip this child
212 *
213 * Sets whether to clip this child.
214 */
215void
216gtk_overlay_layout_child_set_clip_overlay (GtkOverlayLayoutChild *child,
217 gboolean clip_overlay)
218{
219 GtkLayoutManager *layout;
220
221 g_return_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child));
222
223 if (child->clip_overlay == clip_overlay)
224 return;
225
226 child->clip_overlay = clip_overlay;
227
228 layout = gtk_layout_child_get_layout_manager (layout_child: GTK_LAYOUT_CHILD (ptr: child));
229 gtk_layout_manager_layout_changed (manager: layout);
230
231 g_object_notify_by_pspec (G_OBJECT (child), pspec: child_props[PROP_CLIP_OVERLAY]);
232}
233
234/**
235 * gtk_overlay_layout_child_get_clip_overlay: (attributes org.gtk.Method.get_property=clip-overlay)
236 * @child: a `GtkOverlayLayoutChild`
237 *
238 * Retrieves whether the child is clipped.
239 *
240 * Returns: whether the child is clipped
241 */
242gboolean
243gtk_overlay_layout_child_get_clip_overlay (GtkOverlayLayoutChild *child)
244{
245 g_return_val_if_fail (GTK_IS_OVERLAY_LAYOUT_CHILD (child), FALSE);
246
247 return child->clip_overlay;
248}
249
250G_DEFINE_TYPE (GtkOverlayLayout, gtk_overlay_layout, GTK_TYPE_LAYOUT_MANAGER)
251
252static void
253gtk_overlay_layout_measure (GtkLayoutManager *layout_manager,
254 GtkWidget *widget,
255 GtkOrientation orientation,
256 int for_size,
257 int *minimum,
258 int *natural,
259 int *minimum_baseline,
260 int *natural_baseline)
261{
262 GtkOverlayLayoutChild *child_info;
263 GtkWidget *child;
264 int min, nat;
265 GtkWidget *main_widget;
266
267 main_widget = gtk_overlay_get_child (GTK_OVERLAY (widget));
268
269 min = 0;
270 nat = 0;
271
272 for (child = _gtk_widget_get_first_child (widget);
273 child != NULL;
274 child = _gtk_widget_get_next_sibling (widget: child))
275 {
276 if (!gtk_widget_should_layout (widget: child))
277 continue;
278
279 child_info = GTK_OVERLAY_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: layout_manager, child));
280
281 if (child == main_widget || child_info->measure)
282 {
283 int child_min, child_nat, child_min_baseline, child_nat_baseline;
284
285 gtk_widget_measure (widget: child,
286 orientation,
287 for_size,
288 minimum: &child_min, natural: &child_nat,
289 minimum_baseline: &child_min_baseline, natural_baseline: &child_nat_baseline);
290
291 min = MAX (min, child_min);
292 nat = MAX (nat, child_nat);
293 }
294 }
295
296 if (minimum != NULL)
297 *minimum = min;
298 if (natural != NULL)
299 *natural = nat;
300}
301
302static void
303gtk_overlay_compute_child_allocation (GtkOverlay *overlay,
304 GtkWidget *widget,
305 GtkOverlayLayoutChild *child,
306 GtkAllocation *widget_allocation)
307{
308 GtkAllocation allocation;
309 gboolean result;
310
311 g_signal_emit_by_name (instance: overlay, detailed_signal: "get-child-position",
312 widget, &allocation, &result);
313
314 widget_allocation->x = allocation.x;
315 widget_allocation->y = allocation.y;
316 widget_allocation->width = allocation.width;
317 widget_allocation->height = allocation.height;
318}
319
320static GtkAlign
321effective_align (GtkAlign align,
322 GtkTextDirection direction)
323{
324 switch (align)
325 {
326 case GTK_ALIGN_START:
327 return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_END : GTK_ALIGN_START;
328 case GTK_ALIGN_END:
329 return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_START : GTK_ALIGN_END;
330 case GTK_ALIGN_FILL:
331 case GTK_ALIGN_CENTER:
332 case GTK_ALIGN_BASELINE:
333 default:
334 return align;
335 }
336}
337
338static void
339gtk_overlay_child_update_style_classes (GtkOverlay *overlay,
340 GtkWidget *child,
341 GtkAllocation *child_allocation)
342{
343 GtkWidget *widget = GTK_WIDGET (overlay);
344 int width, height;
345 GtkAlign valign, halign;
346 gboolean is_left, is_right, is_top, is_bottom;
347 gboolean has_left, has_right, has_top, has_bottom;
348
349 has_left = gtk_widget_has_css_class (widget: child, css_class: "left");
350 has_right = gtk_widget_has_css_class (widget: child, css_class: "right");
351 has_top = gtk_widget_has_css_class (widget: child, css_class: "top");
352 has_bottom = gtk_widget_has_css_class (widget: child, css_class: "bottom");
353
354 is_left = is_right = is_top = is_bottom = FALSE;
355
356 width = gtk_widget_get_width (widget);
357 height = gtk_widget_get_height (widget);
358
359 halign = effective_align (align: gtk_widget_get_halign (widget: child),
360 direction: gtk_widget_get_direction (widget: child));
361
362 if (halign == GTK_ALIGN_START)
363 is_left = (child_allocation->x == 0);
364 else if (halign == GTK_ALIGN_END)
365 is_right = (child_allocation->x + child_allocation->width == width);
366
367 valign = gtk_widget_get_valign (widget: child);
368
369 if (valign == GTK_ALIGN_START)
370 is_top = (child_allocation->y == 0);
371 else if (valign == GTK_ALIGN_END)
372 is_bottom = (child_allocation->y + child_allocation->height == height);
373
374 if (has_left && !is_left)
375 gtk_widget_remove_css_class (widget: child, css_class: "left");
376 else if (!has_left && is_left)
377 gtk_widget_add_css_class (widget: child, css_class: "left");
378
379 if (has_right && !is_right)
380 gtk_widget_remove_css_class (widget: child, css_class: "right");
381 else if (!has_right && is_right)
382 gtk_widget_add_css_class (widget: child, css_class: "right");
383
384 if (has_top && !is_top)
385 gtk_widget_remove_css_class (widget: child, css_class: "top");
386 else if (!has_top && is_top)
387 gtk_widget_add_css_class (widget: child, css_class: "top");
388
389 if (has_bottom && !is_bottom)
390 gtk_widget_remove_css_class (widget: child, css_class: "bottom");
391 else if (!has_bottom && is_bottom)
392 gtk_widget_add_css_class (widget: child, css_class: "bottom");
393}
394
395static void
396gtk_overlay_child_allocate (GtkOverlay *overlay,
397 GtkWidget *widget,
398 GtkOverlayLayoutChild *child)
399{
400 GtkAllocation child_allocation;
401
402 if (!gtk_widget_should_layout (widget))
403 return;
404
405 gtk_overlay_compute_child_allocation (overlay, widget, child, widget_allocation: &child_allocation);
406
407 gtk_overlay_child_update_style_classes (overlay, child: widget, child_allocation: &child_allocation);
408 gtk_widget_size_allocate (widget, allocation: &child_allocation, baseline: -1);
409}
410
411static void
412gtk_overlay_layout_allocate (GtkLayoutManager *layout_manager,
413 GtkWidget *widget,
414 int width,
415 int height,
416 int baseline)
417{
418 GtkWidget *child;
419 GtkWidget *main_widget;
420
421 main_widget = gtk_overlay_get_child (GTK_OVERLAY (widget));
422 if (main_widget && gtk_widget_get_visible (widget: main_widget))
423 gtk_widget_size_allocate (widget: main_widget,
424 allocation: &(GtkAllocation) { 0, 0, width, height },
425 baseline: -1);
426
427 for (child = _gtk_widget_get_first_child (widget);
428 child != NULL;
429 child = _gtk_widget_get_next_sibling (widget: child))
430 {
431 if (child != main_widget)
432 {
433 GtkOverlayLayoutChild *child_data;
434 child_data = GTK_OVERLAY_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: layout_manager, child));
435
436 gtk_overlay_child_allocate (GTK_OVERLAY (widget), widget: child, child: child_data);
437 }
438 }
439}
440
441static void
442gtk_overlay_layout_class_init (GtkOverlayLayoutClass *klass)
443{
444 GtkLayoutManagerClass *layout_class = GTK_LAYOUT_MANAGER_CLASS (ptr: klass);
445
446 layout_class->layout_child_type = GTK_TYPE_OVERLAY_LAYOUT_CHILD;
447 layout_class->measure = gtk_overlay_layout_measure;
448 layout_class->allocate = gtk_overlay_layout_allocate;
449}
450
451static void
452gtk_overlay_layout_init (GtkOverlayLayout *self)
453{
454}
455
456/**
457 * gtk_overlay_layout_new:
458 *
459 * Creates a new `GtkOverlayLayout` instance.
460 *
461 * Returns: the newly created instance
462 */
463GtkLayoutManager *
464gtk_overlay_layout_new (void)
465{
466 return g_object_new (GTK_TYPE_OVERLAY_LAYOUT, NULL);
467}
468

source code of gtk/gtk/gtkoverlaylayout.c