update to 1.10.4
[profile/ivi/clutter.git] / clutter / clutter-path.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Authored By Matthew Allum  <mallum@openedhand.com>
7  *
8  * Copyright (C) 2008 Intel Corporation
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22  */
23
24 /**
25  * SECTION:clutter-path
26  * @short_description: An object describing a path with straight lines
27  * and bezier curves.
28  *
29  * A #ClutterPath contains a description of a path consisting of
30  * straight lines and bezier curves. This can be used in a
31  * #ClutterBehaviourPath to animate an actor moving along the path.
32  *
33  * The path consists of a series of nodes. Each node is one of the
34  * following four types:
35  *
36  * <variablelist>
37  * <varlistentry><term>%CLUTTER_PATH_MOVE_TO</term>
38  * <listitem><para>
39  * Changes the position of the path to the given pair of
40  * coordinates. This is usually used as the first node of a path to
41  * mark the start position. If it is used in the middle of a path then
42  * the path will be disjoint and the actor will appear to jump to the
43  * new position when animated.
44  * </para></listitem></varlistentry>
45  * <varlistentry><term>%CLUTTER_PATH_LINE_TO</term>
46  * <listitem><para>
47  * Creates a straight line from the previous point to the given point.
48  * </para></listitem></varlistentry>
49  * <varlistentry><term>%CLUTTER_PATH_CURVE_TO</term>
50  * <listitem><para>
51  * Creates a bezier curve. The end of the last node is used as the
52  * first control point and the three subsequent coordinates given in
53  * the node as used as the other three.
54  * </para></listitem></varlistentry>
55  * <varlistentry><term>%CLUTTER_PATH_CLOSE</term>
56  * <listitem><para>
57  * Creates a straight line from the last node to the last
58  * %CLUTTER_PATH_MOVE_TO node. This can be used to close a path so
59  * that it will appear as a loop when animated.
60  * </para></listitem></varlistentry>
61  * </variablelist>
62  *
63  * The first three types have the corresponding relative versions
64  * %CLUTTER_PATH_REL_MOVE_TO, %CLUTTER_PATH_REL_LINE_TO and
65  * %CLUTTER_PATH_REL_CURVE_TO. These are exactly the same except the
66  * coordinates are given relative to the previous node instead of as
67  * direct screen positions.
68  *
69  * You can build a path using the node adding functions such as
70  * clutter_path_add_line_to(). Alternatively the path can be described
71  * in a string using a subset of the SVG path syntax. See
72  * clutter_path_add_string() for details.
73  *
74  * #ClutterPath is available since Clutter 1.0
75  */
76
77 #ifdef HAVE_CONFIG_H
78 #include "config.h"
79 #endif
80
81 #include <string.h>
82 #include <stdarg.h>
83 #include <math.h>
84 #include <glib-object.h>
85
86 #include "clutter-path.h"
87 #include "clutter-types.h"
88 #include "clutter-bezier.h"
89 #include "clutter-private.h"
90
91 #define CLUTTER_PATH_GET_PRIVATE(obj) \
92   (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_PATH, \
93                                 ClutterPathPrivate))
94
95 #define CLUTTER_PATH_NODE_TYPE_IS_VALID(t) \
96   ((((t) & ~CLUTTER_PATH_RELATIVE) >= CLUTTER_PATH_MOVE_TO      \
97     && ((t) & ~CLUTTER_PATH_RELATIVE) <= CLUTTER_PATH_CURVE_TO) \
98    || (t) == CLUTTER_PATH_CLOSE)
99
100 enum
101 {
102   PROP_0,
103
104   PROP_DESCRIPTION,
105   PROP_LENGTH,
106
107   PROP_LAST
108 };
109
110 static GParamSpec *obj_props[PROP_LAST];
111
112 typedef struct _ClutterPathNodeFull ClutterPathNodeFull;
113
114 struct _ClutterPathNodeFull
115 {
116   ClutterPathNode k;
117
118   ClutterBezier *bezier;
119
120   guint length;
121 };
122
123 struct _ClutterPathPrivate
124 {
125   GSList *nodes, *nodes_tail;
126   gboolean nodes_dirty;
127
128   guint total_length;
129 };
130
131 /* Character tests that don't pay attention to the locale */
132 #define clutter_path_isspace(ch) memchr (" \f\n\r\t\v", (ch), 6)
133 #define clutter_path_isdigit(ch) ((ch) >= '0' && (ch) <= '9')
134
135 static ClutterPathNodeFull *clutter_path_node_full_new (void);
136 static void clutter_path_node_full_free (ClutterPathNodeFull *node);
137
138 static void clutter_path_finalize (GObject *object);
139
140 static void clutter_value_transform_path_string (const GValue *src,
141                                                  GValue       *dest);
142 static void clutter_value_transform_string_path (const GValue *src,
143                                                  GValue       *dest);
144
145 G_DEFINE_BOXED_TYPE (ClutterPathNode, clutter_path_node,
146                      clutter_path_node_copy,
147                      clutter_path_node_free);
148
149 G_DEFINE_TYPE_WITH_CODE (ClutterPath,
150                          clutter_path,
151                          G_TYPE_INITIALLY_UNOWNED,
152                          CLUTTER_REGISTER_VALUE_TRANSFORM_TO (G_TYPE_STRING, clutter_value_transform_path_string)
153                          CLUTTER_REGISTER_VALUE_TRANSFORM_FROM (G_TYPE_STRING, clutter_value_transform_string_path));
154
155 static void
156 clutter_path_get_property (GObject      *gobject,
157                            guint         prop_id,
158                            GValue       *value,
159                            GParamSpec   *pspec)
160 {
161   ClutterPath *path = CLUTTER_PATH (gobject);
162
163   switch (prop_id)
164     {
165     case PROP_DESCRIPTION:
166       g_value_take_string (value, clutter_path_get_description (path));
167       break;
168     case PROP_LENGTH:
169       g_value_set_uint (value, clutter_path_get_length (path));
170       break;
171     default:
172       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
173       break;
174     }
175 }
176
177 static void
178 clutter_path_set_property (GObject      *gobject,
179                            guint         prop_id,
180                            const GValue *value,
181                            GParamSpec   *pspec)
182 {
183   ClutterPath *path = CLUTTER_PATH (gobject);
184
185   switch (prop_id)
186     {
187     case PROP_DESCRIPTION:
188       if (!clutter_path_set_description (path, g_value_get_string (value)))
189         g_warning ("Invalid path description");
190       break;
191     default:
192       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
193       break;
194     }
195 }
196
197 static void
198 clutter_path_class_init (ClutterPathClass *klass)
199 {
200   GObjectClass *gobject_class = (GObjectClass *) klass;
201   GParamSpec *pspec;
202
203   gobject_class->get_property = clutter_path_get_property;
204   gobject_class->set_property = clutter_path_set_property;
205   gobject_class->finalize = clutter_path_finalize;
206
207   pspec = g_param_spec_string ("description",
208                                "Description",
209                                "SVG-style description of the path",
210                                "",
211                                CLUTTER_PARAM_READWRITE);
212   obj_props[PROP_DESCRIPTION] = pspec;
213   g_object_class_install_property (gobject_class, PROP_DESCRIPTION, pspec);
214
215   pspec = g_param_spec_uint ("length",
216                              "Length",
217                              "An approximation of the total length "
218                              "of the path.",
219                              0, G_MAXUINT, 0,
220                              CLUTTER_PARAM_READABLE);
221   obj_props[PROP_LENGTH] = pspec;
222   g_object_class_install_property (gobject_class, PROP_LENGTH, pspec);
223
224   g_type_class_add_private (klass, sizeof (ClutterPathPrivate));
225 }
226
227 static void
228 clutter_path_init (ClutterPath *self)
229 {
230   self->priv = CLUTTER_PATH_GET_PRIVATE (self);
231 }
232
233 static void
234 clutter_value_transform_path_string (const GValue *src,
235                                      GValue       *dest)
236 {
237   gchar *string = clutter_path_get_description (src->data[0].v_pointer);
238
239   g_value_take_string (dest, string);
240 }
241
242 static void
243 clutter_value_transform_string_path (const GValue *src,
244                                      GValue       *dest)
245 {
246   ClutterPath *new_path;
247
248   new_path = clutter_path_new_with_description (g_value_get_string (src));
249   g_value_take_object (dest, new_path);
250 }
251
252 static void
253 clutter_path_finalize (GObject *object)
254 {
255   ClutterPath *self = (ClutterPath *) object;
256
257   clutter_path_clear (self);
258
259   G_OBJECT_CLASS (clutter_path_parent_class)->finalize (object);
260 }
261
262 /**
263  * clutter_path_new:
264  *
265  * Creates a new #ClutterPath instance with no nodes.
266  *
267  * The object has a floating reference so if you add it to a
268  * #ClutterBehaviourPath then you do not need to unref it.
269  *
270  * Return value: the newly created #ClutterPath
271  *
272  * Since: 1.0
273  */
274 ClutterPath *
275 clutter_path_new (void)
276 {
277   ClutterPath *self = g_object_new (CLUTTER_TYPE_PATH, NULL);
278
279   return self;
280 }
281
282 /**
283  * clutter_path_new_with_description:
284  * @desc: a string describing the path
285  *
286  * Creates a new #ClutterPath instance with the nodes described in
287  * @desc. See clutter_path_add_string() for details of the format of
288  * the string.
289  *
290  * The object has a floating reference so if you add it to a
291  * #ClutterBehaviourPath then you do not need to unref it.
292  *
293  * Return value: the newly created #ClutterPath
294  *
295  * Since: 1.0
296  */
297 ClutterPath *
298 clutter_path_new_with_description (const gchar *desc)
299 {
300   return g_object_new (CLUTTER_TYPE_PATH,
301                        "description", desc,
302                        NULL);
303 }
304
305 /**
306  * clutter_path_clear:
307  * @path: a #ClutterPath
308  *
309  * Removes all nodes from the path.
310  *
311  * Since: 1.0
312  */
313 void
314 clutter_path_clear (ClutterPath *path)
315 {
316   ClutterPathPrivate *priv = path->priv;
317
318   g_slist_foreach (priv->nodes, (GFunc) clutter_path_node_full_free, NULL);
319   g_slist_free (priv->nodes);
320
321   priv->nodes = priv->nodes_tail = NULL;
322   priv->nodes_dirty = TRUE;
323 }
324
325 /* Takes ownership of the node */
326 static void
327 clutter_path_add_node_full (ClutterPath         *path,
328                             ClutterPathNodeFull *node)
329 {
330   ClutterPathPrivate *priv = path->priv;
331   GSList *new_node;
332
333   new_node = g_slist_prepend (NULL, node);
334
335   if (priv->nodes_tail == NULL)
336     priv->nodes = new_node;
337   else
338     priv->nodes_tail->next = new_node;
339
340   priv->nodes_tail = new_node;
341
342   priv->nodes_dirty = TRUE;
343 }
344
345 /* Helper function to make the rest of teh add_* functions shorter */
346 static void
347 clutter_path_add_node_helper (ClutterPath         *path,
348                               ClutterPathNodeType  type,
349                               int                  num_coords,
350                               ...)
351 {
352   ClutterPathNodeFull *node;
353   int i;
354   va_list ap;
355
356   node = clutter_path_node_full_new ();
357
358   node->k.type = type;
359
360   va_start (ap, num_coords);
361
362   for (i = 0; i < num_coords; i++)
363     {
364       node->k.points[i].x = va_arg (ap, gint);
365       node->k.points[i].y = va_arg (ap, gint);
366     }
367
368   va_end (ap);
369
370   clutter_path_add_node_full (path, node);
371 }
372
373 /**
374  * clutter_path_add_move_to:
375  * @path: a #ClutterPath
376  * @x: the x coordinate
377  * @y: the y coordinate
378  *
379  * Adds a %CLUTTER_PATH_MOVE_TO type node to the path. This is usually
380  * used as the first node in a path. It can also be used in the middle
381  * of the path to cause the actor to jump to the new coordinate.
382  *
383  * Since: 1.0
384  */
385 void
386 clutter_path_add_move_to (ClutterPath *path,
387                           gint         x,
388                           gint         y)
389 {
390   g_return_if_fail (CLUTTER_IS_PATH (path));
391
392   clutter_path_add_node_helper (path, CLUTTER_PATH_MOVE_TO, 1, x, y);
393 }
394
395 /**
396  * clutter_path_add_rel_move_to:
397  * @path: a #ClutterPath
398  * @x: the x coordinate
399  * @y: the y coordinate
400  *
401  * Same as clutter_path_add_move_to() except the coordinates are
402  * relative to the previous node.
403  *
404  * Since: 1.0
405  */
406 void
407 clutter_path_add_rel_move_to (ClutterPath *path,
408                               gint         x,
409                               gint         y)
410 {
411   g_return_if_fail (CLUTTER_IS_PATH (path));
412
413   clutter_path_add_node_helper (path, CLUTTER_PATH_REL_MOVE_TO, 1, x, y);
414 }
415
416 /**
417  * clutter_path_add_line_to:
418  * @path: a #ClutterPath
419  * @x: the x coordinate
420  * @y: the y coordinate
421  *
422  * Adds a %CLUTTER_PATH_LINE_TO type node to the path. This causes the
423  * actor to move to the new coordinates in a straight line.
424  *
425  * Since: 1.0
426  */
427 void
428 clutter_path_add_line_to (ClutterPath *path,
429                           gint         x,
430                           gint         y)
431 {
432   g_return_if_fail (CLUTTER_IS_PATH (path));
433
434   clutter_path_add_node_helper (path, CLUTTER_PATH_LINE_TO, 1, x, y);
435 }
436
437 /**
438  * clutter_path_add_rel_line_to:
439  * @path: a #ClutterPath
440  * @x: the x coordinate
441  * @y: the y coordinate
442  *
443  * Same as clutter_path_add_line_to() except the coordinates are
444  * relative to the previous node.
445  *
446  * Since: 1.0
447  */
448 void
449 clutter_path_add_rel_line_to (ClutterPath *path,
450                               gint         x,
451                               gint         y)
452 {
453   g_return_if_fail (CLUTTER_IS_PATH (path));
454
455   clutter_path_add_node_helper (path, CLUTTER_PATH_REL_LINE_TO, 1, x, y);
456 }
457
458 /**
459  * clutter_path_add_curve_to:
460  * @path: a #ClutterPath
461  * @x_1: the x coordinate of the first control point
462  * @y_1: the y coordinate of the first control point
463  * @x_2: the x coordinate of the second control point
464  * @y_2: the y coordinate of the second control point
465  * @x_3: the x coordinate of the third control point
466  * @y_3: the y coordinate of the third control point
467  *
468  * Adds a %CLUTTER_PATH_CURVE_TO type node to the path. This causes
469  * the actor to follow a bezier from the last node to (@x_3, @y_3) using
470  * (@x_1, @y_1) and (@x_2,@y_2) as control points.
471  *
472  * Since: 1.0
473  */
474 void
475 clutter_path_add_curve_to (ClutterPath *path,
476                            gint         x_1,
477                            gint         y_1,
478                            gint         x_2,
479                            gint         y_2,
480                            gint         x_3,
481                            gint         y_3)
482 {
483   g_return_if_fail (CLUTTER_IS_PATH (path));
484
485   clutter_path_add_node_helper (path, CLUTTER_PATH_CURVE_TO, 3,
486                                 x_1, y_1,
487                                 x_2, y_2,
488                                 x_3, y_3);
489 }
490
491 /**
492  * clutter_path_add_rel_curve_to:
493  * @path: a #ClutterPath
494  * @x_1: the x coordinate of the first control point
495  * @y_1: the y coordinate of the first control point
496  * @x_2: the x coordinate of the second control point
497  * @y_2: the y coordinate of the second control point
498  * @x_3: the x coordinate of the third control point
499  * @y_3: the y coordinate of the third control point
500  *
501  * Same as clutter_path_add_curve_to() except the coordinates are
502  * relative to the previous node.
503  *
504  * Since: 1.0
505  */
506 void
507 clutter_path_add_rel_curve_to (ClutterPath *path,
508                                gint         x_1,
509                                gint         y_1,
510                                gint         x_2,
511                                gint         y_2,
512                                gint         x_3,
513                                gint         y_3)
514 {
515   g_return_if_fail (CLUTTER_IS_PATH (path));
516
517   clutter_path_add_node_helper (path, CLUTTER_PATH_REL_CURVE_TO, 3,
518                                 x_1, y_1,
519                                 x_2, y_2,
520                                 x_3, y_3);
521 }
522
523 /**
524  * clutter_path_add_close:
525  * @path: a #ClutterPath
526  *
527  * Adds a %CLUTTER_PATH_CLOSE type node to the path. This creates a
528  * straight line from the last node to the last %CLUTTER_PATH_MOVE_TO
529  * type node.
530  *
531  * Since: 1.0
532  */
533 void
534 clutter_path_add_close (ClutterPath *path)
535 {
536   g_return_if_fail (CLUTTER_IS_PATH (path));
537
538   clutter_path_add_node_helper (path, CLUTTER_PATH_CLOSE, 0);
539 }
540
541 static gboolean
542 clutter_path_parse_number (const gchar **pin,
543                            gboolean      allow_comma,
544                            gint         *ret)
545 {
546   gint val = 0;
547   gboolean negative = FALSE;
548   gint digit_count = 0;
549   const gchar *p = *pin;
550
551   /* Skip leading spaces */
552   while (clutter_path_isspace (*p))
553     p++;
554
555   /* Optional comma */
556   if (allow_comma && *p == ',')
557     {
558       p++;
559       while (clutter_path_isspace (*p))
560         p++;
561     }
562
563   /* Optional sign */
564   if (*p == '+')
565     p++;
566   else if (*p == '-')
567     {
568       negative = TRUE;
569       p++;
570     }
571
572   /* Some digits */
573   while (clutter_path_isdigit (*p))
574     {
575       val = val * 10 + *p - '0';
576       digit_count++;
577       p++;
578     }
579
580   /* We need at least one digit */
581   if (digit_count < 1)
582     return FALSE;
583
584   /* Optional fractional part which we ignore */
585   if (*p == '.')
586     {
587       p++;
588       digit_count = 0;
589       while (clutter_path_isdigit (*p))
590         {
591           digit_count++;
592           p++;
593         }
594       /* If there is a fractional part then it also needs at least one
595          digit */
596       if (digit_count < 1)
597         return FALSE;
598     }
599
600   *pin = p;
601   *ret = negative ? -val : val;
602
603   return TRUE;
604 }
605
606 static gboolean
607 clutter_path_parse_description (const gchar  *p,
608                                 GSList      **ret)
609 {
610   ClutterPathNodeFull *node;
611   GSList *nodes = NULL;
612
613   if (p == NULL || *p == '\0')
614     return FALSE;
615
616   while (TRUE)
617     {
618       /* Skip leading whitespace */
619       while (clutter_path_isspace (*p))
620         p++;
621
622       /* It is not an error to end now */
623       if (*p == '\0')
624         break;
625
626       switch (*p)
627         {
628         case 'M':
629         case 'm':
630         case 'L':
631         case 'l':
632           node = clutter_path_node_full_new ();
633           nodes = g_slist_prepend (nodes, node);
634
635           node->k.type = (*p == 'M' ? CLUTTER_PATH_MOVE_TO :
636                           *p == 'm' ? CLUTTER_PATH_REL_MOVE_TO :
637                           *p == 'L' ? CLUTTER_PATH_LINE_TO :
638                           CLUTTER_PATH_REL_LINE_TO);
639           p++;
640
641           if (!clutter_path_parse_number (&p, FALSE, &node->k.points[0].x) ||
642               !clutter_path_parse_number (&p, TRUE, &node->k.points[0].y))
643             goto fail;
644           break;
645
646         case 'C':
647         case 'c':
648           node = clutter_path_node_full_new ();
649           nodes = g_slist_prepend (nodes, node);
650
651           node->k.type = (*p == 'C' ? CLUTTER_PATH_CURVE_TO :
652                           CLUTTER_PATH_REL_CURVE_TO);
653           p++;
654
655           if (!clutter_path_parse_number (&p, FALSE, &node->k.points[0].x) ||
656               !clutter_path_parse_number (&p, TRUE, &node->k.points[0].y) ||
657               !clutter_path_parse_number (&p, TRUE, &node->k.points[1].x) ||
658               !clutter_path_parse_number (&p, TRUE, &node->k.points[1].y) ||
659               !clutter_path_parse_number (&p, TRUE, &node->k.points[2].x) ||
660               !clutter_path_parse_number (&p, TRUE, &node->k.points[2].y))
661             goto fail;
662           break;
663
664         case 'Z':
665         case 'z':
666           node = clutter_path_node_full_new ();
667           nodes = g_slist_prepend (nodes, node);
668           p++;
669
670           node->k.type = CLUTTER_PATH_CLOSE;
671           break;
672
673         default:
674           goto fail;
675         }
676     }
677
678   *ret = g_slist_reverse (nodes);
679   return TRUE;
680
681  fail:
682   g_slist_foreach (nodes, (GFunc) clutter_path_node_full_free, NULL);
683   g_slist_free (nodes);
684   return FALSE;
685 }
686
687 /* Takes ownership of the node list */
688 static void
689 clutter_path_add_nodes (ClutterPath *path,
690                         GSList      *nodes)
691 {
692   ClutterPathPrivate *priv = path->priv;
693
694   if (priv->nodes_tail == NULL)
695     priv->nodes = nodes;
696   else
697     priv->nodes_tail->next = nodes;
698
699   while (nodes)
700     {
701       priv->nodes_tail = nodes;
702       nodes = nodes->next;
703     }
704
705   priv->nodes_dirty = TRUE;
706 }
707
708 /**
709  * clutter_path_add_string:
710  * @path: a #ClutterPath
711  * @str: a string describing the new nodes
712  *
713  * Adds new nodes to the end of the path as described in @str. The
714  * format is a subset of the SVG path format. Each node is represented
715  * by a letter and is followed by zero, one or three pairs of
716  * coordinates. The coordinates can be separated by spaces or a
717  * comma. The types are:
718  *
719  * <variablelist>
720  * <varlistentry><term>M</term>
721  * <listitem><para>
722  * Adds a %CLUTTER_PATH_MOVE_TO node. Takes one pair of coordinates.
723  * </para></listitem></varlistentry>
724  * <varlistentry><term>L</term>
725  * <listitem><para>
726  * Adds a %CLUTTER_PATH_LINE_TO node. Takes one pair of coordinates.
727  * </para></listitem></varlistentry>
728  * <varlistentry><term>C</term>
729  * <listitem><para>
730  * Adds a %CLUTTER_PATH_CURVE_TO node. Takes three pairs of coordinates.
731  * </para></listitem></varlistentry>
732  * <varlistentry><term>z</term>
733  * <listitem><para>
734  * Adds a %CLUTTER_PATH_CLOSE node. No coordinates are needed.
735  * </para></listitem></varlistentry>
736  * </variablelist>
737  *
738  * The M, L and C commands can also be specified in lower case which
739  * means the coordinates are relative to the previous node.
740  *
741  * For example, to move an actor in a 100 by 100 pixel square centered
742  * on the point 300,300 you could use the following path:
743  *
744  * <informalexample>
745  *  <programlisting>
746  *   M 250,350 l 0 -100 L 350,250 l 0 100 z
747  *  </programlisting>
748  * </informalexample>
749  *
750  * If the path description isn't valid %FALSE will be returned and no
751  * nodes will be added.
752  *
753  * Return value: %TRUE is the path description was valid or %FALSE
754  * otherwise.
755  *
756  * Since: 1.0
757  */
758 gboolean
759 clutter_path_add_string (ClutterPath *path,
760                          const gchar *str)
761 {
762   GSList *nodes;
763
764   g_return_val_if_fail (CLUTTER_IS_PATH (path), FALSE);
765   g_return_val_if_fail (str != NULL, FALSE);
766
767   if (clutter_path_parse_description (str, &nodes))
768     {
769       clutter_path_add_nodes (path, nodes);
770
771       return TRUE;
772     }
773   else
774     return FALSE;
775 }
776
777 /**
778  * clutter_path_add_node:
779  * @path: a #ClutterPath
780  * @node: a #ClutterPathNode
781  *
782  * Adds @node to the end of the path.
783  *
784  * Since: 1.0
785  */
786 void
787 clutter_path_add_node (ClutterPath           *path,
788                        const ClutterPathNode *node)
789 {
790   ClutterPathNodeFull *node_full;
791
792   g_return_if_fail (CLUTTER_IS_PATH (path));
793   g_return_if_fail (node != NULL);
794   g_return_if_fail (CLUTTER_PATH_NODE_TYPE_IS_VALID (node->type));
795
796   node_full = clutter_path_node_full_new ();
797   node_full->k = *node;
798
799   clutter_path_add_node_full (path, node_full);
800 }
801
802 /**
803  * clutter_path_add_cairo_path:
804  * @path: a #ClutterPath
805  * @cpath: a Cairo path
806  *
807  * Add the nodes of the Cairo path to the end of @path.
808  *
809  * Since: 1.0
810  */
811 void
812 clutter_path_add_cairo_path (ClutterPath        *path,
813                              const cairo_path_t *cpath)
814 {
815   int num_data;
816   const cairo_path_data_t *p;
817
818   g_return_if_fail (CLUTTER_IS_PATH (path));
819   g_return_if_fail (cpath != NULL);
820
821   /* Iterate over each command in the cairo path */
822   for (num_data = cpath->num_data, p = cpath->data;
823        num_data > 0;
824        num_data -= p->header.length, p += p->header.length)
825     {
826       switch (p->header.type)
827         {
828         case CAIRO_PATH_MOVE_TO:
829           g_assert (p->header.length >= 2);
830
831           clutter_path_add_move_to (path, p[1].point.x, p[1].point.y);
832           break;
833
834         case CAIRO_PATH_LINE_TO:
835           g_assert (p->header.length >= 2);
836
837           clutter_path_add_line_to (path, p[1].point.x, p[1].point.y);
838           break;
839
840         case CAIRO_PATH_CURVE_TO:
841           g_assert (p->header.length >= 4);
842
843           clutter_path_add_curve_to (path,
844                                      p[1].point.x, p[1].point.y,
845                                      p[2].point.x, p[2].point.y,
846                                      p[3].point.x, p[3].point.y);
847           break;
848
849         case CAIRO_PATH_CLOSE_PATH:
850           clutter_path_add_close (path);
851         }
852     }
853 }
854
855 static void
856 clutter_path_add_node_to_cairo_path (const ClutterPathNode *node,
857                                      gpointer               data)
858 {
859   cairo_t *cr = data;
860
861   switch (node->type)
862     {
863     case CLUTTER_PATH_MOVE_TO:
864       cairo_move_to (cr, node->points[0].x, node->points[0].y);
865       break;
866
867     case CLUTTER_PATH_LINE_TO:
868       cairo_line_to (cr, node->points[0].x, node->points[0].y);
869       break;
870
871     case CLUTTER_PATH_CURVE_TO:
872       cairo_curve_to (cr,
873                       node->points[0].x, node->points[0].y,
874                       node->points[1].x, node->points[1].y,
875                       node->points[2].x, node->points[2].y);
876       break;
877
878     case CLUTTER_PATH_REL_MOVE_TO:
879       cairo_rel_move_to (cr, node->points[0].x, node->points[0].y);
880       break;
881
882     case CLUTTER_PATH_REL_LINE_TO:
883       cairo_rel_line_to (cr, node->points[0].x, node->points[0].y);
884       break;
885
886     case CLUTTER_PATH_REL_CURVE_TO:
887       cairo_rel_curve_to (cr,
888                           node->points[0].x, node->points[0].y,
889                           node->points[1].x, node->points[1].y,
890                           node->points[2].x, node->points[2].y);
891       break;
892
893     case CLUTTER_PATH_CLOSE:
894       cairo_close_path (cr);
895     }
896 }
897
898 /**
899  * clutter_path_to_cairo_path:
900  * @path: a #ClutterPath
901  * @cr: a Cairo context
902  *
903  * Add the nodes of the ClutterPath to the path in the Cairo context.
904  *
905  * Since: 1.0
906  */
907 void
908 clutter_path_to_cairo_path (ClutterPath *path,
909                             cairo_t     *cr)
910 {
911   g_return_if_fail (CLUTTER_IS_PATH (path));
912   g_return_if_fail (cr != NULL);
913
914   clutter_path_foreach (path, clutter_path_add_node_to_cairo_path, cr);
915 }
916
917 /**
918  * clutter_path_get_n_nodes:
919  * @path: a #ClutterPath
920  *
921  * Retrieves the number of nodes in the path.
922  *
923  * Return value: the number of nodes.
924  *
925  * Since: 1.0
926  */
927 guint
928 clutter_path_get_n_nodes (ClutterPath *path)
929 {
930   ClutterPathPrivate *priv;
931
932   g_return_val_if_fail (CLUTTER_IS_PATH (path), 0);
933
934   priv = path->priv;
935
936   return g_slist_length (priv->nodes);
937 }
938
939 /**
940  * clutter_path_get_node:
941  * @path: a #ClutterPath
942  * @index_: the node number to retrieve
943  * @node: (out): a location to store a copy of the node
944  *
945  * Retrieves the node of the path indexed by @index.
946  *
947  * Since: 1.0
948  */
949 void
950 clutter_path_get_node (ClutterPath     *path,
951                        guint            index_,
952                        ClutterPathNode *node)
953 {
954   ClutterPathNodeFull *node_full;
955   ClutterPathPrivate *priv;
956
957   g_return_if_fail (CLUTTER_IS_PATH (path));
958
959   priv = path->priv;
960
961   node_full = g_slist_nth_data (priv->nodes, index_);
962
963   g_return_if_fail (node_full != NULL);
964
965   *node = node_full->k;
966 }
967
968 /**
969  * clutter_path_get_nodes:
970  * @path: a #ClutterPath
971  *
972  * Returns a #GSList of #ClutterPathNode<!-- -->s. The list should be
973  * freed with g_slist_free(). The nodes are owned by the path and
974  * should not be freed. Altering the path may cause the nodes in the
975  * list to become invalid so you should copy them if you want to keep
976  * the list.
977  *
978  * Return value: (transfer container) (element-type Clutter.PathNode): a
979  *   list of nodes in the path.
980  *
981  * Since: 1.0
982  */
983 GSList *
984 clutter_path_get_nodes (ClutterPath *path)
985 {
986   ClutterPathPrivate *priv;
987
988   g_return_val_if_fail (CLUTTER_IS_PATH (path), NULL);
989
990   priv = path->priv;
991
992   return g_slist_copy (priv->nodes);
993 }
994
995 /**
996  * clutter_path_foreach:
997  * @path: a #ClutterPath
998  * @callback: (scope call): the function to call with each node
999  * @user_data: user data to pass to the function
1000  *
1001  * Calls a function for each node of the path.
1002  *
1003  * Since: 1.0
1004  */
1005 void
1006 clutter_path_foreach (ClutterPath         *path,
1007                       ClutterPathCallback  callback,
1008                       gpointer             user_data)
1009 {
1010   ClutterPathPrivate *priv;
1011
1012   g_return_if_fail (CLUTTER_IS_PATH (path));
1013
1014   priv = path->priv;
1015
1016   g_slist_foreach (priv->nodes, (GFunc) callback, user_data);
1017 }
1018
1019 /**
1020  * clutter_path_insert_node:
1021  * @path: a #ClutterPath
1022  * @index_: offset of where to insert the node
1023  * @node: the node to insert
1024  *
1025  * Inserts @node into the path before the node at the given offset. If
1026  * @index_ is negative it will append the node to the end of the path.
1027  *
1028  * Since: 1.0
1029  */
1030 void
1031 clutter_path_insert_node (ClutterPath           *path,
1032                           gint                   index_,
1033                           const ClutterPathNode *node)
1034 {
1035   ClutterPathPrivate *priv;
1036   ClutterPathNodeFull *node_full;
1037
1038   g_return_if_fail (CLUTTER_IS_PATH (path));
1039   g_return_if_fail (node != NULL);
1040   g_return_if_fail (CLUTTER_PATH_NODE_TYPE_IS_VALID (node->type));
1041
1042   priv = path->priv;
1043
1044   node_full = clutter_path_node_full_new ();
1045   node_full->k = *node;
1046
1047   priv->nodes = g_slist_insert (priv->nodes, node_full, index_);
1048
1049   if (priv->nodes_tail == NULL)
1050     priv->nodes_tail = priv->nodes;
1051   else if (priv->nodes_tail->next)
1052     priv->nodes_tail = priv->nodes_tail->next;
1053
1054   priv->nodes_dirty = TRUE;
1055 }
1056
1057 /**
1058  * clutter_path_remove_node:
1059  * @path: a #ClutterPath
1060  * @index_: index of the node to remove
1061  *
1062  * Removes the node at the given offset from the path.
1063  *
1064  * Since: 1.0
1065  */
1066 void
1067 clutter_path_remove_node (ClutterPath *path,
1068                           guint        index_)
1069 {
1070   ClutterPathPrivate *priv;
1071   GSList *node, *prev = NULL;
1072
1073   g_return_if_fail (CLUTTER_IS_PATH (path));
1074
1075   priv = path->priv;
1076
1077   for (node = priv->nodes; node && index_--; node = node->next)
1078     prev = node;
1079
1080   if (node)
1081     {
1082       clutter_path_node_full_free (node->data);
1083
1084       if (prev)
1085         prev->next = node->next;
1086       else
1087         priv->nodes = node->next;
1088
1089       if (node == priv->nodes_tail)
1090         priv->nodes_tail = prev;
1091
1092       g_slist_free_1 (node);
1093
1094       priv->nodes_dirty = TRUE;
1095     }
1096 }
1097
1098 /**
1099  * clutter_path_replace_node:
1100  * @path: a #ClutterPath
1101  * @index_: index to the existing node
1102  * @node: the replacement node
1103  *
1104  * Replaces the node at offset @index_ with @node.
1105  *
1106  * Since: 1.0
1107  */
1108 void
1109 clutter_path_replace_node (ClutterPath           *path,
1110                            guint                  index_,
1111                            const ClutterPathNode *node)
1112 {
1113   ClutterPathPrivate *priv;
1114   ClutterPathNodeFull *node_full;
1115
1116   g_return_if_fail (CLUTTER_IS_PATH (path));
1117   g_return_if_fail (node != NULL);
1118   g_return_if_fail (CLUTTER_PATH_NODE_TYPE_IS_VALID (node->type));
1119
1120   priv = path->priv;
1121
1122   if ((node_full = g_slist_nth_data (priv->nodes, index_)))
1123     {
1124       node_full->k = *node;
1125
1126       priv->nodes_dirty = TRUE;
1127     }
1128 }
1129
1130 /**
1131  * clutter_path_set_description:
1132  * @path: a #ClutterPath
1133  * @str: a string describing the path
1134  *
1135  * Replaces all of the nodes in the path with nodes described by
1136  * @str. See clutter_path_add_string() for details of the format.
1137  *
1138  * If the string is invalid then %FALSE is returned and the path is
1139  * unaltered.
1140  *
1141  * Return value: %TRUE is the path was valid, %FALSE otherwise.
1142  *
1143  * Since: 1.0
1144  */
1145 gboolean
1146 clutter_path_set_description (ClutterPath *path,
1147                               const gchar *str)
1148 {
1149   GSList *nodes;
1150
1151   g_return_val_if_fail (CLUTTER_IS_PATH (path), FALSE);
1152   g_return_val_if_fail (str != NULL, FALSE);
1153
1154   if (clutter_path_parse_description (str, &nodes))
1155     {
1156       clutter_path_clear (path);
1157       clutter_path_add_nodes (path, nodes);
1158
1159       return TRUE;
1160     }
1161   else
1162     return FALSE;
1163 }
1164
1165 /**
1166  * clutter_path_get_description:
1167  * @path: a #ClutterPath
1168  *
1169  * Returns a newly allocated string describing the path in the same
1170  * format as used by clutter_path_add_string().
1171  *
1172  * Return value: a string description of the path. Free with g_free().
1173  *
1174  * Since: 1.0
1175  */
1176 gchar *
1177 clutter_path_get_description (ClutterPath *path)
1178 {
1179   ClutterPathPrivate *priv;
1180   GString *str;
1181   GSList *l;
1182
1183   g_return_val_if_fail (CLUTTER_IS_PATH (path), NULL);
1184
1185   priv = path->priv;
1186
1187   str = g_string_new ("");
1188
1189   for (l = priv->nodes; l; l = l->next)
1190     {
1191       ClutterPathNodeFull *node = l->data;
1192       gchar letter = '?';
1193       gint params = 0;
1194       gint i;
1195
1196       switch (node->k.type)
1197         {
1198         case CLUTTER_PATH_MOVE_TO:
1199           letter = 'M';
1200           params = 1;
1201           break;
1202
1203         case CLUTTER_PATH_REL_MOVE_TO:
1204           letter = 'm';
1205           params = 1;
1206           break;
1207
1208         case CLUTTER_PATH_LINE_TO:
1209           letter = 'L';
1210           params = 1;
1211           break;
1212
1213         case CLUTTER_PATH_REL_LINE_TO:
1214           letter = 'l';
1215           params = 1;
1216           break;
1217
1218         case CLUTTER_PATH_CURVE_TO:
1219           letter = 'C';
1220           params = 3;
1221           break;
1222
1223         case CLUTTER_PATH_REL_CURVE_TO:
1224           letter = 'c';
1225           params = 3;
1226           break;
1227
1228         case CLUTTER_PATH_CLOSE:
1229           letter = 'z';
1230           params = 0;
1231           break;
1232         }
1233
1234       if (str->len > 0)
1235         g_string_append_c (str, ' ');
1236       g_string_append_c (str, letter);
1237
1238       for (i = 0; i < params; i++)
1239         g_string_append_printf (str, " %i %i",
1240                                 node->k.points[i].x,
1241                                 node->k.points[i].y);
1242     }
1243
1244   return g_string_free (str, FALSE);
1245 }
1246
1247 static guint
1248 clutter_path_node_distance (const ClutterKnot *start,
1249                             const ClutterKnot *end)
1250 {
1251   gint64 x_d, y_d;
1252   float t;
1253
1254   g_return_val_if_fail (start != NULL, 0);
1255   g_return_val_if_fail (end != NULL, 0);
1256
1257   if (clutter_knot_equal (start, end))
1258     return 0;
1259
1260   x_d = end->x - start->x;
1261   y_d = end->y - start->y;
1262
1263   t = floorf (sqrtf ((x_d * x_d) + (y_d * y_d)));
1264
1265   return (guint) t;
1266 }
1267
1268 static void
1269 clutter_path_ensure_node_data (ClutterPath *path)
1270 {
1271   ClutterPathPrivate *priv = path->priv;
1272
1273   /* Recalculate the nodes data if has changed */
1274   if (priv->nodes_dirty)
1275     {
1276       GSList *l;
1277       ClutterKnot last_position = { 0, 0 };
1278       ClutterKnot loop_start = { 0, 0 };
1279       ClutterKnot points[3];
1280
1281       priv->total_length = 0;
1282
1283       for (l = priv->nodes; l; l = l->next)
1284         {
1285           ClutterPathNodeFull *node = l->data;
1286           gboolean relative = (node->k.type & CLUTTER_PATH_RELATIVE) != 0;
1287
1288           switch (node->k.type & ~CLUTTER_PATH_RELATIVE)
1289             {
1290             case CLUTTER_PATH_MOVE_TO:
1291               node->length = 0;
1292
1293               /* Store the actual position in point[1] */
1294               if (relative)
1295                 {
1296                   node->k.points[1].x = last_position.x + node->k.points[0].x;
1297                   node->k.points[1].y = last_position.y + node->k.points[0].y;
1298                 }
1299               else
1300                 node->k.points[1] = node->k.points[0];
1301
1302               last_position = node->k.points[1];
1303               loop_start = node->k.points[1];
1304               break;
1305
1306             case CLUTTER_PATH_LINE_TO:
1307               /* Use point[1] as the start point and point[2] as the end
1308                  point */
1309               node->k.points[1] = last_position;
1310
1311               if (relative)
1312                 {
1313                   node->k.points[2].x = (node->k.points[1].x
1314                                          + node->k.points[0].x);
1315                   node->k.points[2].y = (node->k.points[1].y
1316                                          + node->k.points[0].y);
1317                 }
1318               else
1319                 node->k.points[2] = node->k.points[0];
1320
1321               last_position = node->k.points[2];
1322
1323               node->length = clutter_path_node_distance (node->k.points + 1,
1324                                                          node->k.points + 2);
1325               break;
1326
1327             case CLUTTER_PATH_CURVE_TO:
1328               /* Convert to a bezier curve */
1329               if (node->bezier == NULL)
1330                 node->bezier = _clutter_bezier_new ();
1331
1332               if (relative)
1333                 {
1334                   int i;
1335
1336                   for (i = 0; i < 3; i++)
1337                     {
1338                       points[i].x = last_position.x + node->k.points[i].x;
1339                       points[i].y = last_position.y + node->k.points[i].y;
1340                     }
1341                 }
1342               else
1343                 memcpy (points, node->k.points, sizeof (ClutterKnot) * 3);
1344
1345               _clutter_bezier_init (node->bezier,
1346                                     last_position.x, last_position.y,
1347                                     points[0].x, points[0].y,
1348                                     points[1].x, points[1].y,
1349                                     points[2].x, points[2].y);
1350
1351               last_position = points[2];
1352
1353               node->length = _clutter_bezier_get_length (node->bezier);
1354
1355               break;
1356
1357             case CLUTTER_PATH_CLOSE:
1358               /* Convert to a line to from last_point to loop_start */
1359               node->k.points[1] = last_position;
1360               node->k.points[2] = loop_start;
1361               last_position = node->k.points[2];
1362
1363               node->length = clutter_path_node_distance (node->k.points + 1,
1364                                                          node->k.points + 2);
1365               break;
1366             }
1367
1368           priv->total_length += node->length;
1369         }
1370
1371       priv->nodes_dirty = FALSE;
1372     }
1373 }
1374
1375 /**
1376  * clutter_path_get_position:
1377  * @path: a #ClutterPath
1378  * @progress: a position along the path as a fraction of its length
1379  * @position: (out): location to store the position
1380  *
1381  * The value in @progress represents a position along the path where
1382  * 0.0 is the beginning and 1.0 is the end of the path. An
1383  * interpolated position is then stored in @position.
1384  *
1385  * Return value: index of the node used to calculate the position.
1386  *
1387  * Since: 1.0
1388  */
1389 guint
1390 clutter_path_get_position (ClutterPath *path,
1391                            gdouble progress,
1392                            ClutterKnot *position)
1393 {
1394   ClutterPathPrivate *priv;
1395   GSList *l;
1396   guint point_distance, length = 0, node_num = 0;
1397   ClutterPathNodeFull *node;
1398
1399   g_return_val_if_fail (CLUTTER_IS_PATH (path), 0);
1400   g_return_val_if_fail (progress >= 0.0 && progress <= 1.0, 0);
1401
1402   priv = path->priv;
1403
1404   clutter_path_ensure_node_data (path);
1405
1406   /* Special case if the path is empty, just return 0,0 for want of
1407      something better */
1408   if (priv->nodes == NULL)
1409     {
1410       memset (position, 0, sizeof (ClutterKnot));
1411       return 0;
1412     }
1413
1414   /* Convert the progress to a length along the path */
1415   point_distance = progress * priv->total_length;
1416
1417   /* Find the node that covers this point */
1418   for (l = priv->nodes;
1419        l->next && point_distance >= (((ClutterPathNodeFull *) l->data)->length
1420                                      + length);
1421        l = l->next)
1422     {
1423       length += ((ClutterPathNodeFull *) l->data)->length;
1424       node_num++;
1425     }
1426
1427   node = l->data;
1428
1429   /* Convert the point distance to a distance along the node */
1430   point_distance -= length;
1431   if (point_distance > node->length)
1432     point_distance = node->length;
1433
1434   switch (node->k.type & ~CLUTTER_PATH_RELATIVE)
1435     {
1436     case CLUTTER_PATH_MOVE_TO:
1437       *position = node->k.points[1];
1438       break;
1439
1440     case CLUTTER_PATH_LINE_TO:
1441     case CLUTTER_PATH_CLOSE:
1442       if (node->length == 0)
1443         *position = node->k.points[1];
1444       else
1445         {
1446           position->x = (node->k.points[1].x
1447                          + ((node->k.points[2].x - node->k.points[1].x)
1448                             * (gint) point_distance / (gint) node->length));
1449           position->y = (node->k.points[1].y
1450                          + ((node->k.points[2].y - node->k.points[1].y)
1451                             * (gint) point_distance / (gint) node->length));
1452         }
1453       break;
1454
1455     case CLUTTER_PATH_CURVE_TO:
1456       if (node->length == 0)
1457         *position = node->k.points[2];
1458       else
1459         {
1460           _clutter_bezier_advance (node->bezier,
1461                                    point_distance * CLUTTER_BEZIER_MAX_LENGTH
1462                                    / node->length,
1463                                    position);
1464         }
1465       break;
1466     }
1467
1468   return node_num;
1469 }
1470
1471 /**
1472  * clutter_path_get_length:
1473  * @path: a #ClutterPath
1474  *
1475  * Retrieves an approximation of the total length of the path.
1476  *
1477  * Return value: the length of the path.
1478  *
1479  * Since: 1.0
1480  */
1481 guint
1482 clutter_path_get_length (ClutterPath *path)
1483 {
1484   g_return_val_if_fail (CLUTTER_IS_PATH (path), 0);
1485
1486   clutter_path_ensure_node_data (path);
1487
1488   return path->priv->total_length;
1489 }
1490
1491 static ClutterPathNodeFull *
1492 clutter_path_node_full_new (void)
1493 {
1494   return g_slice_new0 (ClutterPathNodeFull);
1495 }
1496
1497 static void
1498 clutter_path_node_full_free (ClutterPathNodeFull *node)
1499 {
1500   if (node->bezier)
1501     _clutter_bezier_free (node->bezier);
1502
1503   g_slice_free (ClutterPathNodeFull, node);
1504 }
1505
1506 /**
1507  * clutter_path_node_copy:
1508  * @node: a #ClutterPathNode
1509  *
1510  * Makes an allocated copy of a node.
1511  *
1512  * Return value: the copied node.
1513  *
1514  * Since: 1.0
1515  */
1516 ClutterPathNode *
1517 clutter_path_node_copy (const ClutterPathNode *node)
1518 {
1519   return g_slice_dup (ClutterPathNode, node);
1520 }
1521
1522 /**
1523  * clutter_path_node_free:
1524  * @node: a #ClutterPathNode
1525  *
1526  * Frees the memory of an allocated node.
1527  *
1528  * Since: 1.0
1529  */
1530 void
1531 clutter_path_node_free (ClutterPathNode *node)
1532 {
1533   if (G_LIKELY (node))
1534     g_slice_free (ClutterPathNode, node);
1535 }
1536
1537 /**
1538  * clutter_path_node_equal:
1539  * @node_a: First node
1540  * @node_b: Second node
1541  *
1542  * Compares two nodes and checks if they are the same type with the
1543  * same coordinates.
1544  *
1545  * Return value: %TRUE if the nodes are the same.
1546  *
1547  * Since: 1.0
1548  */
1549 gboolean
1550 clutter_path_node_equal (const ClutterPathNode *node_a,
1551                          const ClutterPathNode *node_b)
1552 {
1553   guint n_points, i;
1554
1555   g_return_val_if_fail (node_a != NULL, FALSE);
1556   g_return_val_if_fail (node_b != NULL, FALSE);
1557
1558   if (node_a->type != node_b->type)
1559     return FALSE;
1560
1561   switch (node_a->type & ~CLUTTER_PATH_RELATIVE)
1562     {
1563     case CLUTTER_PATH_MOVE_TO: n_points = 1; break;
1564     case CLUTTER_PATH_LINE_TO: n_points = 1; break;
1565     case CLUTTER_PATH_CURVE_TO: n_points = 3; break;
1566     case CLUTTER_PATH_CLOSE: n_points = 0; break;
1567     default: return FALSE;
1568     }
1569
1570   for (i = 0; i < n_points; i++)
1571     if (node_a->points[i].x != node_b->points[i].x
1572         || node_a->points[i].y != node_b->points[i].y)
1573       return FALSE;
1574
1575   return TRUE;
1576 }
1577
1578 G_DEFINE_BOXED_TYPE (ClutterKnot, clutter_knot,
1579                      clutter_knot_copy,
1580                      clutter_knot_free);
1581
1582 /**
1583  * clutter_knot_copy:
1584  * @knot: a #ClutterKnot
1585  *
1586  * Makes an allocated copy of a knot.
1587  *
1588  * Return value: the copied knot.
1589  *
1590  * Since: 0.2
1591  */
1592 ClutterKnot *
1593 clutter_knot_copy (const ClutterKnot *knot)
1594 {
1595   if (G_UNLIKELY (knot == NULL))
1596     return NULL;
1597
1598   return g_slice_dup (ClutterKnot, knot);
1599 }
1600
1601 /**
1602  * clutter_knot_free:
1603  * @knot: a #ClutterKnot
1604  *
1605  * Frees the memory of an allocated knot.
1606  *
1607  * Since: 0.2
1608  */
1609 void
1610 clutter_knot_free (ClutterKnot *knot)
1611 {
1612   if (G_LIKELY (knot != NULL))
1613     g_slice_free (ClutterKnot, knot);
1614 }
1615
1616 /**
1617  * clutter_knot_equal:
1618  * @knot_a: First knot
1619  * @knot_b: Second knot
1620  *
1621  * Compares to knot and checks if the point to the same location.
1622  *
1623  * Return value: %TRUE if the knots point to the same location.
1624  *
1625  * Since: 0.2
1626  */
1627 gboolean
1628 clutter_knot_equal (const ClutterKnot *knot_a,
1629                     const ClutterKnot *knot_b)
1630 {
1631   g_return_val_if_fail (knot_a != NULL, FALSE);
1632   g_return_val_if_fail (knot_b != NULL, FALSE);
1633
1634   if (knot_a == knot_b)
1635     return TRUE;
1636
1637   return knot_a->x == knot_b->x && knot_a->y == knot_b->y;
1638 }