1 /* Offscreen windows/Rotated button
3 * Offscreen windows can be used to transform parts of a widget
4 * hierarchy. Note that the rotated button is fully functional.
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))
16 typedef struct _GtkRotatedBin GtkRotatedBin;
17 typedef struct _GtkRotatedBinClass GtkRotatedBinClass;
21 GtkContainer container;
24 GdkWindow *offscreen_window;
28 struct _GtkRotatedBinClass
30 GtkContainerClass parent_class;
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,
38 /*** implementation ***/
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,
45 static void gtk_rotated_bin_get_preferred_height (GtkWidget *widget,
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,
55 static void gtk_rotated_bin_add (GtkContainer *container,
57 static void gtk_rotated_bin_remove (GtkContainer *container,
59 static void gtk_rotated_bin_forall (GtkContainer *container,
60 gboolean include_internals,
62 gpointer callback_data);
63 static GType gtk_rotated_bin_child_type (GtkContainer *container);
65 G_DEFINE_TYPE (GtkRotatedBin, gtk_rotated_bin, GTK_TYPE_CONTAINER);
68 to_child (GtkRotatedBin *bin,
74 GtkAllocation child_area;
81 gtk_widget_get_allocation (bin->child, &child_area);
83 w = c * child_area.width + s * child_area.height;
84 h = s * child_area.width + c * child_area.height;
89 x -= (w - child_area.width) / 2;
90 y -= (h - child_area.height) / 2;
92 x -= child_area.width / 2;
93 y -= child_area.height / 2;
100 x += child_area.width / 2;
101 y += child_area.height / 2;
108 to_parent (GtkRotatedBin *bin,
114 GtkAllocation child_area;
119 s = sin (bin->angle);
120 c = cos (bin->angle);
121 gtk_widget_get_allocation (bin->child, &child_area);
123 w = c * child_area.width + s * child_area.height;
124 h = s * child_area.width + c * child_area.height;
129 x -= child_area.width / 2;
130 y -= child_area.height / 2;
137 x += child_area.width / 2;
138 y += child_area.height / 2;
140 x -= (w - child_area.width) / 2;
141 y -= (h - child_area.height) / 2;
148 gtk_rotated_bin_class_init (GtkRotatedBinClass *klass)
150 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
151 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
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;
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),
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;
172 gtk_rotated_bin_init (GtkRotatedBin *bin)
174 gtk_widget_set_has_window (GTK_WIDGET (bin), TRUE);
178 gtk_rotated_bin_new (void)
180 return g_object_new (GTK_TYPE_ROTATED_BIN, NULL);
184 pick_offscreen_child (GdkWindow *offscreen_window,
189 GtkAllocation child_area;
192 if (bin->child && gtk_widget_get_visible (bin->child))
194 to_child (bin, widget_x, widget_y, &x, &y);
196 gtk_widget_get_allocation (bin->child, &child_area);
198 if (x >= 0 && x < child_area.width &&
199 y >= 0 && y < child_area.height)
200 return bin->offscreen_window;
207 offscreen_window_to_parent (GdkWindow *offscreen_window,
214 to_parent (bin, offscreen_x, offscreen_y, parent_x, parent_y);
218 offscreen_window_from_parent (GdkWindow *window,
225 to_child (bin, parent_x, parent_y, offscreen_x, offscreen_y);
229 gtk_rotated_bin_realize (GtkWidget *widget)
231 GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
232 GtkAllocation allocation;
233 GtkStyleContext *context;
235 GdkWindowAttr attributes;
236 gint attributes_mask;
238 GtkRequisition child_requisition;
240 gtk_widget_set_realized (widget, TRUE);
242 gtk_widget_get_allocation (widget, &allocation);
243 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
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)
252 | GDK_POINTER_MOTION_MASK
253 | GDK_BUTTON_PRESS_MASK
254 | GDK_BUTTON_RELEASE_MASK
256 | GDK_ENTER_NOTIFY_MASK
257 | GDK_LEAVE_NOTIFY_MASK;
259 attributes.visual = gtk_widget_get_visual (widget);
260 attributes.wclass = GDK_INPUT_OUTPUT;
262 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
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);
271 attributes.window_type = GDK_WINDOW_OFFSCREEN;
273 child_requisition.width = child_requisition.height = 0;
274 if (bin->child && gtk_widget_get_visible (bin->child))
276 GtkAllocation child_allocation;
278 gtk_widget_get_allocation (bin->child, &child_allocation);
279 attributes.width = child_allocation.width;
280 attributes.height = child_allocation.height;
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);
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);
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);
300 gtk_rotated_bin_unrealize (GtkWidget *widget)
302 GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
304 gdk_window_set_user_data (bin->offscreen_window, NULL);
305 gdk_window_destroy (bin->offscreen_window);
306 bin->offscreen_window = NULL;
308 GTK_WIDGET_CLASS (gtk_rotated_bin_parent_class)->unrealize (widget);
312 gtk_rotated_bin_child_type (GtkContainer *container)
314 GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
319 return GTK_TYPE_WIDGET;
323 gtk_rotated_bin_add (GtkContainer *container,
326 GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
330 gtk_widget_set_parent_window (widget, bin->offscreen_window);
331 gtk_widget_set_parent (widget, GTK_WIDGET (bin));
335 g_warning ("GtkRotatedBin cannot have more than one child\n");
339 gtk_rotated_bin_remove (GtkContainer *container,
342 GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
343 gboolean was_visible;
345 was_visible = gtk_widget_get_visible (widget);
347 if (bin->child == widget)
349 gtk_widget_unparent (widget);
353 if (was_visible && gtk_widget_get_visible (GTK_WIDGET (container)))
354 gtk_widget_queue_resize (GTK_WIDGET (container));
359 gtk_rotated_bin_forall (GtkContainer *container,
360 gboolean include_internals,
361 GtkCallback callback,
362 gpointer callback_data)
364 GtkRotatedBin *bin = GTK_ROTATED_BIN (container);
366 g_return_if_fail (callback != NULL);
369 (*callback) (bin->child, callback_data);
373 gtk_rotated_bin_set_angle (GtkRotatedBin *bin,
376 g_return_if_fail (GTK_IS_ROTATED_BIN (bin));
379 gtk_widget_queue_resize (GTK_WIDGET (bin));
381 gdk_window_geometry_changed (bin->offscreen_window);
385 gtk_rotated_bin_size_request (GtkWidget *widget,
386 GtkRequisition *requisition)
388 GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
389 GtkRequisition child_requisition;
394 child_requisition.width = 0;
395 child_requisition.height = 0;
397 if (bin->child && gtk_widget_get_visible (bin->child))
398 gtk_widget_get_preferred_size ( (bin->child),
399 &child_requisition, NULL);
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;
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;
412 gtk_rotated_bin_get_preferred_width (GtkWidget *widget,
416 GtkRequisition requisition;
418 gtk_rotated_bin_size_request (widget, &requisition);
420 *minimum = *natural = requisition.width;
424 gtk_rotated_bin_get_preferred_height (GtkWidget *widget,
428 GtkRequisition requisition;
430 gtk_rotated_bin_size_request (widget, &requisition);
432 *minimum = *natural = requisition.height;
436 gtk_rotated_bin_size_allocate (GtkWidget *widget,
437 GtkAllocation *allocation)
439 GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
444 gtk_widget_set_allocation (widget, allocation);
446 border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
448 w = allocation->width - border_width * 2;
449 h = allocation->height - border_width * 2;
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,
457 if (bin->child && gtk_widget_get_visible (bin->child))
459 GtkRequisition child_requisition;
460 GtkAllocation child_allocation;
462 s = sin (bin->angle);
463 c = cos (bin->angle);
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;
471 child_allocation.width = h / s;
473 child_allocation.width = w / c;
475 child_allocation.width = MIN ((w - s * child_allocation.height) / c,
476 (h - c * child_allocation.height) / s);
478 if (gtk_widget_get_realized (widget))
479 gdk_window_move_resize (bin->offscreen_window,
482 child_allocation.width,
483 child_allocation.height);
485 child_allocation.x = child_allocation.y = 0;
486 gtk_widget_size_allocate (bin->child, &child_allocation);
491 gtk_rotated_bin_damage (GtkWidget *widget,
492 GdkEventExpose *event)
494 gdk_window_invalidate_rect (gtk_widget_get_window (widget),
501 gtk_rotated_bin_draw (GtkWidget *widget,
504 GtkRotatedBin *bin = GTK_ROTATED_BIN (widget);
509 window = gtk_widget_get_window (widget);
510 if (gtk_cairo_should_draw_window (cr, window))
512 cairo_surface_t *surface;
513 GtkAllocation child_area;
515 if (bin->child && gtk_widget_get_visible (bin->child))
517 surface = gdk_offscreen_window_get_surface (bin->offscreen_window);
518 gtk_widget_get_allocation (bin->child, &child_area);
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;
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);
534 gdk_window_get_width (bin->offscreen_window),
535 gdk_window_get_height (bin->offscreen_window));
538 cairo_set_source_surface (cr, surface, 0, 0);
542 if (gtk_cairo_should_draw_window (cr, bin->offscreen_window))
544 gtk_render_background (gtk_widget_get_style_context (widget),
547 gdk_window_get_width (bin->offscreen_window),
548 gdk_window_get_height (bin->offscreen_window));
551 gtk_container_propagate_draw (GTK_CONTAINER (widget),
562 scale_changed (GtkRange *range,
565 gtk_rotated_bin_set_angle (bin, gtk_range_get_value (range));
568 static GtkWidget *window = NULL;
571 do_offscreen_window (GtkWidget *do_widget)
575 GtkWidget *bin, *vbox, *scale, *button;
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");
583 g_signal_connect (window, "destroy",
584 G_CALLBACK (gtk_widget_destroyed), &window);
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);
590 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
591 scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL,
593 gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
595 button = gtk_button_new_with_label ("A Button");
596 bin = gtk_rotated_bin_new ();
598 g_signal_connect (scale, "value-changed", G_CALLBACK (scale_changed), bin);
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);
606 if (!gtk_widget_get_visible (window))
607 gtk_widget_show_all (window);
610 gtk_widget_destroy (window);