1/*
2 * gtkoverlay.c
3 * This file is part of gtk
4 *
5 * Copyright (C) 2011 - Ignacio Casal Quinteiro, Mike Krüger
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 "gtkoverlay.h"
24
25#include "gtkoverlaylayout.h"
26#include "gtkbuildable.h"
27#include "gtkintl.h"
28#include "gtkmarshalers.h"
29#include "gtkprivate.h"
30#include "gtkscrolledwindow.h"
31#include "gtksnapshot.h"
32#include "gtkwidgetprivate.h"
33
34/**
35 * GtkOverlay
36 *
37 * `GtkOverlay` is a container which contains a single main child, on top
38 * of which it can place “overlay” widgets.
39 *
40 * ![An example GtkOverlay](overlay.png)
41 *
42 * The position of each overlay widget is determined by its
43 * [property@Gtk.Widget:halign] and [property@Gtk.Widget:valign]
44 * properties. E.g. a widget with both alignments set to %GTK_ALIGN_START
45 * will be placed at the top left corner of the `GtkOverlay` container,
46 * whereas an overlay with halign set to %GTK_ALIGN_CENTER and valign set
47 * to %GTK_ALIGN_END will be placed a the bottom edge of the `GtkOverlay`,
48 * horizontally centered. The position can be adjusted by setting the margin
49 * properties of the child to non-zero values.
50 *
51 * More complicated placement of overlays is possible by connecting
52 * to the [signal@Gtk.Overlay::get-child-position] signal.
53 *
54 * An overlay’s minimum and natural sizes are those of its main child.
55 * The sizes of overlay children are not considered when measuring these
56 * preferred sizes.
57 *
58 * # GtkOverlay as GtkBuildable
59 *
60 * The `GtkOverlay` implementation of the `GtkBuildable` interface
61 * supports placing a child as an overlay by specifying “overlay” as
62 * the “type” attribute of a `<child>` element.
63 *
64 * # CSS nodes
65 *
66 * `GtkOverlay` has a single CSS node with the name “overlay”. Overlay children
67 * whose alignments cause them to be positioned at an edge get the style classes
68 * “.left”, “.right”, “.top”, and/or “.bottom” according to their position.
69 */
70
71enum {
72 GET_CHILD_POSITION,
73 LAST_SIGNAL
74};
75
76static guint signals[LAST_SIGNAL] = { 0 };
77
78enum {
79 PROP_CHILD = 1
80};
81
82static void gtk_overlay_buildable_init (GtkBuildableIface *iface);
83
84typedef struct _GtkOverlayClass GtkOverlayClass;
85
86struct _GtkOverlay
87{
88 GtkWidget parent_instance;
89
90 GtkWidget *child;
91};
92
93struct _GtkOverlayClass
94{
95 GtkWidgetClass parent_class;
96
97 gboolean (*get_child_position) (GtkOverlay *overlay,
98 GtkWidget *widget,
99 GtkAllocation *allocation);
100};
101
102G_DEFINE_TYPE_WITH_CODE (GtkOverlay, gtk_overlay, GTK_TYPE_WIDGET,
103 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
104 gtk_overlay_buildable_init))
105
106static GtkAlign
107effective_align (GtkAlign align,
108 GtkTextDirection direction)
109{
110 switch (align)
111 {
112 case GTK_ALIGN_START:
113 return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_END : GTK_ALIGN_START;
114 case GTK_ALIGN_END:
115 return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_START : GTK_ALIGN_END;
116 case GTK_ALIGN_FILL:
117 case GTK_ALIGN_CENTER:
118 case GTK_ALIGN_BASELINE:
119 default:
120 return align;
121 }
122}
123
124static gboolean
125gtk_overlay_get_child_position (GtkOverlay *overlay,
126 GtkWidget *widget,
127 GtkAllocation *alloc)
128{
129 GtkRequisition min, req;
130 GtkAlign halign;
131 GtkTextDirection direction;
132 int width, height;
133
134 gtk_widget_get_preferred_size (widget, minimum_size: &min, natural_size: &req);
135 width = gtk_widget_get_width (GTK_WIDGET (overlay));
136 height = gtk_widget_get_height (GTK_WIDGET (overlay));
137
138 alloc->x = 0;
139 alloc->width = MAX (min.width, MIN (width, req.width));
140
141 direction = _gtk_widget_get_direction (widget);
142
143 halign = gtk_widget_get_halign (widget);
144 switch (effective_align (align: halign, direction))
145 {
146 case GTK_ALIGN_START:
147 /* nothing to do */
148 break;
149 case GTK_ALIGN_FILL:
150 alloc->width = MAX (alloc->width, width);
151 break;
152 case GTK_ALIGN_CENTER:
153 alloc->x += width / 2 - alloc->width / 2;
154 break;
155 case GTK_ALIGN_END:
156 alloc->x += width - alloc->width;
157 break;
158 case GTK_ALIGN_BASELINE:
159 default:
160 g_assert_not_reached ();
161 break;
162 }
163
164 alloc->y = 0;
165 alloc->height = MAX (min.height, MIN (height, req.height));
166
167 switch (gtk_widget_get_valign (widget))
168 {
169 case GTK_ALIGN_START:
170 /* nothing to do */
171 break;
172 case GTK_ALIGN_FILL:
173 alloc->height = MAX (alloc->height, height);
174 break;
175 case GTK_ALIGN_CENTER:
176 alloc->y += height / 2 - alloc->height / 2;
177 break;
178 case GTK_ALIGN_END:
179 alloc->y += height - alloc->height;
180 break;
181 case GTK_ALIGN_BASELINE:
182 default:
183 g_assert_not_reached ();
184 break;
185 }
186
187 return TRUE;
188}
189
190static void
191gtk_overlay_snapshot_child (GtkWidget *overlay,
192 GtkWidget *child,
193 GtkSnapshot *snapshot)
194{
195 graphene_rect_t bounds;
196 gboolean clip_set;
197
198 clip_set = gtk_overlay_get_clip_overlay (GTK_OVERLAY (overlay), widget: child);
199
200 if (!clip_set)
201 {
202 gtk_widget_snapshot_child (widget: overlay, child, snapshot);
203 return;
204 }
205
206 graphene_rect_init (r: &bounds, x: 0, y: 0,
207 width: gtk_widget_get_width (widget: overlay),
208 height: gtk_widget_get_height (widget: overlay));
209
210 gtk_snapshot_push_clip (snapshot, bounds: &bounds);
211 gtk_widget_snapshot_child (widget: overlay, child, snapshot);
212 gtk_snapshot_pop (snapshot);
213}
214
215static void
216gtk_overlay_snapshot (GtkWidget *widget,
217 GtkSnapshot *snapshot)
218{
219 GtkWidget *child;
220
221 for (child = _gtk_widget_get_first_child (widget);
222 child != NULL;
223 child = _gtk_widget_get_next_sibling (widget: child))
224 {
225 gtk_overlay_snapshot_child (overlay: widget, child, snapshot);
226 }
227}
228
229static void
230gtk_overlay_get_property (GObject *object,
231 guint prop_id,
232 GValue *value,
233 GParamSpec *pspec)
234{
235 GtkOverlay *overlay = GTK_OVERLAY (object);
236
237 switch (prop_id)
238 {
239 case PROP_CHILD:
240 g_value_set_object (value, v_object: gtk_overlay_get_child (overlay));
241 break;
242
243 default:
244 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
245 break;
246 }
247}
248
249static void
250gtk_overlay_set_property (GObject *object,
251 guint prop_id,
252 const GValue *value,
253 GParamSpec *pspec)
254{
255 GtkOverlay *overlay = GTK_OVERLAY (object);
256
257 switch (prop_id)
258 {
259 case PROP_CHILD:
260 gtk_overlay_set_child (overlay, child: g_value_get_object (value));
261 break;
262
263 default:
264 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
265 break;
266 }
267}
268
269static void
270gtk_overlay_dispose (GObject *object)
271{
272 GtkOverlay *overlay = GTK_OVERLAY (object);
273 GtkWidget *child;
274
275 g_clear_pointer (&overlay->child, gtk_widget_unparent);
276
277 while ((child = gtk_widget_get_first_child (GTK_WIDGET (overlay))))
278 gtk_widget_unparent (widget: child);
279
280 G_OBJECT_CLASS (gtk_overlay_parent_class)->dispose (object);
281}
282
283static void
284gtk_overlay_compute_expand (GtkWidget *widget,
285 gboolean *hexpand,
286 gboolean *vexpand)
287{
288 GtkOverlay *overlay = GTK_OVERLAY (widget);
289
290 if (overlay->child)
291 {
292 *hexpand = gtk_widget_compute_expand (widget: overlay->child, orientation: GTK_ORIENTATION_HORIZONTAL);
293 *vexpand = gtk_widget_compute_expand (widget: overlay->child, orientation: GTK_ORIENTATION_VERTICAL);
294 }
295 else
296 {
297 *hexpand = FALSE;
298 *vexpand = FALSE;
299 }
300}
301
302static void
303gtk_overlay_class_init (GtkOverlayClass *klass)
304{
305 GObjectClass *object_class = G_OBJECT_CLASS (klass);
306 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
307
308 object_class->dispose = gtk_overlay_dispose;
309
310 object_class->get_property = gtk_overlay_get_property;
311 object_class->set_property = gtk_overlay_set_property;
312
313 widget_class->snapshot = gtk_overlay_snapshot;
314 widget_class->compute_expand = gtk_overlay_compute_expand;
315
316 klass->get_child_position = gtk_overlay_get_child_position;
317
318 g_object_class_install_property (oclass: object_class,
319 property_id: PROP_CHILD,
320 pspec: g_param_spec_object (name: "child",
321 P_("Child"),
322 P_("The child widget"),
323 GTK_TYPE_WIDGET,
324 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
325
326 /**
327 * GtkOverlay::get-child-position:
328 * @overlay: the `GtkOverlay`
329 * @widget: the child widget to position
330 * @allocation: (type Gdk.Rectangle) (out caller-allocates): return
331 * location for the allocation
332 *
333 * Emitted to determine the position and size of any overlay
334 * child widgets.
335 *
336 * A handler for this signal should fill @allocation with
337 * the desired position and size for @widget, relative to
338 * the 'main' child of @overlay.
339 *
340 * The default handler for this signal uses the @widget's
341 * halign and valign properties to determine the position
342 * and gives the widget its natural size (except that an
343 * alignment of %GTK_ALIGN_FILL will cause the overlay to
344 * be full-width/height). If the main child is a
345 * `GtkScrolledWindow`, the overlays are placed relative
346 * to its contents.
347 *
348 * Returns: %TRUE if the @allocation has been filled
349 */
350 signals[GET_CHILD_POSITION] =
351 g_signal_new (I_("get-child-position"),
352 G_TYPE_FROM_CLASS (object_class),
353 signal_flags: G_SIGNAL_RUN_LAST,
354 G_STRUCT_OFFSET (GtkOverlayClass, get_child_position),
355 accumulator: _gtk_boolean_handled_accumulator, NULL,
356 c_marshaller: _gtk_marshal_BOOLEAN__OBJECT_BOXED,
357 G_TYPE_BOOLEAN, n_params: 2,
358 GTK_TYPE_WIDGET,
359 GDK_TYPE_RECTANGLE | G_SIGNAL_TYPE_STATIC_SCOPE);
360 g_signal_set_va_marshaller (signal_id: signals[GET_CHILD_POSITION],
361 G_TYPE_FROM_CLASS (object_class),
362 va_marshaller: _gtk_marshal_BOOLEAN__OBJECT_BOXEDv);
363
364 gtk_widget_class_set_css_name (widget_class, I_("overlay"));
365
366 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_OVERLAY_LAYOUT);
367}
368
369static void
370gtk_overlay_init (GtkOverlay *overlay)
371{
372}
373
374static GtkBuildableIface *parent_buildable_iface;
375
376static void
377gtk_overlay_buildable_add_child (GtkBuildable *buildable,
378 GtkBuilder *builder,
379 GObject *child,
380 const char *type)
381{
382 if (GTK_IS_WIDGET (child))
383 {
384 if (type && strcmp (s1: type, s2: "overlay") == 0)
385 gtk_overlay_add_overlay (GTK_OVERLAY (buildable), GTK_WIDGET (child));
386 else if (!type)
387 gtk_overlay_set_child (GTK_OVERLAY (buildable), GTK_WIDGET (child));
388 else
389 GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type);
390 }
391 else
392 {
393 parent_buildable_iface->add_child (buildable, builder, child, type);
394 }
395}
396
397static void
398gtk_overlay_buildable_init (GtkBuildableIface *iface)
399{
400 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
401
402 iface->add_child = gtk_overlay_buildable_add_child;
403}
404
405/**
406 * gtk_overlay_new:
407 *
408 * Creates a new `GtkOverlay`.
409 *
410 * Returns: a new `GtkOverlay` object.
411 */
412GtkWidget *
413gtk_overlay_new (void)
414{
415 return g_object_new (GTK_TYPE_OVERLAY, NULL);
416}
417
418/**
419 * gtk_overlay_add_overlay:
420 * @overlay: a `GtkOverlay`
421 * @widget: a `GtkWidget` to be added to the container
422 *
423 * Adds @widget to @overlay.
424 *
425 * The widget will be stacked on top of the main widget
426 * added with [method@Gtk.Overlay.set_child].
427 *
428 * The position at which @widget is placed is determined
429 * from its [property@Gtk.Widget:halign] and
430 * [property@Gtk.Widget:valign] properties.
431 */
432void
433gtk_overlay_add_overlay (GtkOverlay *overlay,
434 GtkWidget *widget)
435{
436 g_return_if_fail (GTK_IS_OVERLAY (overlay));
437 g_return_if_fail (GTK_IS_WIDGET (widget));
438 g_return_if_fail (widget != overlay->child);
439
440 gtk_widget_insert_before (widget, GTK_WIDGET (overlay), NULL);
441}
442
443/**
444 * gtk_overlay_remove_overlay:
445 * @overlay: a `GtkOverlay`
446 * @widget: a `GtkWidget` to be removed
447 *
448 * Removes an overlay that was added with gtk_overlay_add_overlay().
449 */
450void
451gtk_overlay_remove_overlay (GtkOverlay *overlay,
452 GtkWidget *widget)
453{
454 g_return_if_fail (GTK_IS_OVERLAY (overlay));
455 g_return_if_fail (GTK_IS_WIDGET (widget));
456 g_return_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (overlay));
457 g_return_if_fail (widget != overlay->child);
458
459 gtk_widget_unparent (widget);
460}
461
462/**
463 * gtk_overlay_set_measure_overlay:
464 * @overlay: a `GtkOverlay`
465 * @widget: an overlay child of `GtkOverlay`
466 * @measure: whether the child should be measured
467 *
468 * Sets whether @widget is included in the measured size of @overlay.
469 *
470 * The overlay will request the size of the largest child that has
471 * this property set to %TRUE. Children who are not included may
472 * be drawn outside of @overlay's allocation if they are too large.
473 */
474void
475gtk_overlay_set_measure_overlay (GtkOverlay *overlay,
476 GtkWidget *widget,
477 gboolean measure)
478{
479 GtkLayoutManager *layout;
480 GtkOverlayLayoutChild *child;
481
482 g_return_if_fail (GTK_IS_OVERLAY (overlay));
483 g_return_if_fail (GTK_IS_WIDGET (widget));
484
485 layout = gtk_widget_get_layout_manager (GTK_WIDGET (overlay));
486 child = GTK_OVERLAY_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: layout, child: widget));
487 gtk_overlay_layout_child_set_measure (child, measure);
488}
489
490/**
491 * gtk_overlay_get_measure_overlay:
492 * @overlay: a `GtkOverlay`
493 * @widget: an overlay child of `GtkOverlay`
494 *
495 * Gets whether @widget's size is included in the measurement of
496 * @overlay.
497 *
498 * Returns: whether the widget is measured
499 */
500gboolean
501gtk_overlay_get_measure_overlay (GtkOverlay *overlay,
502 GtkWidget *widget)
503{
504 GtkLayoutManager *layout;
505 GtkOverlayLayoutChild *child;
506
507 g_return_val_if_fail (GTK_IS_OVERLAY (overlay), FALSE);
508 g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
509
510 layout = gtk_widget_get_layout_manager (GTK_WIDGET (overlay));
511 child = GTK_OVERLAY_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: layout, child: widget));
512 return gtk_overlay_layout_child_get_measure (child);
513}
514
515/**
516 * gtk_overlay_set_clip_overlay:
517 * @overlay: a `GtkOverlay`
518 * @widget: an overlay child of `GtkOverlay`
519 * @clip_overlay: whether the child should be clipped
520 *
521 * Sets whether @widget should be clipped within the parent.
522 */
523void
524gtk_overlay_set_clip_overlay (GtkOverlay *overlay,
525 GtkWidget *widget,
526 gboolean clip_overlay)
527{
528 GtkLayoutManager *layout;
529 GtkOverlayLayoutChild *child;
530
531 g_return_if_fail (GTK_IS_OVERLAY (overlay));
532 g_return_if_fail (GTK_IS_WIDGET (widget));
533
534 layout = gtk_widget_get_layout_manager (GTK_WIDGET (overlay));
535 child = GTK_OVERLAY_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: layout, child: widget));
536 gtk_overlay_layout_child_set_clip_overlay (child, clip_overlay);
537}
538
539/**
540 * gtk_overlay_get_clip_overlay:
541 * @overlay: a `GtkOverlay`
542 * @widget: an overlay child of `GtkOverlay`
543 *
544 * Gets whether @widget should be clipped within the parent.
545 *
546 * Returns: whether the widget is clipped within the parent.
547 */
548gboolean
549gtk_overlay_get_clip_overlay (GtkOverlay *overlay,
550 GtkWidget *widget)
551{
552 GtkLayoutManager *layout;
553 GtkOverlayLayoutChild *child;
554
555 g_return_val_if_fail (GTK_IS_OVERLAY (overlay), FALSE);
556 g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
557
558 layout = gtk_widget_get_layout_manager (GTK_WIDGET (overlay));
559 child = GTK_OVERLAY_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: layout, child: widget));
560
561 return gtk_overlay_layout_child_get_clip_overlay (child);
562}
563
564/**
565 * gtk_overlay_set_child:
566 * @overlay: a `GtkOverlay`
567 * @child: (nullable): the child widget
568 *
569 * Sets the child widget of @overlay.
570 */
571void
572gtk_overlay_set_child (GtkOverlay *overlay,
573 GtkWidget *child)
574{
575 g_return_if_fail (GTK_IS_OVERLAY (overlay));
576 g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
577
578 g_clear_pointer (&overlay->child, gtk_widget_unparent);
579
580 overlay->child = child;
581
582 if (child)
583 {
584 /* Make sure the main-child node is the first one */
585 gtk_widget_insert_after (widget: child, GTK_WIDGET (overlay), NULL);
586 }
587
588 g_object_notify (G_OBJECT (overlay), property_name: "child");
589}
590
591/**
592 * gtk_overlay_get_child:
593 * @overlay: a `GtkOverlay`
594 *
595 * Gets the child widget of @overlay.
596 *
597 * Returns: (nullable) (transfer none): the child widget of @overlay
598 */
599GtkWidget *
600gtk_overlay_get_child (GtkOverlay *overlay)
601{
602 g_return_val_if_fail (GTK_IS_OVERLAY (overlay), NULL);
603
604 return overlay->child;
605}
606

source code of gtk/gtk/gtkoverlay.c