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 | |
52 | struct _GtkOverlayLayout |
53 | { |
54 | GtkLayoutManager parent_instance; |
55 | }; |
56 | |
57 | struct _GtkOverlayLayoutChild |
58 | { |
59 | GtkLayoutChild parent_instance; |
60 | |
61 | guint measure : 1; |
62 | guint clip_overlay : 1; |
63 | }; |
64 | |
65 | enum |
66 | { |
67 | PROP_MEASURE = 1, |
68 | PROP_CLIP_OVERLAY, |
69 | |
70 | N_CHILD_PROPERTIES |
71 | }; |
72 | |
73 | static GParamSpec *child_props[N_CHILD_PROPERTIES]; |
74 | |
75 | G_DEFINE_TYPE (GtkOverlayLayoutChild, gtk_overlay_layout_child, GTK_TYPE_LAYOUT_CHILD) |
76 | |
77 | static void |
78 | gtk_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 | |
101 | static void |
102 | gtk_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 | |
125 | static void |
126 | gtk_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 | |
161 | static void |
162 | gtk_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 | */ |
173 | void |
174 | gtk_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 | */ |
200 | gboolean |
201 | gtk_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 | */ |
215 | void |
216 | gtk_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 | */ |
242 | gboolean |
243 | gtk_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 | |
250 | G_DEFINE_TYPE (GtkOverlayLayout, gtk_overlay_layout, GTK_TYPE_LAYOUT_MANAGER) |
251 | |
252 | static void |
253 | gtk_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 | |
302 | static void |
303 | gtk_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 | |
320 | static GtkAlign |
321 | effective_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 | |
338 | static void |
339 | gtk_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 | |
395 | static void |
396 | gtk_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 | |
411 | static void |
412 | gtk_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 | |
441 | static void |
442 | gtk_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 | |
451 | static void |
452 | gtk_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 | */ |
463 | GtkLayoutManager * |
464 | gtk_overlay_layout_new (void) |
465 | { |
466 | return g_object_new (GTK_TYPE_OVERLAY_LAYOUT, NULL); |
467 | } |
468 | |