1 /* Offscreen windows/Effects
2 *
3 * Offscreen windows can be used to render elements multiple times to achieve
4 * various effects.
5 */
6 #include <gtk/gtk.h>
7
8 #define GTK_TYPE_MIRROR_BIN (gtk_mirror_bin_get_type ())
9 #define GTK_MIRROR_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MIRROR_BIN, GtkMirrorBin))
10 #define GTK_MIRROR_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MIRROR_BIN, GtkMirrorBinClass))
11 #define GTK_IS_MIRROR_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_MIRROR_BIN))
12 #define GTK_IS_MIRROR_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MIRROR_BIN))
13 #define GTK_MIRROR_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MIRROR_BIN, GtkMirrorBinClass))
14
15 typedef struct _GtkMirrorBin GtkMirrorBin;
16 typedef struct _GtkMirrorBinClass GtkMirrorBinClass;
17
18 struct _GtkMirrorBin
19 {
20 GtkContainer container;
21
22 GtkWidget *child;
23 GdkWindow *offscreen_window;
24 };
25
26 struct _GtkMirrorBinClass
27 {
28 GtkContainerClass parent_class;
29 };
30
31 GType gtk_mirror_bin_get_type (void) G_GNUC_CONST;
32 GtkWidget* gtk_mirror_bin_new (void);
33
34 /*** implementation ***/
35
36 static void gtk_mirror_bin_realize (GtkWidget *widget);
37 static void gtk_mirror_bin_unrealize (GtkWidget *widget);
38 static void gtk_mirror_bin_size_request (GtkWidget *widget,
39 GtkRequisition *requisition);
40 static void gtk_mirror_bin_size_allocate (GtkWidget *widget,
41 GtkAllocation *allocation);
42 static gboolean gtk_mirror_bin_damage (GtkWidget *widget,
43 GdkEventExpose *event);
44 static gboolean gtk_mirror_bin_expose (GtkWidget *widget,
45 GdkEventExpose *offscreen);
46
47 static void gtk_mirror_bin_add (GtkContainer *container,
48 GtkWidget *child);
49 static void gtk_mirror_bin_remove (GtkContainer *container,
50 GtkWidget *widget);
51 static void gtk_mirror_bin_forall (GtkContainer *container,
52 gboolean include_internals,
53 GtkCallback callback,
54 gpointer callback_data);
55 static GType gtk_mirror_bin_child_type (GtkContainer *container);
56
57 G_DEFINE_TYPE (GtkMirrorBin, gtk_mirror_bin, GTK_TYPE_CONTAINER);
58
59 static void
to_child(GtkMirrorBin * bin,double widget_x,double widget_y,double * x_out,double * y_out)60 to_child (GtkMirrorBin *bin,
61 double widget_x,
62 double widget_y,
63 double *x_out,
64 double *y_out)
65 {
66 *x_out = widget_x;
67 *y_out = widget_y;
68 }
69
70 static void
to_parent(GtkMirrorBin * bin,double offscreen_x,double offscreen_y,double * x_out,double * y_out)71 to_parent (GtkMirrorBin *bin,
72 double offscreen_x,
73 double offscreen_y,
74 double *x_out,
75 double *y_out)
76 {
77 *x_out = offscreen_x;
78 *y_out = offscreen_y;
79 }
80
81 static void
gtk_mirror_bin_class_init(GtkMirrorBinClass * klass)82 gtk_mirror_bin_class_init (GtkMirrorBinClass *klass)
83 {
84 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
85 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
86
87 widget_class->realize = gtk_mirror_bin_realize;
88 widget_class->unrealize = gtk_mirror_bin_unrealize;
89 widget_class->size_request = gtk_mirror_bin_size_request;
90 widget_class->size_allocate = gtk_mirror_bin_size_allocate;
91 widget_class->expose_event = gtk_mirror_bin_expose;
92
93 g_signal_override_class_closure (g_signal_lookup ("damage-event", GTK_TYPE_WIDGET),
94 GTK_TYPE_MIRROR_BIN,
95 g_cclosure_new (G_CALLBACK (gtk_mirror_bin_damage),
96 NULL, NULL));
97
98 container_class->add = gtk_mirror_bin_add;
99 container_class->remove = gtk_mirror_bin_remove;
100 container_class->forall = gtk_mirror_bin_forall;
101 container_class->child_type = gtk_mirror_bin_child_type;
102 }
103
104 static void
gtk_mirror_bin_init(GtkMirrorBin * bin)105 gtk_mirror_bin_init (GtkMirrorBin *bin)
106 {
107 gtk_widget_set_has_window (GTK_WIDGET (bin), TRUE);
108 }
109
110 GtkWidget *
gtk_mirror_bin_new(void)111 gtk_mirror_bin_new (void)
112 {
113 return g_object_new (GTK_TYPE_MIRROR_BIN, NULL);
114 }
115
116 static GdkWindow *
pick_offscreen_child(GdkWindow * offscreen_window,double widget_x,double widget_y,GtkMirrorBin * bin)117 pick_offscreen_child (GdkWindow *offscreen_window,
118 double widget_x,
119 double widget_y,
120 GtkMirrorBin *bin)
121 {
122 GtkAllocation child_area;
123 double x, y;
124
125 if (bin->child && gtk_widget_get_visible (bin->child))
126 {
127 to_child (bin, widget_x, widget_y, &x, &y);
128
129 gtk_widget_get_allocation (bin->child, &child_area);
130
131 if (x >= 0 && x < child_area.width &&
132 y >= 0 && y < child_area.height)
133 return bin->offscreen_window;
134 }
135
136 return NULL;
137 }
138
139 static void
offscreen_window_to_parent(GdkWindow * offscreen_window,double offscreen_x,double offscreen_y,double * parent_x,double * parent_y,GtkMirrorBin * bin)140 offscreen_window_to_parent (GdkWindow *offscreen_window,
141 double offscreen_x,
142 double offscreen_y,
143 double *parent_x,
144 double *parent_y,
145 GtkMirrorBin *bin)
146 {
147 to_parent (bin, offscreen_x, offscreen_y, parent_x, parent_y);
148 }
149
150 static void
offscreen_window_from_parent(GdkWindow * window,double parent_x,double parent_y,double * offscreen_x,double * offscreen_y,GtkMirrorBin * bin)151 offscreen_window_from_parent (GdkWindow *window,
152 double parent_x,
153 double parent_y,
154 double *offscreen_x,
155 double *offscreen_y,
156 GtkMirrorBin *bin)
157 {
158 to_child (bin, parent_x, parent_y, offscreen_x, offscreen_y);
159 }
160
161 static void
gtk_mirror_bin_realize(GtkWidget * widget)162 gtk_mirror_bin_realize (GtkWidget *widget)
163 {
164 GtkMirrorBin *bin = GTK_MIRROR_BIN (widget);
165 GdkWindowAttr attributes;
166 GdkWindow *gdk_window;
167 gint attributes_mask;
168 gint border_width;
169 GtkRequisition child_requisition;
170 GtkAllocation widget_allocation, bin_child_allocation;
171 GtkStyle *style;
172
173 gtk_widget_set_realized (widget, TRUE);
174
175 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
176 gtk_widget_get_allocation (widget, &widget_allocation);
177
178 attributes.x = widget_allocation.x + border_width;
179 attributes.y = widget_allocation.y + border_width;
180 attributes.width = widget_allocation.width - 2 * border_width;
181 attributes.height = widget_allocation.height - 2 * border_width;
182 attributes.window_type = GDK_WINDOW_CHILD;
183 attributes.event_mask = gtk_widget_get_events (widget)
184 | GDK_EXPOSURE_MASK
185 | GDK_POINTER_MOTION_MASK
186 | GDK_BUTTON_PRESS_MASK
187 | GDK_BUTTON_RELEASE_MASK
188 | GDK_SCROLL_MASK
189 | GDK_ENTER_NOTIFY_MASK
190 | GDK_LEAVE_NOTIFY_MASK;
191
192 attributes.visual = gtk_widget_get_visual (widget);
193 attributes.colormap = gtk_widget_get_colormap (widget);
194 attributes.wclass = GDK_INPUT_OUTPUT;
195
196 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
197
198 gdk_window = gdk_window_new (gtk_widget_get_parent_window (widget),
199 &attributes, attributes_mask);
200 gtk_widget_set_window (widget, gdk_window);
201 gdk_window_set_user_data (gdk_window, widget);
202 g_signal_connect (gdk_window, "pick-embedded-child",
203 G_CALLBACK (pick_offscreen_child), bin);
204
205 attributes.window_type = GDK_WINDOW_OFFSCREEN;
206
207 child_requisition.width = child_requisition.height = 0;
208 if (bin->child && gtk_widget_get_visible (bin->child))
209 {
210 gtk_widget_get_allocation (bin->child, &bin_child_allocation);
211 attributes.width = bin_child_allocation.width;
212 attributes.height = bin_child_allocation.height;
213 }
214 bin->offscreen_window = gdk_window_new (gtk_widget_get_root_window (widget),
215 &attributes, attributes_mask);
216 gdk_window_set_user_data (bin->offscreen_window, widget);
217 if (bin->child)
218 gtk_widget_set_parent_window (bin->child, bin->offscreen_window);
219 gdk_offscreen_window_set_embedder (bin->offscreen_window, gtk_widget_get_window (widget));
220 g_signal_connect (bin->offscreen_window, "to-embedder",
221 G_CALLBACK (offscreen_window_to_parent), bin);
222 g_signal_connect (bin->offscreen_window, "from-embedder",
223 G_CALLBACK (offscreen_window_from_parent), bin);
224
225 gtk_widget_style_attach (widget);
226 style = gtk_widget_get_style (widget);
227 gtk_style_set_background (style, gdk_window, GTK_STATE_NORMAL);
228 gtk_style_set_background (style, bin->offscreen_window, GTK_STATE_NORMAL);
229 gdk_window_show (bin->offscreen_window);
230 }
231
232 static void
gtk_mirror_bin_unrealize(GtkWidget * widget)233 gtk_mirror_bin_unrealize (GtkWidget *widget)
234 {
235 GtkMirrorBin *bin = GTK_MIRROR_BIN (widget);
236
237 gdk_window_set_user_data (bin->offscreen_window, NULL);
238 gdk_window_destroy (bin->offscreen_window);
239 bin->offscreen_window = NULL;
240
241 GTK_WIDGET_CLASS (gtk_mirror_bin_parent_class)->unrealize (widget);
242 }
243
244 static GType
gtk_mirror_bin_child_type(GtkContainer * container)245 gtk_mirror_bin_child_type (GtkContainer *container)
246 {
247 GtkMirrorBin *bin = GTK_MIRROR_BIN (container);
248
249 if (bin->child)
250 return G_TYPE_NONE;
251
252 return GTK_TYPE_WIDGET;
253 }
254
255 static void
gtk_mirror_bin_add(GtkContainer * container,GtkWidget * widget)256 gtk_mirror_bin_add (GtkContainer *container,
257 GtkWidget *widget)
258 {
259 GtkMirrorBin *bin = GTK_MIRROR_BIN (container);
260
261 if (!bin->child)
262 {
263 gtk_widget_set_parent_window (widget, bin->offscreen_window);
264 gtk_widget_set_parent (widget, GTK_WIDGET (bin));
265 bin->child = widget;
266 }
267 else
268 g_warning ("GtkMirrorBin cannot have more than one child\n");
269 }
270
271 static void
gtk_mirror_bin_remove(GtkContainer * container,GtkWidget * widget)272 gtk_mirror_bin_remove (GtkContainer *container,
273 GtkWidget *widget)
274 {
275 GtkMirrorBin *bin = GTK_MIRROR_BIN (container);
276 gboolean was_visible;
277
278 was_visible = gtk_widget_get_visible (widget);
279
280 if (bin->child == widget)
281 {
282 gtk_widget_unparent (widget);
283
284 bin->child = NULL;
285
286 if (was_visible && gtk_widget_get_visible (GTK_WIDGET (container)))
287 gtk_widget_queue_resize (GTK_WIDGET (container));
288 }
289 }
290
291 static void
gtk_mirror_bin_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)292 gtk_mirror_bin_forall (GtkContainer *container,
293 gboolean include_internals,
294 GtkCallback callback,
295 gpointer callback_data)
296 {
297 GtkMirrorBin *bin = GTK_MIRROR_BIN (container);
298
299 g_return_if_fail (callback != NULL);
300
301 if (bin->child)
302 (*callback) (bin->child, callback_data);
303 }
304
305 static void
gtk_mirror_bin_size_request(GtkWidget * widget,GtkRequisition * requisition)306 gtk_mirror_bin_size_request (GtkWidget *widget,
307 GtkRequisition *requisition)
308 {
309 GtkMirrorBin *bin = GTK_MIRROR_BIN (widget);
310 GtkRequisition child_requisition;
311 gint border_width;
312
313 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
314
315 child_requisition.width = 0;
316 child_requisition.height = 0;
317
318 if (bin->child && gtk_widget_get_visible (bin->child))
319 gtk_widget_size_request (bin->child, &child_requisition);
320
321 requisition->width = border_width * 2 + child_requisition.width + 10;
322 requisition->height = border_width * 2 + child_requisition.height * 2 + 10;
323 }
324
325 static void
gtk_mirror_bin_size_allocate(GtkWidget * widget,GtkAllocation * allocation)326 gtk_mirror_bin_size_allocate (GtkWidget *widget,
327 GtkAllocation *allocation)
328 {
329 GtkMirrorBin *bin = GTK_MIRROR_BIN (widget);
330 gint border_width;
331 gint w, h;
332
333 gtk_widget_set_allocation (widget, allocation);
334
335 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
336
337 w = allocation->width - border_width * 2;
338 h = allocation->height - border_width * 2;
339
340 if (gtk_widget_get_realized (widget))
341 gdk_window_move_resize (gtk_widget_get_window (widget),
342 allocation->x + border_width,
343 allocation->y + border_width,
344 w, h);
345
346 if (bin->child && gtk_widget_get_visible (bin->child))
347 {
348 GtkRequisition child_requisition;
349 GtkAllocation child_allocation;
350
351 gtk_widget_get_child_requisition (bin->child, &child_requisition);
352 child_allocation.x = 0;
353 child_allocation.y = 0;
354 child_allocation.height = child_requisition.height;
355 child_allocation.width = child_requisition.width;
356
357 if (gtk_widget_get_realized (widget))
358 gdk_window_move_resize (bin->offscreen_window,
359 allocation->x + border_width,
360 allocation->y + border_width,
361 child_allocation.width, child_allocation.height);
362 gtk_widget_size_allocate (bin->child, &child_allocation);
363 }
364 }
365
366 static gboolean
gtk_mirror_bin_damage(GtkWidget * widget,GdkEventExpose * event)367 gtk_mirror_bin_damage (GtkWidget *widget,
368 GdkEventExpose *event)
369 {
370 gdk_window_invalidate_rect (gtk_widget_get_window (widget), NULL, FALSE);
371
372 return TRUE;
373 }
374
375 static gboolean
gtk_mirror_bin_expose(GtkWidget * widget,GdkEventExpose * event)376 gtk_mirror_bin_expose (GtkWidget *widget,
377 GdkEventExpose *event)
378 {
379 GtkMirrorBin *bin = GTK_MIRROR_BIN (widget);
380 gint width, height;
381
382 if (gtk_widget_is_drawable (widget))
383 {
384 if (event->window == gtk_widget_get_window (widget))
385 {
386 GdkPixmap *pixmap;
387 cairo_t *cr;
388 cairo_matrix_t matrix;
389 cairo_pattern_t *mask;
390
391 if (bin->child && gtk_widget_get_visible (bin->child))
392 {
393 pixmap = gdk_offscreen_window_get_pixmap (bin->offscreen_window);
394 gdk_pixmap_get_size (pixmap, &width, &height);
395
396 cr = gdk_cairo_create (gtk_widget_get_window (widget));
397
398 cairo_save (cr);
399
400 cairo_rectangle (cr, 0, 0, width, height);
401 cairo_clip (cr);
402
403 /* paint the offscreen child */
404 gdk_cairo_set_source_pixmap (cr, pixmap, 0, 0);
405 cairo_paint (cr);
406
407 cairo_restore (cr);
408
409 cairo_matrix_init (&matrix, 1.0, 0.0, 0.3, 1.0, 0.0, 0.0);
410 cairo_matrix_scale (&matrix, 1.0, -1.0);
411 cairo_matrix_translate (&matrix, -10, - 3 * height - 10);
412 cairo_transform (cr, &matrix);
413
414 cairo_rectangle (cr, 0, height, width, height);
415 cairo_clip (cr);
416
417 gdk_cairo_set_source_pixmap (cr, pixmap, 0, height);
418
419 /* create linear gradient as mask-pattern to fade out the source */
420 mask = cairo_pattern_create_linear (0.0, height, 0.0, 2*height);
421 cairo_pattern_add_color_stop_rgba (mask, 0.0, 0.0, 0.0, 0.0, 0.0);
422 cairo_pattern_add_color_stop_rgba (mask, 0.25, 0.0, 0.0, 0.0, 0.01);
423 cairo_pattern_add_color_stop_rgba (mask, 0.5, 0.0, 0.0, 0.0, 0.25);
424 cairo_pattern_add_color_stop_rgba (mask, 0.75, 0.0, 0.0, 0.0, 0.5);
425 cairo_pattern_add_color_stop_rgba (mask, 1.0, 0.0, 0.0, 0.0, 1.0);
426
427 /* paint the reflection */
428 cairo_mask (cr, mask);
429
430 cairo_pattern_destroy (mask);
431 cairo_destroy (cr);
432 }
433 }
434 else if (event->window == bin->offscreen_window)
435 {
436 gtk_paint_flat_box (gtk_widget_get_style (widget), event->window,
437 GTK_STATE_NORMAL, GTK_SHADOW_NONE,
438 &event->area, widget, "blah",
439 0, 0, -1, -1);
440
441 if (bin->child)
442 gtk_container_propagate_expose (GTK_CONTAINER (widget),
443 bin->child,
444 event);
445 }
446 }
447
448 return FALSE;
449 }
450
451 /*** ***/
452
453 static GtkWidget *window = NULL;
454
455 GtkWidget *
do_offscreen_window2(GtkWidget * do_widget)456 do_offscreen_window2 (GtkWidget *do_widget)
457 {
458 if (!window)
459 {
460 GtkWidget *bin, *vbox;
461 GtkWidget *hbox, *entry, *applybutton, *backbutton;
462 GtkSizeGroup *group;
463
464 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
465 gtk_window_set_screen (GTK_WINDOW (window),
466 gtk_widget_get_screen (do_widget));
467 gtk_window_set_title (GTK_WINDOW (window), "Effects");
468
469 g_signal_connect (window, "destroy",
470 G_CALLBACK (gtk_widget_destroyed), &window);
471
472 gtk_container_set_border_width (GTK_CONTAINER (window), 10);
473
474 vbox = gtk_vbox_new (0, FALSE);
475
476 bin = gtk_mirror_bin_new ();
477
478 group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
479
480 hbox = gtk_hbox_new (FALSE, 6);
481 backbutton = gtk_button_new ();
482 gtk_container_add (GTK_CONTAINER (backbutton),
483 gtk_image_new_from_stock (GTK_STOCK_GO_BACK, 4));
484 gtk_size_group_add_widget (group, backbutton);
485 entry = gtk_entry_new ();
486 gtk_size_group_add_widget (group, entry);
487 applybutton = gtk_button_new ();
488 gtk_size_group_add_widget (group, applybutton);
489 gtk_container_add (GTK_CONTAINER (applybutton),
490 gtk_image_new_from_stock (GTK_STOCK_APPLY, 4));
491
492 gtk_container_add (GTK_CONTAINER (window), vbox);
493 gtk_box_pack_start (GTK_BOX (vbox), bin, TRUE, TRUE, 0);
494 gtk_container_add (GTK_CONTAINER (bin), hbox);
495 gtk_box_pack_start (GTK_BOX (hbox), backbutton, FALSE, FALSE, 0);
496 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
497 gtk_box_pack_start (GTK_BOX (hbox), applybutton, FALSE, FALSE, 0);
498 }
499
500 if (!gtk_widget_get_visible (window))
501 gtk_widget_show_all (window);
502 else
503 {
504 gtk_widget_destroy (window);
505 window = NULL;
506 }
507
508 return window;
509 }
510
511