2 * SECTION:gtk-clutter-viewport
3 * @short_description: A scrollable actor
5 * #GtkClutterViewport is a scrollable actor that can contain a single
6 * #ClutterActor. Using two #GtkAdjustment<!-- -->s it is possible to
7 * control the visible area of the child actor if the size of the viewport
8 * is smaller than the size of the child.
10 * The #GtkAdjustment<!-- -->s used to control the horizontal and
11 * vertical scrolling can be attached to a #GtkScrollbar subclass,
12 * like #GtkHScrollbar or #GtkVScrollbar.
14 * The #GtkClutterViewport can be used inside any #ClutterContainer
17 * #GtkClutterViewport is available since Clutter-GTK 0.10
24 #include <cogl/cogl.h>
26 #include "gtk-clutter-viewport.h"
28 #include "gtk-clutter-scrollable.h"
29 #include "gtk-clutter-util.h"
30 #include "gtk-clutter-zoomable.h"
32 /* XXX - GtkAdjustment accessors have been added with GTK+ 2.14,
33 * but I want Clutter-GTK to be future-proof, so let's do this
34 * little #define dance.
36 #if !GTK_CHECK_VERSION (2, 14, 0)
37 #define gtk_adjustment_set_page_size(a,v) ((a)->page_size = (v))
38 #define gtk_adjustment_set_upper(a,v) ((a)->upper = (v))
39 #define gtk_adjustment_set_page_increment(a,v) ((a)->page_increment = (v))
40 #define gtk_adjustment_set_step_increment(a,v) ((a)->step_increment = (v))
41 #define gtk_adjustment_set_lower(a,v) ((a)->lower = (v))
43 #define gtk_adjustment_get_lower(a) ((a)->lower)
44 #define gtk_adjustment_get_upper(a) ((a)->upper)
45 #define gtk_adjustment_get_page_size(a) ((a)->page_size)
48 #define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_CLUTTER_TYPE_VIEWPORT, GtkClutterViewportPrivate))
50 #define I_(str) (g_intern_static_string ((str)))
58 static void clutter_container_iface_init (gpointer g_iface);
59 static void gtk_clutter_scrollable_iface_init (gpointer g_iface);
60 static void gtk_clutter_zoomable_iface_init (gpointer g_iface);
62 G_DEFINE_TYPE_WITH_CODE (GtkClutterViewport,
65 G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
66 clutter_container_iface_init)
67 G_IMPLEMENT_INTERFACE (GTK_CLUTTER_TYPE_SCROLLABLE,
68 gtk_clutter_scrollable_iface_init)
69 G_IMPLEMENT_INTERFACE (GTK_CLUTTER_TYPE_ZOOMABLE,
70 gtk_clutter_zoomable_iface_init));
72 struct _GtkClutterViewportPrivate
78 GtkAdjustment *x_adjustment;
79 GtkAdjustment *y_adjustment;
80 GtkAdjustment *z_adjustment;
96 gtk_clutter_viewport_add (ClutterContainer *container,
99 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
102 clutter_actor_unparent (priv->child);
104 clutter_actor_set_parent (actor, CLUTTER_ACTOR (container));
107 clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
109 g_signal_emit_by_name (container, "actor-added", actor);
110 g_object_notify (G_OBJECT (container), "child");
114 gtk_clutter_viewport_remove (ClutterContainer *container,
117 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
119 if (G_LIKELY (priv->child == actor))
121 g_object_ref (actor);
123 clutter_actor_unparent (actor);
126 clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
128 g_signal_emit_by_name (container, "actor-removed", actor);
130 g_object_unref (actor);
135 gtk_clutter_viewport_foreach (ClutterContainer *container,
136 ClutterCallback callback,
137 gpointer callback_data)
139 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
141 if (G_LIKELY (priv->child))
142 callback (priv->child, callback_data);
146 clutter_container_iface_init (gpointer g_iface)
148 ClutterContainerIface *iface = g_iface;
150 iface->add = gtk_clutter_viewport_add;
151 iface->remove = gtk_clutter_viewport_remove;
152 iface->foreach = gtk_clutter_viewport_foreach;
156 viewport_adjustment_value_changed (GtkAdjustment *adjustment,
157 GtkClutterViewport *viewport)
159 GtkClutterViewportPrivate *priv = viewport->priv;
161 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
163 GtkAdjustment *x_adjust = priv->x_adjustment;
164 GtkAdjustment *y_adjust = priv->y_adjustment;
165 GtkAdjustment *z_adjust = priv->z_adjustment;
166 gfloat new_x, new_y, new_z;
168 new_x = gtk_adjustment_get_value (x_adjust);
169 new_y = gtk_adjustment_get_value (y_adjust);
170 new_z = gtk_adjustment_get_value (z_adjust);
172 /* change the origin and queue a relayout */
173 if (new_x != priv->origin.x ||
174 new_y != priv->origin.y ||
175 new_z != priv->origin.z)
177 priv->origin.x = new_x;
178 priv->origin.y = new_y;
179 priv->origin.z = new_z;
181 clutter_actor_queue_relayout (CLUTTER_ACTOR (viewport));
183 g_object_notify (G_OBJECT (viewport), "origin");
189 viewport_reclamp_adjustment (GtkAdjustment *adjustment)
191 gdouble value = gtk_adjustment_get_value (adjustment);
194 limit = gtk_adjustment_get_upper (adjustment)
195 - gtk_adjustment_get_page_size (adjustment);
197 value = CLAMP (value, 0, limit);
198 if (value != gtk_adjustment_get_value (adjustment))
200 gtk_adjustment_set_value (adjustment, value);
208 viewport_set_hadjustment_values (GtkClutterViewport *viewport,
211 GtkClutterViewportPrivate *priv = viewport->priv;
212 GtkAdjustment *h_adjust = priv->x_adjustment;
215 clutter_actor_get_preferred_width (CLUTTER_ACTOR (viewport), -1,
219 gtk_adjustment_set_page_size (h_adjust, width);
220 gtk_adjustment_set_step_increment (h_adjust, width * 0.1);
221 gtk_adjustment_set_page_increment (h_adjust, width * 0.9);
222 gtk_adjustment_set_lower (h_adjust, 0);
224 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
226 gfloat natural_width;
228 clutter_actor_get_preferred_size (priv->child,
230 &natural_width, NULL);
232 gtk_adjustment_set_upper (h_adjust, MAX (natural_width, width));
235 gtk_adjustment_set_upper (h_adjust, width);
237 return viewport_reclamp_adjustment (h_adjust);
241 viewport_set_vadjustment_values (GtkClutterViewport *viewport,
244 GtkClutterViewportPrivate *priv = viewport->priv;
245 GtkAdjustment *v_adjust = priv->y_adjustment;
248 clutter_actor_get_preferred_height (CLUTTER_ACTOR (viewport), -1,
252 gtk_adjustment_set_page_size (v_adjust, height);
253 gtk_adjustment_set_step_increment (v_adjust, height * 0.1);
254 gtk_adjustment_set_page_increment (v_adjust, height * 0.9);
255 gtk_adjustment_set_lower (v_adjust, 0);
257 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
259 gfloat natural_height;
261 clutter_actor_get_preferred_size (priv->child,
263 NULL, &natural_height);
265 gtk_adjustment_set_upper (v_adjust, MAX (natural_height, height));
268 gtk_adjustment_set_upper (v_adjust, height);
270 return viewport_reclamp_adjustment (v_adjust);
274 viewport_set_zadjustment_values (GtkClutterViewport *viewport,
278 GtkClutterViewportPrivate *priv = viewport->priv;
279 GtkAdjustment *z_adjust = priv->z_adjustment;
283 clutter_actor_get_preferred_width (CLUTTER_ACTOR (viewport), -1,
288 clutter_actor_get_preferred_height (CLUTTER_ACTOR (viewport), -1,
292 depth = clutter_actor_get_depth (CLUTTER_ACTOR (viewport));
294 gtk_adjustment_set_page_size (z_adjust, 0.0);
295 gtk_adjustment_set_step_increment (z_adjust, MIN (width, height) * 0.1);
296 gtk_adjustment_set_page_increment (z_adjust, MIN (width, height) * 0.9);
297 gtk_adjustment_set_lower (z_adjust, 0.0);
298 gtk_adjustment_set_upper (z_adjust, MAX (width, height));
301 g_debug ("%s: zadjustment: { %.2f lower, %.2f page, %.2f upper }",
303 gtk_adjustment_get_lower (z_adjust),
304 gtk_adjustment_get_page_size (z_adjust),
305 gtk_adjustment_get_upper (z_adjust));
308 return viewport_reclamp_adjustment (z_adjust);
312 disconnect_adjustment (GtkClutterViewport *viewport,
315 GtkClutterViewportPrivate *priv = viewport->priv;
316 GtkAdjustment **adj_p = NULL;
320 case VIEWPORT_X_AXIS:
321 adj_p = &priv->x_adjustment;
324 case VIEWPORT_Y_AXIS:
325 adj_p = &priv->y_adjustment;
328 case VIEWPORT_Z_AXIS:
329 adj_p = &priv->z_adjustment;
333 g_assert_not_reached ();
339 g_signal_handlers_disconnect_by_func (*adj_p,
340 viewport_adjustment_value_changed,
342 g_object_unref (*adj_p);
348 connect_adjustment (GtkClutterViewport *viewport,
350 GtkAdjustment *adjustment)
352 GtkClutterViewportPrivate *priv = viewport->priv;
353 GtkAdjustment **adj_p = NULL;
354 gboolean value_changed = FALSE;
355 gfloat width, height;
359 case VIEWPORT_X_AXIS:
360 adj_p = &priv->x_adjustment;
363 case VIEWPORT_Y_AXIS:
364 adj_p = &priv->y_adjustment;
367 case VIEWPORT_Z_AXIS:
368 adj_p = &priv->z_adjustment;
372 g_assert_not_reached ();
376 if (adjustment && adjustment == *adj_p)
380 adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 0, 0, 0, 0));
382 disconnect_adjustment (viewport, axis);
383 *adj_p = g_object_ref_sink (adjustment);
385 clutter_actor_get_size (CLUTTER_ACTOR (viewport), &width, &height);
389 case VIEWPORT_X_AXIS:
390 value_changed = viewport_set_hadjustment_values (viewport, width);
393 case VIEWPORT_Y_AXIS:
394 value_changed = viewport_set_vadjustment_values (viewport, height);
397 case VIEWPORT_Z_AXIS:
398 value_changed = viewport_set_zadjustment_values (viewport,
404 g_assert_not_reached ();
408 g_signal_connect (adjustment, "value-changed",
409 G_CALLBACK (viewport_adjustment_value_changed),
412 gtk_adjustment_changed (adjustment);
415 gtk_adjustment_value_changed (adjustment);
417 viewport_adjustment_value_changed (adjustment, viewport);
421 case VIEWPORT_X_AXIS:
422 g_object_notify (G_OBJECT (viewport), "hadjustment");
425 case VIEWPORT_Y_AXIS:
426 g_object_notify (G_OBJECT (viewport), "vadjustment");
429 case VIEWPORT_Z_AXIS:
430 g_object_notify (G_OBJECT (viewport), "zadjustment");
434 g_assert_not_reached ();
440 scrollable_set_adjustments (GtkClutterScrollable *scrollable,
441 GtkAdjustment *h_adjust,
442 GtkAdjustment *v_adjust)
444 g_object_freeze_notify (G_OBJECT (scrollable));
446 connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
449 connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
453 g_object_thaw_notify (G_OBJECT (scrollable));
457 scrollable_get_adjustments (GtkClutterScrollable *scrollable,
458 GtkAdjustment **h_adjust,
459 GtkAdjustment **v_adjust)
461 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (scrollable)->priv;
464 *h_adjust = priv->x_adjustment;
467 *v_adjust = priv->y_adjustment;
471 gtk_clutter_scrollable_iface_init (gpointer g_iface)
473 GtkClutterScrollableIface *iface = g_iface;
475 iface->set_adjustments = scrollable_set_adjustments;
476 iface->get_adjustments = scrollable_get_adjustments;
480 zoomable_set_adjustment (GtkClutterZoomable *zoomable,
481 GtkAdjustment *adjust)
483 g_object_freeze_notify (G_OBJECT (zoomable));
485 connect_adjustment (GTK_CLUTTER_VIEWPORT (zoomable),
489 g_object_thaw_notify (G_OBJECT (zoomable));
492 static GtkAdjustment *
493 zoomable_get_adjustment (GtkClutterZoomable *zoomable)
495 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (zoomable)->priv;
497 return priv->z_adjustment;
501 gtk_clutter_zoomable_iface_init (gpointer g_iface)
503 GtkClutterZoomableIface *iface = g_iface;
505 iface->set_adjustment = zoomable_set_adjustment;
506 iface->get_adjustment = zoomable_get_adjustment;
510 gtk_clutter_viewport_set_property (GObject *gobject,
515 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
520 clutter_container_add_actor (CLUTTER_CONTAINER (gobject),
521 g_value_get_object (value));
526 ClutterVertex *v = g_value_get_boxed (value);
530 if (CLUTTER_ACTOR_IS_VISIBLE (gobject))
531 clutter_actor_queue_redraw (CLUTTER_ACTOR (gobject));
535 case PROP_H_ADJUSTMENT:
536 connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
538 g_value_get_object (value));
541 case PROP_V_ADJUSTMENT:
542 connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
544 g_value_get_object (value));
547 case PROP_Z_ADJUSTMENT:
548 connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
550 g_value_get_object (value));
554 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
560 gtk_clutter_viewport_get_property (GObject *gobject,
565 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
570 g_value_set_object (value, priv->child);
574 g_value_set_boxed (value, &priv->origin);
577 case PROP_H_ADJUSTMENT:
578 g_value_set_object (value, priv->x_adjustment);
581 case PROP_V_ADJUSTMENT:
582 g_value_set_object (value, priv->y_adjustment);
585 case PROP_Z_ADJUSTMENT:
586 g_value_set_object (value, priv->z_adjustment);
590 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
596 gtk_clutter_viewport_dispose (GObject *gobject)
598 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
602 clutter_actor_destroy (priv->child);
606 disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
608 disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
610 disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
613 G_OBJECT_CLASS (gtk_clutter_viewport_parent_class)->dispose (gobject);
617 gtk_clutter_viewport_get_preferred_width (ClutterActor *actor,
620 gfloat *natural_width_p)
622 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
624 /* we don't have a minimum size */
628 /* if we have a child, we want to be as big as the child
629 * wishes to be; otherwise, we don't have a preferred width
632 clutter_actor_get_preferred_width (priv->child, for_height,
638 *natural_width_p = 0;
643 gtk_clutter_viewport_get_preferred_height (ClutterActor *actor,
645 gfloat *min_height_p,
646 gfloat *natural_height_p)
648 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
650 /* we don't have a minimum size */
654 /* if we have a child, we want to be as big as the child
655 * wishes to be; otherwise, we don't have a preferred height
658 clutter_actor_get_preferred_height (priv->child, for_width,
663 if (natural_height_p)
664 *natural_height_p = 0;
669 gtk_clutter_viewport_allocate (ClutterActor *actor,
670 const ClutterActorBox *box,
671 ClutterAllocationFlags flags)
673 GtkClutterViewport *viewport = GTK_CLUTTER_VIEWPORT (actor);
674 GtkClutterViewportPrivate *priv = viewport->priv;
675 ClutterActorClass *parent_class;
676 gboolean x_adjustment_value_changed;
677 gboolean y_adjustment_value_changed;
678 gboolean z_adjustment_value_changed;
679 gfloat width, height;
681 parent_class = CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class);
682 parent_class->allocate (actor, box, flags);
684 width = box->x2 - box->x1;
685 height = box->y2 - box->y1;
687 x_adjustment_value_changed =
688 viewport_set_hadjustment_values (viewport, width);
689 y_adjustment_value_changed =
690 viewport_set_vadjustment_values (viewport, height);
691 z_adjustment_value_changed =
692 viewport_set_zadjustment_values (viewport, width, height);
694 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
696 ClutterActorBox child_allocation = { 0, };
697 gfloat alloc_width, alloc_height;
699 /* a viewport is a boundless actor which can contain a child
700 * without constraints; hence, we give any child exactly the
701 * wanted natural size, no matter how small the viewport
704 clutter_actor_get_preferred_size (priv->child,
709 child_allocation.x1 = 0;
710 child_allocation.y1 = 0;
711 child_allocation.x2 = child_allocation.x1 + alloc_width;
712 child_allocation.y2 = child_allocation.y1 + alloc_height;
714 clutter_actor_allocate (priv->child, &child_allocation, flags);
717 gtk_adjustment_changed (priv->x_adjustment);
718 gtk_adjustment_changed (priv->y_adjustment);
719 gtk_adjustment_changed (priv->z_adjustment);
721 if (x_adjustment_value_changed)
722 gtk_adjustment_value_changed (priv->x_adjustment);
724 if (y_adjustment_value_changed)
725 gtk_adjustment_value_changed (priv->y_adjustment);
727 if (z_adjustment_value_changed)
728 gtk_adjustment_value_changed (priv->z_adjustment);
732 gtk_clutter_viewport_apply_transform (ClutterActor *actor,
735 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
736 ClutterActorClass *parent_class;
738 parent_class = CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class);
739 parent_class->apply_transform (actor, matrix);
741 cogl_matrix_translate (matrix,
744 priv->origin.z * -1);
748 gtk_clutter_viewport_paint (ClutterActor *actor)
750 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
753 clutter_actor_paint (priv->child);
757 gtk_clutter_viewport_pick (ClutterActor *actor,
758 const ClutterColor *pick_color)
760 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
761 ClutterActorClass *parent_class;
763 parent_class = CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class);
764 parent_class->pick (actor, pick_color);
767 clutter_actor_paint (priv->child);
771 gtk_clutter_viewport_class_init (GtkClutterViewportClass *klass)
773 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
774 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
777 g_type_class_add_private (klass, sizeof (GtkClutterViewportPrivate));
779 gobject_class->set_property = gtk_clutter_viewport_set_property;
780 gobject_class->get_property = gtk_clutter_viewport_get_property;
781 gobject_class->dispose = gtk_clutter_viewport_dispose;
783 actor_class->get_preferred_width = gtk_clutter_viewport_get_preferred_width;
784 actor_class->get_preferred_height = gtk_clutter_viewport_get_preferred_height;
785 actor_class->allocate = gtk_clutter_viewport_allocate;
786 actor_class->apply_transform = gtk_clutter_viewport_apply_transform;
787 actor_class->paint = gtk_clutter_viewport_paint;
788 actor_class->pick = gtk_clutter_viewport_pick;
791 * GtkClutterViewport:child:
793 * The #ClutterActor inside the viewport.
797 pspec = g_param_spec_object ("child",
799 "The ClutterActor inside the viewport",
802 g_object_class_install_property (gobject_class, PROP_CHILD, pspec);
805 * GtkClutterViewport:origin:
807 * The current origin of the viewport. You should use the
808 * vertex to convert event coordinates for the child of the
813 pspec = g_param_spec_boxed ("origin",
815 "The current origin of the viewport",
818 g_object_class_install_property (gobject_class, PROP_ORIGIN, pspec);
820 /* GtkClutterScrollable properties */
821 g_object_class_override_property (gobject_class,
824 g_object_class_override_property (gobject_class,
828 /* GtkClutterZoomable properties */
829 g_object_class_override_property (gobject_class,
835 gtk_clutter_viewport_init (GtkClutterViewport *viewport)
837 GtkClutterViewportPrivate *priv;
839 viewport->priv = priv = GET_PRIVATE (viewport);
843 * gtk_clutter_viewport_new:
844 * @h_adjust: horizontal adjustment, or %NULL
845 * @v_adjust: vertical adjustment, or %NULL
846 * @z_adjust: zoom adjustment, or %NULL
848 * Creates a new #GtkClutterViewport with the given adjustments.
850 * Return value: the newly created viewport actor
855 gtk_clutter_viewport_new (GtkAdjustment *h_adjust,
856 GtkAdjustment *v_adjust,
857 GtkAdjustment *z_adjust)
859 return g_object_new (GTK_CLUTTER_TYPE_VIEWPORT,
860 "hadjustment", h_adjust,
861 "vadjustment", v_adjust,
862 "zadjustment", z_adjust,
867 * gtk_clutter_viewport_get_origin:
868 * @viewport: a #GtkClutterViewport
869 * @x: return location for the X origin in pixels, or %NULL
870 * @y: return location for the Y origin in pixels, or %NULL
871 * @z: return location for the Z origin in pixels, or %NULL
873 * Retrieves the current translation factor ("origin") used when
874 * displaying the child of @viewport.
879 gtk_clutter_viewport_get_origin (GtkClutterViewport *viewport,
884 GtkClutterViewportPrivate *priv;
886 g_return_if_fail (GTK_CLUTTER_IS_VIEWPORT (viewport));
888 priv = viewport->priv;