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_CLUTTER_TYPE_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_CLUTTER_TYPE_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;
152 new_x = gtk_adjustment_get_value (h_adjust);
153 new_y = 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 gfloat natural_width;
201 clutter_actor_get_preferred_size (priv->child,
203 &natural_width, NULL);
205 gtk_adjustment_set_upper (h_adjust, MAX (natural_width, width));
208 gtk_adjustment_set_upper (h_adjust, width);
210 return viewport_reclamp_adjustment (h_adjust);
214 viewport_set_vadjustment_values (GtkClutterViewport *viewport,
217 GtkClutterViewportPrivate *priv = viewport->priv;
218 GtkAdjustment *v_adjust = priv->v_adjustment;
220 height = clutter_actor_get_height (CLUTTER_ACTOR (viewport));
222 gtk_adjustment_set_page_size (v_adjust, height);
223 gtk_adjustment_set_step_increment (v_adjust, height * 0.1);
224 gtk_adjustment_set_page_increment (v_adjust, height * 0.9);
225 gtk_adjustment_set_lower (v_adjust, 0);
227 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
229 gfloat natural_height;
231 clutter_actor_get_preferred_size (priv->child,
233 NULL, &natural_height);
235 gtk_adjustment_set_upper (v_adjust, MAX (natural_height, height));
238 gtk_adjustment_set_upper (v_adjust, height);
240 return viewport_reclamp_adjustment (v_adjust);
244 disconnect_adjustment (GtkClutterViewport *viewport,
245 GtkOrientation orientation)
247 GtkClutterViewportPrivate *priv = viewport->priv;
248 GtkAdjustment **adj_p;
250 adj_p = (orientation == GTK_ORIENTATION_HORIZONTAL) ? &priv->h_adjustment
251 : &priv->v_adjustment;
255 g_signal_handlers_disconnect_by_func (*adj_p,
256 viewport_adjustment_value_changed,
258 g_object_unref (*adj_p);
264 connect_adjustment (GtkClutterViewport *viewport,
265 GtkOrientation orientation,
266 GtkAdjustment *adjustment)
268 GtkClutterViewportPrivate *priv = viewport->priv;
269 GtkAdjustment **adj_p;
270 gboolean value_changed = FALSE;
271 gfloat width, height;
273 adj_p = (orientation == GTK_ORIENTATION_HORIZONTAL) ? &priv->h_adjustment
274 : &priv->v_adjustment;
276 if (adjustment && adjustment == *adj_p)
280 adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 0, 0, 0, 0));
282 disconnect_adjustment (viewport, orientation);
283 *adj_p = g_object_ref_sink (adjustment);
285 clutter_actor_get_size (CLUTTER_ACTOR (viewport), &width, &height);
287 if (orientation == GTK_ORIENTATION_HORIZONTAL)
288 value_changed = viewport_set_hadjustment_values (viewport, width);
290 value_changed = viewport_set_vadjustment_values (viewport, height);
292 g_signal_connect (adjustment, "value-changed",
293 G_CALLBACK (viewport_adjustment_value_changed),
296 gtk_adjustment_changed (adjustment);
299 gtk_adjustment_value_changed (adjustment);
301 viewport_adjustment_value_changed (adjustment, viewport);
303 if (orientation == GTK_ORIENTATION_HORIZONTAL)
304 g_object_notify (G_OBJECT (viewport), "hadjustment");
306 g_object_notify (G_OBJECT (viewport), "vadjustment");
310 gtk_clutter_viewport_set_adjustments (GtkClutterScrollable *scrollable,
311 GtkAdjustment *h_adjust,
312 GtkAdjustment *v_adjust)
314 g_object_freeze_notify (G_OBJECT (scrollable));
316 connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
317 GTK_ORIENTATION_HORIZONTAL,
319 connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
320 GTK_ORIENTATION_VERTICAL,
323 g_object_thaw_notify (G_OBJECT (scrollable));
327 gtk_clutter_viewport_get_adjustments (GtkClutterScrollable *scrollable,
328 GtkAdjustment **h_adjust,
329 GtkAdjustment **v_adjust)
331 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (scrollable)->priv;
334 *h_adjust = priv->h_adjustment;
337 *v_adjust = priv->v_adjustment;
341 gtk_clutter_scrollable_iface_init (gpointer g_iface)
343 GtkClutterScrollableIface *iface = g_iface;
345 iface->set_adjustments = gtk_clutter_viewport_set_adjustments;
346 iface->get_adjustments = gtk_clutter_viewport_get_adjustments;
350 gtk_clutter_viewport_set_property (GObject *gobject,
355 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
360 clutter_container_add_actor (CLUTTER_CONTAINER (gobject),
361 g_value_get_object (value));
366 ClutterVertex *v = g_value_get_boxed (value);
370 if (CLUTTER_ACTOR_IS_VISIBLE (gobject))
371 clutter_actor_queue_redraw (CLUTTER_ACTOR (gobject));
375 case PROP_H_ADJUSTMENT:
376 connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
377 GTK_ORIENTATION_HORIZONTAL,
378 g_value_get_object (value));
381 case PROP_V_ADJUSTMENT:
382 connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
383 GTK_ORIENTATION_VERTICAL,
384 g_value_get_object (value));
388 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
394 gtk_clutter_viewport_get_property (GObject *gobject,
399 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
404 g_value_set_object (value, priv->child);
408 g_value_set_boxed (value, &priv->origin);
411 case PROP_H_ADJUSTMENT:
412 g_value_set_object (value, priv->h_adjustment);
415 case PROP_V_ADJUSTMENT:
416 g_value_set_object (value, priv->v_adjustment);
420 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
426 gtk_clutter_viewport_dispose (GObject *gobject)
428 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
432 clutter_actor_destroy (priv->child);
436 disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
437 GTK_ORIENTATION_HORIZONTAL);
438 disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
439 GTK_ORIENTATION_VERTICAL);
441 G_OBJECT_CLASS (gtk_clutter_viewport_parent_class)->dispose (gobject);
445 gtk_clutter_viewport_get_preferred_width (ClutterActor *actor,
448 gfloat *natural_width_p)
450 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
452 /* we don't have a minimum size */
456 /* if we have a child, we want to be as big as the child
457 * wishes to be; otherwise, we don't have a preferred width
460 clutter_actor_get_preferred_width (priv->child, for_height,
466 *natural_width_p = 0;
471 gtk_clutter_viewport_get_preferred_height (ClutterActor *actor,
473 gfloat *min_height_p,
474 gfloat *natural_height_p)
476 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
478 /* we don't have a minimum size */
482 /* if we have a child, we want to be as big as the child
483 * wishes to be; otherwise, we don't have a preferred height
486 clutter_actor_get_preferred_height (priv->child, for_width,
491 if (natural_height_p)
492 *natural_height_p = 0;
497 gtk_clutter_viewport_allocate (ClutterActor *actor,
498 const ClutterActorBox *box,
499 ClutterAllocationFlags flags)
501 GtkClutterViewport *viewport = GTK_CLUTTER_VIEWPORT (actor);
502 GtkClutterViewportPrivate *priv = viewport->priv;
503 ClutterActorClass *parent_class;
504 gboolean h_adjustment_value_changed, v_adjustment_value_changed;
505 gfloat width, height;
507 parent_class = CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class);
508 parent_class->allocate (actor, box, flags);
510 width = box->x2 - box->x1;
511 height = box->y2 - box->y1;
513 h_adjustment_value_changed =
514 viewport_set_hadjustment_values (viewport, width);
515 v_adjustment_value_changed =
516 viewport_set_vadjustment_values (viewport, height);
518 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
520 ClutterActorBox child_allocation = { 0, };
521 gfloat alloc_width, alloc_height;
523 /* a viewport is a boundless actor which can contain a child
524 * without constraints; hence, we give any child exactly the
525 * wanted natural size, no matter how small the viewport
528 clutter_actor_get_preferred_size (priv->child,
530 &alloc_width, &alloc_height);
532 child_allocation.x1 = clutter_actor_get_x (priv->child);
533 child_allocation.y1 = clutter_actor_get_y (priv->child);
534 child_allocation.x2 = child_allocation.x1 + alloc_width;
535 child_allocation.y2 = child_allocation.y1 + alloc_height;
537 clutter_actor_allocate (priv->child, &child_allocation, flags);
540 gtk_adjustment_changed (priv->h_adjustment);
541 gtk_adjustment_changed (priv->v_adjustment);
543 if (h_adjustment_value_changed)
544 gtk_adjustment_value_changed (priv->h_adjustment);
546 if (v_adjustment_value_changed)
547 gtk_adjustment_value_changed (priv->v_adjustment);
551 gtk_clutter_viewport_paint (ClutterActor *actor)
553 GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
557 /* translate the paint environment by the same amount
558 * defined by the origin value
560 cogl_translate (priv->origin.x * -1,
562 priv->origin.z * -1);
564 /* the child will be painted in the new frame of reference */
565 if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
566 clutter_actor_paint (priv->child);
572 gtk_clutter_viewport_pick (ClutterActor *actor,
573 const ClutterColor *pick_color)
575 /* chain up to get the default pick */
576 CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class)->pick (actor, pick_color);
578 /* this will cause the child (if any) to be painted in pick mode */
579 gtk_clutter_viewport_paint (actor);
583 gtk_clutter_viewport_class_init (GtkClutterViewportClass *klass)
585 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
586 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
589 g_type_class_add_private (klass, sizeof (GtkClutterViewportPrivate));
591 gobject_class->set_property = gtk_clutter_viewport_set_property;
592 gobject_class->get_property = gtk_clutter_viewport_get_property;
593 gobject_class->dispose = gtk_clutter_viewport_dispose;
595 actor_class->get_preferred_width = gtk_clutter_viewport_get_preferred_width;
596 actor_class->get_preferred_height = gtk_clutter_viewport_get_preferred_height;
597 actor_class->allocate = gtk_clutter_viewport_allocate;
598 actor_class->paint = gtk_clutter_viewport_paint;
599 actor_class->pick = gtk_clutter_viewport_pick;
602 * GtkClutterViewport:child:
604 * The #ClutterActor inside the viewport.
608 pspec = g_param_spec_object ("child",
610 "The ClutterActor inside the viewport",
613 g_object_class_install_property (gobject_class, PROP_CHILD, pspec);
616 * GtkClutterViewport:origin:
618 * The current origin of the viewport.
622 pspec = g_param_spec_boxed ("origin",
624 "The current origin of the viewport",
627 g_object_class_install_property (gobject_class, PROP_ORIGIN, pspec);
629 /* GtkClutterScrollable properties */
630 g_object_class_override_property (gobject_class, PROP_H_ADJUSTMENT, "hadjustment");
631 g_object_class_override_property (gobject_class, PROP_V_ADJUSTMENT, "vadjustment");
635 gtk_clutter_viewport_init (GtkClutterViewport *viewport)
637 GtkClutterViewportPrivate *priv;
639 viewport->priv = priv = GET_PRIVATE (viewport);
643 * gtk_clutter_viewport_new:
644 * @h_adjust: horizontal adjustment, or %NULL
645 * @v_adjust: vertical adjustment, or %NULL
647 * Creates a new #GtkClutterViewport with the given adjustments.
649 * Return value: the newly created viewport actor
654 gtk_clutter_viewport_new (GtkAdjustment *h_adjust,
655 GtkAdjustment *v_adjust)
657 return g_object_new (GTK_CLUTTER_TYPE_VIEWPORT,
658 "hadjustment", h_adjust,
659 "vadjustment", v_adjust,
664 * gtk_clutter_viewport_get_origin:
665 * @viewport: a #GtkClutterViewport
666 * @x: return location for the X origin in pixels, or %NULL
667 * @y: return location for the Y origin in pixels, or %NULL
668 * @z: return location for the Z origin in pixels, or %NULL
670 * Retrieves the current translation factor ("origin") used when
671 * displaying the child of @viewport.
676 gtk_clutter_viewport_get_origin (GtkClutterViewport *viewport,
681 GtkClutterViewportPrivate *priv;
683 g_return_if_fail (GTK_CLUTTER_IS_VIEWPORT (viewport));
685 priv = viewport->priv;