1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Authored By:
7  *      Matthew Allum  <mallum@openedhand.com>
8  *      Neil Roberts  <neil@linux.intel.com>
9  *
10  * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd
11  * Copyright (C) 2009, 2010 Intel Corp
12  *
13  * This library is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU Lesser General Public
15  * License as published by the Free Software Foundation; either
16  * version 2 of the License, or (at your option) any later version.
17  *
18  * This library is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21  * Lesser General Public License for more details.
22  *
23  * You should have received a copy of the GNU Lesser General Public
24  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 /**
28  * SECTION:clutter-behaviour-path
29  * @Title: ClutterBehaviourPath
30  * @short_description: A behaviour for moving actors along a #ClutterPath
31  * @Deprecated: 1.6: Use #ClutterPathConstraint and clutter_actor_animate()
32  *   with the #ClutterPathConstraint:offset property instead.
33  *
34  * #ClutterBehaviourPath interpolates actors along a defined path.
35  *
36  * A path is described by a #ClutterPath object. The path can contain
37  * straight line parts and bezier curves. If the path contains
38  * %CLUTTER_PATH_MOVE_TO parts then the actors will jump to those
39  * coordinates. This can be used make disjoint paths.
40  *
41  * When creating a path behaviour in a #ClutterScript, you can specify
42  * the path property directly as a string. For example:
43  *
44  * |[
45  * {
46  *   "id"     : "spline-path",
47  *   "type"   : "ClutterBehaviourPath",
48  *   "path"   : "M 50 50 L 100 100",
49  *   "alpha"  : {
50  *      "timeline" : "main-timeline",
51  *      "function" : "ramp
52  *    }
53  * }
54  * ]|
55  *
56  * If the alpha function is a periodic function, i.e. it returns to
57  * 0.0 after reaching 1.0, then the actors will walk the path back to the
58  * starting #ClutterKnot.
59  *
60  * #ClutterBehaviourPath is available since Clutter 0.2
61  *
62  * Deprecated: 1.6: Use #ClutterPath and #ClutterPathConstraint with
63  *   clutter_actor_animate() instead.
64  */
65 
66 #ifdef HAVE_CONFIG_H
67 #include "clutter-build-config.h"
68 #endif
69 
70 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
71 
72 #include "clutter-alpha.h"
73 #include "clutter-behaviour.h"
74 #include "clutter-behaviour-path.h"
75 #include "clutter-bezier.h"
76 #include "clutter-debug.h"
77 #include "clutter-enum-types.h"
78 #include "clutter-main.h"
79 #include "clutter-marshal.h"
80 #include "clutter-private.h"
81 #include "clutter-script-private.h"
82 #include "clutter-scriptable.h"
83 
84 #include <math.h>
85 
86 struct _ClutterBehaviourPathPrivate
87 {
88   ClutterPath *path;
89   guint        last_knot_passed;
90 };
91 
92 enum
93 {
94   KNOT_REACHED,
95 
96   LAST_SIGNAL
97 };
98 
99 static guint path_signals[LAST_SIGNAL] = { 0, };
100 
101 enum
102 {
103   PROP_0,
104 
105   PROP_PATH,
106 
107   PROP_LAST
108 };
109 
110 static GParamSpec *obj_props[PROP_LAST];
111 
112 static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);
113 
G_DEFINE_TYPE_WITH_CODE(ClutterBehaviourPath,clutter_behaviour_path,CLUTTER_TYPE_BEHAVIOUR,G_ADD_PRIVATE (ClutterBehaviourPath)G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,clutter_scriptable_iface_init))114 G_DEFINE_TYPE_WITH_CODE (ClutterBehaviourPath,
115                          clutter_behaviour_path,
116                          CLUTTER_TYPE_BEHAVIOUR,
117                          G_ADD_PRIVATE (ClutterBehaviourPath)
118                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
119                                                 clutter_scriptable_iface_init))
120 
121 static void
122 actor_apply_knot_foreach (ClutterBehaviour *behaviour,
123                           ClutterActor     *actor,
124                           gpointer          data)
125 {
126   ClutterKnot *knot = data;
127 
128   CLUTTER_NOTE (ANIMATION, "Setting actor to %ix%i", knot->x, knot->y);
129 
130   clutter_actor_set_position (actor, knot->x, knot->y);
131 }
132 
133 static void
clutter_behaviour_path_alpha_notify(ClutterBehaviour * behave,gdouble alpha_value)134 clutter_behaviour_path_alpha_notify (ClutterBehaviour *behave,
135                                      gdouble           alpha_value)
136 {
137   ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (behave);
138   ClutterBehaviourPathPrivate *priv = pathb->priv;
139   ClutterKnot position;
140   guint knot_num;
141 
142   if (priv->path)
143     knot_num = clutter_path_get_position (priv->path, alpha_value, &position);
144   else
145     {
146       memset (&position, 0, sizeof (position));
147       knot_num = 0;
148     }
149 
150   clutter_behaviour_actors_foreach (behave,
151                                     actor_apply_knot_foreach,
152                                     &position);
153 
154   if (knot_num != priv->last_knot_passed)
155     {
156       g_signal_emit (behave, path_signals[KNOT_REACHED], 0, knot_num);
157       priv->last_knot_passed = knot_num;
158     }
159 }
160 
161 static void
clutter_behaviour_path_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)162 clutter_behaviour_path_get_property (GObject      *gobject,
163                                      guint         prop_id,
164                                      GValue       *value,
165                                      GParamSpec   *pspec)
166 {
167   ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (gobject);
168 
169   switch (prop_id)
170     {
171     case PROP_PATH:
172       g_value_set_object (value, clutter_behaviour_path_get_path (pathb));
173       break;
174     default:
175       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
176       break;
177     }
178 }
179 
180 static void
clutter_behaviour_path_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)181 clutter_behaviour_path_set_property (GObject      *gobject,
182                                      guint         prop_id,
183                                      const GValue *value,
184                                      GParamSpec   *pspec)
185 {
186   ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (gobject);
187 
188   switch (prop_id)
189     {
190     case PROP_PATH:
191       clutter_behaviour_path_set_path (pathb, g_value_get_object (value));
192       break;
193     default:
194       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
195       break;
196     }
197 }
198 
199 static void
clutter_behaviour_path_dispose(GObject * gobject)200 clutter_behaviour_path_dispose (GObject *gobject)
201 {
202   ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (gobject);
203 
204   clutter_behaviour_path_set_path (pathb, NULL);
205 
206   G_OBJECT_CLASS (clutter_behaviour_path_parent_class)->dispose (gobject);
207 }
208 
209 static void
clutter_behaviour_path_class_init(ClutterBehaviourPathClass * klass)210 clutter_behaviour_path_class_init (ClutterBehaviourPathClass *klass)
211 {
212   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
213   ClutterBehaviourClass *behave_class = CLUTTER_BEHAVIOUR_CLASS (klass);
214   GParamSpec *pspec;
215 
216   gobject_class->get_property = clutter_behaviour_path_get_property;
217   gobject_class->set_property = clutter_behaviour_path_set_property;
218   gobject_class->dispose = clutter_behaviour_path_dispose;
219 
220   pspec = g_param_spec_object ("path",
221                                P_("Path"),
222                                P_("The ClutterPath object representing the path "
223                                   "to animate along"),
224                                CLUTTER_TYPE_PATH,
225                                CLUTTER_PARAM_READWRITE);
226   obj_props[PROP_PATH] = pspec;
227   g_object_class_install_property (gobject_class, PROP_PATH, pspec);
228 
229   /**
230    * ClutterBehaviourPath::knot-reached:
231    * @pathb: the object which received the signal
232    * @knot_num: the index of the #ClutterKnot reached
233    *
234    * This signal is emitted each time a node defined inside the path
235    * is reached.
236    *
237    * Since: 0.2
238    *
239    * Deprecated: 1.6
240    */
241   path_signals[KNOT_REACHED] =
242     g_signal_new ("knot-reached",
243                   G_TYPE_FROM_CLASS (gobject_class),
244                   G_SIGNAL_RUN_LAST,
245                   G_STRUCT_OFFSET (ClutterBehaviourPathClass, knot_reached),
246                   NULL, NULL,
247                   _clutter_marshal_VOID__UINT,
248                   G_TYPE_NONE, 1,
249                   G_TYPE_UINT);
250 
251   behave_class->alpha_notify = clutter_behaviour_path_alpha_notify;
252 }
253 
254 static ClutterScriptableIface *parent_scriptable_iface = NULL;
255 
256 static gboolean
clutter_behaviour_path_parse_custom_node(ClutterScriptable * scriptable,ClutterScript * script,GValue * value,const gchar * name,JsonNode * node)257 clutter_behaviour_path_parse_custom_node (ClutterScriptable *scriptable,
258                                           ClutterScript     *script,
259                                           GValue            *value,
260                                           const gchar       *name,
261                                           JsonNode          *node)
262 {
263   if (strcmp ("path", name) == 0)
264     {
265       ClutterPath *path;
266       GValue node_value = { 0 };
267 
268       path = g_object_ref_sink (clutter_path_new ());
269 
270       json_node_get_value (node, &node_value);
271 
272       if (!G_VALUE_HOLDS (&node_value, G_TYPE_STRING)
273           || !clutter_path_set_description (path,
274                                             g_value_get_string (&node_value)))
275         g_warning ("Invalid path description");
276 
277       g_value_unset (&node_value);
278 
279       g_value_init (value, G_TYPE_OBJECT);
280       g_value_take_object (value, path);
281 
282       return TRUE;
283     }
284   /* chain up */
285   else if (parent_scriptable_iface->parse_custom_node)
286     return parent_scriptable_iface->parse_custom_node (scriptable, script,
287                                                        value, name, node);
288   else
289     return FALSE;
290 }
291 
292 static void
clutter_scriptable_iface_init(ClutterScriptableIface * iface)293 clutter_scriptable_iface_init (ClutterScriptableIface *iface)
294 {
295   parent_scriptable_iface = g_type_interface_peek_parent (iface);
296 
297   if (!parent_scriptable_iface)
298     parent_scriptable_iface
299       = g_type_default_interface_peek (CLUTTER_TYPE_SCRIPTABLE);
300 
301   iface->parse_custom_node = clutter_behaviour_path_parse_custom_node;
302 }
303 
304 static void
clutter_behaviour_path_init(ClutterBehaviourPath * self)305 clutter_behaviour_path_init (ClutterBehaviourPath *self)
306 {
307   self->priv = clutter_behaviour_path_get_instance_private (self);
308   self->priv->last_knot_passed = G_MAXUINT;
309 }
310 
311 /**
312  * clutter_behaviour_path_new:
313  * @alpha: (allow-none): a #ClutterAlpha instance, or %NULL
314  * @path: a #ClutterPath or %NULL for an empty path
315  *
316  * Creates a new path behaviour. You can use this behaviour to drive
317  * actors along the nodes of a path, described by @path.
318  *
319  * This will claim the floating reference on the #ClutterPath so you
320  * do not need to unref if it.
321  *
322  * If @alpha is not %NULL, the #ClutterBehaviour will take ownership
323  * of the #ClutterAlpha instance. In the case when @alpha is %NULL,
324  * it can be set later with clutter_behaviour_set_alpha().
325  *
326  * Return value: (transfer full): a #ClutterBehaviour
327  *
328  * Since: 0.2
329  *
330  * Deprecated: 1.6
331  */
332 ClutterBehaviour *
clutter_behaviour_path_new(ClutterAlpha * alpha,ClutterPath * path)333 clutter_behaviour_path_new (ClutterAlpha *alpha,
334                             ClutterPath  *path)
335 {
336   return g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH,
337                        "alpha", alpha,
338                        "path", path,
339                        NULL);
340 }
341 
342 /**
343  * clutter_behaviour_path_new_with_description:
344  * @alpha: (allow-none): a #ClutterAlpha instance, or %NULL
345  * @desc: a string description of the path
346  *
347  * Creates a new path behaviour using the path described by @desc. See
348  * clutter_path_add_string() for a description of the format.
349  *
350  * If @alpha is not %NULL, the #ClutterBehaviour will take ownership
351  * of the #ClutterAlpha instance. In the case when @alpha is %NULL,
352  * it can be set later with clutter_behaviour_set_alpha().
353  *
354  * Return value: (transfer full): a #ClutterBehaviour
355  *
356  * Since: 1.0
357  *
358  * Deprecated: 1.6
359  */
360 ClutterBehaviour *
clutter_behaviour_path_new_with_description(ClutterAlpha * alpha,const gchar * desc)361 clutter_behaviour_path_new_with_description (ClutterAlpha *alpha,
362                                              const gchar  *desc)
363 {
364   return g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH,
365                        "alpha", alpha,
366                        "path", clutter_path_new_with_description (desc),
367                        NULL);
368 }
369 
370 /**
371  * clutter_behaviour_path_new_with_knots:
372  * @alpha: (allow-none): a #ClutterAlpha instance, or %NULL
373  * @knots: (array length=n_knots): an array of #ClutterKnot<!-- -->s
374  * @n_knots: number of entries in @knots
375  *
376  * Creates a new path behaviour that will make the actors visit all of
377  * the given knots in order with straight lines in between.
378  *
379  * A path will be created where the first knot is used in a
380  * %CLUTTER_PATH_MOVE_TO and the subsequent knots are used in
381  * %CLUTTER_PATH_LINE_TO<!-- -->s.
382  *
383  * If @alpha is not %NULL, the #ClutterBehaviour will take ownership
384  * of the #ClutterAlpha instance. In the case when @alpha is %NULL,
385  * it can be set later with clutter_behaviour_set_alpha().
386  *
387  * Return value: (transfer full): a #ClutterBehaviour
388  *
389  * Since: 1.0
390  *
391  * Deprecated: 1.6
392  */
393 ClutterBehaviour *
clutter_behaviour_path_new_with_knots(ClutterAlpha * alpha,const ClutterKnot * knots,guint n_knots)394 clutter_behaviour_path_new_with_knots (ClutterAlpha      *alpha,
395                                        const ClutterKnot *knots,
396                                        guint              n_knots)
397 {
398   ClutterPath *path = clutter_path_new ();
399   guint i;
400 
401   if (n_knots > 0)
402     {
403       clutter_path_add_move_to (path, knots[0].x, knots[0].y);
404 
405       for (i = 1; i < n_knots; i++)
406         clutter_path_add_line_to (path, knots[i].x, knots[i].y);
407     }
408 
409   return g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH,
410                        "alpha", alpha,
411                        "path", path,
412                        NULL);
413 }
414 
415 /**
416  * clutter_behaviour_path_set_path:
417  * @pathb: the path behaviour
418  * @path: the new path to follow
419  *
420  * Change the path that the actors will follow. This will take the
421  * floating reference on the #ClutterPath so you do not need to unref
422  * it.
423  *
424  * Since: 1.0
425  *
426  * Deprecated: 1.6
427  */
428 void
clutter_behaviour_path_set_path(ClutterBehaviourPath * pathb,ClutterPath * path)429 clutter_behaviour_path_set_path (ClutterBehaviourPath *pathb,
430                                  ClutterPath          *path)
431 {
432   ClutterBehaviourPathPrivate *priv;
433 
434   g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb));
435 
436   priv = pathb->priv;
437 
438   if (path)
439     g_object_ref_sink (path);
440 
441   if (priv->path)
442     g_object_unref (priv->path);
443 
444   priv->path = path;
445 
446   g_object_notify_by_pspec (G_OBJECT (pathb), obj_props[PROP_PATH]);
447 }
448 
449 /**
450  * clutter_behaviour_path_get_path:
451  * @pathb: a #ClutterBehaviourPath instance
452  *
453  * Get the current path of the behaviour
454  *
455  * Return value: (transfer none): the path
456  *
457  * Since: 1.0
458  *
459  * Deprecated: 1.6
460  */
461 ClutterPath *
clutter_behaviour_path_get_path(ClutterBehaviourPath * pathb)462 clutter_behaviour_path_get_path (ClutterBehaviourPath *pathb)
463 {
464   g_return_val_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb), NULL);
465 
466   return pathb->priv->path;
467 }
468