4 * An OpenGL based 'interactive canvas' library.
6 * Authored By Matthew Allum <mallum@openedhand.com>
8 * Copyright (C) 2008 Intel Corporation
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.
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.
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/>.
25 * SECTION:clutter-path
26 * @short_description: An object describing a path with straight lines
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.
33 * The path consists of a series of nodes. Each node is one of the
34 * following four types:
37 * <varlistentry><term>%CLUTTER_PATH_MOVE_TO</term>
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>
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>
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>
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>
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.
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.
74 * #ClutterPath is available since Clutter 1.0
84 #include <glib-object.h>
86 #include "clutter-path.h"
87 #include "clutter-types.h"
88 #include "clutter-bezier.h"
89 #include "clutter-private.h"
91 #define CLUTTER_PATH_GET_PRIVATE(obj) \
92 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_PATH, \
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)
110 static GParamSpec *obj_props[PROP_LAST];
112 typedef struct _ClutterPathNodeFull ClutterPathNodeFull;
114 struct _ClutterPathNodeFull
118 ClutterBezier *bezier;
123 struct _ClutterPathPrivate
125 GSList *nodes, *nodes_tail;
126 gboolean nodes_dirty;
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')
135 static ClutterPathNodeFull *clutter_path_node_full_new (void);
136 static void clutter_path_node_full_free (ClutterPathNodeFull *node);
138 static void clutter_path_finalize (GObject *object);
140 static void clutter_value_transform_path_string (const GValue *src,
142 static void clutter_value_transform_string_path (const GValue *src,
145 G_DEFINE_BOXED_TYPE (ClutterPathNode, clutter_path_node,
146 clutter_path_node_copy,
147 clutter_path_node_free);
149 G_DEFINE_TYPE_WITH_CODE (ClutterPath,
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));
156 clutter_path_get_property (GObject *gobject,
161 ClutterPath *path = CLUTTER_PATH (gobject);
165 case PROP_DESCRIPTION:
166 g_value_take_string (value, clutter_path_get_description (path));
169 g_value_set_uint (value, clutter_path_get_length (path));
172 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
178 clutter_path_set_property (GObject *gobject,
183 ClutterPath *path = CLUTTER_PATH (gobject);
187 case PROP_DESCRIPTION:
188 if (!clutter_path_set_description (path, g_value_get_string (value)))
189 g_warning ("Invalid path description");
192 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
198 clutter_path_class_init (ClutterPathClass *klass)
200 GObjectClass *gobject_class = (GObjectClass *) klass;
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;
207 pspec = g_param_spec_string ("description",
209 "SVG-style description of the path",
211 CLUTTER_PARAM_READWRITE);
212 obj_props[PROP_DESCRIPTION] = pspec;
213 g_object_class_install_property (gobject_class, PROP_DESCRIPTION, pspec);
215 pspec = g_param_spec_uint ("length",
217 "An approximation of the total length "
220 CLUTTER_PARAM_READABLE);
221 obj_props[PROP_LENGTH] = pspec;
222 g_object_class_install_property (gobject_class, PROP_LENGTH, pspec);
224 g_type_class_add_private (klass, sizeof (ClutterPathPrivate));
228 clutter_path_init (ClutterPath *self)
230 self->priv = CLUTTER_PATH_GET_PRIVATE (self);
234 clutter_value_transform_path_string (const GValue *src,
237 gchar *string = clutter_path_get_description (src->data[0].v_pointer);
239 g_value_take_string (dest, string);
243 clutter_value_transform_string_path (const GValue *src,
246 ClutterPath *new_path;
248 new_path = clutter_path_new_with_description (g_value_get_string (src));
249 g_value_take_object (dest, new_path);
253 clutter_path_finalize (GObject *object)
255 ClutterPath *self = (ClutterPath *) object;
257 clutter_path_clear (self);
259 G_OBJECT_CLASS (clutter_path_parent_class)->finalize (object);
265 * Creates a new #ClutterPath instance with no nodes.
267 * The object has a floating reference so if you add it to a
268 * #ClutterBehaviourPath then you do not need to unref it.
270 * Return value: the newly created #ClutterPath
275 clutter_path_new (void)
277 ClutterPath *self = g_object_new (CLUTTER_TYPE_PATH, NULL);
283 * clutter_path_new_with_description:
284 * @desc: a string describing the path
286 * Creates a new #ClutterPath instance with the nodes described in
287 * @desc. See clutter_path_add_string() for details of the format of
290 * The object has a floating reference so if you add it to a
291 * #ClutterBehaviourPath then you do not need to unref it.
293 * Return value: the newly created #ClutterPath
298 clutter_path_new_with_description (const gchar *desc)
300 return g_object_new (CLUTTER_TYPE_PATH,
306 * clutter_path_clear:
307 * @path: a #ClutterPath
309 * Removes all nodes from the path.
314 clutter_path_clear (ClutterPath *path)
316 ClutterPathPrivate *priv = path->priv;
318 g_slist_foreach (priv->nodes, (GFunc) clutter_path_node_full_free, NULL);
319 g_slist_free (priv->nodes);
321 priv->nodes = priv->nodes_tail = NULL;
322 priv->nodes_dirty = TRUE;
325 /* Takes ownership of the node */
327 clutter_path_add_node_full (ClutterPath *path,
328 ClutterPathNodeFull *node)
330 ClutterPathPrivate *priv = path->priv;
333 new_node = g_slist_prepend (NULL, node);
335 if (priv->nodes_tail == NULL)
336 priv->nodes = new_node;
338 priv->nodes_tail->next = new_node;
340 priv->nodes_tail = new_node;
342 priv->nodes_dirty = TRUE;
345 /* Helper function to make the rest of teh add_* functions shorter */
347 clutter_path_add_node_helper (ClutterPath *path,
348 ClutterPathNodeType type,
352 ClutterPathNodeFull *node;
356 node = clutter_path_node_full_new ();
360 va_start (ap, num_coords);
362 for (i = 0; i < num_coords; i++)
364 node->k.points[i].x = va_arg (ap, gint);
365 node->k.points[i].y = va_arg (ap, gint);
370 clutter_path_add_node_full (path, node);
374 * clutter_path_add_move_to:
375 * @path: a #ClutterPath
376 * @x: the x coordinate
377 * @y: the y coordinate
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.
386 clutter_path_add_move_to (ClutterPath *path,
390 g_return_if_fail (CLUTTER_IS_PATH (path));
392 clutter_path_add_node_helper (path, CLUTTER_PATH_MOVE_TO, 1, x, y);
396 * clutter_path_add_rel_move_to:
397 * @path: a #ClutterPath
398 * @x: the x coordinate
399 * @y: the y coordinate
401 * Same as clutter_path_add_move_to() except the coordinates are
402 * relative to the previous node.
407 clutter_path_add_rel_move_to (ClutterPath *path,
411 g_return_if_fail (CLUTTER_IS_PATH (path));
413 clutter_path_add_node_helper (path, CLUTTER_PATH_REL_MOVE_TO, 1, x, y);
417 * clutter_path_add_line_to:
418 * @path: a #ClutterPath
419 * @x: the x coordinate
420 * @y: the y coordinate
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.
428 clutter_path_add_line_to (ClutterPath *path,
432 g_return_if_fail (CLUTTER_IS_PATH (path));
434 clutter_path_add_node_helper (path, CLUTTER_PATH_LINE_TO, 1, x, y);
438 * clutter_path_add_rel_line_to:
439 * @path: a #ClutterPath
440 * @x: the x coordinate
441 * @y: the y coordinate
443 * Same as clutter_path_add_line_to() except the coordinates are
444 * relative to the previous node.
449 clutter_path_add_rel_line_to (ClutterPath *path,
453 g_return_if_fail (CLUTTER_IS_PATH (path));
455 clutter_path_add_node_helper (path, CLUTTER_PATH_REL_LINE_TO, 1, x, y);
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
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.
475 clutter_path_add_curve_to (ClutterPath *path,
483 g_return_if_fail (CLUTTER_IS_PATH (path));
485 clutter_path_add_node_helper (path, CLUTTER_PATH_CURVE_TO, 3,
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
501 * Same as clutter_path_add_curve_to() except the coordinates are
502 * relative to the previous node.
507 clutter_path_add_rel_curve_to (ClutterPath *path,
515 g_return_if_fail (CLUTTER_IS_PATH (path));
517 clutter_path_add_node_helper (path, CLUTTER_PATH_REL_CURVE_TO, 3,
524 * clutter_path_add_close:
525 * @path: a #ClutterPath
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
534 clutter_path_add_close (ClutterPath *path)
536 g_return_if_fail (CLUTTER_IS_PATH (path));
538 clutter_path_add_node_helper (path, CLUTTER_PATH_CLOSE, 0);
542 clutter_path_parse_number (const gchar **pin,
543 gboolean allow_comma,
547 gboolean negative = FALSE;
548 gint digit_count = 0;
549 const gchar *p = *pin;
551 /* Skip leading spaces */
552 while (clutter_path_isspace (*p))
556 if (allow_comma && *p == ',')
559 while (clutter_path_isspace (*p))
573 while (clutter_path_isdigit (*p))
575 val = val * 10 + *p - '0';
580 /* We need at least one digit */
584 /* Optional fractional part which we ignore */
589 while (clutter_path_isdigit (*p))
594 /* If there is a fractional part then it also needs at least one
601 *ret = negative ? -val : val;
607 clutter_path_parse_description (const gchar *p,
610 ClutterPathNodeFull *node;
611 GSList *nodes = NULL;
613 if (p == NULL || *p == '\0')
618 /* Skip leading whitespace */
619 while (clutter_path_isspace (*p))
622 /* It is not an error to end now */
632 node = clutter_path_node_full_new ();
633 nodes = g_slist_prepend (nodes, node);
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);
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))
648 node = clutter_path_node_full_new ();
649 nodes = g_slist_prepend (nodes, node);
651 node->k.type = (*p == 'C' ? CLUTTER_PATH_CURVE_TO :
652 CLUTTER_PATH_REL_CURVE_TO);
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))
666 node = clutter_path_node_full_new ();
667 nodes = g_slist_prepend (nodes, node);
670 node->k.type = CLUTTER_PATH_CLOSE;
678 *ret = g_slist_reverse (nodes);
682 g_slist_foreach (nodes, (GFunc) clutter_path_node_full_free, NULL);
683 g_slist_free (nodes);
687 /* Takes ownership of the node list */
689 clutter_path_add_nodes (ClutterPath *path,
692 ClutterPathPrivate *priv = path->priv;
694 if (priv->nodes_tail == NULL)
697 priv->nodes_tail->next = nodes;
701 priv->nodes_tail = nodes;
705 priv->nodes_dirty = TRUE;
709 * clutter_path_add_string:
710 * @path: a #ClutterPath
711 * @str: a string describing the new nodes
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:
720 * <varlistentry><term>M</term>
722 * Adds a %CLUTTER_PATH_MOVE_TO node. Takes one pair of coordinates.
723 * </para></listitem></varlistentry>
724 * <varlistentry><term>L</term>
726 * Adds a %CLUTTER_PATH_LINE_TO node. Takes one pair of coordinates.
727 * </para></listitem></varlistentry>
728 * <varlistentry><term>C</term>
730 * Adds a %CLUTTER_PATH_CURVE_TO node. Takes three pairs of coordinates.
731 * </para></listitem></varlistentry>
732 * <varlistentry><term>z</term>
734 * Adds a %CLUTTER_PATH_CLOSE node. No coordinates are needed.
735 * </para></listitem></varlistentry>
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.
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:
746 * M 250,350 l 0 -100 L 350,250 l 0 100 z
750 * If the path description isn't valid %FALSE will be returned and no
751 * nodes will be added.
753 * Return value: %TRUE is the path description was valid or %FALSE
759 clutter_path_add_string (ClutterPath *path,
764 g_return_val_if_fail (CLUTTER_IS_PATH (path), FALSE);
765 g_return_val_if_fail (str != NULL, FALSE);
767 if (clutter_path_parse_description (str, &nodes))
769 clutter_path_add_nodes (path, nodes);
778 * clutter_path_add_node:
779 * @path: a #ClutterPath
780 * @node: a #ClutterPathNode
782 * Adds @node to the end of the path.
787 clutter_path_add_node (ClutterPath *path,
788 const ClutterPathNode *node)
790 ClutterPathNodeFull *node_full;
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));
796 node_full = clutter_path_node_full_new ();
797 node_full->k = *node;
799 clutter_path_add_node_full (path, node_full);
803 * clutter_path_add_cairo_path:
804 * @path: a #ClutterPath
805 * @cpath: a Cairo path
807 * Add the nodes of the Cairo path to the end of @path.
812 clutter_path_add_cairo_path (ClutterPath *path,
813 const cairo_path_t *cpath)
816 const cairo_path_data_t *p;
818 g_return_if_fail (CLUTTER_IS_PATH (path));
819 g_return_if_fail (cpath != NULL);
821 /* Iterate over each command in the cairo path */
822 for (num_data = cpath->num_data, p = cpath->data;
824 num_data -= p->header.length, p += p->header.length)
826 switch (p->header.type)
828 case CAIRO_PATH_MOVE_TO:
829 g_assert (p->header.length >= 2);
831 clutter_path_add_move_to (path, p[1].point.x, p[1].point.y);
834 case CAIRO_PATH_LINE_TO:
835 g_assert (p->header.length >= 2);
837 clutter_path_add_line_to (path, p[1].point.x, p[1].point.y);
840 case CAIRO_PATH_CURVE_TO:
841 g_assert (p->header.length >= 4);
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);
849 case CAIRO_PATH_CLOSE_PATH:
850 clutter_path_add_close (path);
856 clutter_path_add_node_to_cairo_path (const ClutterPathNode *node,
863 case CLUTTER_PATH_MOVE_TO:
864 cairo_move_to (cr, node->points[0].x, node->points[0].y);
867 case CLUTTER_PATH_LINE_TO:
868 cairo_line_to (cr, node->points[0].x, node->points[0].y);
871 case CLUTTER_PATH_CURVE_TO:
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);
878 case CLUTTER_PATH_REL_MOVE_TO:
879 cairo_rel_move_to (cr, node->points[0].x, node->points[0].y);
882 case CLUTTER_PATH_REL_LINE_TO:
883 cairo_rel_line_to (cr, node->points[0].x, node->points[0].y);
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);
893 case CLUTTER_PATH_CLOSE:
894 cairo_close_path (cr);
899 * clutter_path_to_cairo_path:
900 * @path: a #ClutterPath
901 * @cr: a Cairo context
903 * Add the nodes of the ClutterPath to the path in the Cairo context.
908 clutter_path_to_cairo_path (ClutterPath *path,
911 g_return_if_fail (CLUTTER_IS_PATH (path));
912 g_return_if_fail (cr != NULL);
914 clutter_path_foreach (path, clutter_path_add_node_to_cairo_path, cr);
918 * clutter_path_get_n_nodes:
919 * @path: a #ClutterPath
921 * Retrieves the number of nodes in the path.
923 * Return value: the number of nodes.
928 clutter_path_get_n_nodes (ClutterPath *path)
930 ClutterPathPrivate *priv;
932 g_return_val_if_fail (CLUTTER_IS_PATH (path), 0);
936 return g_slist_length (priv->nodes);
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
945 * Retrieves the node of the path indexed by @index.
950 clutter_path_get_node (ClutterPath *path,
952 ClutterPathNode *node)
954 ClutterPathNodeFull *node_full;
955 ClutterPathPrivate *priv;
957 g_return_if_fail (CLUTTER_IS_PATH (path));
961 node_full = g_slist_nth_data (priv->nodes, index_);
963 g_return_if_fail (node_full != NULL);
965 *node = node_full->k;
969 * clutter_path_get_nodes:
970 * @path: a #ClutterPath
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
978 * Return value: (transfer container) (element-type Clutter.PathNode): a
979 * list of nodes in the path.
984 clutter_path_get_nodes (ClutterPath *path)
986 ClutterPathPrivate *priv;
988 g_return_val_if_fail (CLUTTER_IS_PATH (path), NULL);
992 return g_slist_copy (priv->nodes);
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
1001 * Calls a function for each node of the path.
1006 clutter_path_foreach (ClutterPath *path,
1007 ClutterPathCallback callback,
1010 ClutterPathPrivate *priv;
1012 g_return_if_fail (CLUTTER_IS_PATH (path));
1016 g_slist_foreach (priv->nodes, (GFunc) callback, user_data);
1020 * clutter_path_insert_node:
1021 * @path: a #ClutterPath
1022 * @index_: offset of where to insert the node
1023 * @node: the node to insert
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.
1031 clutter_path_insert_node (ClutterPath *path,
1033 const ClutterPathNode *node)
1035 ClutterPathPrivate *priv;
1036 ClutterPathNodeFull *node_full;
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));
1044 node_full = clutter_path_node_full_new ();
1045 node_full->k = *node;
1047 priv->nodes = g_slist_insert (priv->nodes, node_full, index_);
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;
1054 priv->nodes_dirty = TRUE;
1058 * clutter_path_remove_node:
1059 * @path: a #ClutterPath
1060 * @index_: index of the node to remove
1062 * Removes the node at the given offset from the path.
1067 clutter_path_remove_node (ClutterPath *path,
1070 ClutterPathPrivate *priv;
1071 GSList *node, *prev = NULL;
1073 g_return_if_fail (CLUTTER_IS_PATH (path));
1077 for (node = priv->nodes; node && index_--; node = node->next)
1082 clutter_path_node_full_free (node->data);
1085 prev->next = node->next;
1087 priv->nodes = node->next;
1089 if (node == priv->nodes_tail)
1090 priv->nodes_tail = prev;
1092 g_slist_free_1 (node);
1094 priv->nodes_dirty = TRUE;
1099 * clutter_path_replace_node:
1100 * @path: a #ClutterPath
1101 * @index_: index to the existing node
1102 * @node: the replacement node
1104 * Replaces the node at offset @index_ with @node.
1109 clutter_path_replace_node (ClutterPath *path,
1111 const ClutterPathNode *node)
1113 ClutterPathPrivate *priv;
1114 ClutterPathNodeFull *node_full;
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));
1122 if ((node_full = g_slist_nth_data (priv->nodes, index_)))
1124 node_full->k = *node;
1126 priv->nodes_dirty = TRUE;
1131 * clutter_path_set_description:
1132 * @path: a #ClutterPath
1133 * @str: a string describing the path
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.
1138 * If the string is invalid then %FALSE is returned and the path is
1141 * Return value: %TRUE is the path was valid, %FALSE otherwise.
1146 clutter_path_set_description (ClutterPath *path,
1151 g_return_val_if_fail (CLUTTER_IS_PATH (path), FALSE);
1152 g_return_val_if_fail (str != NULL, FALSE);
1154 if (clutter_path_parse_description (str, &nodes))
1156 clutter_path_clear (path);
1157 clutter_path_add_nodes (path, nodes);
1166 * clutter_path_get_description:
1167 * @path: a #ClutterPath
1169 * Returns a newly allocated string describing the path in the same
1170 * format as used by clutter_path_add_string().
1172 * Return value: a string description of the path. Free with g_free().
1177 clutter_path_get_description (ClutterPath *path)
1179 ClutterPathPrivate *priv;
1183 g_return_val_if_fail (CLUTTER_IS_PATH (path), NULL);
1187 str = g_string_new ("");
1189 for (l = priv->nodes; l; l = l->next)
1191 ClutterPathNodeFull *node = l->data;
1196 switch (node->k.type)
1198 case CLUTTER_PATH_MOVE_TO:
1203 case CLUTTER_PATH_REL_MOVE_TO:
1208 case CLUTTER_PATH_LINE_TO:
1213 case CLUTTER_PATH_REL_LINE_TO:
1218 case CLUTTER_PATH_CURVE_TO:
1223 case CLUTTER_PATH_REL_CURVE_TO:
1228 case CLUTTER_PATH_CLOSE:
1235 g_string_append_c (str, ' ');
1236 g_string_append_c (str, letter);
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);
1244 return g_string_free (str, FALSE);
1248 clutter_path_node_distance (const ClutterKnot *start,
1249 const ClutterKnot *end)
1254 g_return_val_if_fail (start != NULL, 0);
1255 g_return_val_if_fail (end != NULL, 0);
1257 if (clutter_knot_equal (start, end))
1260 x_d = end->x - start->x;
1261 y_d = end->y - start->y;
1263 t = floorf (sqrtf ((x_d * x_d) + (y_d * y_d)));
1269 clutter_path_ensure_node_data (ClutterPath *path)
1271 ClutterPathPrivate *priv = path->priv;
1273 /* Recalculate the nodes data if has changed */
1274 if (priv->nodes_dirty)
1277 ClutterKnot last_position = { 0, 0 };
1278 ClutterKnot loop_start = { 0, 0 };
1279 ClutterKnot points[3];
1281 priv->total_length = 0;
1283 for (l = priv->nodes; l; l = l->next)
1285 ClutterPathNodeFull *node = l->data;
1286 gboolean relative = (node->k.type & CLUTTER_PATH_RELATIVE) != 0;
1288 switch (node->k.type & ~CLUTTER_PATH_RELATIVE)
1290 case CLUTTER_PATH_MOVE_TO:
1293 /* Store the actual position in point[1] */
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;
1300 node->k.points[1] = node->k.points[0];
1302 last_position = node->k.points[1];
1303 loop_start = node->k.points[1];
1306 case CLUTTER_PATH_LINE_TO:
1307 /* Use point[1] as the start point and point[2] as the end
1309 node->k.points[1] = last_position;
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);
1319 node->k.points[2] = node->k.points[0];
1321 last_position = node->k.points[2];
1323 node->length = clutter_path_node_distance (node->k.points + 1,
1324 node->k.points + 2);
1327 case CLUTTER_PATH_CURVE_TO:
1328 /* Convert to a bezier curve */
1329 if (node->bezier == NULL)
1330 node->bezier = _clutter_bezier_new ();
1336 for (i = 0; i < 3; i++)
1338 points[i].x = last_position.x + node->k.points[i].x;
1339 points[i].y = last_position.y + node->k.points[i].y;
1343 memcpy (points, node->k.points, sizeof (ClutterKnot) * 3);
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);
1351 last_position = points[2];
1353 node->length = _clutter_bezier_get_length (node->bezier);
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];
1363 node->length = clutter_path_node_distance (node->k.points + 1,
1364 node->k.points + 2);
1368 priv->total_length += node->length;
1371 priv->nodes_dirty = FALSE;
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
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.
1385 * Return value: index of the node used to calculate the position.
1390 clutter_path_get_position (ClutterPath *path,
1392 ClutterKnot *position)
1394 ClutterPathPrivate *priv;
1396 guint point_distance, length = 0, node_num = 0;
1397 ClutterPathNodeFull *node;
1399 g_return_val_if_fail (CLUTTER_IS_PATH (path), 0);
1400 g_return_val_if_fail (progress >= 0.0 && progress <= 1.0, 0);
1404 clutter_path_ensure_node_data (path);
1406 /* Special case if the path is empty, just return 0,0 for want of
1408 if (priv->nodes == NULL)
1410 memset (position, 0, sizeof (ClutterKnot));
1414 /* Convert the progress to a length along the path */
1415 point_distance = progress * priv->total_length;
1417 /* Find the node that covers this point */
1418 for (l = priv->nodes;
1419 l->next && point_distance >= (((ClutterPathNodeFull *) l->data)->length
1423 length += ((ClutterPathNodeFull *) l->data)->length;
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;
1434 switch (node->k.type & ~CLUTTER_PATH_RELATIVE)
1436 case CLUTTER_PATH_MOVE_TO:
1437 *position = node->k.points[1];
1440 case CLUTTER_PATH_LINE_TO:
1441 case CLUTTER_PATH_CLOSE:
1442 if (node->length == 0)
1443 *position = node->k.points[1];
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));
1455 case CLUTTER_PATH_CURVE_TO:
1456 if (node->length == 0)
1457 *position = node->k.points[2];
1460 _clutter_bezier_advance (node->bezier,
1461 point_distance * CLUTTER_BEZIER_MAX_LENGTH
1472 * clutter_path_get_length:
1473 * @path: a #ClutterPath
1475 * Retrieves an approximation of the total length of the path.
1477 * Return value: the length of the path.
1482 clutter_path_get_length (ClutterPath *path)
1484 g_return_val_if_fail (CLUTTER_IS_PATH (path), 0);
1486 clutter_path_ensure_node_data (path);
1488 return path->priv->total_length;
1491 static ClutterPathNodeFull *
1492 clutter_path_node_full_new (void)
1494 return g_slice_new0 (ClutterPathNodeFull);
1498 clutter_path_node_full_free (ClutterPathNodeFull *node)
1501 _clutter_bezier_free (node->bezier);
1503 g_slice_free (ClutterPathNodeFull, node);
1507 * clutter_path_node_copy:
1508 * @node: a #ClutterPathNode
1510 * Makes an allocated copy of a node.
1512 * Return value: the copied node.
1517 clutter_path_node_copy (const ClutterPathNode *node)
1519 return g_slice_dup (ClutterPathNode, node);
1523 * clutter_path_node_free:
1524 * @node: a #ClutterPathNode
1526 * Frees the memory of an allocated node.
1531 clutter_path_node_free (ClutterPathNode *node)
1533 if (G_LIKELY (node))
1534 g_slice_free (ClutterPathNode, node);
1538 * clutter_path_node_equal:
1539 * @node_a: First node
1540 * @node_b: Second node
1542 * Compares two nodes and checks if they are the same type with the
1545 * Return value: %TRUE if the nodes are the same.
1550 clutter_path_node_equal (const ClutterPathNode *node_a,
1551 const ClutterPathNode *node_b)
1555 g_return_val_if_fail (node_a != NULL, FALSE);
1556 g_return_val_if_fail (node_b != NULL, FALSE);
1558 if (node_a->type != node_b->type)
1561 switch (node_a->type & ~CLUTTER_PATH_RELATIVE)
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;
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)
1578 G_DEFINE_BOXED_TYPE (ClutterKnot, clutter_knot,
1583 * clutter_knot_copy:
1584 * @knot: a #ClutterKnot
1586 * Makes an allocated copy of a knot.
1588 * Return value: the copied knot.
1593 clutter_knot_copy (const ClutterKnot *knot)
1595 if (G_UNLIKELY (knot == NULL))
1598 return g_slice_dup (ClutterKnot, knot);
1602 * clutter_knot_free:
1603 * @knot: a #ClutterKnot
1605 * Frees the memory of an allocated knot.
1610 clutter_knot_free (ClutterKnot *knot)
1612 if (G_LIKELY (knot != NULL))
1613 g_slice_free (ClutterKnot, knot);
1617 * clutter_knot_equal:
1618 * @knot_a: First knot
1619 * @knot_b: Second knot
1621 * Compares to knot and checks if the point to the same location.
1623 * Return value: %TRUE if the knots point to the same location.
1628 clutter_knot_equal (const ClutterKnot *knot_a,
1629 const ClutterKnot *knot_b)
1631 g_return_val_if_fail (knot_a != NULL, FALSE);
1632 g_return_val_if_fail (knot_b != NULL, FALSE);
1634 if (knot_a == knot_b)
1637 return knot_a->x == knot_b->x && knot_a->y == knot_b->y;