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 1.0
24 #include <cogl/cogl.h>
26 #include "gtk-clutter-scrollable.h"
27 #include "gtk-clutter-util.h"
28 #include "gtk-clutter-viewport.h"
30 /* XXX - GtkAdjustment accessors have been added with GTK+ 2.14,
31 * but I want Clutter-GTK to be future-proof, so let's do this
32 * little #define dance.
34 #if !GTK_CHECK_VERSION (2, 14, 0)
35 #define gtk_adjustment_set_page_size(a,v) ((a)->page_size = (v))
36 #define gtk_adjustment_set_upper(a,v) ((a)->upper = (v))
37 #define gtk_adjustment_set_page_increment(a,v) ((a)->page_increment = (v))
38 #define gtk_adjustment_set_step_increment(a,v) ((a)->step_increment = (v))
39 #define gtk_adjustment_set_lower(a,v) ((a)->lower = (v))
41 #define gtk_adjustment_get_upper(a) ((a)->upper)
42 #define gtk_adjustment_get_page_size(a) ((a)->page_size)
45 #define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_CLUTTER_VIEWPORT, GtkClutterViewportPrivate))
47 #define I_(str) (g_intern_static_string ((str)))
49 static void clutter_container_iface_init (gpointer g_iface);
50 static void gtk_clutter_scrollable_iface_init (gpointer g_iface);
52 G_DEFINE_TYPE_WITH_CODE (GtkClutterViewport,
55 G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
56 clutter_container_iface_init)
57 G_IMPLEMENT_INTERFACE (GTK_TYPE_CLUTTER_SCROLLABLE,
58 gtk_clutter_scrollable_iface_init));
60 struct _GtkClutterViewportPrivate
66 GtkAdjustment *h_adjustment;
67 GtkAdjustment *v_adjustment;
81 gtk_clutter_viewport_add (ClutterContainer *container,
84 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
87 clutter_actor_unparent (priv->child);
89 clutter_actor_set_parent (actor, CLUTTER_ACTOR (container));
92 clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
94 g_signal_emit_by_name (container, "actor-added", actor);
95 g_object_notify (G_OBJECT (container), "child");
99 gtk_clutter_viewport_remove (ClutterContainer *container,
102 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
104 if (G_LIKELY (priv->child == actor))
106 g_object_ref (actor);
108 clutter_actor_unparent (actor);
111 clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
113 g_signal_emit_by_name (container, "actor-removed", actor);
115 g_object_unref (actor);
120 gtk_clutter_viewport_foreach (ClutterContainer *container,
121 ClutterCallback callback,
122 gpointer callback_data)
124 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
126 if (G_LIKELY (priv->child))
127 callback (priv->child, callback_data);
131 clutter_container_iface_init (gpointer g_iface)
133 ClutterContainerIface *iface = g_iface;
135 iface->add = gtk_clutter_viewport_add;
136 iface->remove = gtk_clutter_viewport_remove;
137 iface->foreach = gtk_clutter_viewport_foreach;
141 viewport_adjustment_value_changed (GtkAdjustment *adjustment,
142 GtkClutterViewport *viewport)
144 GtkClutterViewportPrivate *priv = viewport->priv;
146 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
148 GtkAdjustment *h_adjust = priv->h_adjustment;
149 GtkAdjustment *v_adjust = priv->v_adjustment;
150 ClutterUnit new_x, new_y;
152 new_x = CLUTTER_UNITS_FROM_FLOAT (gtk_adjustment_get_value (h_adjust));
153 new_y = CLUTTER_UNITS_FROM_FLOAT (gtk_adjustment_get_value (v_adjust));
155 /* change the origin and queue a relayout */
156 if (new_x != priv->origin.x || new_y != priv->origin.y)
158 priv->origin.x = new_x;
159 priv->origin.y = new_y;
161 clutter_actor_queue_relayout (CLUTTER_ACTOR (viewport));
167 viewport_reclamp_adjustment (GtkAdjustment *adjustment)
169 gdouble value = gtk_adjustment_get_value (adjustment);
172 limit = gtk_adjustment_get_upper (adjustment)
173 - gtk_adjustment_get_page_size (adjustment);
175 value = CLAMP (value, 0, limit);
176 if (value != gtk_adjustment_get_value (adjustment))
178 gtk_adjustment_set_value (adjustment, value);
186 viewport_set_hadjustment_values (GtkClutterViewport *viewport)
188 GtkClutterViewportPrivate *priv = viewport->priv;
189 GtkAdjustment *h_adjust = priv->h_adjustment;
192 width = clutter_actor_get_width (CLUTTER_ACTOR (viewport));
194 gtk_adjustment_set_page_size (h_adjust, width);
195 gtk_adjustment_set_step_increment (h_adjust, width * 0.1);
196 gtk_adjustment_set_page_increment (h_adjust, width * 0.9);
197 gtk_adjustment_set_lower (h_adjust, 0);
199 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
201 ClutterUnit natural_width;
203 clutter_actor_get_preferred_size (priv->child,
205 &natural_width, NULL);
207 gtk_adjustment_set_upper (h_adjust,
208 MAX (CLUTTER_UNITS_TO_DEVICE (natural_width),
212 gtk_adjustment_set_upper (h_adjust, width);
214 return viewport_reclamp_adjustment (h_adjust);
218 viewport_set_vadjustment_values (GtkClutterViewport *viewport)
220 GtkClutterViewportPrivate *priv = viewport->priv;
221 GtkAdjustment *v_adjust = priv->v_adjustment;
224 height = clutter_actor_get_height (CLUTTER_ACTOR (viewport));
226 gtk_adjustment_set_page_size (v_adjust, height);
227 gtk_adjustment_set_step_increment (v_adjust, height * 0.1);
228 gtk_adjustment_set_page_increment (v_adjust, height * 0.9);
229 gtk_adjustment_set_lower (v_adjust, 0);
231 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
233 ClutterUnit natural_height;
235 clutter_actor_get_preferred_size (priv->child,
237 NULL, &natural_height);
239 gtk_adjustment_set_upper (v_adjust,
240 MAX (CLUTTER_UNITS_TO_DEVICE (natural_height),
244 gtk_adjustment_set_upper (v_adjust, height);
246 return viewport_reclamp_adjustment (v_adjust);
250 disconnect_adjustment (GtkClutterViewport *viewport,
251 GtkOrientation orientation)
253 GtkClutterViewportPrivate *priv = viewport->priv;
254 GtkAdjustment **adj_p;
256 adj_p = (orientation == GTK_ORIENTATION_HORIZONTAL) ? &priv->h_adjustment
257 : &priv->v_adjustment;
261 g_signal_handlers_disconnect_by_func (*adj_p,
262 viewport_adjustment_value_changed,
264 g_object_unref (*adj_p);
270 connect_adjustment (GtkClutterViewport *viewport,
271 GtkOrientation orientation,
272 GtkAdjustment *adjustment)
274 GtkClutterViewportPrivate *priv = viewport->priv;
275 GtkAdjustment **adj_p;
276 gboolean value_changed = FALSE;
278 adj_p = (orientation == GTK_ORIENTATION_HORIZONTAL) ? &priv->h_adjustment
279 : &priv->v_adjustment;
281 if (adjustment && adjustment == *adj_p)
285 adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 0, 0, 0, 0));
287 disconnect_adjustment (viewport, orientation);
288 *adj_p = g_object_ref_sink (adjustment);
290 if (orientation == GTK_ORIENTATION_HORIZONTAL)
291 value_changed = viewport_set_hadjustment_values (viewport);
293 value_changed = viewport_set_vadjustment_values (viewport);
295 g_signal_connect (adjustment, "value-changed",
296 G_CALLBACK (viewport_adjustment_value_changed),
299 gtk_adjustment_changed (adjustment);
302 gtk_adjustment_value_changed (adjustment);
304 viewport_adjustment_value_changed (adjustment, viewport);
306 if (orientation == GTK_ORIENTATION_HORIZONTAL)
307 g_object_notify (G_OBJECT (viewport), "hadjustment");
309 g_object_notify (G_OBJECT (viewport), "vadjustment");
313 gtk_clutter_viewport_set_adjustments (GtkClutterScrollable *scrollable,
314 GtkAdjustment *h_adjust,
315 GtkAdjustment *v_adjust)
317 g_object_freeze_notify (G_OBJECT (scrollable));
319 connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
320 GTK_ORIENTATION_HORIZONTAL,
322 connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
323 GTK_ORIENTATION_VERTICAL,
326 g_object_thaw_notify (G_OBJECT (scrollable));
330 gtk_clutter_viewport_get_adjustments (GtkClutterScrollable *scrollable,
331 GtkAdjustment **h_adjust,
332 GtkAdjustment **v_adjust)
334 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (scrollable)->priv;
337 *h_adjust = priv->h_adjustment;
340 *v_adjust = priv->v_adjustment;
344 gtk_clutter_scrollable_iface_init (gpointer g_iface)
346 GtkClutterScrollableIface *iface = g_iface;
348 iface->set_adjustments = gtk_clutter_viewport_set_adjustments;
349 iface->get_adjustments = gtk_clutter_viewport_get_adjustments;
353 gtk_clutter_viewport_set_property (GObject *gobject,
358 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
363 clutter_container_add_actor (CLUTTER_CONTAINER (gobject),
364 g_value_get_object (value));
369 ClutterVertex *v = g_value_get_boxed (value);
373 if (CLUTTER_ACTOR_IS_VISIBLE (gobject))
374 clutter_actor_queue_redraw (CLUTTER_ACTOR (gobject));
378 case PROP_H_ADJUSTMENT:
379 connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
380 GTK_ORIENTATION_HORIZONTAL,
381 g_value_get_object (value));
384 case PROP_V_ADJUSTMENT:
385 connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
386 GTK_ORIENTATION_VERTICAL,
387 g_value_get_object (value));
391 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
397 gtk_clutter_viewport_get_property (GObject *gobject,
402 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
407 g_value_set_object (value, priv->child);
411 g_value_set_boxed (value, &priv->origin);
414 case PROP_H_ADJUSTMENT:
415 g_value_set_object (value, priv->h_adjustment);
418 case PROP_V_ADJUSTMENT:
419 g_value_set_object (value, priv->v_adjustment);
423 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
429 gtk_clutter_viewport_dispose (GObject *gobject)
431 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
435 clutter_actor_destroy (priv->child);
439 disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
440 GTK_ORIENTATION_HORIZONTAL);
441 disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
442 GTK_ORIENTATION_VERTICAL);
444 G_OBJECT_CLASS (gtk_clutter_viewport_parent_class)->dispose (gobject);
448 gtk_clutter_viewport_get_preferred_width (ClutterActor *actor,
449 ClutterUnit for_height,
450 ClutterUnit *min_width_p,
451 ClutterUnit *natural_width_p)
453 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
455 /* if we have a child, we want to be as big as the child
456 * wishes to be; otherwise, we don't have a preferred width
459 clutter_actor_get_preferred_width (priv->child, for_height,
468 *natural_width_p = 0;
473 gtk_clutter_viewport_get_preferred_height (ClutterActor *actor,
474 ClutterUnit for_width,
475 ClutterUnit *min_height_p,
476 ClutterUnit *natural_height_p)
478 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
480 /* if we have a child, we want to be as big as the child
481 * wishes to be; otherwise, we don't have a preferred height
484 clutter_actor_get_preferred_height (priv->child, for_width,
492 if (natural_height_p)
493 *natural_height_p = 0;
498 gtk_clutter_viewport_allocate (ClutterActor *actor,
499 const ClutterActorBox *box,
500 gboolean origin_changed)
502 GtkClutterViewport *viewport = GTK_CLUTTER_VIEWPORT (actor);
503 GtkClutterViewportPrivate *priv = viewport->priv;
504 ClutterActorClass *parent_class;
505 gboolean h_adjustment_value_changed, v_adjustment_value_changed;
507 parent_class = CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class);
508 parent_class->allocate (actor, box, origin_changed);
510 h_adjustment_value_changed = viewport_set_hadjustment_values (viewport);
511 v_adjustment_value_changed = viewport_set_vadjustment_values (viewport);
515 ClutterActorBox child_allocation = { 0, };
516 ClutterUnit alloc_width, alloc_height;
518 /* a viewport is a boundless actor which can contain a child
519 * without constraints; hence, we give any child exactly the
520 * wanted natural size, no matter how small the viewport
523 clutter_actor_get_preferred_size (priv->child,
525 &alloc_width, &alloc_height);
527 child_allocation.x1 = clutter_actor_get_xu (priv->child);
528 child_allocation.y1 = clutter_actor_get_yu (priv->child);
529 child_allocation.x2 = child_allocation.x1 + alloc_width;
530 child_allocation.y2 = child_allocation.y1 + alloc_height;
532 clutter_actor_allocate (priv->child, &child_allocation, origin_changed);
535 gtk_adjustment_changed (priv->h_adjustment);
536 gtk_adjustment_changed (priv->v_adjustment);
538 if (h_adjustment_value_changed)
539 gtk_adjustment_value_changed (priv->h_adjustment);
541 if (v_adjustment_value_changed)
542 gtk_adjustment_value_changed (priv->v_adjustment);
546 gtk_clutter_viewport_paint (ClutterActor *actor)
548 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
552 /* translate the paint environment by the same amount
553 * defined by the origin value
555 cogl_translatex (CLUTTER_UNITS_TO_FIXED (priv->origin.x) * -1,
556 CLUTTER_UNITS_TO_FIXED (priv->origin.y) * -1,
557 CLUTTER_UNITS_TO_FIXED (priv->origin.z) * -1);
559 /* the child will be painted in the new frame of reference */
560 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
561 clutter_actor_paint (priv->child);
567 gtk_clutter_viewport_pick (ClutterActor *actor,
568 const ClutterColor *pick_color)
570 /* chain up to get the default pick */
571 CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class)->pick (actor, pick_color);
573 /* this will cause the child (if any) to be painted in pick mode */
574 gtk_clutter_viewport_paint (actor);
578 gtk_clutter_viewport_class_init (GtkClutterViewportClass *klass)
580 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
581 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
584 g_type_class_add_private (klass, sizeof (GtkClutterViewportPrivate));
586 gobject_class->set_property = gtk_clutter_viewport_set_property;
587 gobject_class->get_property = gtk_clutter_viewport_get_property;
588 gobject_class->dispose = gtk_clutter_viewport_dispose;
590 actor_class->get_preferred_width = gtk_clutter_viewport_get_preferred_width;
591 actor_class->get_preferred_height = gtk_clutter_viewport_get_preferred_height;
592 actor_class->allocate = gtk_clutter_viewport_allocate;
593 actor_class->paint = gtk_clutter_viewport_paint;
594 actor_class->pick = gtk_clutter_viewport_pick;
597 * GtkClutterViewport:child:
599 * The #ClutterActor inside the viewport.
603 pspec = g_param_spec_object ("child",
605 "The ClutterActor inside the viewport",
608 g_object_class_install_property (gobject_class, PROP_CHILD, pspec);
611 * GtkClutterViewport:origin:
613 * The current origin of the viewport.
617 pspec = g_param_spec_boxed ("origin",
619 "The current origin of the viewport",
622 g_object_class_install_property (gobject_class, PROP_ORIGIN, pspec);
624 /* GtkClutterScrollable properties */
625 g_object_class_override_property (gobject_class, PROP_H_ADJUSTMENT, "hadjustment");
626 g_object_class_override_property (gobject_class, PROP_V_ADJUSTMENT, "vadjustment");
630 gtk_clutter_viewport_init (GtkClutterViewport *viewport)
632 GtkClutterViewportPrivate *priv;
634 viewport->priv = priv = GET_PRIVATE (viewport);
638 * gtk_clutter_viewport_new:
639 * @h_adjust: horizontal adjustment, or %NULL
640 * @v_adjust: vertical adjustment, or %NULL
642 * Creates a new #GtkClutterViewport with the given adjustments.
644 * Return value: the newly created viewport actor
649 gtk_clutter_viewport_new (GtkAdjustment *h_adjust,
650 GtkAdjustment *v_adjust)
652 return g_object_new (GTK_TYPE_CLUTTER_VIEWPORT,
653 "hadjustment", h_adjust,
654 "vadjustment", v_adjust,
659 * gtk_clutter_viewport_get_origin:
660 * @viewport: a #GtkClutterViewport
661 * @x: return location for the X origin in pixels, or %NULL
662 * @y: return location for the Y origin in pixels, or %NULL
663 * @z: return location for the Z origin in pixels, or %NULL
665 * Retrieves the current translation factor ("origin") used when
666 * displaying the child of @viewport.
671 gtk_clutter_viewport_get_origin (GtkClutterViewport *viewport,
676 GtkClutterViewportPrivate *priv;
678 g_return_if_fail (GTK_IS_CLUTTER_VIEWPORT (viewport));
680 priv = viewport->priv;
683 *x = CLUTTER_UNITS_TO_DEVICE (priv->origin.x);
686 *y = CLUTTER_UNITS_TO_DEVICE (priv->origin.y);
689 *z = CLUTTER_UNITS_TO_DEVICE (priv->origin.z);
693 * gtk_clutter_viewport_get_originu:
694 * @viewport: a #GtkClutterViewport
695 * @origin: a #ClutterVertex
697 * Retrieves the current translation factor ("origin") used
698 * when displaying the child of @viewport.
700 * This function is the units-based version of
701 * gtk_clutter_viewport_get_origin().
706 gtk_clutter_viewport_get_originu (GtkClutterViewport *viewport,
707 ClutterVertex *origin)
709 g_return_if_fail (GTK_IS_CLUTTER_VIEWPORT (viewport));
711 *origin = viewport->priv->origin;