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,
189 GtkClutterViewportPrivate *priv = viewport->priv;
190 GtkAdjustment *h_adjust = priv->h_adjustment;
192 gtk_adjustment_set_page_size (h_adjust, width);
193 gtk_adjustment_set_step_increment (h_adjust, width * 0.1);
194 gtk_adjustment_set_page_increment (h_adjust, width * 0.9);
195 gtk_adjustment_set_lower (h_adjust, 0);
197 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
199 ClutterUnit natural_width;
201 clutter_actor_get_preferred_size (priv->child,
203 &natural_width, NULL);
205 gtk_adjustment_set_upper (h_adjust,
206 MAX (CLUTTER_UNITS_TO_DEVICE (natural_width),
210 gtk_adjustment_set_upper (h_adjust, width);
212 return viewport_reclamp_adjustment (h_adjust);
216 viewport_set_vadjustment_values (GtkClutterViewport *viewport,
219 GtkClutterViewportPrivate *priv = viewport->priv;
220 GtkAdjustment *v_adjust = priv->v_adjustment;
222 height = clutter_actor_get_height (CLUTTER_ACTOR (viewport));
224 gtk_adjustment_set_page_size (v_adjust, height);
225 gtk_adjustment_set_step_increment (v_adjust, height * 0.1);
226 gtk_adjustment_set_page_increment (v_adjust, height * 0.9);
227 gtk_adjustment_set_lower (v_adjust, 0);
229 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
231 ClutterUnit natural_height;
233 clutter_actor_get_preferred_size (priv->child,
235 NULL, &natural_height);
237 gtk_adjustment_set_upper (v_adjust,
238 MAX (CLUTTER_UNITS_TO_DEVICE (natural_height),
242 gtk_adjustment_set_upper (v_adjust, height);
244 return viewport_reclamp_adjustment (v_adjust);
248 disconnect_adjustment (GtkClutterViewport *viewport,
249 GtkOrientation orientation)
251 GtkClutterViewportPrivate *priv = viewport->priv;
252 GtkAdjustment **adj_p;
254 adj_p = (orientation == GTK_ORIENTATION_HORIZONTAL) ? &priv->h_adjustment
255 : &priv->v_adjustment;
259 g_signal_handlers_disconnect_by_func (*adj_p,
260 viewport_adjustment_value_changed,
262 g_object_unref (*adj_p);
268 connect_adjustment (GtkClutterViewport *viewport,
269 GtkOrientation orientation,
270 GtkAdjustment *adjustment)
272 GtkClutterViewportPrivate *priv = viewport->priv;
273 GtkAdjustment **adj_p;
274 gboolean value_changed = FALSE;
277 adj_p = (orientation == GTK_ORIENTATION_HORIZONTAL) ? &priv->h_adjustment
278 : &priv->v_adjustment;
280 if (adjustment && adjustment == *adj_p)
284 adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 0, 0, 0, 0));
286 disconnect_adjustment (viewport, orientation);
287 *adj_p = g_object_ref_sink (adjustment);
289 clutter_actor_get_size (CLUTTER_ACTOR (viewport), &width, &height);
291 if (orientation == GTK_ORIENTATION_HORIZONTAL)
292 value_changed = viewport_set_hadjustment_values (viewport, width);
294 value_changed = viewport_set_vadjustment_values (viewport, height);
296 g_signal_connect (adjustment, "value-changed",
297 G_CALLBACK (viewport_adjustment_value_changed),
300 gtk_adjustment_changed (adjustment);
303 gtk_adjustment_value_changed (adjustment);
305 viewport_adjustment_value_changed (adjustment, viewport);
307 if (orientation == GTK_ORIENTATION_HORIZONTAL)
308 g_object_notify (G_OBJECT (viewport), "hadjustment");
310 g_object_notify (G_OBJECT (viewport), "vadjustment");
314 gtk_clutter_viewport_set_adjustments (GtkClutterScrollable *scrollable,
315 GtkAdjustment *h_adjust,
316 GtkAdjustment *v_adjust)
318 g_object_freeze_notify (G_OBJECT (scrollable));
320 connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
321 GTK_ORIENTATION_HORIZONTAL,
323 connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
324 GTK_ORIENTATION_VERTICAL,
327 g_object_thaw_notify (G_OBJECT (scrollable));
331 gtk_clutter_viewport_get_adjustments (GtkClutterScrollable *scrollable,
332 GtkAdjustment **h_adjust,
333 GtkAdjustment **v_adjust)
335 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (scrollable)->priv;
338 *h_adjust = priv->h_adjustment;
341 *v_adjust = priv->v_adjustment;
345 gtk_clutter_scrollable_iface_init (gpointer g_iface)
347 GtkClutterScrollableIface *iface = g_iface;
349 iface->set_adjustments = gtk_clutter_viewport_set_adjustments;
350 iface->get_adjustments = gtk_clutter_viewport_get_adjustments;
354 gtk_clutter_viewport_set_property (GObject *gobject,
359 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
364 clutter_container_add_actor (CLUTTER_CONTAINER (gobject),
365 g_value_get_object (value));
370 ClutterVertex *v = g_value_get_boxed (value);
374 if (CLUTTER_ACTOR_IS_VISIBLE (gobject))
375 clutter_actor_queue_redraw (CLUTTER_ACTOR (gobject));
379 case PROP_H_ADJUSTMENT:
380 connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
381 GTK_ORIENTATION_HORIZONTAL,
382 g_value_get_object (value));
385 case PROP_V_ADJUSTMENT:
386 connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
387 GTK_ORIENTATION_VERTICAL,
388 g_value_get_object (value));
392 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
398 gtk_clutter_viewport_get_property (GObject *gobject,
403 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
408 g_value_set_object (value, priv->child);
412 g_value_set_boxed (value, &priv->origin);
415 case PROP_H_ADJUSTMENT:
416 g_value_set_object (value, priv->h_adjustment);
419 case PROP_V_ADJUSTMENT:
420 g_value_set_object (value, priv->v_adjustment);
424 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
430 gtk_clutter_viewport_dispose (GObject *gobject)
432 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
436 clutter_actor_destroy (priv->child);
440 disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
441 GTK_ORIENTATION_HORIZONTAL);
442 disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
443 GTK_ORIENTATION_VERTICAL);
445 G_OBJECT_CLASS (gtk_clutter_viewport_parent_class)->dispose (gobject);
449 gtk_clutter_viewport_get_preferred_width (ClutterActor *actor,
450 ClutterUnit for_height,
451 ClutterUnit *min_width_p,
452 ClutterUnit *natural_width_p)
454 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
456 /* we don't have a minimum size */
460 /* if we have a child, we want to be as big as the child
461 * wishes to be; otherwise, we don't have a preferred width
464 clutter_actor_get_preferred_width (priv->child, for_height,
470 *natural_width_p = 0;
475 gtk_clutter_viewport_get_preferred_height (ClutterActor *actor,
476 ClutterUnit for_width,
477 ClutterUnit *min_height_p,
478 ClutterUnit *natural_height_p)
480 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
482 /* we don't have a minimum size */
486 /* if we have a child, we want to be as big as the child
487 * wishes to be; otherwise, we don't have a preferred height
490 clutter_actor_get_preferred_height (priv->child, for_width,
495 if (natural_height_p)
496 *natural_height_p = 0;
501 gtk_clutter_viewport_allocate (ClutterActor *actor,
502 const ClutterActorBox *box,
503 gboolean origin_changed)
505 GtkClutterViewport *viewport = GTK_CLUTTER_VIEWPORT (actor);
506 GtkClutterViewportPrivate *priv = viewport->priv;
507 ClutterActorClass *parent_class;
508 gboolean h_adjustment_value_changed, v_adjustment_value_changed;
511 parent_class = CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class);
512 parent_class->allocate (actor, box, origin_changed);
514 width = CLUTTER_UNITS_TO_DEVICE (box->x2 - box->x1);
515 height = CLUTTER_UNITS_TO_DEVICE (box->y2 - box->y1);
517 h_adjustment_value_changed =
518 viewport_set_hadjustment_values (viewport, width);
519 v_adjustment_value_changed =
520 viewport_set_vadjustment_values (viewport, height);
522 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
524 ClutterActorBox child_allocation = { 0, };
525 ClutterUnit alloc_width, alloc_height;
527 /* a viewport is a boundless actor which can contain a child
528 * without constraints; hence, we give any child exactly the
529 * wanted natural size, no matter how small the viewport
532 clutter_actor_get_preferred_size (priv->child,
534 &alloc_width, &alloc_height);
536 child_allocation.x1 = clutter_actor_get_xu (priv->child);
537 child_allocation.y1 = clutter_actor_get_yu (priv->child);
538 child_allocation.x2 = child_allocation.x1 + alloc_width;
539 child_allocation.y2 = child_allocation.y1 + alloc_height;
541 clutter_actor_allocate (priv->child, &child_allocation, origin_changed);
544 gtk_adjustment_changed (priv->h_adjustment);
545 gtk_adjustment_changed (priv->v_adjustment);
547 if (h_adjustment_value_changed)
548 gtk_adjustment_value_changed (priv->h_adjustment);
550 if (v_adjustment_value_changed)
551 gtk_adjustment_value_changed (priv->v_adjustment);
555 gtk_clutter_viewport_paint (ClutterActor *actor)
557 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
561 /* translate the paint environment by the same amount
562 * defined by the origin value
564 cogl_translatex (CLUTTER_UNITS_TO_FIXED (priv->origin.x) * -1,
565 CLUTTER_UNITS_TO_FIXED (priv->origin.y) * -1,
566 CLUTTER_UNITS_TO_FIXED (priv->origin.z) * -1);
568 /* the child will be painted in the new frame of reference */
569 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
570 clutter_actor_paint (priv->child);
576 gtk_clutter_viewport_pick (ClutterActor *actor,
577 const ClutterColor *pick_color)
579 /* chain up to get the default pick */
580 CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class)->pick (actor, pick_color);
582 /* this will cause the child (if any) to be painted in pick mode */
583 gtk_clutter_viewport_paint (actor);
587 gtk_clutter_viewport_class_init (GtkClutterViewportClass *klass)
589 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
590 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
593 g_type_class_add_private (klass, sizeof (GtkClutterViewportPrivate));
595 gobject_class->set_property = gtk_clutter_viewport_set_property;
596 gobject_class->get_property = gtk_clutter_viewport_get_property;
597 gobject_class->dispose = gtk_clutter_viewport_dispose;
599 actor_class->get_preferred_width = gtk_clutter_viewport_get_preferred_width;
600 actor_class->get_preferred_height = gtk_clutter_viewport_get_preferred_height;
601 actor_class->allocate = gtk_clutter_viewport_allocate;
602 actor_class->paint = gtk_clutter_viewport_paint;
603 actor_class->pick = gtk_clutter_viewport_pick;
606 * GtkClutterViewport:child:
608 * The #ClutterActor inside the viewport.
612 pspec = g_param_spec_object ("child",
614 "The ClutterActor inside the viewport",
617 g_object_class_install_property (gobject_class, PROP_CHILD, pspec);
620 * GtkClutterViewport:origin:
622 * The current origin of the viewport.
626 pspec = g_param_spec_boxed ("origin",
628 "The current origin of the viewport",
631 g_object_class_install_property (gobject_class, PROP_ORIGIN, pspec);
633 /* GtkClutterScrollable properties */
634 g_object_class_override_property (gobject_class, PROP_H_ADJUSTMENT, "hadjustment");
635 g_object_class_override_property (gobject_class, PROP_V_ADJUSTMENT, "vadjustment");
639 gtk_clutter_viewport_init (GtkClutterViewport *viewport)
641 GtkClutterViewportPrivate *priv;
643 viewport->priv = priv = GET_PRIVATE (viewport);
647 * gtk_clutter_viewport_new:
648 * @h_adjust: horizontal adjustment, or %NULL
649 * @v_adjust: vertical adjustment, or %NULL
651 * Creates a new #GtkClutterViewport with the given adjustments.
653 * Return value: the newly created viewport actor
658 gtk_clutter_viewport_new (GtkAdjustment *h_adjust,
659 GtkAdjustment *v_adjust)
661 return g_object_new (GTK_TYPE_CLUTTER_VIEWPORT,
662 "hadjustment", h_adjust,
663 "vadjustment", v_adjust,
668 * gtk_clutter_viewport_get_origin:
669 * @viewport: a #GtkClutterViewport
670 * @x: return location for the X origin in pixels, or %NULL
671 * @y: return location for the Y origin in pixels, or %NULL
672 * @z: return location for the Z origin in pixels, or %NULL
674 * Retrieves the current translation factor ("origin") used when
675 * displaying the child of @viewport.
680 gtk_clutter_viewport_get_origin (GtkClutterViewport *viewport,
685 GtkClutterViewportPrivate *priv;
687 g_return_if_fail (GTK_IS_CLUTTER_VIEWPORT (viewport));
689 priv = viewport->priv;
692 *x = CLUTTER_UNITS_TO_DEVICE (priv->origin.x);
695 *y = CLUTTER_UNITS_TO_DEVICE (priv->origin.y);
698 *z = CLUTTER_UNITS_TO_DEVICE (priv->origin.z);
702 * gtk_clutter_viewport_get_originu:
703 * @viewport: a #GtkClutterViewport
704 * @origin: a #ClutterVertex
706 * Retrieves the current translation factor ("origin") used
707 * when displaying the child of @viewport.
709 * This function is the units-based version of
710 * gtk_clutter_viewport_get_origin().
715 gtk_clutter_viewport_get_originu (GtkClutterViewport *viewport,
716 ClutterVertex *origin)
718 g_return_if_fail (GTK_IS_CLUTTER_VIEWPORT (viewport));
720 *origin = viewport->priv->origin;