mirror of
https://github.com/brl/mutter.git
synced 2024-11-24 00:50:42 -05:00
0fca11ec2f
The get_distance() API uses machine integers to compute the distance; this means that on 32bit we can overflow the integer size. This gets hidden by the fact that get_distance() returns an unsigned integer as well. In reality, ClutterPath is an unmitigated mess, and the only way to actually fix it is to break API. https://bugzilla.gnome.org/show_bug.cgi?id=652521
1639 lines
42 KiB
C
1639 lines
42 KiB
C
/*
|
|
* Clutter.
|
|
*
|
|
* An OpenGL based 'interactive canvas' library.
|
|
*
|
|
* Authored By Matthew Allum <mallum@openedhand.com>
|
|
*
|
|
* Copyright (C) 2008 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:clutter-path
|
|
* @short_description: An object describing a path with straight lines
|
|
* and bezier curves.
|
|
*
|
|
* A #ClutterPath contains a description of a path consisting of
|
|
* straight lines and bezier curves. This can be used in a
|
|
* #ClutterBehaviourPath to animate an actor moving along the path.
|
|
*
|
|
* The path consists of a series of nodes. Each node is one of the
|
|
* following four types:
|
|
*
|
|
* <variablelist>
|
|
* <varlistentry><term>%CLUTTER_PATH_MOVE_TO</term>
|
|
* <listitem><para>
|
|
* Changes the position of the path to the given pair of
|
|
* coordinates. This is usually used as the first node of a path to
|
|
* mark the start position. If it is used in the middle of a path then
|
|
* the path will be disjoint and the actor will appear to jump to the
|
|
* new position when animated.
|
|
* </para></listitem></varlistentry>
|
|
* <varlistentry><term>%CLUTTER_PATH_LINE_TO</term>
|
|
* <listitem><para>
|
|
* Creates a straight line from the previous point to the given point.
|
|
* </para></listitem></varlistentry>
|
|
* <varlistentry><term>%CLUTTER_PATH_CURVE_TO</term>
|
|
* <listitem><para>
|
|
* Creates a bezier curve. The end of the last node is used as the
|
|
* first control point and the three subsequent coordinates given in
|
|
* the node as used as the other three.
|
|
* </para></listitem></varlistentry>
|
|
* <varlistentry><term>%CLUTTER_PATH_CLOSE</term>
|
|
* <listitem><para>
|
|
* Creates a straight line from the last node to the last
|
|
* %CLUTTER_PATH_MOVE_TO node. This can be used to close a path so
|
|
* that it will appear as a loop when animated.
|
|
* </para></listitem></varlistentry>
|
|
* </variablelist>
|
|
*
|
|
* The first three types have the corresponding relative versions
|
|
* %CLUTTER_PATH_REL_MOVE_TO, %CLUTTER_PATH_REL_LINE_TO and
|
|
* %CLUTTER_PATH_REL_CURVE_TO. These are exactly the same except the
|
|
* coordinates are given relative to the previous node instead of as
|
|
* direct screen positions.
|
|
*
|
|
* You can build a path using the node adding functions such as
|
|
* clutter_path_add_line_to(). Alternatively the path can be described
|
|
* in a string using a subset of the SVG path syntax. See
|
|
* clutter_path_add_string() for details.
|
|
*
|
|
* #ClutterPath is available since Clutter 1.0
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <math.h>
|
|
#include <glib-object.h>
|
|
|
|
#include "clutter-path.h"
|
|
#include "clutter-types.h"
|
|
#include "clutter-bezier.h"
|
|
#include "clutter-private.h"
|
|
|
|
#define CLUTTER_PATH_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_PATH, \
|
|
ClutterPathPrivate))
|
|
|
|
#define CLUTTER_PATH_NODE_TYPE_IS_VALID(t) \
|
|
((((t) & ~CLUTTER_PATH_RELATIVE) >= CLUTTER_PATH_MOVE_TO \
|
|
&& ((t) & ~CLUTTER_PATH_RELATIVE) <= CLUTTER_PATH_CURVE_TO) \
|
|
|| (t) == CLUTTER_PATH_CLOSE)
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_DESCRIPTION,
|
|
PROP_LENGTH,
|
|
|
|
PROP_LAST
|
|
};
|
|
|
|
static GParamSpec *obj_props[PROP_LAST];
|
|
|
|
typedef struct _ClutterPathNodeFull ClutterPathNodeFull;
|
|
|
|
struct _ClutterPathNodeFull
|
|
{
|
|
ClutterPathNode k;
|
|
|
|
ClutterBezier *bezier;
|
|
|
|
guint length;
|
|
};
|
|
|
|
struct _ClutterPathPrivate
|
|
{
|
|
GSList *nodes, *nodes_tail;
|
|
gboolean nodes_dirty;
|
|
|
|
guint total_length;
|
|
};
|
|
|
|
/* Character tests that don't pay attention to the locale */
|
|
#define clutter_path_isspace(ch) memchr (" \f\n\r\t\v", (ch), 6)
|
|
#define clutter_path_isdigit(ch) ((ch) >= '0' && (ch) <= '9')
|
|
|
|
static ClutterPathNodeFull *clutter_path_node_full_new (void);
|
|
static void clutter_path_node_full_free (ClutterPathNodeFull *node);
|
|
|
|
static void clutter_path_finalize (GObject *object);
|
|
|
|
static void clutter_value_transform_path_string (const GValue *src,
|
|
GValue *dest);
|
|
static void clutter_value_transform_string_path (const GValue *src,
|
|
GValue *dest);
|
|
|
|
G_DEFINE_BOXED_TYPE (ClutterPathNode, clutter_path_node,
|
|
clutter_path_node_copy,
|
|
clutter_path_node_free);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (ClutterPath,
|
|
clutter_path,
|
|
G_TYPE_INITIALLY_UNOWNED,
|
|
CLUTTER_REGISTER_VALUE_TRANSFORM_TO (G_TYPE_STRING, clutter_value_transform_path_string)
|
|
CLUTTER_REGISTER_VALUE_TRANSFORM_FROM (G_TYPE_STRING, clutter_value_transform_string_path));
|
|
|
|
static void
|
|
clutter_path_get_property (GObject *gobject,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterPath *path = CLUTTER_PATH (gobject);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_DESCRIPTION:
|
|
g_value_take_string (value, clutter_path_get_description (path));
|
|
break;
|
|
case PROP_LENGTH:
|
|
g_value_set_uint (value, clutter_path_get_length (path));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_path_set_property (GObject *gobject,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterPath *path = CLUTTER_PATH (gobject);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_DESCRIPTION:
|
|
if (!clutter_path_set_description (path, g_value_get_string (value)))
|
|
g_warning ("Invalid path description");
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_path_class_init (ClutterPathClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
GParamSpec *pspec;
|
|
|
|
gobject_class->get_property = clutter_path_get_property;
|
|
gobject_class->set_property = clutter_path_set_property;
|
|
gobject_class->finalize = clutter_path_finalize;
|
|
|
|
pspec = g_param_spec_string ("description",
|
|
"Description",
|
|
"SVG-style description of the path",
|
|
"",
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_DESCRIPTION] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_DESCRIPTION, pspec);
|
|
|
|
pspec = g_param_spec_uint ("length",
|
|
"Length",
|
|
"An approximation of the total length "
|
|
"of the path.",
|
|
0, G_MAXUINT, 0,
|
|
CLUTTER_PARAM_READABLE);
|
|
obj_props[PROP_LENGTH] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_LENGTH, pspec);
|
|
|
|
g_type_class_add_private (klass, sizeof (ClutterPathPrivate));
|
|
}
|
|
|
|
static void
|
|
clutter_path_init (ClutterPath *self)
|
|
{
|
|
self->priv = CLUTTER_PATH_GET_PRIVATE (self);
|
|
}
|
|
|
|
static void
|
|
clutter_value_transform_path_string (const GValue *src,
|
|
GValue *dest)
|
|
{
|
|
gchar *string = clutter_path_get_description (src->data[0].v_pointer);
|
|
|
|
g_value_take_string (dest, string);
|
|
}
|
|
|
|
static void
|
|
clutter_value_transform_string_path (const GValue *src,
|
|
GValue *dest)
|
|
{
|
|
ClutterPath *new_path;
|
|
|
|
new_path = clutter_path_new_with_description (g_value_get_string (src));
|
|
g_value_take_object (dest, new_path);
|
|
}
|
|
|
|
static void
|
|
clutter_path_finalize (GObject *object)
|
|
{
|
|
ClutterPath *self = (ClutterPath *) object;
|
|
|
|
clutter_path_clear (self);
|
|
|
|
G_OBJECT_CLASS (clutter_path_parent_class)->finalize (object);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_new:
|
|
*
|
|
* Creates a new #ClutterPath instance with no nodes.
|
|
*
|
|
* The object has a floating reference so if you add it to a
|
|
* #ClutterBehaviourPath then you do not need to unref it.
|
|
*
|
|
* Return value: the newly created #ClutterPath
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
ClutterPath *
|
|
clutter_path_new (void)
|
|
{
|
|
ClutterPath *self = g_object_new (CLUTTER_TYPE_PATH, NULL);
|
|
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* clutter_path_new_with_description:
|
|
* @desc: a string describing the path
|
|
*
|
|
* Creates a new #ClutterPath instance with the nodes described in
|
|
* @desc. See clutter_path_add_string() for details of the format of
|
|
* the string.
|
|
*
|
|
* The object has a floating reference so if you add it to a
|
|
* #ClutterBehaviourPath then you do not need to unref it.
|
|
*
|
|
* Return value: the newly created #ClutterPath
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
ClutterPath *
|
|
clutter_path_new_with_description (const gchar *desc)
|
|
{
|
|
return g_object_new (CLUTTER_TYPE_PATH,
|
|
"description", desc,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_clear:
|
|
* @path: a #ClutterPath
|
|
*
|
|
* Removes all nodes from the path.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_clear (ClutterPath *path)
|
|
{
|
|
ClutterPathPrivate *priv = path->priv;
|
|
|
|
g_slist_foreach (priv->nodes, (GFunc) clutter_path_node_full_free, NULL);
|
|
g_slist_free (priv->nodes);
|
|
|
|
priv->nodes = priv->nodes_tail = NULL;
|
|
priv->nodes_dirty = TRUE;
|
|
}
|
|
|
|
/* Takes ownership of the node */
|
|
static void
|
|
clutter_path_add_node_full (ClutterPath *path,
|
|
ClutterPathNodeFull *node)
|
|
{
|
|
ClutterPathPrivate *priv = path->priv;
|
|
GSList *new_node;
|
|
|
|
new_node = g_slist_prepend (NULL, node);
|
|
|
|
if (priv->nodes_tail == NULL)
|
|
priv->nodes = new_node;
|
|
else
|
|
priv->nodes_tail->next = new_node;
|
|
|
|
priv->nodes_tail = new_node;
|
|
|
|
priv->nodes_dirty = TRUE;
|
|
}
|
|
|
|
/* Helper function to make the rest of teh add_* functions shorter */
|
|
static void
|
|
clutter_path_add_node_helper (ClutterPath *path,
|
|
ClutterPathNodeType type,
|
|
int num_coords,
|
|
...)
|
|
{
|
|
ClutterPathNodeFull *node;
|
|
int i;
|
|
va_list ap;
|
|
|
|
node = clutter_path_node_full_new ();
|
|
|
|
node->k.type = type;
|
|
|
|
va_start (ap, num_coords);
|
|
|
|
for (i = 0; i < num_coords; i++)
|
|
{
|
|
node->k.points[i].x = va_arg (ap, gint);
|
|
node->k.points[i].y = va_arg (ap, gint);
|
|
}
|
|
|
|
va_end (ap);
|
|
|
|
clutter_path_add_node_full (path, node);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_add_move_to:
|
|
* @path: a #ClutterPath
|
|
* @x: the x coordinate
|
|
* @y: the y coordinate
|
|
*
|
|
* Adds a %CLUTTER_PATH_MOVE_TO type node to the path. This is usually
|
|
* used as the first node in a path. It can also be used in the middle
|
|
* of the path to cause the actor to jump to the new coordinate.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_add_move_to (ClutterPath *path,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
|
|
clutter_path_add_node_helper (path, CLUTTER_PATH_MOVE_TO, 1, x, y);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_add_rel_move_to:
|
|
* @path: a #ClutterPath
|
|
* @x: the x coordinate
|
|
* @y: the y coordinate
|
|
*
|
|
* Same as clutter_path_add_move_to() except the coordinates are
|
|
* relative to the previous node.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_add_rel_move_to (ClutterPath *path,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
|
|
clutter_path_add_node_helper (path, CLUTTER_PATH_REL_MOVE_TO, 1, x, y);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_add_line_to:
|
|
* @path: a #ClutterPath
|
|
* @x: the x coordinate
|
|
* @y: the y coordinate
|
|
*
|
|
* Adds a %CLUTTER_PATH_LINE_TO type node to the path. This causes the
|
|
* actor to move to the new coordinates in a straight line.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_add_line_to (ClutterPath *path,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
|
|
clutter_path_add_node_helper (path, CLUTTER_PATH_LINE_TO, 1, x, y);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_add_rel_line_to:
|
|
* @path: a #ClutterPath
|
|
* @x: the x coordinate
|
|
* @y: the y coordinate
|
|
*
|
|
* Same as clutter_path_add_line_to() except the coordinates are
|
|
* relative to the previous node.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_add_rel_line_to (ClutterPath *path,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
|
|
clutter_path_add_node_helper (path, CLUTTER_PATH_REL_LINE_TO, 1, x, y);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_add_curve_to:
|
|
* @path: a #ClutterPath
|
|
* @x_1: the x coordinate of the first control point
|
|
* @y_1: the y coordinate of the first control point
|
|
* @x_2: the x coordinate of the second control point
|
|
* @y_2: the y coordinate of the second control point
|
|
* @x_3: the x coordinate of the third control point
|
|
* @y_3: the y coordinate of the third control point
|
|
*
|
|
* Adds a %CLUTTER_PATH_CURVE_TO type node to the path. This causes
|
|
* the actor to follow a bezier from the last node to (@x_3, @y_3) using
|
|
* (@x_1, @y_1) and (@x_2,@y_2) as control points.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_add_curve_to (ClutterPath *path,
|
|
gint x_1,
|
|
gint y_1,
|
|
gint x_2,
|
|
gint y_2,
|
|
gint x_3,
|
|
gint y_3)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
|
|
clutter_path_add_node_helper (path, CLUTTER_PATH_CURVE_TO, 3,
|
|
x_1, y_1,
|
|
x_2, y_2,
|
|
x_3, y_3);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_add_rel_curve_to:
|
|
* @path: a #ClutterPath
|
|
* @x_1: the x coordinate of the first control point
|
|
* @y_1: the y coordinate of the first control point
|
|
* @x_2: the x coordinate of the second control point
|
|
* @y_2: the y coordinate of the second control point
|
|
* @x_3: the x coordinate of the third control point
|
|
* @y_3: the y coordinate of the third control point
|
|
*
|
|
* Same as clutter_path_add_curve_to() except the coordinates are
|
|
* relative to the previous node.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_add_rel_curve_to (ClutterPath *path,
|
|
gint x_1,
|
|
gint y_1,
|
|
gint x_2,
|
|
gint y_2,
|
|
gint x_3,
|
|
gint y_3)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
|
|
clutter_path_add_node_helper (path, CLUTTER_PATH_REL_CURVE_TO, 3,
|
|
x_1, y_1,
|
|
x_2, y_2,
|
|
x_3, y_3);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_add_close:
|
|
* @path: a #ClutterPath
|
|
*
|
|
* Adds a %CLUTTER_PATH_CLOSE type node to the path. This creates a
|
|
* straight line from the last node to the last %CLUTTER_PATH_MOVE_TO
|
|
* type node.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_add_close (ClutterPath *path)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
|
|
clutter_path_add_node_helper (path, CLUTTER_PATH_CLOSE, 0);
|
|
}
|
|
|
|
static gboolean
|
|
clutter_path_parse_number (const gchar **pin,
|
|
gboolean allow_comma,
|
|
gint *ret)
|
|
{
|
|
gint val = 0;
|
|
gboolean negative = FALSE;
|
|
gint digit_count = 0;
|
|
const gchar *p = *pin;
|
|
|
|
/* Skip leading spaces */
|
|
while (clutter_path_isspace (*p))
|
|
p++;
|
|
|
|
/* Optional comma */
|
|
if (allow_comma && *p == ',')
|
|
{
|
|
p++;
|
|
while (clutter_path_isspace (*p))
|
|
p++;
|
|
}
|
|
|
|
/* Optional sign */
|
|
if (*p == '+')
|
|
p++;
|
|
else if (*p == '-')
|
|
{
|
|
negative = TRUE;
|
|
p++;
|
|
}
|
|
|
|
/* Some digits */
|
|
while (clutter_path_isdigit (*p))
|
|
{
|
|
val = val * 10 + *p - '0';
|
|
digit_count++;
|
|
p++;
|
|
}
|
|
|
|
/* We need at least one digit */
|
|
if (digit_count < 1)
|
|
return FALSE;
|
|
|
|
/* Optional fractional part which we ignore */
|
|
if (*p == '.')
|
|
{
|
|
p++;
|
|
digit_count = 0;
|
|
while (clutter_path_isdigit (*p))
|
|
{
|
|
digit_count++;
|
|
p++;
|
|
}
|
|
/* If there is a fractional part then it also needs at least one
|
|
digit */
|
|
if (digit_count < 1)
|
|
return FALSE;
|
|
}
|
|
|
|
*pin = p;
|
|
*ret = negative ? -val : val;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_path_parse_description (const gchar *p,
|
|
GSList **ret)
|
|
{
|
|
ClutterPathNodeFull *node;
|
|
GSList *nodes = NULL;
|
|
|
|
if (p == NULL || *p == '\0')
|
|
return FALSE;
|
|
|
|
while (TRUE)
|
|
{
|
|
/* Skip leading whitespace */
|
|
while (clutter_path_isspace (*p))
|
|
p++;
|
|
|
|
/* It is not an error to end now */
|
|
if (*p == '\0')
|
|
break;
|
|
|
|
switch (*p)
|
|
{
|
|
case 'M':
|
|
case 'm':
|
|
case 'L':
|
|
case 'l':
|
|
node = clutter_path_node_full_new ();
|
|
nodes = g_slist_prepend (nodes, node);
|
|
|
|
node->k.type = (*p == 'M' ? CLUTTER_PATH_MOVE_TO :
|
|
*p == 'm' ? CLUTTER_PATH_REL_MOVE_TO :
|
|
*p == 'L' ? CLUTTER_PATH_LINE_TO :
|
|
CLUTTER_PATH_REL_LINE_TO);
|
|
p++;
|
|
|
|
if (!clutter_path_parse_number (&p, FALSE, &node->k.points[0].x) ||
|
|
!clutter_path_parse_number (&p, TRUE, &node->k.points[0].y))
|
|
goto fail;
|
|
break;
|
|
|
|
case 'C':
|
|
case 'c':
|
|
node = clutter_path_node_full_new ();
|
|
nodes = g_slist_prepend (nodes, node);
|
|
|
|
node->k.type = (*p == 'C' ? CLUTTER_PATH_CURVE_TO :
|
|
CLUTTER_PATH_REL_CURVE_TO);
|
|
p++;
|
|
|
|
if (!clutter_path_parse_number (&p, FALSE, &node->k.points[0].x) ||
|
|
!clutter_path_parse_number (&p, TRUE, &node->k.points[0].y) ||
|
|
!clutter_path_parse_number (&p, TRUE, &node->k.points[1].x) ||
|
|
!clutter_path_parse_number (&p, TRUE, &node->k.points[1].y) ||
|
|
!clutter_path_parse_number (&p, TRUE, &node->k.points[2].x) ||
|
|
!clutter_path_parse_number (&p, TRUE, &node->k.points[2].y))
|
|
goto fail;
|
|
break;
|
|
|
|
case 'Z':
|
|
case 'z':
|
|
node = clutter_path_node_full_new ();
|
|
nodes = g_slist_prepend (nodes, node);
|
|
p++;
|
|
|
|
node->k.type = CLUTTER_PATH_CLOSE;
|
|
break;
|
|
|
|
default:
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
*ret = g_slist_reverse (nodes);
|
|
return TRUE;
|
|
|
|
fail:
|
|
g_slist_foreach (nodes, (GFunc) clutter_path_node_full_free, NULL);
|
|
g_slist_free (nodes);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Takes ownership of the node list */
|
|
static void
|
|
clutter_path_add_nodes (ClutterPath *path,
|
|
GSList *nodes)
|
|
{
|
|
ClutterPathPrivate *priv = path->priv;
|
|
|
|
if (priv->nodes_tail == NULL)
|
|
priv->nodes = nodes;
|
|
else
|
|
priv->nodes_tail->next = nodes;
|
|
|
|
while (nodes)
|
|
{
|
|
priv->nodes_tail = nodes;
|
|
nodes = nodes->next;
|
|
}
|
|
|
|
priv->nodes_dirty = TRUE;
|
|
}
|
|
|
|
/**
|
|
* clutter_path_add_string:
|
|
* @path: a #ClutterPath
|
|
* @str: a string describing the new nodes
|
|
*
|
|
* Adds new nodes to the end of the path as described in @str. The
|
|
* format is a subset of the SVG path format. Each node is represented
|
|
* by a letter and is followed by zero, one or three pairs of
|
|
* coordinates. The coordinates can be separated by spaces or a
|
|
* comma. The types are:
|
|
*
|
|
* <variablelist>
|
|
* <varlistentry><term>M</term>
|
|
* <listitem><para>
|
|
* Adds a %CLUTTER_PATH_MOVE_TO node. Takes one pair of coordinates.
|
|
* </para></listitem></varlistentry>
|
|
* <varlistentry><term>L</term>
|
|
* <listitem><para>
|
|
* Adds a %CLUTTER_PATH_LINE_TO node. Takes one pair of coordinates.
|
|
* </para></listitem></varlistentry>
|
|
* <varlistentry><term>C</term>
|
|
* <listitem><para>
|
|
* Adds a %CLUTTER_PATH_CURVE_TO node. Takes three pairs of coordinates.
|
|
* </para></listitem></varlistentry>
|
|
* <varlistentry><term>z</term>
|
|
* <listitem><para>
|
|
* Adds a %CLUTTER_PATH_CLOSE node. No coordinates are needed.
|
|
* </para></listitem></varlistentry>
|
|
* </variablelist>
|
|
*
|
|
* The M, L and C commands can also be specified in lower case which
|
|
* means the coordinates are relative to the previous node.
|
|
*
|
|
* For example, to move an actor in a 100 by 100 pixel square centered
|
|
* on the point 300,300 you could use the following path:
|
|
*
|
|
* <informalexample>
|
|
* <programlisting>
|
|
* M 250,350 l 0 -100 L 350,250 l 0 100 z
|
|
* </programlisting>
|
|
* </informalexample>
|
|
*
|
|
* If the path description isn't valid %FALSE will be returned and no
|
|
* nodes will be added.
|
|
*
|
|
* Return value: %TRUE is the path description was valid or %FALSE
|
|
* otherwise.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_path_add_string (ClutterPath *path,
|
|
const gchar *str)
|
|
{
|
|
GSList *nodes;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_PATH (path), FALSE);
|
|
g_return_val_if_fail (str != NULL, FALSE);
|
|
|
|
if (clutter_path_parse_description (str, &nodes))
|
|
{
|
|
clutter_path_add_nodes (path, nodes);
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* clutter_path_add_node:
|
|
* @path: a #ClutterPath
|
|
* @node: a #ClutterPathNode
|
|
*
|
|
* Adds @node to the end of the path.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_add_node (ClutterPath *path,
|
|
const ClutterPathNode *node)
|
|
{
|
|
ClutterPathNodeFull *node_full;
|
|
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
g_return_if_fail (node != NULL);
|
|
g_return_if_fail (CLUTTER_PATH_NODE_TYPE_IS_VALID (node->type));
|
|
|
|
node_full = clutter_path_node_full_new ();
|
|
node_full->k = *node;
|
|
|
|
clutter_path_add_node_full (path, node_full);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_add_cairo_path:
|
|
* @path: a #ClutterPath
|
|
* @cpath: a Cairo path
|
|
*
|
|
* Add the nodes of the Cairo path to the end of @path.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_add_cairo_path (ClutterPath *path,
|
|
const cairo_path_t *cpath)
|
|
{
|
|
int num_data;
|
|
const cairo_path_data_t *p;
|
|
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
g_return_if_fail (cpath != NULL);
|
|
|
|
/* Iterate over each command in the cairo path */
|
|
for (num_data = cpath->num_data, p = cpath->data;
|
|
num_data > 0;
|
|
num_data -= p->header.length, p += p->header.length)
|
|
{
|
|
switch (p->header.type)
|
|
{
|
|
case CAIRO_PATH_MOVE_TO:
|
|
g_assert (p->header.length >= 2);
|
|
|
|
clutter_path_add_move_to (path, p[1].point.x, p[1].point.y);
|
|
break;
|
|
|
|
case CAIRO_PATH_LINE_TO:
|
|
g_assert (p->header.length >= 2);
|
|
|
|
clutter_path_add_line_to (path, p[1].point.x, p[1].point.y);
|
|
break;
|
|
|
|
case CAIRO_PATH_CURVE_TO:
|
|
g_assert (p->header.length >= 4);
|
|
|
|
clutter_path_add_curve_to (path,
|
|
p[1].point.x, p[1].point.y,
|
|
p[2].point.x, p[2].point.y,
|
|
p[3].point.x, p[3].point.y);
|
|
break;
|
|
|
|
case CAIRO_PATH_CLOSE_PATH:
|
|
clutter_path_add_close (path);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_path_add_node_to_cairo_path (const ClutterPathNode *node,
|
|
gpointer data)
|
|
{
|
|
cairo_t *cr = data;
|
|
|
|
switch (node->type)
|
|
{
|
|
case CLUTTER_PATH_MOVE_TO:
|
|
cairo_move_to (cr, node->points[0].x, node->points[0].y);
|
|
break;
|
|
|
|
case CLUTTER_PATH_LINE_TO:
|
|
cairo_line_to (cr, node->points[0].x, node->points[0].y);
|
|
break;
|
|
|
|
case CLUTTER_PATH_CURVE_TO:
|
|
cairo_curve_to (cr,
|
|
node->points[0].x, node->points[0].y,
|
|
node->points[1].x, node->points[1].y,
|
|
node->points[2].x, node->points[2].y);
|
|
break;
|
|
|
|
case CLUTTER_PATH_REL_MOVE_TO:
|
|
cairo_rel_move_to (cr, node->points[0].x, node->points[0].y);
|
|
break;
|
|
|
|
case CLUTTER_PATH_REL_LINE_TO:
|
|
cairo_rel_line_to (cr, node->points[0].x, node->points[0].y);
|
|
break;
|
|
|
|
case CLUTTER_PATH_REL_CURVE_TO:
|
|
cairo_rel_curve_to (cr,
|
|
node->points[0].x, node->points[0].y,
|
|
node->points[1].x, node->points[1].y,
|
|
node->points[2].x, node->points[2].y);
|
|
break;
|
|
|
|
case CLUTTER_PATH_CLOSE:
|
|
cairo_close_path (cr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_path_to_cairo_path:
|
|
* @path: a #ClutterPath
|
|
* @cr: a Cairo context
|
|
*
|
|
* Add the nodes of the ClutterPath to the path in the Cairo context.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_to_cairo_path (ClutterPath *path,
|
|
cairo_t *cr)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
g_return_if_fail (cr != NULL);
|
|
|
|
clutter_path_foreach (path, clutter_path_add_node_to_cairo_path, cr);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_get_n_nodes:
|
|
* @path: a #ClutterPath
|
|
*
|
|
* Retrieves the number of nodes in the path.
|
|
*
|
|
* Return value: the number of nodes.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
guint
|
|
clutter_path_get_n_nodes (ClutterPath *path)
|
|
{
|
|
ClutterPathPrivate *priv;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_PATH (path), 0);
|
|
|
|
priv = path->priv;
|
|
|
|
return g_slist_length (priv->nodes);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_get_node:
|
|
* @path: a #ClutterPath
|
|
* @index_: the node number to retrieve
|
|
* @node: (out): a location to store a copy of the node
|
|
*
|
|
* Retrieves the node of the path indexed by @index.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_get_node (ClutterPath *path,
|
|
guint index_,
|
|
ClutterPathNode *node)
|
|
{
|
|
ClutterPathNodeFull *node_full;
|
|
ClutterPathPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
|
|
priv = path->priv;
|
|
|
|
node_full = g_slist_nth_data (priv->nodes, index_);
|
|
|
|
g_return_if_fail (node_full != NULL);
|
|
|
|
*node = node_full->k;
|
|
}
|
|
|
|
/**
|
|
* clutter_path_get_nodes:
|
|
* @path: a #ClutterPath
|
|
*
|
|
* Returns a #GSList of #ClutterPathNode<!-- -->s. The list should be
|
|
* freed with g_slist_free(). The nodes are owned by the path and
|
|
* should not be freed. Altering the path may cause the nodes in the
|
|
* list to become invalid so you should copy them if you want to keep
|
|
* the list.
|
|
*
|
|
* Return value: (transfer container) (element-type Clutter.PathNode): a
|
|
* list of nodes in the path.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
GSList *
|
|
clutter_path_get_nodes (ClutterPath *path)
|
|
{
|
|
ClutterPathPrivate *priv;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_PATH (path), NULL);
|
|
|
|
priv = path->priv;
|
|
|
|
return g_slist_copy (priv->nodes);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_foreach:
|
|
* @path: a #ClutterPath
|
|
* @callback: (scope call): the function to call with each node
|
|
* @user_data: user data to pass to the function
|
|
*
|
|
* Calls a function for each node of the path.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_foreach (ClutterPath *path,
|
|
ClutterPathCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
ClutterPathPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
|
|
priv = path->priv;
|
|
|
|
g_slist_foreach (priv->nodes, (GFunc) callback, user_data);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_insert_node:
|
|
* @path: a #ClutterPath
|
|
* @index_: offset of where to insert the node
|
|
* @node: the node to insert
|
|
*
|
|
* Inserts @node into the path before the node at the given offset. If
|
|
* @index_ is negative it will append the node to the end of the path.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_insert_node (ClutterPath *path,
|
|
gint index_,
|
|
const ClutterPathNode *node)
|
|
{
|
|
ClutterPathPrivate *priv;
|
|
ClutterPathNodeFull *node_full;
|
|
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
g_return_if_fail (node != NULL);
|
|
g_return_if_fail (CLUTTER_PATH_NODE_TYPE_IS_VALID (node->type));
|
|
|
|
priv = path->priv;
|
|
|
|
node_full = clutter_path_node_full_new ();
|
|
node_full->k = *node;
|
|
|
|
priv->nodes = g_slist_insert (priv->nodes, node_full, index_);
|
|
|
|
if (priv->nodes_tail == NULL)
|
|
priv->nodes_tail = priv->nodes;
|
|
else if (priv->nodes_tail->next)
|
|
priv->nodes_tail = priv->nodes_tail->next;
|
|
|
|
priv->nodes_dirty = TRUE;
|
|
}
|
|
|
|
/**
|
|
* clutter_path_remove_node:
|
|
* @path: a #ClutterPath
|
|
* @index_: index of the node to remove
|
|
*
|
|
* Removes the node at the given offset from the path.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_remove_node (ClutterPath *path,
|
|
guint index_)
|
|
{
|
|
ClutterPathPrivate *priv;
|
|
GSList *node, *prev = NULL;
|
|
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
|
|
priv = path->priv;
|
|
|
|
for (node = priv->nodes; node && index_--; node = node->next)
|
|
prev = node;
|
|
|
|
if (node)
|
|
{
|
|
clutter_path_node_full_free (node->data);
|
|
|
|
if (prev)
|
|
prev->next = node->next;
|
|
else
|
|
priv->nodes = node->next;
|
|
|
|
if (node == priv->nodes_tail)
|
|
priv->nodes_tail = prev;
|
|
|
|
g_slist_free_1 (node);
|
|
|
|
priv->nodes_dirty = TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_path_replace_node:
|
|
* @path: a #ClutterPath
|
|
* @index_: index to the existing node
|
|
* @node: the replacement node
|
|
*
|
|
* Replaces the node at offset @index_ with @node.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_replace_node (ClutterPath *path,
|
|
guint index_,
|
|
const ClutterPathNode *node)
|
|
{
|
|
ClutterPathPrivate *priv;
|
|
ClutterPathNodeFull *node_full;
|
|
|
|
g_return_if_fail (CLUTTER_IS_PATH (path));
|
|
g_return_if_fail (node != NULL);
|
|
g_return_if_fail (CLUTTER_PATH_NODE_TYPE_IS_VALID (node->type));
|
|
|
|
priv = path->priv;
|
|
|
|
if ((node_full = g_slist_nth_data (priv->nodes, index_)))
|
|
{
|
|
node_full->k = *node;
|
|
|
|
priv->nodes_dirty = TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_path_set_description:
|
|
* @path: a #ClutterPath
|
|
* @str: a string describing the path
|
|
*
|
|
* Replaces all of the nodes in the path with nodes described by
|
|
* @str. See clutter_path_add_string() for details of the format.
|
|
*
|
|
* If the string is invalid then %FALSE is returned and the path is
|
|
* unaltered.
|
|
*
|
|
* Return value: %TRUE is the path was valid, %FALSE otherwise.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_path_set_description (ClutterPath *path,
|
|
const gchar *str)
|
|
{
|
|
GSList *nodes;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_PATH (path), FALSE);
|
|
g_return_val_if_fail (str != NULL, FALSE);
|
|
|
|
if (clutter_path_parse_description (str, &nodes))
|
|
{
|
|
clutter_path_clear (path);
|
|
clutter_path_add_nodes (path, nodes);
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* clutter_path_get_description:
|
|
* @path: a #ClutterPath
|
|
*
|
|
* Returns a newly allocated string describing the path in the same
|
|
* format as used by clutter_path_add_string().
|
|
*
|
|
* Return value: a string description of the path. Free with g_free().
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gchar *
|
|
clutter_path_get_description (ClutterPath *path)
|
|
{
|
|
ClutterPathPrivate *priv;
|
|
GString *str;
|
|
GSList *l;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_PATH (path), NULL);
|
|
|
|
priv = path->priv;
|
|
|
|
str = g_string_new ("");
|
|
|
|
for (l = priv->nodes; l; l = l->next)
|
|
{
|
|
ClutterPathNodeFull *node = l->data;
|
|
gchar letter = '?';
|
|
gint params = 0;
|
|
gint i;
|
|
|
|
switch (node->k.type)
|
|
{
|
|
case CLUTTER_PATH_MOVE_TO:
|
|
letter = 'M';
|
|
params = 1;
|
|
break;
|
|
|
|
case CLUTTER_PATH_REL_MOVE_TO:
|
|
letter = 'm';
|
|
params = 1;
|
|
break;
|
|
|
|
case CLUTTER_PATH_LINE_TO:
|
|
letter = 'L';
|
|
params = 1;
|
|
break;
|
|
|
|
case CLUTTER_PATH_REL_LINE_TO:
|
|
letter = 'l';
|
|
params = 1;
|
|
break;
|
|
|
|
case CLUTTER_PATH_CURVE_TO:
|
|
letter = 'C';
|
|
params = 3;
|
|
break;
|
|
|
|
case CLUTTER_PATH_REL_CURVE_TO:
|
|
letter = 'c';
|
|
params = 3;
|
|
break;
|
|
|
|
case CLUTTER_PATH_CLOSE:
|
|
letter = 'z';
|
|
params = 0;
|
|
break;
|
|
}
|
|
|
|
if (str->len > 0)
|
|
g_string_append_c (str, ' ');
|
|
g_string_append_c (str, letter);
|
|
|
|
for (i = 0; i < params; i++)
|
|
g_string_append_printf (str, " %i %i",
|
|
node->k.points[i].x,
|
|
node->k.points[i].y);
|
|
}
|
|
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
|
|
static guint
|
|
clutter_path_node_distance (const ClutterKnot *start,
|
|
const ClutterKnot *end)
|
|
{
|
|
gint64 x_d, y_d;
|
|
float t;
|
|
|
|
g_return_val_if_fail (start != NULL, 0);
|
|
g_return_val_if_fail (end != NULL, 0);
|
|
|
|
if (clutter_knot_equal (start, end))
|
|
return 0;
|
|
|
|
x_d = end->x - start->x;
|
|
y_d = end->y - start->y;
|
|
|
|
t = floorf (sqrtf ((x_d * x_d) + (y_d * y_d)));
|
|
|
|
return (guint) t;
|
|
}
|
|
|
|
static void
|
|
clutter_path_ensure_node_data (ClutterPath *path)
|
|
{
|
|
ClutterPathPrivate *priv = path->priv;
|
|
|
|
/* Recalculate the nodes data if has changed */
|
|
if (priv->nodes_dirty)
|
|
{
|
|
GSList *l;
|
|
ClutterKnot last_position = { 0, 0 };
|
|
ClutterKnot loop_start = { 0, 0 };
|
|
ClutterKnot points[3];
|
|
|
|
priv->total_length = 0;
|
|
|
|
for (l = priv->nodes; l; l = l->next)
|
|
{
|
|
ClutterPathNodeFull *node = l->data;
|
|
gboolean relative = (node->k.type & CLUTTER_PATH_RELATIVE) != 0;
|
|
|
|
switch (node->k.type & ~CLUTTER_PATH_RELATIVE)
|
|
{
|
|
case CLUTTER_PATH_MOVE_TO:
|
|
node->length = 0;
|
|
|
|
/* Store the actual position in point[1] */
|
|
if (relative)
|
|
{
|
|
node->k.points[1].x = last_position.x + node->k.points[0].x;
|
|
node->k.points[1].y = last_position.y + node->k.points[0].y;
|
|
}
|
|
else
|
|
node->k.points[1] = node->k.points[0];
|
|
|
|
last_position = node->k.points[1];
|
|
loop_start = node->k.points[1];
|
|
break;
|
|
|
|
case CLUTTER_PATH_LINE_TO:
|
|
/* Use point[1] as the start point and point[2] as the end
|
|
point */
|
|
node->k.points[1] = last_position;
|
|
|
|
if (relative)
|
|
{
|
|
node->k.points[2].x = (node->k.points[1].x
|
|
+ node->k.points[0].x);
|
|
node->k.points[2].y = (node->k.points[1].y
|
|
+ node->k.points[0].y);
|
|
}
|
|
else
|
|
node->k.points[2] = node->k.points[0];
|
|
|
|
last_position = node->k.points[2];
|
|
|
|
node->length = clutter_path_node_distance (node->k.points + 1,
|
|
node->k.points + 2);
|
|
break;
|
|
|
|
case CLUTTER_PATH_CURVE_TO:
|
|
/* Convert to a bezier curve */
|
|
if (node->bezier == NULL)
|
|
node->bezier = _clutter_bezier_new ();
|
|
|
|
if (relative)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
points[i].x = last_position.x + node->k.points[i].x;
|
|
points[i].y = last_position.y + node->k.points[i].y;
|
|
}
|
|
}
|
|
else
|
|
memcpy (points, node->k.points, sizeof (ClutterKnot) * 3);
|
|
|
|
_clutter_bezier_init (node->bezier,
|
|
last_position.x, last_position.y,
|
|
points[0].x, points[0].y,
|
|
points[1].x, points[1].y,
|
|
points[2].x, points[2].y);
|
|
|
|
last_position = points[2];
|
|
|
|
node->length = _clutter_bezier_get_length (node->bezier);
|
|
|
|
break;
|
|
|
|
case CLUTTER_PATH_CLOSE:
|
|
/* Convert to a line to from last_point to loop_start */
|
|
node->k.points[1] = last_position;
|
|
node->k.points[2] = loop_start;
|
|
last_position = node->k.points[2];
|
|
|
|
node->length = clutter_path_node_distance (node->k.points + 1,
|
|
node->k.points + 2);
|
|
break;
|
|
}
|
|
|
|
priv->total_length += node->length;
|
|
}
|
|
|
|
priv->nodes_dirty = FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_path_get_position:
|
|
* @path: a #ClutterPath
|
|
* @progress: a position along the path as a fraction of its length
|
|
* @position: (out): location to store the position
|
|
*
|
|
* The value in @progress represents a position along the path where
|
|
* 0.0 is the beginning and 1.0 is the end of the path. An
|
|
* interpolated position is then stored in @position.
|
|
*
|
|
* Return value: index of the node used to calculate the position.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
guint
|
|
clutter_path_get_position (ClutterPath *path,
|
|
gdouble progress,
|
|
ClutterKnot *position)
|
|
{
|
|
ClutterPathPrivate *priv;
|
|
GSList *l;
|
|
guint point_distance, length = 0, node_num = 0;
|
|
ClutterPathNodeFull *node;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_PATH (path), 0);
|
|
g_return_val_if_fail (progress >= 0.0 && progress <= 1.0, 0);
|
|
|
|
priv = path->priv;
|
|
|
|
clutter_path_ensure_node_data (path);
|
|
|
|
/* Special case if the path is empty, just return 0,0 for want of
|
|
something better */
|
|
if (priv->nodes == NULL)
|
|
{
|
|
memset (position, 0, sizeof (ClutterKnot));
|
|
return 0;
|
|
}
|
|
|
|
/* Convert the progress to a length along the path */
|
|
point_distance = progress * priv->total_length;
|
|
|
|
/* Find the node that covers this point */
|
|
for (l = priv->nodes;
|
|
l->next && point_distance >= (((ClutterPathNodeFull *) l->data)->length
|
|
+ length);
|
|
l = l->next)
|
|
{
|
|
length += ((ClutterPathNodeFull *) l->data)->length;
|
|
node_num++;
|
|
}
|
|
|
|
node = l->data;
|
|
|
|
/* Convert the point distance to a distance along the node */
|
|
point_distance -= length;
|
|
if (point_distance > node->length)
|
|
point_distance = node->length;
|
|
|
|
switch (node->k.type & ~CLUTTER_PATH_RELATIVE)
|
|
{
|
|
case CLUTTER_PATH_MOVE_TO:
|
|
*position = node->k.points[1];
|
|
break;
|
|
|
|
case CLUTTER_PATH_LINE_TO:
|
|
case CLUTTER_PATH_CLOSE:
|
|
if (node->length == 0)
|
|
*position = node->k.points[1];
|
|
else
|
|
{
|
|
position->x = (node->k.points[1].x
|
|
+ ((node->k.points[2].x - node->k.points[1].x)
|
|
* (gint) point_distance / (gint) node->length));
|
|
position->y = (node->k.points[1].y
|
|
+ ((node->k.points[2].y - node->k.points[1].y)
|
|
* (gint) point_distance / (gint) node->length));
|
|
}
|
|
break;
|
|
|
|
case CLUTTER_PATH_CURVE_TO:
|
|
if (node->length == 0)
|
|
*position = node->k.points[2];
|
|
else
|
|
{
|
|
_clutter_bezier_advance (node->bezier,
|
|
point_distance * CLUTTER_BEZIER_MAX_LENGTH
|
|
/ node->length,
|
|
position);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return node_num;
|
|
}
|
|
|
|
/**
|
|
* clutter_path_get_length:
|
|
* @path: a #ClutterPath
|
|
*
|
|
* Retrieves an approximation of the total length of the path.
|
|
*
|
|
* Return value: the length of the path.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
guint
|
|
clutter_path_get_length (ClutterPath *path)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_PATH (path), 0);
|
|
|
|
clutter_path_ensure_node_data (path);
|
|
|
|
return path->priv->total_length;
|
|
}
|
|
|
|
static ClutterPathNodeFull *
|
|
clutter_path_node_full_new (void)
|
|
{
|
|
return g_slice_new0 (ClutterPathNodeFull);
|
|
}
|
|
|
|
static void
|
|
clutter_path_node_full_free (ClutterPathNodeFull *node)
|
|
{
|
|
if (node->bezier)
|
|
_clutter_bezier_free (node->bezier);
|
|
|
|
g_slice_free (ClutterPathNodeFull, node);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_node_copy:
|
|
* @node: a #ClutterPathNode
|
|
*
|
|
* Makes an allocated copy of a node.
|
|
*
|
|
* Return value: the copied node.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
ClutterPathNode *
|
|
clutter_path_node_copy (const ClutterPathNode *node)
|
|
{
|
|
return g_slice_dup (ClutterPathNode, node);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_node_free:
|
|
* @node: a #ClutterPathNode
|
|
*
|
|
* Frees the memory of an allocated node.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_path_node_free (ClutterPathNode *node)
|
|
{
|
|
if (G_LIKELY (node))
|
|
g_slice_free (ClutterPathNode, node);
|
|
}
|
|
|
|
/**
|
|
* clutter_path_node_equal:
|
|
* @node_a: First node
|
|
* @node_b: Second node
|
|
*
|
|
* Compares two nodes and checks if they are the same type with the
|
|
* same coordinates.
|
|
*
|
|
* Return value: %TRUE if the nodes are the same.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_path_node_equal (const ClutterPathNode *node_a,
|
|
const ClutterPathNode *node_b)
|
|
{
|
|
guint n_points, i;
|
|
|
|
g_return_val_if_fail (node_a != NULL, FALSE);
|
|
g_return_val_if_fail (node_b != NULL, FALSE);
|
|
|
|
if (node_a->type != node_b->type)
|
|
return FALSE;
|
|
|
|
switch (node_a->type & ~CLUTTER_PATH_RELATIVE)
|
|
{
|
|
case CLUTTER_PATH_MOVE_TO: n_points = 1; break;
|
|
case CLUTTER_PATH_LINE_TO: n_points = 1; break;
|
|
case CLUTTER_PATH_CURVE_TO: n_points = 3; break;
|
|
case CLUTTER_PATH_CLOSE: n_points = 0; break;
|
|
default: return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < n_points; i++)
|
|
if (node_a->points[i].x != node_b->points[i].x
|
|
|| node_a->points[i].y != node_b->points[i].y)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (ClutterKnot, clutter_knot,
|
|
clutter_knot_copy,
|
|
clutter_knot_free);
|
|
|
|
/**
|
|
* clutter_knot_copy:
|
|
* @knot: a #ClutterKnot
|
|
*
|
|
* Makes an allocated copy of a knot.
|
|
*
|
|
* Return value: the copied knot.
|
|
*
|
|
* Since: 0.2
|
|
*/
|
|
ClutterKnot *
|
|
clutter_knot_copy (const ClutterKnot *knot)
|
|
{
|
|
if (G_UNLIKELY (knot == NULL))
|
|
return NULL;
|
|
|
|
return g_slice_dup (ClutterKnot, knot);
|
|
}
|
|
|
|
/**
|
|
* clutter_knot_free:
|
|
* @knot: a #ClutterKnot
|
|
*
|
|
* Frees the memory of an allocated knot.
|
|
*
|
|
* Since: 0.2
|
|
*/
|
|
void
|
|
clutter_knot_free (ClutterKnot *knot)
|
|
{
|
|
if (G_LIKELY (knot != NULL))
|
|
g_slice_free (ClutterKnot, knot);
|
|
}
|
|
|
|
/**
|
|
* clutter_knot_equal:
|
|
* @knot_a: First knot
|
|
* @knot_b: Second knot
|
|
*
|
|
* Compares to knot and checks if the point to the same location.
|
|
*
|
|
* Return value: %TRUE if the knots point to the same location.
|
|
*
|
|
* Since: 0.2
|
|
*/
|
|
gboolean
|
|
clutter_knot_equal (const ClutterKnot *knot_a,
|
|
const ClutterKnot *knot_b)
|
|
{
|
|
g_return_val_if_fail (knot_a != NULL, FALSE);
|
|
g_return_val_if_fail (knot_b != NULL, FALSE);
|
|
|
|
if (knot_a == knot_b)
|
|
return TRUE;
|
|
|
|
return knot_a->x == knot_b->x && knot_a->y == knot_b->y;
|
|
}
|