1 | /* |
2 | * bluroverlay.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 "bluroverlay.h" |
22 | |
23 | /* |
24 | * This is a cut-down copy of gtkoverlay.c with a custom snapshot |
25 | * function that support a limited form of blur-under. |
26 | */ |
27 | typedef struct _BlurOverlayChild BlurOverlayChild; |
28 | |
29 | struct _BlurOverlayChild |
30 | { |
31 | double blur; |
32 | }; |
33 | |
34 | enum { |
35 | GET_CHILD_POSITION, |
36 | LAST_SIGNAL |
37 | }; |
38 | |
39 | static guint signals[LAST_SIGNAL] = { 0 }; |
40 | static GQuark child_data_quark = 0; |
41 | |
42 | G_DEFINE_TYPE (BlurOverlay, blur_overlay, GTK_TYPE_WIDGET) |
43 | |
44 | static void |
45 | blur_overlay_set_overlay_child (GtkWidget *widget, |
46 | BlurOverlayChild *child_data) |
47 | { |
48 | g_object_set_qdata_full (G_OBJECT (widget), quark: child_data_quark, data: child_data, destroy: g_free); |
49 | } |
50 | |
51 | static BlurOverlayChild * |
52 | blur_overlay_get_overlay_child (GtkWidget *widget) |
53 | { |
54 | return (BlurOverlayChild *) g_object_get_qdata (G_OBJECT (widget), quark: child_data_quark); |
55 | } |
56 | |
57 | static void |
58 | blur_overlay_measure (GtkWidget *widget, |
59 | GtkOrientation orientation, |
60 | int for_size, |
61 | int *minimum, |
62 | int *natural, |
63 | int *minimum_baseline, |
64 | int *natural_baseline) |
65 | { |
66 | GtkWidget *child; |
67 | |
68 | for (child = gtk_widget_get_first_child (widget); |
69 | child != NULL; |
70 | child = gtk_widget_get_next_sibling (widget: child)) |
71 | { |
72 | int child_min, child_nat, child_min_baseline, child_nat_baseline; |
73 | |
74 | gtk_widget_measure (widget: child, |
75 | orientation, |
76 | for_size, |
77 | minimum: &child_min, natural: &child_nat, |
78 | minimum_baseline: &child_min_baseline, natural_baseline: &child_nat_baseline); |
79 | |
80 | *minimum = MAX (*minimum, child_min); |
81 | *natural = MAX (*natural, child_nat); |
82 | if (child_min_baseline > -1) |
83 | *minimum_baseline = MAX (*minimum_baseline, child_min_baseline); |
84 | if (child_nat_baseline > -1) |
85 | *natural_baseline = MAX (*natural_baseline, child_nat_baseline); |
86 | } |
87 | } |
88 | |
89 | static void |
90 | blur_overlay_compute_child_allocation (BlurOverlay *overlay, |
91 | GtkWidget *widget, |
92 | BlurOverlayChild *child, |
93 | GtkAllocation *widget_allocation) |
94 | { |
95 | GtkAllocation allocation; |
96 | gboolean result; |
97 | |
98 | g_signal_emit (instance: overlay, signal_id: signals[GET_CHILD_POSITION], |
99 | detail: 0, widget, &allocation, &result); |
100 | |
101 | widget_allocation->x = allocation.x; |
102 | widget_allocation->y = allocation.y; |
103 | widget_allocation->width = allocation.width; |
104 | widget_allocation->height = allocation.height; |
105 | } |
106 | |
107 | static GtkAlign |
108 | effective_align (GtkAlign align, |
109 | GtkTextDirection direction) |
110 | { |
111 | switch (align) |
112 | { |
113 | case GTK_ALIGN_START: |
114 | return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_END : GTK_ALIGN_START; |
115 | case GTK_ALIGN_END: |
116 | return direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_START : GTK_ALIGN_END; |
117 | case GTK_ALIGN_FILL: |
118 | case GTK_ALIGN_CENTER: |
119 | case GTK_ALIGN_BASELINE: |
120 | default: |
121 | return align; |
122 | } |
123 | } |
124 | |
125 | static void |
126 | blur_overlay_child_update_style_classes (BlurOverlay *overlay, |
127 | GtkWidget *child, |
128 | GtkAllocation *child_allocation) |
129 | { |
130 | int width, height; |
131 | GtkAlign valign, halign; |
132 | gboolean is_left, is_right, is_top, is_bottom; |
133 | gboolean has_left, has_right, has_top, has_bottom; |
134 | |
135 | has_left = gtk_widget_has_css_class (widget: child, css_class: "left" ); |
136 | has_right = gtk_widget_has_css_class (widget: child, css_class: "right" ); |
137 | has_top = gtk_widget_has_css_class (widget: child, css_class: "top" ); |
138 | has_bottom = gtk_widget_has_css_class (widget: child, css_class: "bottom" ); |
139 | |
140 | is_left = is_right = is_top = is_bottom = FALSE; |
141 | |
142 | width = gtk_widget_get_width (GTK_WIDGET (overlay)); |
143 | height = gtk_widget_get_height (GTK_WIDGET (overlay)); |
144 | |
145 | halign = effective_align (align: gtk_widget_get_halign (widget: child), |
146 | direction: gtk_widget_get_direction (widget: child)); |
147 | |
148 | if (halign == GTK_ALIGN_START) |
149 | is_left = (child_allocation->x == 0); |
150 | else if (halign == GTK_ALIGN_END) |
151 | is_right = (child_allocation->x + child_allocation->width == width); |
152 | |
153 | valign = gtk_widget_get_valign (widget: child); |
154 | |
155 | if (valign == GTK_ALIGN_START) |
156 | is_top = (child_allocation->y == 0); |
157 | else if (valign == GTK_ALIGN_END) |
158 | is_bottom = (child_allocation->y + child_allocation->height == height); |
159 | |
160 | if (has_left && !is_left) |
161 | gtk_widget_remove_css_class (widget: child, css_class: "left" ); |
162 | else if (!has_left && is_left) |
163 | gtk_widget_add_css_class (widget: child, css_class: "left" ); |
164 | |
165 | if (has_right && !is_right) |
166 | gtk_widget_remove_css_class (widget: child, css_class: "right" ); |
167 | else if (!has_right && is_right) |
168 | gtk_widget_add_css_class (widget: child, css_class: "right" ); |
169 | |
170 | if (has_top && !is_top) |
171 | gtk_widget_remove_css_class (widget: child, css_class: "top" ); |
172 | else if (!has_top && is_top) |
173 | gtk_widget_add_css_class (widget: child, css_class: "top" ); |
174 | |
175 | if (has_bottom && !is_bottom) |
176 | gtk_widget_remove_css_class (widget: child, css_class: "bottom" ); |
177 | else if (!has_bottom && is_bottom) |
178 | gtk_widget_add_css_class (widget: child, css_class: "bottom" ); |
179 | } |
180 | |
181 | static void |
182 | blur_overlay_child_allocate (BlurOverlay *overlay, |
183 | GtkWidget *widget, |
184 | BlurOverlayChild *child) |
185 | { |
186 | GtkAllocation child_allocation; |
187 | |
188 | if (!gtk_widget_get_visible (widget)) |
189 | return; |
190 | |
191 | blur_overlay_compute_child_allocation (overlay, widget, child, widget_allocation: &child_allocation); |
192 | |
193 | blur_overlay_child_update_style_classes (overlay, child: widget, child_allocation: &child_allocation); |
194 | gtk_widget_size_allocate (widget, allocation: &child_allocation, baseline: -1); |
195 | } |
196 | |
197 | static void |
198 | blur_overlay_size_allocate (GtkWidget *widget, |
199 | int width, |
200 | int height, |
201 | int baseline) |
202 | { |
203 | BlurOverlay *overlay = BLUR_OVERLAY (widget); |
204 | GtkWidget *child; |
205 | GtkWidget *main_widget; |
206 | |
207 | main_widget = overlay->main_widget; |
208 | if (main_widget && gtk_widget_get_visible (widget: main_widget)) |
209 | gtk_widget_size_allocate (widget: main_widget, |
210 | allocation: &(GtkAllocation) { |
211 | 0, 0, |
212 | width, height |
213 | }, baseline: -1); |
214 | |
215 | for (child = gtk_widget_get_first_child (widget); |
216 | child != NULL; |
217 | child = gtk_widget_get_next_sibling (widget: child)) |
218 | { |
219 | if (child != main_widget) |
220 | { |
221 | BlurOverlayChild *child_data = blur_overlay_get_overlay_child (widget: child); |
222 | blur_overlay_child_allocate (overlay, widget: child, child: child_data); |
223 | } |
224 | } |
225 | } |
226 | |
227 | static gboolean |
228 | blur_overlay_get_child_position (BlurOverlay *overlay, |
229 | GtkWidget *widget, |
230 | GtkAllocation *alloc) |
231 | { |
232 | GtkRequisition min, req; |
233 | GtkAlign halign; |
234 | GtkTextDirection direction; |
235 | int width, height; |
236 | |
237 | gtk_widget_get_preferred_size (widget, minimum_size: &min, natural_size: &req); |
238 | width = gtk_widget_get_width (GTK_WIDGET (overlay)); |
239 | height = gtk_widget_get_height (GTK_WIDGET (overlay)); |
240 | |
241 | alloc->x = 0; |
242 | alloc->width = MAX (min.width, MIN (width, req.width)); |
243 | |
244 | direction = gtk_widget_get_direction (widget); |
245 | |
246 | halign = gtk_widget_get_halign (widget); |
247 | switch (effective_align (align: halign, direction)) |
248 | { |
249 | case GTK_ALIGN_START: |
250 | /* nothing to do */ |
251 | break; |
252 | case GTK_ALIGN_FILL: |
253 | alloc->width = MAX (alloc->width, width); |
254 | break; |
255 | case GTK_ALIGN_CENTER: |
256 | alloc->x += width / 2 - alloc->width / 2; |
257 | break; |
258 | case GTK_ALIGN_END: |
259 | alloc->x += width - alloc->width; |
260 | break; |
261 | case GTK_ALIGN_BASELINE: |
262 | default: |
263 | g_assert_not_reached (); |
264 | break; |
265 | } |
266 | |
267 | alloc->y = 0; |
268 | alloc->height = MAX (min.height, MIN (height, req.height)); |
269 | |
270 | switch (gtk_widget_get_valign (widget)) |
271 | { |
272 | case GTK_ALIGN_START: |
273 | /* nothing to do */ |
274 | break; |
275 | case GTK_ALIGN_FILL: |
276 | alloc->height = MAX (alloc->height, height); |
277 | break; |
278 | case GTK_ALIGN_CENTER: |
279 | alloc->y += height / 2 - alloc->height / 2; |
280 | break; |
281 | case GTK_ALIGN_END: |
282 | alloc->y += height - alloc->height; |
283 | break; |
284 | case GTK_ALIGN_BASELINE: |
285 | default: |
286 | g_assert_not_reached (); |
287 | break; |
288 | } |
289 | |
290 | return TRUE; |
291 | } |
292 | |
293 | static void |
294 | blur_overlay_snapshot (GtkWidget *widget, |
295 | GtkSnapshot *snapshot) |
296 | { |
297 | GtkWidget *main_widget; |
298 | GskRenderNode *main_widget_node = NULL; |
299 | GtkWidget *child; |
300 | GtkAllocation main_alloc; |
301 | cairo_region_t *clip = NULL; |
302 | int i; |
303 | |
304 | main_widget = BLUR_OVERLAY (widget)->main_widget; |
305 | gtk_widget_get_allocation (widget, allocation: &main_alloc); |
306 | |
307 | for (child = gtk_widget_get_first_child (widget); |
308 | child != NULL; |
309 | child = gtk_widget_get_next_sibling (widget: child)) |
310 | { |
311 | BlurOverlayChild *child_info = blur_overlay_get_overlay_child (widget: child); |
312 | double blur = 0; |
313 | if (child_info) |
314 | blur = child_info->blur; |
315 | |
316 | if (blur > 0) |
317 | { |
318 | GtkAllocation alloc; |
319 | graphene_rect_t bounds; |
320 | |
321 | if (main_widget_node == NULL) |
322 | { |
323 | GtkSnapshot *child_snapshot; |
324 | |
325 | child_snapshot = gtk_snapshot_new (); |
326 | gtk_widget_snapshot_child (widget, child: main_widget, snapshot: child_snapshot); |
327 | main_widget_node = gtk_snapshot_free_to_node (snapshot: child_snapshot); |
328 | } |
329 | |
330 | gtk_widget_get_allocation (widget: child, allocation: &alloc); |
331 | graphene_rect_init (r: &bounds, x: alloc.x, y: alloc.y, width: alloc.width, height: alloc.height); |
332 | gtk_snapshot_push_blur (snapshot, radius: blur); |
333 | gtk_snapshot_push_clip (snapshot, bounds: &bounds); |
334 | gtk_snapshot_append_node (snapshot, node: main_widget_node); |
335 | gtk_snapshot_pop (snapshot); |
336 | gtk_snapshot_pop (snapshot); |
337 | |
338 | if (clip == NULL) |
339 | { |
340 | cairo_rectangle_int_t rect; |
341 | rect.x = rect.y = 0; |
342 | rect.width = main_alloc.width; |
343 | rect.height = main_alloc.height; |
344 | clip = cairo_region_create_rectangle (rectangle: &rect); |
345 | } |
346 | cairo_region_subtract_rectangle (dst: clip, rectangle: (cairo_rectangle_int_t *)&alloc); |
347 | } |
348 | } |
349 | |
350 | if (clip == NULL) |
351 | { |
352 | for (child = gtk_widget_get_first_child (widget); |
353 | child != NULL; |
354 | child = gtk_widget_get_next_sibling (widget: child)) |
355 | { |
356 | gtk_widget_snapshot_child (widget, child, snapshot); |
357 | } |
358 | return; |
359 | } |
360 | |
361 | for (i = 0; i < cairo_region_num_rectangles (region: clip); i++) |
362 | { |
363 | cairo_rectangle_int_t rect; |
364 | graphene_rect_t bounds; |
365 | |
366 | cairo_region_get_rectangle (region: clip, nth: i, rectangle: &rect); |
367 | graphene_rect_init (r: &bounds, x: rect.x, y: rect.y, width: rect.width, height: rect.height); |
368 | gtk_snapshot_push_clip (snapshot, bounds: &bounds); |
369 | gtk_snapshot_append_node (snapshot, node: main_widget_node); |
370 | gtk_snapshot_pop (snapshot); |
371 | } |
372 | |
373 | cairo_region_destroy (region: clip); |
374 | |
375 | for (child = gtk_widget_get_first_child (widget); |
376 | child != NULL; |
377 | child = gtk_widget_get_next_sibling (widget: child)) |
378 | { |
379 | if (child != main_widget) |
380 | gtk_widget_snapshot_child (widget, child, snapshot); |
381 | } |
382 | |
383 | gsk_render_node_unref (node: main_widget_node); |
384 | } |
385 | |
386 | static void |
387 | blur_overlay_dispose (GObject *object) |
388 | { |
389 | BlurOverlay *overlay = BLUR_OVERLAY (object); |
390 | GtkWidget *child; |
391 | |
392 | g_clear_pointer (&overlay->main_widget, gtk_widget_unparent); |
393 | |
394 | while ((child = gtk_widget_get_first_child (GTK_WIDGET (overlay)))) |
395 | gtk_widget_unparent (widget: child); |
396 | |
397 | G_OBJECT_CLASS (blur_overlay_parent_class)->dispose (object); |
398 | } |
399 | |
400 | static void |
401 | blur_overlay_class_init (BlurOverlayClass *klass) |
402 | { |
403 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
404 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
405 | |
406 | object_class->dispose = blur_overlay_dispose; |
407 | |
408 | widget_class->measure = blur_overlay_measure; |
409 | widget_class->size_allocate = blur_overlay_size_allocate; |
410 | widget_class->snapshot = blur_overlay_snapshot; |
411 | |
412 | klass->get_child_position = blur_overlay_get_child_position; |
413 | |
414 | signals[GET_CHILD_POSITION] = |
415 | g_signal_new (signal_name: "get-child-position" , |
416 | G_TYPE_FROM_CLASS (object_class), |
417 | signal_flags: G_SIGNAL_RUN_LAST, |
418 | G_STRUCT_OFFSET (BlurOverlayClass, get_child_position), |
419 | accumulator: g_signal_accumulator_true_handled, NULL, |
420 | NULL, |
421 | G_TYPE_BOOLEAN, n_params: 2, |
422 | GTK_TYPE_WIDGET, |
423 | GDK_TYPE_RECTANGLE | G_SIGNAL_TYPE_STATIC_SCOPE); |
424 | |
425 | child_data_quark = g_quark_from_static_string (string: "gtk-overlay-child-data" ); |
426 | |
427 | gtk_widget_class_set_css_name (widget_class, name: "overlay" ); |
428 | } |
429 | |
430 | static void |
431 | blur_overlay_init (BlurOverlay *overlay) |
432 | { |
433 | } |
434 | |
435 | GtkWidget * |
436 | blur_overlay_new (void) |
437 | { |
438 | return g_object_new (BLUR_TYPE_OVERLAY, NULL); |
439 | } |
440 | |
441 | void |
442 | blur_overlay_add_overlay (BlurOverlay *overlay, |
443 | GtkWidget *widget, |
444 | double blur) |
445 | { |
446 | BlurOverlayChild *child = g_new0 (BlurOverlayChild, 1); |
447 | |
448 | gtk_widget_insert_before (widget, GTK_WIDGET (overlay), NULL); |
449 | |
450 | child->blur = blur; |
451 | |
452 | blur_overlay_set_overlay_child (widget, child_data: child); |
453 | } |
454 | |
455 | void |
456 | blur_overlay_set_child (BlurOverlay *overlay, |
457 | GtkWidget *widget) |
458 | { |
459 | gtk_widget_insert_after (widget, GTK_WIDGET (overlay), NULL); |
460 | overlay->main_widget = widget; |
461 | } |
462 | |