]> Pileus Git - ~andy/gtk/blob - demos/gtk-demo/offscreen_window.c
gtk-demo: Add a CSS shadows demo
[~andy/gtk] / demos / gtk-demo / offscreen_window.c
1 /* Offscreen windows/Rotated button
2  *
3  * Offscreen windows can be used to transform parts of a widget
4  * hierarchy. Note that the rotated button is fully functional.
5  */
6 #include <math.h>
7 #include <gtk/gtk.h>
8
9 #define GTK_TYPE_ROTATED_BIN              (gtk_rotated_bin_get_type ())
10 #define GTK_ROTATED_BIN(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ROTATED_BIN, GtkRotatedBin))
11 #define GTK_ROTATED_BIN_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ROTATED_BIN, GtkRotatedBinClass))
12 #define GTK_IS_ROTATED_BIN(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ROTATED_BIN))
13 #define GTK_IS_ROTATED_BIN_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ROTATED_BIN))
14 #define GTK_ROTATED_BIN_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ROTATED_BIN, GtkRotatedBinClass))
15
16 typedef struct _GtkRotatedBin   GtkRotatedBin;
17 typedef struct _GtkRotatedBinClass  GtkRotatedBinClass;
18
19 struct _GtkRotatedBin
20 {
21   GtkContainer container;
22
23   GtkWidget *child;
24   GdkWindow *offscreen_window;
25   gdouble angle;
26 };
27
28 struct _GtkRotatedBinClass
29 {
30   GtkContainerClass parent_class;
31 };
32
33 GType      gtk_rotated_bin_get_type  (void) G_GNUC_CONST;
34 GtkWidget* gtk_rotated_bin_new       (void);
35 void       gtk_rotated_bin_set_angle (GtkRotatedBin *bin,
36                                       gdouble        angle);
37
38 /*** implementation ***/
39
40 static void     gtk_rotated_bin_realize       (GtkWidget       *widget);
41 static void     gtk_rotated_bin_unrealize     (GtkWidget       *widget);
42 static void     gtk_rotated_bin_get_preferred_width  (GtkWidget *widget,
43                                                       gint      *minimum,
44                                                       gint      *natural);
45 static void     gtk_rotated_bin_get_preferred_height (GtkWidget *widget,
46                                                       gint      *minimum,
47                                                       gint      *natural);
48 static void     gtk_rotated_bin_size_allocate (GtkWidget       *widget,
49                                                GtkAllocation   *allocation);
50 static gboolean gtk_rotated_bin_damage        (GtkWidget       *widget,
51                                                GdkEventExpose  *event);
52 static gboolean gtk_rotated_bin_draw          (GtkWidget       *widget,
53                                                cairo_t         *cr);
54
55 static void     gtk_rotated_bin_add           (GtkContainer    *container,
56                                                GtkWidget       *child);
57 static void     gtk_rotated_bin_remove        (GtkContainer    *container,
58                                                GtkWidget       *widget);
59 static void     gtk_rotated_bin_forall        (GtkContainer    *container,
60                                                gboolean         include_internals,
61                                                GtkCallback      callback,
62                                                gpointer         callback_data);
63 static GType    gtk_rotated_bin_child_type    (GtkContainer    *container);
64
65 G_DEFINE_TYPE (GtkRotatedBin, gtk_rotated_bin, GTK_TYPE_CONTAINER);
66
67 static void
68 to_child (GtkRotatedBin *bin,
69           double         widget_x,
70           double         widget_y,
71           double        *x_out,
72           double        *y_out)
73 {
74   GtkAllocation child_area;
75   double x, y, xr, yr;
76   double c, s;
77   double w, h;
78
79   s = sin (bin->angle);
80   c = cos (bin->angle);
81   gtk_widget_get_allocation (bin->child, &child_area);
82
83   w = c * child_area.width + s * child_area.height;
84   h = s * child_area.width + c * child_area.height;
85
86   x = widget_x;
87   y = widget_y;
88
89   x -= (w - child_area.width) / 2;
90   y -= (h - child_area.height) / 2;
91
92   x -= child_area.width / 2;
93   y -= child_area.height / 2;
94
95   xr = x * c + y * s;
96   yr = y * c - x * s;
97   x = xr;
98   y = yr;
99
100   x += child_area.width / 2;
101   y += child_area.height / 2;
102
103   *x_out = x;
104   *y_out = y;
105 }
106
107 static void
108 to_parent (GtkRotatedBin *bin,
109            double         offscreen_x,
110            double         offscreen_y,
111            double        *x_out,
112            double        *y_out)
113 {
114   GtkAllocation child_area;
115   double x, y, xr, yr;
116   double c, s;
117   double w, h;
118
119   s = sin (bin->angle);
120   c = cos (bin->angle);
121   gtk_widget_get_allocation (bin->child, &child_area);
122
123   w = c * child_area.width + s * child_area.height;
124   h = s * child_area.width + c * child_area.height;
125
126   x = offscreen_x;
127   y = offscreen_y;
128
129   x -= child_area.width / 2;
130   y -= child_area.height / 2;
131
132   xr = x * c - y * s;
133   yr = x * s + y * c;
134   x = xr;
135   y = yr;
136
137   x += child_area.width / 2;
138   y += child_area.height / 2;
139
140   x -= (w - child_area.width) / 2;
141   y -= (h - child_area.height) / 2;
142
143   *x_out = x;
144   *y_out = y;
145 }
146
147 static void
148 gtk_rotated_bin_class_init (GtkRotatedBinClass *klass)
149 {
150   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
151   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
152
153   widget_class->realize = gtk_rotated_bin_realize;
154   widget_class->unrealize = gtk_rotated_bin_unrealize;
155   widget_class->get_preferred_width = gtk_rotated_bin_get_preferred_width;
156   widget_class->get_preferred_height = gtk_rotated_bin_get_preferred_height;
157   widget_class->size_allocate = gtk_rotated_bin_size_allocate;
158   widget_class->draw = gtk_rotated_bin_draw;
159
160   g_signal_override_class_closure (g_signal_lookup ("damage-event", GTK_TYPE_WIDGET),
161                                    GTK_TYPE_ROTATED_BIN,
162                                    g_cclosure_new (G_CALLBACK (gtk_rotated_bin_damage),
163                                                    NULL, NULL));
164
165   container_class->add = gtk_rotated_bin_add;
166   container_class->remove = gtk_rotated_bin_remove;
167   container_class->forall = gtk_rotated_bin_forall;
168   container_class->child_type = gtk_rotated_bin_child_type;
169 }
170
171 static void
172 gtk_rotated_bin_init (GtkRotatedBin *bin)
173 {
174   gtk_widget_set_has_window (GTK_WIDGET (bin), TRUE);
175 }
176
177 GtkWidget *
178 gtk_rotated_bin_new (void)
179 {
180   return g_object_new (GTK_TYPE_ROTATED_BIN, NULL);
181 }
182
183 static GdkWindow *
184 pick_offscreen_child (GdkWindow     *offscreen_window,
185                       double         widget_x,
186                       double         widget_y,
187                       GtkRotatedBin *bin)
188 {
189  GtkAllocation child_area;
190  double x, y;
191
192  if (bin->child && gtk_widget_get_visible (bin->child))
193     {
194       to_child (bin, widget_x, widget_y, &x, &y);
195
196       gtk_widget_get_allocation (bin->child, &child_area);
197
198       if (x >= 0 && x < child_area.width &&
199           y >= 0 && y < child_area.height)
200         return bin->offscreen_window;
201     }
202
203   return NULL;
204 }
205
206 static void
207 offscreen_window_to_parent (GdkWindow     *offscreen_window,
208                             double         offscreen_x,
209                             double         offscreen_y,
210                             double        *parent_x,
211                             double        *parent_y,
212                             GtkRotatedBin *bin)
213 {
214   to_parent (bin, offscreen_x, offscreen_y, parent_x, parent_y);
215 }
216
217 static void
218 offscreen_window_from_parent (GdkWindow     *window,
219                               double         parent_x,
220                               double         parent_y,
221                               double        *offscreen_x,
222                               double        *offscreen_y,
223                               GtkRotatedBin *bin)
224 {
225   to_child (bin, parent_x, parent_y, offscreen_x, offscreen_y);
226 }
227
228 static void
229 gtk_rotated_bin_realize (GtkWidget *widget)
230 {
231   GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
232   GtkAllocation allocation;
233   GtkStyleContext *context;
234   GdkWindow *window;
235   GdkWindowAttr attributes;
236   gint attributes_mask;
237   guint border_width;
238   GtkRequisition child_requisition;
239
240   gtk_widget_set_realized (widget, TRUE);
241
242   gtk_widget_get_allocation (widget, &allocation);
243   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
244
245   attributes.x = allocation.x + border_width;
246   attributes.y = allocation.y + border_width;
247   attributes.width = allocation.width - 2 * border_width;
248   attributes.height = allocation.height - 2 * border_width;
249   attributes.window_type = GDK_WINDOW_CHILD;
250   attributes.event_mask = gtk_widget_get_events (widget)
251                         | GDK_EXPOSURE_MASK
252                         | GDK_POINTER_MOTION_MASK
253                         | GDK_BUTTON_PRESS_MASK
254                         | GDK_BUTTON_RELEASE_MASK
255                         | GDK_SCROLL_MASK
256                         | GDK_ENTER_NOTIFY_MASK
257                         | GDK_LEAVE_NOTIFY_MASK;
258
259   attributes.visual = gtk_widget_get_visual (widget);
260   attributes.wclass = GDK_INPUT_OUTPUT;
261
262   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
263
264   window = gdk_window_new (gtk_widget_get_parent_window (widget),
265                            &attributes, attributes_mask);
266   gtk_widget_set_window (widget, window);
267   gdk_window_set_user_data (window, widget);
268   g_signal_connect (window, "pick-embedded-child",
269                     G_CALLBACK (pick_offscreen_child), bin);
270
271   attributes.window_type = GDK_WINDOW_OFFSCREEN;
272
273   child_requisition.width = child_requisition.height = 0;
274   if (bin->child && gtk_widget_get_visible (bin->child))
275     {
276       GtkAllocation child_allocation;
277
278       gtk_widget_get_allocation (bin->child, &child_allocation);
279       attributes.width = child_allocation.width;
280       attributes.height = child_allocation.height;
281     }
282   bin->offscreen_window = gdk_window_new (gtk_widget_get_root_window (widget),
283                                           &attributes, attributes_mask);
284   gdk_window_set_user_data (bin->offscreen_window, widget);
285   if (bin->child)
286     gtk_widget_set_parent_window (bin->child, bin->offscreen_window);
287   gdk_offscreen_window_set_embedder (bin->offscreen_window, window);
288   g_signal_connect (bin->offscreen_window, "to-embedder",
289                     G_CALLBACK (offscreen_window_to_parent), bin);
290   g_signal_connect (bin->offscreen_window, "from-embedder",
291                     G_CALLBACK (offscreen_window_from_parent), bin);
292
293   context = gtk_widget_get_style_context (widget);
294   gtk_style_context_set_background (context, window);
295   gtk_style_context_set_background (context, bin->offscreen_window);
296   gdk_window_show (bin->offscreen_window);
297 }
298
299 static void
300 gtk_rotated_bin_unrealize (GtkWidget *widget)
301 {
302   GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
303
304   gdk_window_set_user_data (bin->offscreen_window, NULL);
305   gdk_window_destroy (bin->offscreen_window);
306   bin->offscreen_window = NULL;
307
308   GTK_WIDGET_CLASS (gtk_rotated_bin_parent_class)->unrealize (widget);
309 }
310
311 static GType
312 gtk_rotated_bin_child_type (GtkContainer *container)
313 {
314   GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
315
316   if (bin->child)
317     return G_TYPE_NONE;
318
319   return GTK_TYPE_WIDGET;
320 }
321
322 static void
323 gtk_rotated_bin_add (GtkContainer *container,
324                      GtkWidget    *widget)
325 {
326   GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
327
328   if (!bin->child)
329     {
330       gtk_widget_set_parent_window (widget, bin->offscreen_window);
331       gtk_widget_set_parent (widget, GTK_WIDGET (bin));
332       bin->child = widget;
333     }
334   else
335     g_warning ("GtkRotatedBin cannot have more than one child\n");
336 }
337
338 static void
339 gtk_rotated_bin_remove (GtkContainer *container,
340                         GtkWidget    *widget)
341 {
342   GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
343   gboolean was_visible;
344
345   was_visible = gtk_widget_get_visible (widget);
346
347   if (bin->child == widget)
348     {
349       gtk_widget_unparent (widget);
350
351       bin->child = NULL;
352
353       if (was_visible && gtk_widget_get_visible (GTK_WIDGET (container)))
354         gtk_widget_queue_resize (GTK_WIDGET (container));
355     }
356 }
357
358 static void
359 gtk_rotated_bin_forall (GtkContainer *container,
360                         gboolean      include_internals,
361                         GtkCallback   callback,
362                         gpointer      callback_data)
363 {
364   GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
365
366   g_return_if_fail (callback != NULL);
367
368   if (bin->child)
369     (*callback) (bin->child, callback_data);
370 }
371
372 void
373 gtk_rotated_bin_set_angle (GtkRotatedBin *bin,
374                            gdouble        angle)
375 {
376   g_return_if_fail (GTK_IS_ROTATED_BIN (bin));
377
378   bin->angle = angle;
379   gtk_widget_queue_resize (GTK_WIDGET (bin));
380
381   gdk_window_geometry_changed (bin->offscreen_window);
382 }
383
384 static void
385 gtk_rotated_bin_size_request (GtkWidget      *widget,
386                               GtkRequisition *requisition)
387 {
388   GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
389   GtkRequisition child_requisition;
390   double s, c;
391   double w, h;
392   guint border_width;
393
394   child_requisition.width = 0;
395   child_requisition.height = 0;
396
397   if (bin->child && gtk_widget_get_visible (bin->child))
398     gtk_widget_get_preferred_size ( (bin->child),
399                                &child_requisition, NULL);
400
401   s = sin (bin->angle);
402   c = cos (bin->angle);
403   w = c * child_requisition.width + s * child_requisition.height;
404   h = s * child_requisition.width + c * child_requisition.height;
405
406   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
407   requisition->width = border_width * 2 + w;
408   requisition->height = border_width * 2 + h;
409 }
410
411 static void
412 gtk_rotated_bin_get_preferred_width (GtkWidget *widget,
413                                      gint      *minimum,
414                                      gint      *natural)
415 {
416   GtkRequisition requisition;
417
418   gtk_rotated_bin_size_request (widget, &requisition);
419
420   *minimum = *natural = requisition.width;
421 }
422
423 static void
424 gtk_rotated_bin_get_preferred_height (GtkWidget *widget,
425                                       gint      *minimum,
426                                       gint      *natural)
427 {
428   GtkRequisition requisition;
429
430   gtk_rotated_bin_size_request (widget, &requisition);
431
432   *minimum = *natural = requisition.height;
433 }
434
435 static void
436 gtk_rotated_bin_size_allocate (GtkWidget     *widget,
437                                GtkAllocation *allocation)
438 {
439   GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
440   guint border_width;
441   gint w, h;
442   gdouble s, c;
443
444   gtk_widget_set_allocation (widget, allocation);
445
446   border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
447
448   w = allocation->width - border_width * 2;
449   h = allocation->height - border_width * 2;
450
451   if (gtk_widget_get_realized (widget))
452     gdk_window_move_resize (gtk_widget_get_window (widget),
453                             allocation->x + border_width,
454                             allocation->y + border_width,
455                             w, h);
456
457   if (bin->child && gtk_widget_get_visible (bin->child))
458     {
459       GtkRequisition child_requisition;
460       GtkAllocation child_allocation;
461
462       s = sin (bin->angle);
463       c = cos (bin->angle);
464
465       gtk_widget_get_preferred_size (bin->child,
466                                      &child_requisition, NULL);
467       child_allocation.x = 0;
468       child_allocation.y = 0;
469       child_allocation.height = child_requisition.height;
470       if (c == 0.0)
471         child_allocation.width = h / s;
472       else if (s == 0.0)
473         child_allocation.width = w / c;
474       else
475         child_allocation.width = MIN ((w - s * child_allocation.height) / c,
476                                       (h - c * child_allocation.height) / s);
477
478       if (gtk_widget_get_realized (widget))
479         gdk_window_move_resize (bin->offscreen_window,
480                                 child_allocation.x,
481                                 child_allocation.y,
482                                 child_allocation.width,
483                                 child_allocation.height);
484
485       child_allocation.x = child_allocation.y = 0;
486       gtk_widget_size_allocate (bin->child, &child_allocation);
487     }
488 }
489
490 static gboolean
491 gtk_rotated_bin_damage (GtkWidget      *widget,
492                         GdkEventExpose *event)
493 {
494   gdk_window_invalidate_rect (gtk_widget_get_window (widget),
495                               NULL, FALSE);
496
497   return TRUE;
498 }
499
500 static gboolean
501 gtk_rotated_bin_draw (GtkWidget *widget,
502                       cairo_t   *cr)
503 {
504   GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
505   GdkWindow *window;
506   gdouble s, c;
507   gdouble w, h;
508
509   window = gtk_widget_get_window (widget);
510   if (gtk_cairo_should_draw_window (cr, window))
511     {
512       cairo_surface_t *surface;
513       GtkAllocation child_area;
514
515       if (bin->child && gtk_widget_get_visible (bin->child))
516         {
517           surface = gdk_offscreen_window_get_surface (bin->offscreen_window);
518           gtk_widget_get_allocation (bin->child, &child_area);
519
520           /* transform */
521           s = sin (bin->angle);
522           c = cos (bin->angle);
523           w = c * child_area.width + s * child_area.height;
524           h = s * child_area.width + c * child_area.height;
525
526           cairo_translate (cr, (w - child_area.width) / 2, (h - child_area.height) / 2);
527           cairo_translate (cr, child_area.width / 2, child_area.height / 2);
528           cairo_rotate (cr, bin->angle);
529           cairo_translate (cr, -child_area.width / 2, -child_area.height / 2);
530
531           /* clip */
532           cairo_rectangle (cr,
533                            0, 0,
534                            gdk_window_get_width (bin->offscreen_window),
535                            gdk_window_get_height (bin->offscreen_window));
536           cairo_clip (cr);
537           /* paint */
538           cairo_set_source_surface (cr, surface, 0, 0);
539           cairo_paint (cr);
540         }
541     }
542   if (gtk_cairo_should_draw_window (cr, bin->offscreen_window))
543     {
544       gtk_render_background (gtk_widget_get_style_context (widget),
545                              cr,
546                              0, 0,
547                              gdk_window_get_width (bin->offscreen_window),
548                              gdk_window_get_height (bin->offscreen_window));
549
550       if (bin->child)
551         gtk_container_propagate_draw (GTK_CONTAINER (widget),
552                                       bin->child,
553                                       cr);
554     }
555
556   return FALSE;
557 }
558
559 /*** ***/
560
561 static void
562 scale_changed (GtkRange      *range,
563                GtkRotatedBin *bin)
564 {
565   gtk_rotated_bin_set_angle (bin, gtk_range_get_value (range));
566 }
567
568 static GtkWidget *window = NULL;
569
570 GtkWidget *
571 do_offscreen_window (GtkWidget *do_widget)
572 {
573   if (!window)
574     {
575       GtkWidget *bin, *vbox, *scale, *button;
576       GdkRGBA black;
577
578       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
579       gtk_window_set_screen (GTK_WINDOW (window),
580                              gtk_widget_get_screen (do_widget));
581       gtk_window_set_title (GTK_WINDOW (window), "Rotated widget");
582
583       g_signal_connect (window, "destroy",
584                         G_CALLBACK (gtk_widget_destroyed), &window);
585
586       gdk_rgba_parse (&black, "black");
587       gtk_widget_override_background_color (window, 0, &black);
588       gtk_container_set_border_width (GTK_CONTAINER (window), 10);
589
590       vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
591       scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL,
592                                         0, G_PI/2, 0.01);
593       gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
594
595       button = gtk_button_new_with_label ("A Button");
596       bin = gtk_rotated_bin_new ();
597
598       g_signal_connect (scale, "value-changed", G_CALLBACK (scale_changed), bin);
599
600       gtk_container_add (GTK_CONTAINER (window), vbox);
601       gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
602       gtk_box_pack_start (GTK_BOX (vbox), bin, TRUE, TRUE, 0);
603       gtk_container_add (GTK_CONTAINER (bin), button);
604     }
605
606   if (!gtk_widget_get_visible (window))
607     gtk_widget_show_all (window);
608   else
609     {
610       gtk_widget_destroy (window);
611       window = NULL;
612     }
613
614   return window;
615 }