2008-10-20 Emmanuele Bassi <ebassi@linux.intel.com>
[clutter-gtk] / clutter-gtk / gtk-clutter-viewport.c
1 /**
2  * SECTION:gtk-clutter-viewport
3  * @short_description: A scrollable actor
4  *
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.
9  *
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.
13  *
14  * The #GtkClutterViewport can be used inside any #ClutterContainer
15  * implementation.
16  *
17  * #GtkClutterViewport is available since Clutter-GTK 1.0
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <cogl/cogl.h>
25
26 #include "gtk-clutter-scrollable.h"
27 #include "gtk-clutter-util.h"
28 #include "gtk-clutter-viewport.h"
29
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.
33  */
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))
40
41 #define gtk_adjustment_get_upper(a)             ((a)->upper)
42 #define gtk_adjustment_get_page_size(a)         ((a)->page_size)
43 #endif
44
45 #define GET_PRIVATE(obj)        (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_CLUTTER_VIEWPORT, GtkClutterViewportPrivate))
46
47 #define I_(str) (g_intern_static_string ((str)))
48
49 static void clutter_container_iface_init      (gpointer g_iface);
50 static void gtk_clutter_scrollable_iface_init (gpointer g_iface);
51
52 G_DEFINE_TYPE_WITH_CODE (GtkClutterViewport,
53                          gtk_clutter_viewport,
54                          CLUTTER_TYPE_ACTOR,
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));
59
60 struct _GtkClutterViewportPrivate
61 {
62   ClutterVertex origin;
63
64   ClutterActor *child;
65
66   GtkAdjustment *h_adjustment;
67   GtkAdjustment *v_adjustment;
68 };
69
70 enum
71 {
72   PROP_0,
73
74   PROP_CHILD,
75   PROP_ORIGIN,
76   PROP_H_ADJUSTMENT,
77   PROP_V_ADJUSTMENT
78 };
79
80 static void
81 gtk_clutter_viewport_add (ClutterContainer *container,
82                           ClutterActor     *actor)
83 {
84   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
85
86   if (priv->child)
87     clutter_actor_unparent (priv->child);
88
89   clutter_actor_set_parent (actor, CLUTTER_ACTOR (container));
90   priv->child = actor;
91
92   clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
93
94   g_signal_emit_by_name (container, "actor-added", actor);
95   g_object_notify (G_OBJECT (container), "child");
96 }
97
98 static void
99 gtk_clutter_viewport_remove (ClutterContainer *container,
100                              ClutterActor     *actor)
101 {
102   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
103
104   if (G_LIKELY (priv->child == actor))
105     {
106       g_object_ref (actor);
107
108       clutter_actor_unparent (actor);
109       priv->child = NULL;
110
111       clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
112
113       g_signal_emit_by_name (container, "actor-removed", actor);
114
115       g_object_unref (actor);
116     }
117 }
118
119 static void
120 gtk_clutter_viewport_foreach (ClutterContainer *container,
121                               ClutterCallback   callback,
122                               gpointer          callback_data)
123 {
124   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
125
126   if (G_LIKELY (priv->child))
127     callback (priv->child, callback_data);
128 }
129
130 static void
131 clutter_container_iface_init (gpointer g_iface)
132 {
133   ClutterContainerIface *iface = g_iface;
134
135   iface->add = gtk_clutter_viewport_add;
136   iface->remove = gtk_clutter_viewport_remove;
137   iface->foreach = gtk_clutter_viewport_foreach;
138 }
139
140 static void
141 viewport_adjustment_value_changed (GtkAdjustment      *adjustment,
142                                    GtkClutterViewport *viewport)
143 {
144   GtkClutterViewportPrivate *priv = viewport->priv;
145
146   if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
147     {
148       GtkAdjustment *h_adjust = priv->h_adjustment;
149       GtkAdjustment *v_adjust = priv->v_adjustment;
150       ClutterUnit new_x, new_y;
151
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));
154
155       /* change the origin and queue a relayout */
156       if (new_x != priv->origin.x || new_y != priv->origin.y)
157         {
158           priv->origin.x = new_x;
159           priv->origin.y = new_y;
160
161           clutter_actor_queue_relayout (CLUTTER_ACTOR (viewport));
162         }
163     }
164 }
165
166 static gboolean
167 viewport_reclamp_adjustment (GtkAdjustment *adjustment)
168 {
169   gdouble value = gtk_adjustment_get_value (adjustment);
170   gdouble limit;
171
172   limit = gtk_adjustment_get_upper (adjustment)
173         - gtk_adjustment_get_page_size (adjustment);
174
175   value = CLAMP (value, 0, limit);
176   if (value != gtk_adjustment_get_value (adjustment))
177     {
178       gtk_adjustment_set_value (adjustment, value);
179       return TRUE;
180     }
181   else
182     return FALSE;
183 }
184
185 static gboolean
186 viewport_set_hadjustment_values (GtkClutterViewport *viewport)
187 {
188   GtkClutterViewportPrivate *priv = viewport->priv;
189   GtkAdjustment *h_adjust = priv->h_adjustment;
190   guint width;
191
192   width = clutter_actor_get_width (CLUTTER_ACTOR (viewport));
193
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);
198
199   if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
200     {
201       ClutterUnit natural_width;
202
203       clutter_actor_get_preferred_size (priv->child,
204                                         NULL, NULL,
205                                         &natural_width, NULL);
206
207       gtk_adjustment_set_upper (h_adjust,
208                                 MAX (CLUTTER_UNITS_TO_DEVICE (natural_width),
209                                      width));
210     }
211   else
212     gtk_adjustment_set_upper (h_adjust, width);
213
214   return viewport_reclamp_adjustment (h_adjust);
215 }
216
217 static gboolean
218 viewport_set_vadjustment_values (GtkClutterViewport *viewport)
219 {
220   GtkClutterViewportPrivate *priv = viewport->priv;
221   GtkAdjustment *v_adjust = priv->v_adjustment;
222   guint height;
223
224   height = clutter_actor_get_height (CLUTTER_ACTOR (viewport));
225
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);
230
231   if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
232     {
233       ClutterUnit natural_height;
234
235       clutter_actor_get_preferred_size (priv->child,
236                                         NULL, NULL,
237                                         NULL, &natural_height);
238
239       gtk_adjustment_set_upper (v_adjust,
240                                 MAX (CLUTTER_UNITS_TO_DEVICE (natural_height),
241                                      height));
242     }
243   else
244     gtk_adjustment_set_upper (v_adjust, height);
245
246   return viewport_reclamp_adjustment (v_adjust);
247 }
248
249 static inline void
250 disconnect_adjustment (GtkClutterViewport *viewport,
251                        GtkOrientation      orientation)
252 {
253   GtkClutterViewportPrivate *priv = viewport->priv;
254   GtkAdjustment **adj_p;
255
256   adj_p = (orientation == GTK_ORIENTATION_HORIZONTAL) ? &priv->h_adjustment
257                                                       : &priv->v_adjustment;
258
259   if (*adj_p)
260     {
261       g_signal_handlers_disconnect_by_func (*adj_p,
262                                             viewport_adjustment_value_changed,
263                                             viewport);
264       g_object_unref (*adj_p);
265       *adj_p = NULL;
266     }
267 }
268
269 static inline void
270 connect_adjustment (GtkClutterViewport *viewport,
271                     GtkOrientation      orientation,
272                     GtkAdjustment      *adjustment)
273 {
274   GtkClutterViewportPrivate *priv = viewport->priv;
275   GtkAdjustment **adj_p;
276   gboolean value_changed = FALSE;
277
278   adj_p = (orientation == GTK_ORIENTATION_HORIZONTAL) ? &priv->h_adjustment
279                                                       : &priv->v_adjustment;
280
281   if (adjustment && adjustment == *adj_p)
282     return;
283
284   if (!adjustment)
285     adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 0, 0, 0, 0));
286
287   disconnect_adjustment (viewport, orientation);
288   *adj_p = g_object_ref_sink (adjustment);
289
290   if (orientation == GTK_ORIENTATION_HORIZONTAL)
291     value_changed = viewport_set_hadjustment_values (viewport);
292   else
293     value_changed = viewport_set_vadjustment_values (viewport);
294
295   g_signal_connect (adjustment, "value-changed",
296                     G_CALLBACK (viewport_adjustment_value_changed),
297                     viewport);
298
299   gtk_adjustment_changed (adjustment);
300
301   if (value_changed)
302     gtk_adjustment_value_changed (adjustment);
303   else
304     viewport_adjustment_value_changed (adjustment, viewport);
305
306   if (orientation == GTK_ORIENTATION_HORIZONTAL)
307     g_object_notify (G_OBJECT (viewport), "hadjustment");
308   else
309     g_object_notify (G_OBJECT (viewport), "vadjustment");
310 }
311
312 static void
313 gtk_clutter_viewport_set_adjustments (GtkClutterScrollable *scrollable,
314                                       GtkAdjustment        *h_adjust,
315                                       GtkAdjustment        *v_adjust)
316 {
317   g_object_freeze_notify (G_OBJECT (scrollable));
318
319   connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
320                       GTK_ORIENTATION_HORIZONTAL,
321                       h_adjust);
322   connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
323                       GTK_ORIENTATION_VERTICAL,
324                       v_adjust);
325
326   g_object_thaw_notify (G_OBJECT (scrollable));
327 }
328
329 static void
330 gtk_clutter_viewport_get_adjustments (GtkClutterScrollable  *scrollable,
331                                       GtkAdjustment        **h_adjust,
332                                       GtkAdjustment        **v_adjust)
333 {
334   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (scrollable)->priv;
335
336   if (h_adjust)
337     *h_adjust = priv->h_adjustment;
338
339   if (v_adjust)
340     *v_adjust = priv->v_adjustment;
341 }
342
343 static void
344 gtk_clutter_scrollable_iface_init (gpointer g_iface)
345 {
346   GtkClutterScrollableIface *iface = g_iface;
347
348   iface->set_adjustments = gtk_clutter_viewport_set_adjustments;
349   iface->get_adjustments = gtk_clutter_viewport_get_adjustments;
350 }
351
352 static void
353 gtk_clutter_viewport_set_property (GObject      *gobject,
354                                    guint         prop_id,
355                                    const GValue *value,
356                                    GParamSpec   *pspec)
357 {
358   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
359
360   switch (prop_id)
361     {
362     case PROP_CHILD:
363       clutter_container_add_actor (CLUTTER_CONTAINER (gobject),
364                                    g_value_get_object (value));
365       break;
366
367     case PROP_ORIGIN:
368       {
369         ClutterVertex *v = g_value_get_boxed (value);
370
371         priv->origin = *v;
372
373         if (CLUTTER_ACTOR_IS_VISIBLE (gobject))
374           clutter_actor_queue_redraw (CLUTTER_ACTOR (gobject));
375       }
376       break;
377
378     case PROP_H_ADJUSTMENT:
379       connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
380                           GTK_ORIENTATION_HORIZONTAL,
381                           g_value_get_object (value));
382       break;
383
384     case PROP_V_ADJUSTMENT:
385       connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
386                           GTK_ORIENTATION_VERTICAL,
387                           g_value_get_object (value));
388       break;
389
390     default:
391       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
392       break;
393     }
394 }
395
396 static void
397 gtk_clutter_viewport_get_property (GObject    *gobject,
398                                    guint       prop_id,
399                                    GValue     *value,
400                                    GParamSpec *pspec)
401 {
402   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
403
404   switch (prop_id)
405     {
406     case PROP_CHILD:
407       g_value_set_object (value, priv->child);
408       break;
409
410     case PROP_ORIGIN:
411       g_value_set_boxed (value, &priv->origin);
412       break;
413
414     case PROP_H_ADJUSTMENT:
415       g_value_set_object (value, priv->h_adjustment);
416       break;
417
418     case PROP_V_ADJUSTMENT:
419       g_value_set_object (value, priv->v_adjustment);
420       break;
421
422     default:
423       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
424       break;
425     }
426 }
427
428 static void
429 gtk_clutter_viewport_dispose (GObject *gobject)
430 {
431   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
432
433   if (priv->child)
434     {
435       clutter_actor_destroy (priv->child);
436       priv->child = NULL;
437     }
438
439   disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
440                          GTK_ORIENTATION_HORIZONTAL);
441   disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
442                          GTK_ORIENTATION_VERTICAL);
443
444   G_OBJECT_CLASS (gtk_clutter_viewport_parent_class)->dispose (gobject);
445 }
446
447 static void
448 gtk_clutter_viewport_get_preferred_width (ClutterActor *actor,
449                                           ClutterUnit   for_height,
450                                           ClutterUnit  *min_width_p,
451                                           ClutterUnit  *natural_width_p)
452 {
453   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
454
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
457    */
458   if (priv->child)
459     clutter_actor_get_preferred_width (priv->child, for_height,
460                                        min_width_p,
461                                        natural_width_p);
462   else
463     {
464       if (min_width_p)
465         *min_width_p = 0;
466
467       if (natural_width_p)
468         *natural_width_p = 0;
469     }
470 }
471
472 static void
473 gtk_clutter_viewport_get_preferred_height (ClutterActor *actor,
474                                            ClutterUnit   for_width,
475                                            ClutterUnit  *min_height_p,
476                                            ClutterUnit  *natural_height_p)
477 {
478   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
479
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
482    */
483   if (priv->child)
484     clutter_actor_get_preferred_height (priv->child, for_width,
485                                         min_height_p,
486                                         natural_height_p);
487   else
488     {
489       if (min_height_p)
490         *min_height_p = 0;
491
492       if (natural_height_p)
493         *natural_height_p = 0;
494     }
495 }
496
497 static void
498 gtk_clutter_viewport_allocate (ClutterActor          *actor,
499                                const ClutterActorBox *box,
500                                gboolean               origin_changed)
501 {
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;
506
507   parent_class = CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class);
508   parent_class->allocate (actor, box, origin_changed);
509
510   h_adjustment_value_changed = viewport_set_hadjustment_values (viewport);
511   v_adjustment_value_changed = viewport_set_vadjustment_values (viewport);
512
513   if (priv->child)
514     {
515       ClutterActorBox child_allocation = { 0, };
516       ClutterUnit alloc_width, alloc_height;
517
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
521        * actually is.
522        */
523       clutter_actor_get_preferred_size (priv->child,
524                                         NULL, NULL,
525                                         &alloc_width, &alloc_height);
526
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;
531
532       clutter_actor_allocate (priv->child, &child_allocation, origin_changed);
533     }
534
535   gtk_adjustment_changed (priv->h_adjustment);
536   gtk_adjustment_changed (priv->v_adjustment);
537
538   if (h_adjustment_value_changed)
539     gtk_adjustment_value_changed (priv->h_adjustment);
540
541   if (v_adjustment_value_changed)
542     gtk_adjustment_value_changed (priv->v_adjustment);
543 }
544
545 static void
546 gtk_clutter_viewport_paint (ClutterActor *actor)
547 {
548   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
549
550   cogl_push_matrix ();
551
552   /* translate the paint environment by the same amount
553    * defined by the origin value
554    */
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);
558
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);
562
563   cogl_pop_matrix ();
564 }
565
566 static void
567 gtk_clutter_viewport_pick (ClutterActor       *actor,
568                            const ClutterColor *pick_color)
569 {
570   /* chain up to get the default pick */
571   CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class)->pick (actor, pick_color);
572
573   /* this will cause the child (if any) to be painted in pick mode */
574   gtk_clutter_viewport_paint (actor);
575 }
576
577 static void
578 gtk_clutter_viewport_class_init (GtkClutterViewportClass *klass)
579 {
580   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
581   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
582   GParamSpec *pspec;
583
584   g_type_class_add_private (klass, sizeof (GtkClutterViewportPrivate));
585
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;
589
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;
595
596   /**
597    * GtkClutterViewport:child:
598    *
599    * The #ClutterActor inside the viewport.
600    *
601    * Since: 1.0
602    */
603   pspec = g_param_spec_object ("child",
604                                "Child",
605                                "The ClutterActor inside the viewport",
606                                CLUTTER_TYPE_ACTOR,
607                                G_PARAM_READWRITE);
608   g_object_class_install_property (gobject_class, PROP_CHILD, pspec);
609
610   /**
611    * GtkClutterViewport:origin:
612    *
613    * The current origin of the viewport.
614    *
615    * Since: 1.0
616    */
617   pspec = g_param_spec_boxed ("origin",
618                               "Origin",
619                               "The current origin of the viewport",
620                               CLUTTER_TYPE_VERTEX,
621                               G_PARAM_READABLE);
622   g_object_class_install_property (gobject_class, PROP_ORIGIN, pspec);
623
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");
627 }
628
629 static void
630 gtk_clutter_viewport_init (GtkClutterViewport *viewport)
631 {
632   GtkClutterViewportPrivate *priv;
633
634   viewport->priv = priv = GET_PRIVATE (viewport);
635 }
636
637 /**
638  * gtk_clutter_viewport_new:
639  * @h_adjust: horizontal adjustment, or %NULL
640  * @v_adjust: vertical adjustment, or %NULL
641  *
642  * Creates a new #GtkClutterViewport with the given adjustments.
643  *
644  * Return value: the newly created viewport actor
645  *
646  * Since: 1.0
647  */
648 ClutterActor *
649 gtk_clutter_viewport_new (GtkAdjustment *h_adjust,
650                           GtkAdjustment *v_adjust)
651 {
652   return g_object_new (GTK_TYPE_CLUTTER_VIEWPORT,
653                        "hadjustment", h_adjust,
654                        "vadjustment", v_adjust,
655                        NULL);
656 }
657
658 /**
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
664  *
665  * Retrieves the current translation factor ("origin") used when
666  * displaying the child of @viewport.
667  *
668  * Since: 1.0.
669  */
670 void
671 gtk_clutter_viewport_get_origin (GtkClutterViewport *viewport,
672                                  gint               *x,
673                                  gint               *y,
674                                  gint               *z)
675 {
676   GtkClutterViewportPrivate *priv;
677
678   g_return_if_fail (GTK_IS_CLUTTER_VIEWPORT (viewport));
679
680   priv = viewport->priv;
681
682   if (x)
683     *x = CLUTTER_UNITS_TO_DEVICE (priv->origin.x);
684
685   if (y)
686     *y = CLUTTER_UNITS_TO_DEVICE (priv->origin.y);
687
688   if (z)
689     *z = CLUTTER_UNITS_TO_DEVICE (priv->origin.z);
690 }
691
692 /**
693  * gtk_clutter_viewport_get_originu:
694  * @viewport: a #GtkClutterViewport
695  * @origin: a #ClutterVertex
696  *
697  * Retrieves the current translation factor ("origin") used
698  * when displaying the child of @viewport.
699  *
700  * This function is the units-based version of
701  * gtk_clutter_viewport_get_origin().
702  *
703  * Since: 1.0
704  */
705 void
706 gtk_clutter_viewport_get_originu (GtkClutterViewport *viewport,
707                                   ClutterVertex      *origin)
708 {
709   g_return_if_fail (GTK_IS_CLUTTER_VIEWPORT (viewport));
710
711   *origin = viewport->priv->origin;
712 }