/tests/conform/test_initial_state
/tests/conform/test_label_cache
/tests/conform/test_mapped
+/tests/conform/test_path
/tests/conform/test_pick
/tests/conform/test_realized
/tests/conform/test_rect_set_color
+2008-12-05 Neil Roberts <neil@linux.intel.com>
+
+ Bug 1252 - Merge ClutterBehaviourPath and ClutterBehaviourBspline
+
+ * clutter/clutter-path.h:
+ * clutter/clutter-path.c: Implementation of new ClutterPath object
+ to represent a path combining straight line and bezier curve
+ elements.
+
+ * clutter/clutter.h: Include clutter-path.h and remove
+ clutter-behaviour-bspline.h
+
+ * tests/interactive/test-threads.c (test_threads_main):
+ * tests/interactive/test-script.c:
+ * tests/interactive/test-behave.c (test_behave_main): Use new path
+ API
+
+ * clutter/clutter-effect.c: Use the new ClutterBehaviourPath API.
+
+ * clutter/clutter-bezier.h:
+ * clutter/clutter-bezier.c: Moved bezier curve handling code out
+ from clutter-behaviour-bspline.c to a separate file.
+
+ * clutter/clutter-behaviour-path.h:
+ * clutter/clutter-behaviour-path.c: Reimplemented to work with a
+ ClutterPath
+
+ * clutter/clutter-behaviour-bspline.h:
+ * clutter/clutter-behaviour-bspline.c: Removed
+
+ * clutter/Makefile.am: Add clutter-path and clutter-bezier, remove
+ clutter-behaviour-bspline.
+
+ * tests/conform/test-path.c: New automatic test for ClutterPath
+ consistency
+
+ * tests/conform/test-conform-main.c (main): Add test_path
+
+ * tests/conform/Makefile.am (test_conformance_SOURCES): Add
+ test-path.c
+
+ * clutter/clutter-sections.txt: Add ClutterPath docs
+
+ * clutter/clutter.types:
+ * clutter/clutter-docs.xml:
+ * doc/reference/clutter/clutter-animation-tutorial.xml: Remove
+ mention of ClutterBehaviourBspline
+
+ * clutter/clutter-marshal.list: Add VOID:UINT
+
2008-12-04 Neil Roberts <neil@linux.intel.com>
Bug 1297 - Bring back support for GL_ARB_texture_rectangle
$(srcdir)/clutter-animation.h \
$(srcdir)/clutter-backend.h \
$(srcdir)/clutter-behaviour.h \
- $(srcdir)/clutter-behaviour-bspline.h \
$(srcdir)/clutter-behaviour-depth.h \
$(srcdir)/clutter-behaviour-ellipse.h \
$(srcdir)/clutter-behaviour-opacity.h \
$(srcdir)/clutter-main.h \
$(srcdir)/clutter-media.h \
$(srcdir)/clutter-model.h \
+ $(srcdir)/clutter-path.h \
$(srcdir)/clutter-rectangle.h \
$(srcdir)/clutter-score.h \
$(srcdir)/clutter-script.h \
clutter-animation.c \
clutter-backend.c \
clutter-behaviour.c \
- clutter-behaviour-bspline.c \
clutter-behaviour-depth.c \
clutter-behaviour-ellipse.c \
clutter-behaviour-opacity.c \
clutter-behaviour-path.c \
clutter-behaviour-rotate.c \
clutter-behaviour-scale.c \
+ clutter-bezier.c \
clutter-child-meta.c \
clutter-clone-texture.c \
clutter-color.c \
clutter-marshal.c \
clutter-media.c \
clutter-model.c \
+ clutter-path.c \
clutter-rectangle.c \
clutter-score.c \
clutter-script.c \
$(NULL)
source_h_priv = \
+ clutter-bezier.h \
clutter-debug.h \
clutter-keysyms-table.h \
clutter-model-private.h \
+++ /dev/null
-/* -*- mode:C; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/*
- * Clutter.
- *
- * An OpenGL based 'interactive canvas' library.
- *
- * Authored By Tomas Frydrych <tf@openedhand.com>
- *
- * Copyright (C) 2007 OpenedHand
- *
- * 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, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- */
-
-/**
- * SECTION:clutter-behaviour-bspline
- * @short_description: A behaviour interpolating position along a B-Spline
- *
- * #ClutterBehaviourBspline interpolates actors along a defined B-Spline path.
- *
- * A bezier spline is a set of cubic bezier curves defined by a sequence of
- * control points given when creating a new #ClutterBehaviourBspline instance.
- *
- * Additional bezier curves can be added to the end of the bspline using
- * clutter_behaviour_bspline_append_* family of functions, control points can
- * be moved using clutter_behaviour_bspline_adjust(). The bspline can be split
- * into two with clutter_behaviour_bspline_split(), and bsplines can be
- * concatenated using clutter_behaviour_bspline_join().
- *
- * Each time the behaviour reaches a point on the path, the "knot-reached"
- * signal is emitted.
- *
- * Since: 0.4
- */
-
-#include "clutter-behaviour-bspline.h"
-#include "clutter-debug.h"
-#include "clutter-fixed.h"
-#include "clutter-marshal.h"
-#include "clutter-private.h"
-#include "clutter-scriptable.h"
-#include "clutter-script-private.h"
-
-#include <stdlib.h>
-#include <memory.h>
-
-/*
- * We have some experimental code here to allow for constant velocity
- * movement of actors along the bezier path, this macro enables it.
- */
-#undef CBZ_L2T_INTERPOLATION
-
-/****************************************************************************
- * ClutterBezier -- represenation of a cubic bezier curve *
- * (private; a building block for the public bspline object) *
- ****************************************************************************/
-
-/*
- * The t parameter of the bezier is from interval <0,1>, so we can use
- * 14.18 format and special multiplication functions that preserve
- * more of the least significant bits but would overflow if the value
- * is > 1
- */
-#define CBZ_T_Q 18
-#define CBZ_T_ONE (1 << CBZ_T_Q)
-#define CBZ_T_MUL(x,y) ((((x) >> 3) * ((y) >> 3)) >> 12)
-#define CBZ_T_POW2(x) CBZ_T_MUL (x, x)
-#define CBZ_T_POW3(x) CBZ_T_MUL (CBZ_T_POW2 (x), x)
-#define CBZ_T_DIV(x,y) ((((x) << 9)/(y)) << 9)
-
-/*
- * Constants for sampling of the bezier
- */
-#define CBZ_T_SAMPLES 128
-#define CBZ_T_STEP (CBZ_T_ONE / CBZ_T_SAMPLES)
-#define CBZ_L_STEP (CBZ_T_ONE / CBZ_T_SAMPLES)
-
-typedef gint32 _FixedT;
-
-/*
- * This is a private type representing a single cubic bezier
- */
-typedef struct _ClutterBezier
-{
- /*
- * bezier coefficients -- these are calculated using multiplication and
- * addition from integer input, so these are also integers
- */
- gint ax;
- gint bx;
- gint cx;
- gint dx;
-
- gint ay;
- gint by;
- gint cy;
- gint dy;
-
- /* length of the bezier */
- guint length;
-
-#ifdef CBZ_L2T_INTERPOLATION
- /*
- * coefficients for the L -> t bezier; these are calculated from fixed
- * point input, and more specifically numbers that have been normalised
- * to fit <0,1>, so these are also fixed point, and we can used the
- * _FixedT type here.
- */
- _FixedT La;
- _FixedT Lb;
- _FixedT Lc;
- /* _FixedT Ld; == 0 */
-#endif
-} ClutterBezier;
-
-static ClutterBezier *
-clutter_bezier_new ()
-{
- return g_slice_new0 (ClutterBezier);
-}
-
-static void
-clutter_bezier_free (ClutterBezier * b)
-{
- if (G_LIKELY (b))
- {
- g_slice_free (ClutterBezier, b);
- }
-}
-
-static ClutterBezier *
-clutter_bezier_clone_and_move (ClutterBezier *b, gint x, gint y)
-{
- ClutterBezier * b2 = clutter_bezier_new ();
- memcpy (b2, b, sizeof (ClutterBezier));
-
- b2->dx += x;
- b2->dy += y;
-
- return b2;
-}
-
-#ifdef CBZ_L2T_INTERPOLATION
-/*
- * L is relative advance along the bezier curve from interval <0,1>
- */
-static _FixedT
-clutter_bezier_L2t (ClutterBezier *b, _FixedT L)
-{
- _FixedT t = CBZ_T_MUL (b->La, CBZ_T_POW3(L))
- + CBZ_T_MUL (b->Lb, CBZ_T_POW2(L))
- + CBZ_T_MUL (b->Lc, L);
-
- if (t > CBZ_T_ONE)
- t = CBZ_T_ONE;
- else if (t < 0)
- t = 0;
-
- return t;
-}
-#endif
-
-static gint
-clutter_bezier_t2x (ClutterBezier * b, _FixedT t)
-{
- /*
- * NB -- the int coefficients can be at most 8192 for the multiplication
- * to work in this fashion due to the limits of the 14.18 fixed.
- */
- return ((b->ax*CBZ_T_POW3(t) + b->bx*CBZ_T_POW2(t) + b->cx*t) >> CBZ_T_Q)
- + b->dx;
-}
-
-static gint
-clutter_bezier_t2y (ClutterBezier * b, _FixedT t)
-{
- /*
- * NB -- the int coefficients can be at most 8192 for the multiplication
- * to work in this fashion due to the limits of the 14.18 fixed.
- */
- return ((b->ay*CBZ_T_POW3(t) + b->by*CBZ_T_POW2(t) + b->cy*t) >> CBZ_T_Q)
- + b->dy;
-}
-
-/*
- * Advances along the bezier to relative length L and returns the coordinances
- * in knot
- */
-static void
-clutter_bezier_advance (ClutterBezier *b, _FixedT L, ClutterKnot * knot)
-{
-#ifdef CBZ_L2T_INTERPOLATION
- _FixedT t = clutter_bezier_L2t (b, L);
-#else
- _FixedT t = L;
-#endif
-
- knot->x = clutter_bezier_t2x (b, t);
- knot->y = clutter_bezier_t2y (b, t);
-
- CLUTTER_NOTE (BEHAVIOUR, "advancing to relative pt %f: t %f, {%d,%d}",
- (double) L / (double) CBZ_T_ONE,
- (double) t / (double) CBZ_T_ONE,
- knot->x, knot->y);
-}
-
-static void
-clutter_bezier_init (ClutterBezier *b,
- gint x_0, gint y_0,
- gint x_1, gint y_1,
- gint x_2, gint y_2,
- gint x_3, gint y_3)
-{
- _FixedT t;
- int i;
- int xp = x_0;
- int yp = y_0;
- _FixedT length [CBZ_T_SAMPLES + 1];
-
-#ifdef CBZ_L2T_INTERPOLATION
- int j, k;
- _FixedT L;
- _FixedT t_equalized [CBZ_T_SAMPLES + 1];
-#endif
-
-#if 0
- g_debug ("Initializing bezier at {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}",
- x0, y0, x1, y1, x2, y2, x3, y3);
-#endif
-
- b->dx = x_0;
- b->dy = y_0;
-
- b->cx = 3 * (x_1 - x_0);
- b->cy = 3 * (y_1 - y_0);
-
- b->bx = 3 * (x_2 - x_1) - b->cx;
- b->by = 3 * (y_2 - y_1) - b->cy;
-
- b->ax = x_3 - 3 * x_2 + 3 * x_1 - x_0;
- b->ay = y_3 - 3 * y_2 + 3 * y_1 - y_0;
-
-#if 0
- g_debug ("Cooeficients {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}",
- b->ax, b->ay, b->bx, b->by, b->cx, b->cy, b->dx, b->dy);
-#endif
-
- /*
- * Because of the way we do the multiplication in bezeir_t2x,y
- * these coefficients need to be at most 0x1fff; this should be the case,
- * I think, but have added this warning to catch any problems -- if it
- * triggers, we need to change those two functions a bit.
- */
- if (b->ax > 0x1fff || b->bx > 0x1fff || b->cx > 0x1fff)
- g_warning ("Calculated coefficents will result in multiplication "
- "overflow in clutter_bezier_t2x and clutter_bezier_t2y.");
-
- /*
- * Sample the bezier with CBZ_T_SAMPLES and calculate length at
- * each point.
- *
- * We are working with integers here, so we use the fast sqrti function.
- */
- length[0] = 0;
-
- for (t = CBZ_T_STEP, i = 1; i <= CBZ_T_SAMPLES; ++i, t += CBZ_T_STEP)
- {
- int x = clutter_bezier_t2x (b, t);
- int y = clutter_bezier_t2y (b, t);
-
- guint l = clutter_sqrti ((y - yp)*(y - yp) + (x - xp)*(x - xp));
-
- l += length[i-1];
-
- length[i] = l;
-
- xp = x;
- yp = y;
- }
-
- b->length = length[CBZ_T_SAMPLES];
-
-#if 0
- g_debug ("length %d", b->length);
-#endif
-
-#ifdef CBZ_L2T_INTERPOLATION
- /*
- * Now normalize the length values, converting them into _FixedT
- */
- for (i = 0; i <= CBZ_T_SAMPLES; ++i)
- {
- length[i] = (length[i] << CBZ_T_Q) / b->length;
- }
-
- /*
- * Now generate a L -> t table such that the L will equidistant
- * over <0,1>
- */
- t_equalized[0] = 0;
-
- for (i = 1, j = 1, L = CBZ_L_STEP; i < CBZ_T_SAMPLES; ++i, L += CBZ_L_STEP)
- {
- _FixedT l1, l2;
- _FixedT d1, d2, d;
- _FixedT t1, t2;
-
- /* find the band for our L */
- for (k = j; k < CBZ_T_SAMPLES; ++k)
- {
- if (L < length[k])
- break;
- }
-
- /*
- * Now we know that L is from (length[k-1],length[k]>
- * We remember k-1 in order not to have to iterate over the
- * whole length array in the next iteration of the main loop
- */
- j = k - 1;
-
- /*
- * Now interpolate equlised t as a weighted average
- */
- l1 = length[k-1];
- l2 = length[k];
- d1 = l2 - L;
- d2 = L - l1;
- d = l2 - l1;
- t1 = (k - 1) * CBZ_T_STEP;
- t2 = k * CBZ_T_STEP;
-
- t_equalized[i] = (t1*d1 + t2*d2)/d;
-
- if (t_equalized[i] < t_equalized[i-1])
- g_debug ("wrong t: L %f, l1 %f, l2 %f, t1 %f, t2 %f",
- (double) (L)/(double)CBZ_T_ONE,
- (double) (l1)/(double)CBZ_T_ONE,
- (double) (l2)/(double)CBZ_T_ONE,
- (double) (t1)/(double)CBZ_T_ONE,
- (double) (t2)/(double)CBZ_T_ONE);
-
- }
-
- t_equalized[CBZ_T_SAMPLES] = CBZ_T_ONE;
-
- /* We now fit a bezier -- at this stage, do a single fit through our values
- * at 0, 1/3, 2/3 and 1
- *
- * FIXME -- do we need to use a better fitting approach to choose the best
- * beziere. The actual curve we acquire this way is not too bad shapwise,
- * but (probably due to rounding errors) the resulting curve no longer
- * satisfies the necessary condition that for L2 > L1, t2 > t1, which
- * causes oscilation.
- */
-
-#if 0
- /*
- * These are the control points we use to calculate the curve coefficients
- * for bezier t(L); these are not needed directly, but are implied in the
- * calculations below.
- *
- * (p0 is 0,0, and p3 is 1,1)
- */
- p1 = (18 * t_equalized[CBZ_T_SAMPLES/3] -
- 9 * t_equalized[2*CBZ_T_SAMPLES/3] +
- 2 << CBZ_T_Q) / 6;
-
- p2 = (18 * t_equalized[2*CBZ_T_SAMPLES/3] -
- 9 * t_equalized[CBZ_T_SAMPLES/3] -
- (5 << CBZ_T_Q)) / 6;
-#endif
-
- b->Lc = (18 * t_equalized[CBZ_T_SAMPLES/3] -
- 9 * t_equalized[2*CBZ_T_SAMPLES/3] +
- (2 << CBZ_T_Q)) >> 1;
-
- b->Lb = (36 * t_equalized[2*CBZ_T_SAMPLES/3] -
- 45 * t_equalized[CBZ_T_SAMPLES/3] -
- (9 << CBZ_T_Q)) >> 1;
-
- b->La = ((27 * (t_equalized[CBZ_T_SAMPLES/3] -
- t_equalized[2*CBZ_T_SAMPLES/3]) +
- (7 << CBZ_T_Q)) >> 1) + CBZ_T_ONE;
-
- g_debug ("t(1/3) %f, t(2/3) %f",
- (double)t_equalized[CBZ_T_SAMPLES/3]/(double)CBZ_T_ONE,
- (double)t_equalized[2*CBZ_T_SAMPLES/3]/(double)CBZ_T_ONE);
-
- g_debug ("L -> t coefficients: %f, %f, %f",
- (double)b->La/(double)CBZ_T_ONE,
- (double)b->Lb/(double)CBZ_T_ONE,
- (double)b->Lc/(double)CBZ_T_ONE);
-
-
- /*
- * For debugging, you can load these values into a spreadsheet and graph
- * them to see how well the approximation matches the data
- */
- for (i = 0; i < CBZ_T_SAMPLES; ++i)
- {
- g_print ("%f, %f, %f\n",
- (double)(i*CBZ_T_STEP)/(double)CBZ_T_ONE,
- (double)(t_equalized[i])/(double)CBZ_T_ONE,
- (double)(clutter_bezier_L2t(b,i*CBZ_T_STEP))/(double)CBZ_T_ONE);
- }
-#endif
-}
-
-/*
- * Moves a control point at indx to location represented by knot
- */
-static void
-clutter_bezier_adjust (ClutterBezier * b, ClutterKnot * knot, guint indx)
-{
- guint x[4], y[4];
-
- g_assert (indx < 4);
-
- x[0] = b->dx;
- y[0] = b->dy;
-
- x[1] = b->cx / 3 + x[0];
- y[1] = b->cy / 3 + y[0];
-
- x[2] = b->bx / 3 + b->cx + x[1];
- y[2] = b->by / 3 + b->cy + y[1];
-
- x[3] = b->ax + x[0] + b->cx + b->bx;
- y[3] = b->ay + y[0] + b->cy + b->by;
-
- x[indx] = knot->x;
- y[indx] = knot->y;
-
- clutter_bezier_init (b, x[0], y[0], x[1], y[1], x[2], y[2], x[3], y[3]);
-}
-
-
-/****************************************************************************
- * *
- * ClutterBehaviourBspline *
- * *
- ****************************************************************************/
-
-static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);
-
-G_DEFINE_TYPE_WITH_CODE (ClutterBehaviourBspline,
- clutter_behaviour_bspline,
- CLUTTER_TYPE_BEHAVIOUR,
- G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
- clutter_scriptable_iface_init));
-
-#define CLUTTER_BEHAVIOUR_BSPLINE_GET_PRIVATE(obj) \
- (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
- CLUTTER_TYPE_BEHAVIOUR_BSPLINE, \
- ClutterBehaviourBsplinePrivate))
-
-enum
- {
- KNOT_REACHED,
-
- LAST_SIGNAL
- };
-
-static guint bspline_signals[LAST_SIGNAL] = { 0, };
-
-struct _ClutterBehaviourBsplinePrivate
-{
- /*
- * The individual bezier curves that make up this bspline
- */
- GArray * splines;
-
- /*
- * The length of the bspline
- */
- guint length;
-
- /*
- * Bspline offsets (these allow us to move the bspline without having to
- * mess about with the individual beziers).
- *
- * NB: this is not the actual origin, but an adjustment to the origin of
- * the first bezier; it defaults to 0 unless the user explicitely changes
- * the bspline offset.
- */
- gint x;
- gint y;
-
- /*
- * A temporary stack of control points used by the append methods
- */
- GArray * point_stack;
-};
-
-static void
-clutter_behaviour_bspline_finalize (GObject *object)
-{
- ClutterBehaviourBspline *self = CLUTTER_BEHAVIOUR_BSPLINE (object);
- ClutterBehaviourBsplinePrivate *priv = self->priv;
- gint i;
-
- for (i = 0; i < priv->splines->len; ++i)
- clutter_bezier_free (g_array_index (priv->splines, ClutterBezier*, i));
-
- g_array_free (priv->splines, TRUE);
-
- for (i = 0; i < priv->point_stack->len; ++i)
- clutter_knot_free (g_array_index (priv->point_stack, ClutterKnot*, i));
-
- g_array_free (priv->point_stack, TRUE);
-
- G_OBJECT_CLASS (clutter_behaviour_bspline_parent_class)->finalize (object);
-}
-
-static void
-actor_apply_knot_foreach (ClutterBehaviour *behaviour,
- ClutterActor *actor,
- gpointer data)
-{
- ClutterKnot *knot = data;
-
- clutter_actor_set_position (actor, knot->x, knot->y);
-}
-
-/*
- * Advances to a point that is at distance 'to' along the spline;
- *
- * returns FALSE if the length is beyond the end of the bspline.
- */
-static gboolean
-clutter_behaviour_bspline_advance (ClutterBehaviourBspline *bs,
- guint to)
-{
- ClutterBehaviourBsplinePrivate *priv = bs->priv;
- gint i;
- guint length = 0;
- ClutterKnot knot;
-
- if (to > priv->length)
- return FALSE;
-
- for (i = 0; i < priv->splines->len; ++i)
- {
- ClutterBezier *b = g_array_index (priv->splines, ClutterBezier*, i);
-
- if (length + b->length >= to)
- {
- _FixedT L = ((to - length) << CBZ_T_Q) / b->length;
-
- clutter_bezier_advance (b, L, &knot);
-
- knot.x += bs->priv->x;
- knot.y += bs->priv->y;
-
- CLUTTER_NOTE (BEHAVIOUR, "advancing to length %d: (%d, %d)",
- to, knot.x, knot.y);
-
- clutter_behaviour_actors_foreach (CLUTTER_BEHAVIOUR (bs),
- actor_apply_knot_foreach,
- &knot);
-
- g_signal_emit (bs, bspline_signals[KNOT_REACHED], 0, &knot);
-
- return TRUE;
- }
-
- length += b->length;
- }
-
- /* should not be reached */
- return FALSE;
-}
-
-static void
-clutter_behaviour_bspline_alpha_notify (ClutterBehaviour * behave,
- guint32 alpha)
-{
- ClutterBehaviourBspline * bs = CLUTTER_BEHAVIOUR_BSPLINE (behave);
- gint to = (alpha * bs->priv->length) / CLUTTER_ALPHA_MAX_ALPHA;
-
- clutter_behaviour_bspline_advance (bs, to);
-}
-
-static void
-clutter_behaviour_bspline_class_init (ClutterBehaviourBsplineClass *klass)
-{
- GObjectClass * object_class = G_OBJECT_CLASS (klass);
- ClutterBehaviourClass * behave_class = CLUTTER_BEHAVIOUR_CLASS (klass);
-
- object_class->finalize = clutter_behaviour_bspline_finalize;
-
- behave_class->alpha_notify = clutter_behaviour_bspline_alpha_notify;
-
- /**
- * ClutterBehaviourBspline::knot-reached:
- * @pathb: the object which received the signal
- * @knot: the #ClutterKnot reached
- *
- * This signal is emitted at the end of each frame.
- *
- * Since: 0.2
- */
- bspline_signals[KNOT_REACHED] =
- g_signal_new ("knot-reached",
- G_TYPE_FROM_CLASS (object_class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (ClutterBehaviourBsplineClass, knot_reached),
- NULL, NULL,
- clutter_marshal_VOID__BOXED,
- G_TYPE_NONE, 1,
- CLUTTER_TYPE_KNOT);
-
- g_type_class_add_private (klass, sizeof (ClutterBehaviourBsplinePrivate));
-}
-
-static void
-clutter_behaviour_bspline_init (ClutterBehaviourBspline * self)
-{
- ClutterBehaviourBsplinePrivate *priv;
-
- self->priv = priv = CLUTTER_BEHAVIOUR_BSPLINE_GET_PRIVATE (self);
-
- priv->splines = g_array_new (FALSE, FALSE, sizeof (ClutterBezier *));
- priv->point_stack = g_array_new (FALSE, FALSE, sizeof (ClutterKnot *));
- priv->length = 0;
-}
-
-static void
-clutter_behaviour_bspline_set_custom_property (ClutterScriptable *scriptable,
- ClutterScript *script,
- const gchar *name,
- const GValue *value)
-{
- if (strcmp (name, "knots") == 0)
- {
- ClutterBehaviourBspline *bspline;
- GSList *knots, *l;
-
- if (!G_VALUE_HOLDS (value, G_TYPE_POINTER))
- return;
-
- bspline = CLUTTER_BEHAVIOUR_BSPLINE (scriptable);
-
- knots = g_value_get_pointer (value);
- for (l = knots; l != NULL; l = l->next)
- {
- ClutterKnot *knot = l->data;
-
- clutter_behaviour_bspline_append_knot (bspline, knot);
- clutter_knot_free (knot);
- }
-
- g_slist_free (knots);
- }
- else
- g_object_set_property (G_OBJECT (scriptable), name, value);
-}
-
-static gboolean
-clutter_behaviour_bspline_parse_custom_node (ClutterScriptable *scriptable,
- ClutterScript *script,
- GValue *value,
- const gchar *name,
- JsonNode *node)
-{
- if (strcmp (name, "knots") == 0)
- {
- JsonArray *array;
- guint knots_len, i;
- GSList *knots = NULL;
-
- array = json_node_get_array (node);
- knots_len = json_array_get_length (array);
-
- for (i = 0; i < knots_len; i++)
- {
- JsonNode *val = json_array_get_element (array, i);
- ClutterKnot knot = { 0, };
-
- if (clutter_script_parse_knot (script, val, &knot))
- {
- CLUTTER_NOTE (SCRIPT, "parsed knot [ x:%d, y:%d ]",
- knot.x, knot.y);
-
- knots = g_slist_prepend (knots, clutter_knot_copy (&knot));
- }
- }
-
- g_value_init (value, G_TYPE_POINTER);
- g_value_set_pointer (value, g_slist_reverse (knots));
-
- return TRUE;
- }
-
- return FALSE;
-}
-
-static void
-clutter_scriptable_iface_init (ClutterScriptableIface *iface)
-{
- iface->parse_custom_node = clutter_behaviour_bspline_parse_custom_node;
- iface->set_custom_property = clutter_behaviour_bspline_set_custom_property;
-}
-
-/**
- * clutter_behaviour_bspline_new:
- * @alpha: a #ClutterAlpha, or %NULL
- * @knots: a list of #ClutterKnots representing individual control points
- * @n_knots: the number of control points
- *
- * Creates a new bezier spline behaviour. You can use this behaviour to drive
- * actors along the bezier spline, described by the @knots control points.
- *
- * Bspline is defined by 3n + 1 points, n >=1; any trailing points passed
- * into this function are stored internally and used during any subsequent
- * clutter_behaviour_bspline_append_* operations.
- *
- * Return value: a #ClutterBehaviour
- *
- * Since: 0.4
- */
-ClutterBehaviour *
-clutter_behaviour_bspline_new (ClutterAlpha *alpha,
- const ClutterKnot *knots,
- guint n_knots)
-{
- ClutterBehaviourBspline *bs;
- gint i;
-
- g_return_val_if_fail (alpha == NULL || CLUTTER_IS_ALPHA (alpha), NULL);
-
- bs = g_object_new (CLUTTER_TYPE_BEHAVIOUR_BSPLINE, "alpha", alpha, NULL);
-
- for (i = 0; i < n_knots; ++i)
- clutter_behaviour_bspline_append_knot (bs, &knots[i]);
-
- return CLUTTER_BEHAVIOUR (bs);
-}
-
-/*
- * Appends a single spline; knots points to 4 knots if this is first
- * bezier in the spline, 3 subsequently
- */
-static void
-clutter_behaviour_bspline_append_spline (ClutterBehaviourBspline * bs,
- const ClutterKnot ** knots)
-{
- ClutterBehaviourBsplinePrivate *priv;
- gint i;
- ClutterBezier * b;
- ClutterKnot knot0;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs));
- priv = bs->priv;
-
- if (priv->splines->len)
- {
- /* Get the first point from the last curve */
- ClutterBezier *b_last;
-
- b_last = g_array_index (priv->splines,
- ClutterBezier *,
- priv->splines->len - 1);
-
- knot0.x = b_last->ax + b_last->bx + b_last->cx + b_last->dx;
- knot0.y = b_last->ay + b_last->by + b_last->cy + b_last->dy;
-
- i = 0;
- }
- else
- {
- knot0.x = knots[0]->x;
- knot0.y = knots[0]->y;
- i = 1;
- }
-
- b = clutter_bezier_new ();
- clutter_bezier_init (b,
- knot0.x,
- knot0.y,
- knots[i]->x, knots[i]->y,
- knots[i + 1]->x, knots[i + 1]->y,
- knots[i + 2]->x, knots[i + 2]->y);
-
- priv->splines = g_array_append_val (priv->splines, b);
-
- priv->length += b->length;
-}
-
-/**
- * clutter_behaviour_bspline_append_knot:
- * @bs: a #ClutterBehaviourBspline
- * @knot: a #ClutterKnot control point to append.
- *
- * Appends a #ClutterKnot control point to the bezier spline bs. Note, that
- * since a bezier is defined by 4 control points, the point gets stored in
- * a temporary chache, and only when there are enough control points to
- * create a new bezier curve will the bspline extended.
- *
- * Since: 0.4
- */
-void
-clutter_behaviour_bspline_append_knot (ClutterBehaviourBspline * bs,
- const ClutterKnot * knot)
-{
- ClutterBehaviourBsplinePrivate *priv;
- ClutterKnot * k = clutter_knot_copy (knot);
- guint needed = 3;
- guint i;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs));
- priv = bs->priv;
-
- g_array_append_val (priv->point_stack, k);
-
- if (priv->splines->len == 0)
- needed = 4;
-
- if (priv->point_stack->len == needed)
- {
- clutter_behaviour_bspline_append_spline (bs,
- (const ClutterKnot**) priv->point_stack->data);
-
- for (i = 0; i < needed; ++i)
- {
- clutter_knot_free (g_array_index (priv->point_stack,
- ClutterKnot *,
- i));
- }
-
- g_array_set_size (priv->point_stack, 0);
- }
-}
-
-static void
-clutter_behaviour_bspline_append_knots_valist (ClutterBehaviourBspline *bs,
- const ClutterKnot *first_knot,
- va_list args)
-{
- const ClutterKnot * knot;
-
- knot = first_knot;
- while (knot)
- {
- clutter_behaviour_bspline_append_knot (bs, knot);
- knot = va_arg (args, ClutterKnot*);
- }
-}
-
-/**
- * clutter_behaviour_bspline_append_knots:
- * @bs: a #ClutterBehaviourBspline
- * @first_knot: first #ClutterKnot
- * @VarArgs: a NULL-terminated array of #ClutterKnot control points.
- *
- * Appends a bezier spline defined by the last control point of bezier spline
- * bs and the array of #ClutterKnot control points to the orginal bezier spline
- * bs.
- *
- * Since: 0.6
- */
-void
-clutter_behaviour_bspline_append_knots (ClutterBehaviourBspline * bs,
- const ClutterKnot * first_knot,
- ...)
-{
- va_list args;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs));
- g_return_if_fail (first_knot != NULL);
-
- va_start (args, first_knot);
- clutter_behaviour_bspline_append_knots_valist (bs, first_knot, args);
- va_end (args);
-}
-
-/**
- * clutter_behaviour_bspline_truncate:
- * @bs: a #ClutterBehaviourBspline
- * @offset: offset of control where the bspline should be truncated
- *
- * Truncates the bezier spline at the control point; if the control point at
- * offset is not one of the on-curve points, the bspline will be
- * truncated at the nearest preceeding on-curve point.
- *
- * Since: 0.4
- */
-void
-clutter_behaviour_bspline_truncate (ClutterBehaviourBspline *bs,
- guint offset)
-{
- ClutterBehaviourBsplinePrivate *priv;
- guint i;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs));
-
- if (!offset)
- {
- clutter_behaviour_bspline_clear (bs);
- return;
- }
-
- priv = bs->priv;
-
- /* convert control point offset to the offset of last spline to keep */
- offset = (offset - 1) / 3;
-
- priv->splines = g_array_set_size (priv->splines, offset + 1);
- priv->length = 0;
-
- for (i = 0; i < priv->splines->len; ++i)
- {
- ClutterBezier *b;
-
- b = g_array_index (priv->splines, ClutterBezier*, i);
-
- priv->length += b->length;
- }
-
-}
-
-/**
- * clutter_behaviour_bspline_clear:
- * @bs: a #ClutterBehaviourBspline
- *
- * Empties a bspline.
- *
- * Since: 0.4
- */
-void
-clutter_behaviour_bspline_clear (ClutterBehaviourBspline *bs)
-{
- ClutterBehaviourBsplinePrivate *priv;
- gint i;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs));
-
- priv = bs->priv;
-
- for (i = 0; i < priv->splines->len; ++i)
- clutter_bezier_free (g_array_index (priv->splines, ClutterBezier*, i));
-
- g_array_set_size (priv->splines, 0);
-
- for (i = 0; i < priv->point_stack->len; ++i)
- clutter_knot_free (g_array_index (priv->point_stack, ClutterKnot*, i));
-
- g_array_set_size (priv->point_stack, 0);
-
- priv->x = 0;
- priv->y = 0;
- priv->length = 0;
-}
-
-/**
- * clutter_behaviour_bspline_join:
- * @bs1: a #ClutterBehaviourBspline
- * @bs2: a #ClutterBehaviourBspline
- *
- * Joins a copy of bezier spline bs2 onto the end of bezier spline bs1; bs2 is
- * not modified.
- *
- * Since: 0.4
- */
-void
-clutter_behaviour_bspline_join (ClutterBehaviourBspline *bs1,
- ClutterBehaviourBspline *bs2)
-{
- ClutterBehaviourBsplinePrivate *priv;
- gint i, x_1, y_1;
- ClutterKnot knot;
- ClutterBezier *b, *b2;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs1));
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs2));
-
- clutter_behaviour_bspline_get_origin (bs2, &knot);
-
- priv = bs1->priv;
-
- b = g_array_index (priv->splines, ClutterBezier*, priv->splines->len - 1);
-
- x_1 = clutter_bezier_t2x (b, CBZ_T_ONE);
- y_1 = clutter_bezier_t2y (b, CBZ_T_ONE);
-
- /*
- * need to move bs2 so it joins bs1
- */
- x_1 -= knot.x;
- y_1 -= knot.y;
-
- for (i = 0; i < priv->splines->len; ++i)
- {
- b = g_array_index (bs2->priv->splines, ClutterBezier*, i);
- b2 = clutter_bezier_clone_and_move (b, x_1, y_1);
-
- priv->length += b2->length;
- g_array_append_val (priv->splines, b2);
- }
-}
-
-/**
- * clutter_behaviour_bspline_split:
- * @bs: a #ClutterBehaviourBspline
- * @offset: an offset of the control point at which to split the spline.
- *
- * Splits a bezier spline into two at the control point at offset; if the
- * control point at offset is not one of the on-curve bezier points, the
- * bspline will be split at the nearest on-curve point before the offset.
- * The original bspline is shortened appropriately.
- *
- * Return value: new ClutterBehaviourBspline.
- *
- * Since: 0.4
- */
-ClutterBehaviour *
-clutter_behaviour_bspline_split (ClutterBehaviourBspline *bs,
- guint offset)
-{
- ClutterBehaviourBsplinePrivate *priv;
- ClutterBehaviourBspline * bs2 = NULL;
- ClutterAlpha * alpha;
- guint i, split, length2 = 0;
-
- g_return_val_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs), NULL);
-
- priv = bs->priv;
-
- split = offset / 3;
-
- if (split == 0 || split >= priv->splines->len)
- return NULL;
-
- alpha = clutter_behaviour_get_alpha (CLUTTER_BEHAVIOUR (bs));
-
- bs2 = g_object_new (CLUTTER_TYPE_BEHAVIOUR_BSPLINE, "alpha", alpha, NULL);
-
- bs2->priv->x = priv->x;
- bs2->priv->y = priv->y;
-
- for (i = split; i < priv->splines->len; ++i)
- {
- ClutterBezier *b;
-
- b = g_array_index (priv->splines, ClutterBezier*, i);
- g_array_append_val (bs2->priv->splines, b);
-
- length2 += b->length;
- }
-
- bs2->priv->length -= length2;
- bs2->priv->length = length2;
-
- g_array_set_size (priv->splines, split);
-
- return CLUTTER_BEHAVIOUR (bs2);
-}
-
-/**
- * clutter_behaviour_bspline_adjust:
- * @bs: a #ClutterBehaviourBspline
- * @offset: an index of control point to ajdust
- * @knot: a #ClutterKnot with new coordinances for the control point.
- *
- * Change the coordinaces of control point at index to those represented by
- * the knot.
- *
- * Since: 0.4
- */
-void
-clutter_behaviour_bspline_adjust (ClutterBehaviourBspline *bs,
- guint offset,
- ClutterKnot *knot)
-{
- ClutterBehaviourBsplinePrivate *priv;
- ClutterBezier * b1 = NULL;
- ClutterBezier * b2 = NULL;
- guint p1_indx = 0;
- guint p2_indx = 0;
- guint old_length;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs));
-
- priv = bs->priv;
-
- /*
- * Find the bezier(s) affected by change of this control point
- * and the relative position of the control point within them
- */
-
- if (offset == 0)
- b1 = g_array_index (priv->splines, ClutterBezier*, 0);
- else if (offset + 1 == priv->splines->len)
- {
- b2 = g_array_index (priv->splines,
- ClutterBezier*,
- priv->splines->len - 1);
- p2_indx = 3;
- }
- else
- {
- guint mod3 = offset % 3;
- guint i = offset / 3;
-
- if (mod3 == 0)
- {
- /* on-curve point, i.e., two beziers */
- b1 = g_array_index (priv->splines, ClutterBezier*, i - 1);
- b2 = g_array_index (priv->splines, ClutterBezier*, i);
- p1_indx = 3;
- }
- else
- {
- b1 = g_array_index (priv->splines, ClutterBezier*, i);
- p1_indx = mod3;
- }
- }
-
- /*
- * Adjust the bezier(s) and total bspline length
- */
- if (b1)
- {
- old_length = b1->length;
- clutter_bezier_adjust (b1, knot, p1_indx);
- priv->length = priv->length - old_length + b1->length;
- }
-
- if (b2)
- {
- old_length = b2->length;
- clutter_bezier_adjust (b2, knot, p2_indx);
- priv->length = priv->length - old_length + b2->length;
- }
-}
-
-/**
- * clutter_behaviour_bspline_set_origin
- * @bs: a #ClutterBehaviourBspline
- * @knot: a #ClutterKnot origin for the bezier
- *
- * Sets the origin of the bezier to the point represented by knot. (Initially
- * the origin of a bspline is given by the position of the first control point
- * of the first bezier curve.)
- *
- * Since: 0.4
- */
-void
-clutter_behaviour_bspline_set_origin (ClutterBehaviourBspline * bs,
- ClutterKnot * knot)
-{
- ClutterBehaviourBsplinePrivate *priv;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs));
-
- priv = bs->priv;
-
- if (priv->splines->len == 0)
- {
- priv->x = knot->x;
- priv->y = knot->y;
- }
- else
- {
- ClutterBezier *b;
-
- b = g_array_index (priv->splines, ClutterBezier*, 0);
-
- priv->x = knot->x - b->dx;
- priv->y = knot->y - b->dy;
-
- CLUTTER_NOTE (BEHAVIOUR, "setting origin to (%d, %d): "
- "b (%d, %d), adjustment (%d, %d)",
- knot->x, knot->y,
- b->dx, b->dy,
- priv->x, priv->y);
- }
-}
-
-/**
- * clutter_behaviour_bspline_get_origin
- * @bs: a #ClutterBehaviourBspline
- * @knot: a #ClutterKnot where to store the origin of the bezier
- *
- * Gets the origin of the bezier.
- *
- * Since: 0.4
- */
-void
-clutter_behaviour_bspline_get_origin (ClutterBehaviourBspline *bs,
- ClutterKnot *knot)
-{
- ClutterBehaviourBsplinePrivate *priv;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs));
- g_return_if_fail (knot != NULL);
-
- priv = bs->priv;
-
- if (priv->splines->len == 0)
- {
- knot->x = priv->x;
- knot->y = priv->y;
- }
- else
- {
- ClutterBezier *b;
-
- b = g_array_index (priv->splines, ClutterBezier*, 0);
-
- knot->x = priv->x + b->dx;
- knot->y = priv->y + b->dy;
- }
-}
-
+++ /dev/null
-/*
- * Clutter.
- *
- * An OpenGL based 'interactive canvas' library.
- *
- * Authored By Tomas Frydrych <tf@openedhand.com>
- *
- * Copyright (C) 2006, 2007 OpenedHand
- *
- * 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/>.
- */
-
-#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
-#error "Only <clutter/clutter.h> can be included directly."
-#endif
-
-#ifndef __CLUTTER_BEHAVIOUR_BSPLINE_H__
-#define __CLUTTER_BEHAVIOUR_BSPLINE_H__
-
-#include <clutter/clutter-alpha.h>
-#include <clutter/clutter-actor.h>
-#include <clutter/clutter-behaviour.h>
-
-G_BEGIN_DECLS
-
-#define CLUTTER_TYPE_BEHAVIOUR_BSPLINE (clutter_behaviour_bspline_get_type ())
-
-#define CLUTTER_BEHAVIOUR_BSPLINE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- CLUTTER_TYPE_BEHAVIOUR_BSPLINE, ClutterBehaviourBspline))
-
-#define CLUTTER_BEHAVIOUR_BSPLINE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST ((klass), \
- CLUTTER_TYPE_BEHAVIOUR_BSPLINE, ClutterBehaviourBsplineClass))
-
-#define CLUTTER_IS_BEHAVIOUR_BSPLINE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- CLUTTER_TYPE_BEHAVIOUR_BSPLINE))
-
-#define CLUTTER_IS_BEHAVIOUR_BSPLINE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE ((klass), \
- CLUTTER_TYPE_BEHAVIOUR_BSPLINE))
-
-#define CLUTTER_BEHAVIOUR_BSPLINE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- CLUTTER_TYPE_BEHAVIOUR_BSPLINE, ClutterBehaviourBsplineClass))
-
-typedef struct _ClutterBehaviourBspline ClutterBehaviourBspline;
-typedef struct _ClutterBehaviourBsplinePrivate ClutterBehaviourBsplinePrivate;
-typedef struct _ClutterBehaviourBsplineClass ClutterBehaviourBsplineClass;
-
-/**
- * ClutterBehaviourBspline:
- *
- * #ClutterBehaviourBspline-struct contains only private data and
- * should be accessed using the functions below.
- *
- * Since: 0.2
- */
-struct _ClutterBehaviourBspline
-{
- ClutterBehaviour parent_instance;
- ClutterBehaviourBsplinePrivate *priv;
-};
-
-/**
- * ClutterBehaviourBsplineClass:
- * @knot_reached: class handler for the #ClutterBehaviourBspline::knot-reached
- * signal
- *
- * #ClutterBehaviourBsplineClass-struct contains only private data
- * and should be accessed using the functions below.
- *
- * Since: 0.2
- */
-struct _ClutterBehaviourBsplineClass
-{
- /*< private >*/
- ClutterBehaviourClass parent_class;
-
- /*< public >*/
- void (*knot_reached) (ClutterBehaviourBspline *bsplineb,
- const ClutterKnot *knot);
-
- /*< private >*/
- /* padding for future expansion */
- void (*_clutter_bspline_1) (void);
- void (*_clutter_bspline_2) (void);
- void (*_clutter_bspline_3) (void);
- void (*_clutter_bspline_4) (void);
-};
-
-GType clutter_behaviour_bspline_get_type (void) G_GNUC_CONST;
-
-ClutterBehaviour *clutter_behaviour_bspline_new (ClutterAlpha *alpha,
- const ClutterKnot *knots,
- guint n_knots);
-void clutter_behaviour_bspline_append_knot (ClutterBehaviourBspline *bs,
- const ClutterKnot *knot);
-void clutter_behaviour_bspline_append_knots (ClutterBehaviourBspline *bs,
- const ClutterKnot *first_knot,
- ...) G_GNUC_NULL_TERMINATED;
-void clutter_behaviour_bspline_truncate (ClutterBehaviourBspline *bs,
- guint offset);
-void clutter_behaviour_bspline_join (ClutterBehaviourBspline *bs1,
- ClutterBehaviourBspline *bs2);
-ClutterBehaviour *clutter_behaviour_bspline_split (ClutterBehaviourBspline *bs,
- guint offset);
-void clutter_behaviour_bspline_clear (ClutterBehaviourBspline *bs);
-void clutter_behaviour_bspline_adjust (ClutterBehaviourBspline *bs,
- guint offset,
- ClutterKnot *knot);
-void clutter_behaviour_bspline_set_origin (ClutterBehaviourBspline *bs,
- ClutterKnot *knot);
-void clutter_behaviour_bspline_get_origin (ClutterBehaviourBspline *bs,
- ClutterKnot *knot);
-
-G_END_DECLS
-
-#endif /* __CLUTTER_BEHAVIOUR_BSPLINE_H__ */
/**
* SECTION:clutter-behaviour-path
- * @short_description: A behaviour interpolating position along a path
+ * @short_description: A behaviour for moving actors along a #ClutterPath
*
* #ClutterBehaviourPath interpolates actors along a defined path.
*
- * A path is a set of #ClutterKnots object given when creating a new
- * #ClutterBehaviourPath instance. Knots can be also added to the path
- * using clutter_behaviour_path_append_knot(). The whole path can be
- * cleared using clutter_behaviour_path_clear(). Each time the behaviour
- * reaches a knot in the path, the "knot-reached" signal is emitted.
+ * A path is described by a #ClutterPath object. The path can contain
+ * straight line parts and bezier curves. If the path contains
+ * %CLUTTER_PATH_MOVE_TO parts then the actors will jump to those
+ * coordinates. This can be used make disjoint paths.
*
- * This first knot in the path is reached with the lower bound value
- * provided by the #ClutterAlpha objectused by the behaviour; the last
- * knot in the path is reached with the upper bound value provided by
- * the #ClutterAlpha object used by the behaviour.
+ * When creating a path behaviour in a #ClutterScript, you can specify
+ * the path property directly as a string. For example:
+ *
+ * |[
+ * {
+ * "id" : "spline-path",
+ * "type" : "ClutterBehaviourPath",
+ * "path" : "M 50 50 L 100 100",
+ * "alpha" : {
+ * "timeline" : "main-timeline",
+ * "function" : "ramp
+ * }
+ * }
+ * ]|
*
* <note>If the alpha function is a periodic function, i.e. it returns to
* 0 after reaching %CLUTTER_ALPHA_MAX_ALPHA, then the actors will walk
#include "clutter-actor.h"
#include "clutter-behaviour.h"
#include "clutter-behaviour-path.h"
+#include "clutter-bezier.h"
#include "clutter-debug.h"
#include "clutter-enum-types.h"
#include "clutter-main.h"
#include "clutter-marshal.h"
#include "clutter-private.h"
-#include "clutter-scriptable.h"
#include "clutter-script-private.h"
+#include "clutter-scriptable.h"
#include <math.h>
G_DEFINE_TYPE_WITH_CODE (ClutterBehaviourPath,
clutter_behaviour_path,
- CLUTTER_TYPE_BEHAVIOUR,
+ CLUTTER_TYPE_BEHAVIOUR,
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
clutter_scriptable_iface_init));
struct _ClutterBehaviourPathPrivate
{
- GSList *knots;
- ClutterKnot *last_knot_passed;
+ ClutterPath *path;
+ guint last_knot_passed;
};
#define CLUTTER_BEHAVIOUR_PATH_GET_PRIVATE(obj) \
{
PROP_0,
- PROP_KNOT
+ PROP_PATH
};
static void
-clutter_behaviour_path_finalize (GObject *object)
-{
- ClutterBehaviourPath *self = CLUTTER_BEHAVIOUR_PATH(object);
-
- g_slist_foreach (self->priv->knots, (GFunc) clutter_knot_free, NULL);
- g_slist_free (self->priv->knots);
-
- G_OBJECT_CLASS (clutter_behaviour_path_parent_class)->finalize (object);
-}
-
-static inline void
-interpolate (const ClutterKnot *start,
- const ClutterKnot *end,
- ClutterKnot *out,
- ClutterFixed t)
-{
- out->x = start->x + COGL_FIXED_TO_INT (t * (end->x - start->x));
- out->y = start->y + COGL_FIXED_TO_INT (t * (end->y - start->y));
-}
-
-static gint
-node_distance (const ClutterKnot *start,
- const ClutterKnot *end)
-{
- gint 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;
-
- t = (end->x - start->x) * (end->x - start->x) +
- (end->y - start->y) * (end->y - start->y);
-
- /*
- * If we are using limited precision sqrti implementation, fallback on
- * clib sqrt if the precission would be less than 10%
- */
-#if INT_MAX > CLUTTER_SQRTI_ARG_10_PERCENT
- if (t <= COGL_SQRTI_ARG_10_PERCENT)
- return cogl_sqrti (t);
- else
- return COGL_FLOAT_TO_INT (sqrt(t));
-#else
- return cogl_sqrti (t);
-#endif
-}
-
-static gint
-path_total_length (ClutterBehaviourPath *behave)
-{
- GSList *l;
- gint len = 0;
-
- for (l = behave->priv->knots; l != NULL; l = l->next)
- if (l->next && l->next->data)
- len += node_distance (l->data, l->next->data);
-
- return len;
-}
-
-static void
actor_apply_knot_foreach (ClutterBehaviour *behaviour,
ClutterActor *actor,
gpointer data)
}
static void
-path_alpha_to_position (ClutterBehaviourPath *behave,
- guint32 alpha)
+clutter_behaviour_path_alpha_notify (ClutterBehaviour *behave,
+ guint32 alpha_value)
{
- ClutterBehaviourPathPrivate *priv = behave->priv;
- ClutterBehaviour *behaviour = CLUTTER_BEHAVIOUR (behave);
- GSList *l;
- gint total_len, offset, dist = 0;
-
- /* FIXME: Optimise. Much of the data used here can be pre-generated
- * ( total_len, dist between knots ) when knots are added/removed.
- */
-
- /* Calculation as follows:
- * o Get total length of path
- * o Find the offset on path where alpha val corresponds to
- * o Figure out between which knots this offset lies.
- * o Interpolate new co-ords via dist between these knots
- * o Apply to actors.
- */
-
- total_len = path_total_length (behave);
- offset = (alpha * total_len) / CLUTTER_ALPHA_MAX_ALPHA;
-
- CLUTTER_NOTE (BEHAVIOUR, "alpha %i vs %i, len: %i vs %i",
- alpha, CLUTTER_ALPHA_MAX_ALPHA,
- offset, total_len);
-
- if (offset == 0)
- {
- /* first knot */
- clutter_behaviour_actors_foreach (behaviour,
- actor_apply_knot_foreach,
- priv->knots->data);
-
- priv->last_knot_passed = (ClutterKnot*)priv->knots->data;
- g_signal_emit (behave, path_signals[KNOT_REACHED], 0,
- priv->knots->data);
- return;
- }
+ ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (behave);
+ ClutterBehaviourPathPrivate *priv = pathb->priv;
+ ClutterKnot position;
+ guint knot_num;
- if (offset == total_len)
+ if (priv->path)
+ knot_num = clutter_path_get_position (priv->path, alpha_value, &position);
+ else
{
- /* Special case for last knot */
- ClutterKnot *last_knot = (g_slist_last (priv->knots))->data;
-
- clutter_behaviour_actors_foreach (behaviour,
- actor_apply_knot_foreach,
- last_knot);
-
- priv->last_knot_passed = (ClutterKnot*)priv->knots->data;
- g_signal_emit (behave, path_signals[KNOT_REACHED], 0, last_knot);
-
- return;
+ memset (&position, 0, sizeof (position));
+ knot_num = 0;
}
- for (l = priv->knots; l != NULL; l = l->next)
- {
- gint dist_to_next = 0;
- ClutterKnot *knot = l->data;
-
- if (l->next)
- {
- ClutterKnot *next = l->next->data;
-
- dist_to_next = node_distance (knot, next);
-
- if (offset >= dist && offset < (dist + dist_to_next))
- {
- ClutterKnot new;
- ClutterFixed t;
-
- t = COGL_FIXED_FROM_INT (offset - dist) / dist_to_next;
-
- interpolate (knot, next, &new, t);
+ clutter_behaviour_actors_foreach (behave,
+ actor_apply_knot_foreach,
+ &position);
- clutter_behaviour_actors_foreach (behaviour,
- actor_apply_knot_foreach,
- &new);
-
- if (knot != priv->last_knot_passed)
- {
- /* We just passed a new Knot */
- priv->last_knot_passed = knot;
- g_signal_emit (behave, path_signals[KNOT_REACHED], 0, knot);
- }
-
- return;
- }
- }
-
- dist += dist_to_next;
+ if (knot_num != priv->last_knot_passed)
+ {
+ g_signal_emit (behave, path_signals[KNOT_REACHED], 0, knot_num);
+ priv->last_knot_passed = knot_num;
}
}
static void
-clutter_behaviour_path_alpha_notify (ClutterBehaviour *behave,
- guint32 alpha_value)
+clutter_behaviour_path_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
{
- path_alpha_to_position (CLUTTER_BEHAVIOUR_PATH (behave), alpha_value);
+ ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_PATH:
+ g_value_set_object (value, clutter_behaviour_path_get_path (pathb));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
}
static void
switch (prop_id)
{
- case PROP_KNOT:
- clutter_behaviour_path_append_knot (pathb, g_value_get_boxed (value));
+ case PROP_PATH:
+ clutter_behaviour_path_set_path (pathb, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
static void
+clutter_behaviour_path_dispose (GObject *gobject)
+{
+ ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (gobject);
+
+ clutter_behaviour_path_set_path (pathb, NULL);
+
+ G_OBJECT_CLASS (clutter_behaviour_path_parent_class)->dispose (gobject);
+}
+
+static void
clutter_behaviour_path_class_init (ClutterBehaviourPathClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterBehaviourClass *behave_class = CLUTTER_BEHAVIOUR_CLASS (klass);
+ GParamSpec *pspec;
+ gobject_class->get_property = clutter_behaviour_path_get_property;
gobject_class->set_property = clutter_behaviour_path_set_property;
- gobject_class->finalize = clutter_behaviour_path_finalize;
+ gobject_class->dispose = clutter_behaviour_path_dispose;
- /**
- * ClutterBehaviourPath:knot:
- *
- * This property can be used to append a new knot to the path.
- *
- * Since: 0.2
- */
- g_object_class_install_property (gobject_class,
- PROP_KNOT,
- g_param_spec_boxed ("knot",
- "Knot",
- "Can be used to append a knot to the path",
- CLUTTER_TYPE_KNOT,
- CLUTTER_PARAM_WRITABLE));
+ pspec = g_param_spec_object ("path",
+ "Path",
+ "The ClutterPath object representing the path "
+ "to animate along",
+ CLUTTER_TYPE_PATH,
+ CLUTTER_PARAM_READWRITE);
+ g_object_class_install_property (gobject_class, PROP_PATH, pspec);
/**
* ClutterBehaviourPath::knot-reached:
* @pathb: the object which received the signal
- * @knot: the #ClutterKnot reached
+ * @knot_num: the index of the #ClutterPathKnot reached
*
* This signal is emitted each time a node defined inside the path
* is reached.
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterBehaviourPathClass, knot_reached),
NULL, NULL,
- clutter_marshal_VOID__BOXED,
+ clutter_marshal_VOID__UINT,
G_TYPE_NONE, 1,
- CLUTTER_TYPE_KNOT);
+ G_TYPE_UINT);
behave_class->alpha_notify = clutter_behaviour_path_alpha_notify;
g_type_class_add_private (klass, sizeof (ClutterBehaviourPathPrivate));
}
-static void
-clutter_behaviour_path_init (ClutterBehaviourPath *self)
-{
- ClutterBehaviourPathPrivate *priv;
-
- self->priv = priv = CLUTTER_BEHAVIOUR_PATH_GET_PRIVATE (self);
-}
-
-static void
-clutter_behaviour_path_set_custom_property (ClutterScriptable *scriptable,
- ClutterScript *script,
- const gchar *name,
- const GValue *value)
-{
- if (strcmp (name, "knots") == 0)
- {
- ClutterBehaviourPath *path = CLUTTER_BEHAVIOUR_PATH (scriptable);
- GSList *knots, *l;
-
- if (!G_VALUE_HOLDS (value, G_TYPE_POINTER))
- return;
-
- knots = g_value_get_pointer (value);
- for (l = knots; l != NULL; l = l->next)
- {
- ClutterKnot *knot = l->data;
-
- clutter_behaviour_path_append_knot (path, knot);
- clutter_knot_free (knot);
- }
-
- g_slist_free (knots);
- }
- else
- g_object_set_property (G_OBJECT (scriptable), name, value);
-}
+static ClutterScriptableIface *parent_scriptable_iface = NULL;
static gboolean
clutter_behaviour_path_parse_custom_node (ClutterScriptable *scriptable,
const gchar *name,
JsonNode *node)
{
- if (strcmp (name, "knots") == 0)
+ if (strcmp ("path", name) == 0)
{
- JsonArray *array;
- guint knots_len, i;
- GSList *knots = NULL;
+ ClutterPath *path;
+ GValue node_value = { 0 };
- array = json_node_get_array (node);
- knots_len = json_array_get_length (array);
+ path = g_object_ref_sink (clutter_path_new ());
- for (i = 0; i < knots_len; i++)
- {
- JsonNode *val = json_array_get_element (array, i);
- ClutterKnot knot = { 0, };
+ json_node_get_value (node, &node_value);
- if (clutter_script_parse_knot (script, val, &knot))
- {
- CLUTTER_NOTE (SCRIPT, "parsed knot [ x:%d, y:%d ]",
- knot.x, knot.y);
+ if (!G_VALUE_HOLDS (&node_value, G_TYPE_STRING)
+ || !clutter_path_set_description (path,
+ g_value_get_string (&node_value)))
+ g_warning ("Invalid path description");
- knots = g_slist_prepend (knots, clutter_knot_copy (&knot));
- }
- }
+ g_value_unset (&node_value);
- g_value_init (value, G_TYPE_POINTER);
- g_value_set_pointer (value, g_slist_reverse (knots));
+ g_value_init (value, G_TYPE_OBJECT);
+ g_value_take_object (value, path);
return TRUE;
}
-
- return FALSE;
+ /* chain up */
+ else if (parent_scriptable_iface->parse_custom_node)
+ return parent_scriptable_iface->parse_custom_node (scriptable, script,
+ value, name, node);
+ else
+ return FALSE;
}
static void
clutter_scriptable_iface_init (ClutterScriptableIface *iface)
{
+ parent_scriptable_iface = g_type_interface_peek_parent (iface);
+
+ if (!parent_scriptable_iface)
+ parent_scriptable_iface
+ = g_type_default_interface_peek (CLUTTER_TYPE_SCRIPTABLE);
+
iface->parse_custom_node = clutter_behaviour_path_parse_custom_node;
- iface->set_custom_property = clutter_behaviour_path_set_custom_property;
+}
+
+static void
+clutter_behaviour_path_init (ClutterBehaviourPath *self)
+{
+ ClutterBehaviourPathPrivate *priv;
+
+ self->priv = priv = CLUTTER_BEHAVIOUR_PATH_GET_PRIVATE (self);
+
+ priv->path = NULL;
+ priv->last_knot_passed = G_MAXUINT;
}
/**
* clutter_behaviour_path_new:
* @alpha: a #ClutterAlpha, or %NULL
- * @knots: a list of #ClutterKnots, or %NULL for an empty path
- * @n_knots: the number of nodes in the path
+ * @path: a #ClutterPath or %NULL for an empty path
*
* Creates a new path behaviour. You can use this behaviour to drive
- * actors along the nodes of a path, described by the @knots.
+ * actors along the nodes of a path, described by @path.
+ *
+ * This will claim the floating reference on the #ClutterPath so you
+ * do not need to unref if it.
*
* Return value: a #ClutterBehaviour
*
* Since: 0.2
*/
ClutterBehaviour *
-clutter_behaviour_path_new (ClutterAlpha *alpha,
- const ClutterKnot *knots,
- guint n_knots)
+clutter_behaviour_path_new (ClutterAlpha *alpha,
+ ClutterPath *path)
{
- ClutterBehaviourPath *behave;
- gint i;
-
- behave = g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH,
- "alpha", alpha,
- NULL);
-
- for (i = 0; i < n_knots; i++)
- {
- ClutterKnot knot = knots[i];
-
- clutter_behaviour_path_append_knot (behave, &knot);
- }
-
- return CLUTTER_BEHAVIOUR (behave);
+ return g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH,
+ "alpha", alpha,
+ "path", path,
+ NULL);
}
/**
- * clutter_behaviour_path_get_knots:
- * @pathb: a #ClutterBehvaiourPath
+ * clutter_behaviour_path_new_with_description:
+ * @alpha: a #ClutterAlpha
+ * @desc: a string description of the path
*
- * Returns a copy of the list of knots contained by @pathb
+ * Creates a new path behaviour using the path described by @desc. See
+ * clutter_path_add_string() for a description of the format.
*
- * Return value: a #GSList of the paths knots.
+ * Return value: a #ClutterBehaviour
*
- * Since: 0.2
+ * Since: 1.0
*/
-GSList *
-clutter_behaviour_path_get_knots (ClutterBehaviourPath *pathb)
+ClutterBehaviour *
+clutter_behaviour_path_new_with_description (ClutterAlpha *alpha,
+ const gchar *desc)
{
- GSList *retval, *l;
-
- g_return_val_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb), NULL);
-
- retval = NULL;
- for (l = pathb->priv->knots; l != NULL; l = l->next)
- retval = g_slist_prepend (retval, l->data);
-
- return g_slist_reverse (retval);
+ return g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH,
+ "alpha", alpha,
+ "path", clutter_path_new_with_description (desc),
+ NULL);
}
/**
- * clutter_behaviour_path_append_knot:
- * @pathb: a #ClutterBehvaiourPath
- * @knot: a #ClutterKnot to append.
+ * clutter_behaviour_path_new_with_knots:
+ * @alpha: a #ClutterAlpha
+ * @knots: an array of #ClutterKnot<!-- -->s
+ * @n_knots: number of entries in @knots
*
- * Appends a #ClutterKnot to the path
+ * Creates a new path behaviour that will make the actors visit all of
+ * the given knots in order with straight lines in between.
*
- * Since: 0.2
- */
-void
-clutter_behaviour_path_append_knot (ClutterBehaviourPath *pathb,
- const ClutterKnot *knot)
-{
- ClutterBehaviourPathPrivate *priv;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb));
- g_return_if_fail (knot != NULL);
-
- priv = pathb->priv;
- priv->knots = g_slist_append (priv->knots, clutter_knot_copy (knot));
-}
-
-/**
- * clutter_behaviour_path_insert_knot:
- * @pathb: a #ClutterBehvaiourPath
- * @offset: position in path to insert knot.
- * @knot: a #ClutterKnot to append.
+ * A path will be created where the first knot is used in a
+ * %CLUTTER_PATH_MOVE_TO and the subsequent knots are used in
+ * %CLUTTER_PATH_LINE_TO<!-- -->s.
*
- * Inserts a #ClutterKnot in the path at specified position. Values greater
- * than total number of knots will append the knot at the end of path.
+ * Return value: a #ClutterBehaviour
*
- * Since: 0.2
+ * Since: 1.0
*/
-void
-clutter_behaviour_path_insert_knot (ClutterBehaviourPath *pathb,
- guint offset,
- const ClutterKnot *knot)
+ClutterBehaviour *
+clutter_behaviour_path_new_with_knots (ClutterAlpha *alpha,
+ const ClutterKnot *knots,
+ guint n_knots)
{
- ClutterBehaviourPathPrivate *priv;
+ ClutterPath *path = clutter_path_new ();
+ guint i;
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb));
- g_return_if_fail (knot != NULL);
+ if (n_knots > 0)
+ {
+ clutter_path_add_move_to (path, knots[0].x, knots[0].y);
- priv = pathb->priv;
- priv->knots = g_slist_insert (priv->knots, clutter_knot_copy (knot), offset);
+ for (i = 1; i < n_knots; i++)
+ clutter_path_add_line_to (path, knots[i].x, knots[i].y);
+ }
+
+ return g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH,
+ "alpha", alpha,
+ "path", path,
+ NULL);
}
/**
- * clutter_behaviour_path_remove_knot:
- * @pathb: a #ClutterBehvaiourPath
- * @offset: position in path to remove knot.
+ * clutter_behaviour_path_set_path:
+ * @pathb: the path behaviour
+ * @path: the new path to follow
*
- * Removes a #ClutterKnot in the path at specified offset.
+ * Change the path that the actors will follow. This will take the
+ * floating reference on the #ClutterPath so you do not need to unref
+ * it.
*
- * Since: 0.2
+ * Since: 1.0
*/
void
-clutter_behaviour_path_remove_knot (ClutterBehaviourPath *pathb,
- guint offset)
+clutter_behaviour_path_set_path (ClutterBehaviourPath *pathb,
+ ClutterPath *path)
{
ClutterBehaviourPathPrivate *priv;
- GSList *togo;
g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb));
priv = pathb->priv;
- togo = g_slist_nth (priv->knots, offset);
+ if (path)
+ g_object_ref_sink (path);
- if (togo)
- {
- clutter_knot_free ((ClutterKnot*)togo->data);
- priv->knots = g_slist_delete_link (priv->knots, togo);
- }
-}
+ if (priv->path)
+ g_object_unref (priv->path);
-static void
-clutter_behaviour_path_append_knots_valist (ClutterBehaviourPath *pathb,
- const ClutterKnot *first_knot,
- va_list args)
-{
- const ClutterKnot * knot;
+ priv->path = path;
- knot = first_knot;
- while (knot)
- {
- clutter_behaviour_path_append_knot (pathb, knot);
- knot = va_arg (args, ClutterKnot*);
- }
+ g_object_notify (G_OBJECT (pathb), "path");
}
/**
- * clutter_behaviour_path_append_knots:
- * @pathb: a #ClutterBehvaiourPath
- * @first_knot: the #ClutterKnot knot to add to the path
- * @Varargs: additional knots to add to the path
- *
- * Adds a NULL-terminated list of knots to a path. This function is
- * equivalent to calling clutter_behaviour_path_append_knot() for each
- * member of the list.
+ * clutter_behaviour_path_get_path:
+ * @pathb: a #ClutterBehaviourPath instance
*
- * Since: 0.2
- */
-void
-clutter_behaviour_path_append_knots (ClutterBehaviourPath *pathb,
- const ClutterKnot *first_knot,
- ...)
-{
- va_list args;
-
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb));
- g_return_if_fail (first_knot != NULL);
-
- va_start (args, first_knot);
- clutter_behaviour_path_append_knots_valist (pathb, first_knot, args);
- va_end (args);
-}
-
-/**
- * clutter_behaviour_path_clear:
- * @pathb: a #ClutterBehvaiourPath
+ * Get the current path of the behaviour
*
- * Removes all knots from a path
+ * Return value: the path
*
- * Since: 0.2
+ * Since 1.0:
*/
-void
-clutter_behaviour_path_clear (ClutterBehaviourPath *pathb)
+ClutterPath *
+clutter_behaviour_path_get_path (ClutterBehaviourPath *pathb)
{
- g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb));
-
- g_slist_foreach (pathb->priv->knots, (GFunc) clutter_knot_free, NULL);
- g_slist_free (pathb->priv->knots);
+ g_return_val_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb), NULL);
- pathb->priv->knots = NULL;
+ return pathb->priv->path;
}
#include <clutter/clutter-alpha.h>
#include <clutter/clutter-behaviour.h>
+#include <clutter/clutter-path.h>
G_BEGIN_DECLS
/*< public >*/
void (*knot_reached) (ClutterBehaviourPath *pathb,
- const ClutterKnot *knot);
+ guint knot_num);
/*< private >*/
void (*_clutter_path_1) (void);
GType clutter_behaviour_path_get_type (void) G_GNUC_CONST;
ClutterBehaviour *clutter_behaviour_path_new (ClutterAlpha *alpha,
+ ClutterPath *path);
+
+ClutterBehaviour *clutter_behaviour_path_new_with_description
+ (ClutterAlpha *alpha,
+ const gchar *desc);
+
+ClutterBehaviour *clutter_behaviour_path_new_with_knots
+ (ClutterAlpha *alpha,
const ClutterKnot *knots,
guint n_knots);
-GSList * clutter_behaviour_path_get_knots (ClutterBehaviourPath *pathb);
-void clutter_behaviour_path_append_knot (ClutterBehaviourPath *pathb,
- const ClutterKnot *knot);
-void clutter_behaviour_path_append_knots (ClutterBehaviourPath *pathb,
- const ClutterKnot *first_knot,
- ...) G_GNUC_NULL_TERMINATED;
-void clutter_behaviour_path_insert_knot (ClutterBehaviourPath *pathb,
- guint offset,
- const ClutterKnot *knot);
-void clutter_behaviour_path_remove_knot (ClutterBehaviourPath *pathb,
- guint offset);
-
-void clutter_behaviour_path_clear (ClutterBehaviourPath *pathb);
+
+void clutter_behaviour_path_set_path (ClutterBehaviourPath *pathb,
+ ClutterPath *path);
+
+ClutterPath * clutter_behaviour_path_get_path (ClutterBehaviourPath *pathb);
G_END_DECLS
--- /dev/null
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Tomas Frydrych <tf@openedhand.com>
+ *
+ * Copyright (C) 2007 OpenedHand
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib.h>
+#include <string.h>
+#include "clutter-bezier.h"
+#include "clutter-debug.h"
+
+/*
+ * We have some experimental code here to allow for constant velocity
+ * movement of actors along the bezier path, this macro enables it.
+ */
+#undef CBZ_L2T_INTERPOLATION
+
+/****************************************************************************
+ * ClutterBezier -- represenation of a cubic bezier curve *
+ * (private; a building block for the public bspline object) *
+ ****************************************************************************/
+
+/*
+ * The t parameter of the bezier is from interval <0,1>, so we can use
+ * 14.18 format and special multiplication functions that preserve
+ * more of the least significant bits but would overflow if the value
+ * is > 1
+ */
+#define CBZ_T_Q 18
+#define CBZ_T_ONE (1 << CBZ_T_Q)
+#define CBZ_T_MUL(x,y) ((((x) >> 3) * ((y) >> 3)) >> 12)
+#define CBZ_T_POW2(x) CBZ_T_MUL (x, x)
+#define CBZ_T_POW3(x) CBZ_T_MUL (CBZ_T_POW2 (x), x)
+#define CBZ_T_DIV(x,y) ((((x) << 9)/(y)) << 9)
+
+/*
+ * Constants for sampling of the bezier
+ */
+#define CBZ_T_SAMPLES 128
+#define CBZ_T_STEP (CBZ_T_ONE / CBZ_T_SAMPLES)
+#define CBZ_L_STEP (CBZ_T_ONE / CBZ_T_SAMPLES)
+
+typedef gint32 _FixedT;
+
+/*
+ * This is a private type representing a single cubic bezier
+ */
+struct _ClutterBezier
+{
+ /*
+ * bezier coefficients -- these are calculated using multiplication and
+ * addition from integer input, so these are also integers
+ */
+ gint ax;
+ gint bx;
+ gint cx;
+ gint dx;
+
+ gint ay;
+ gint by;
+ gint cy;
+ gint dy;
+
+ /* length of the bezier */
+ guint length;
+
+#ifdef CBZ_L2T_INTERPOLATION
+ /*
+ * coefficients for the L -> t bezier; these are calculated from fixed
+ * point input, and more specifically numbers that have been normalised
+ * to fit <0,1>, so these are also fixed point, and we can used the
+ * _FixedT type here.
+ */
+ _FixedT La;
+ _FixedT Lb;
+ _FixedT Lc;
+ /* _FixedT Ld; == 0 */
+#endif
+};
+
+ClutterBezier *
+_clutter_bezier_new ()
+{
+ return g_slice_new0 (ClutterBezier);
+}
+
+void
+_clutter_bezier_free (ClutterBezier * b)
+{
+ if (G_LIKELY (b))
+ {
+ g_slice_free (ClutterBezier, b);
+ }
+}
+
+ClutterBezier *
+_clutter_bezier_clone_and_move (const ClutterBezier *b, gint x, gint y)
+{
+ ClutterBezier * b2 = _clutter_bezier_new ();
+ memcpy (b2, b, sizeof (ClutterBezier));
+
+ b2->dx += x;
+ b2->dy += y;
+
+ return b2;
+}
+
+#ifdef CBZ_L2T_INTERPOLATION
+/*
+ * L is relative advance along the bezier curve from interval <0,1>
+ */
+static _FixedT
+_clutter_bezier_L2t (const ClutterBezier *b, _FixedT L)
+{
+ _FixedT t = CBZ_T_MUL (b->La, CBZ_T_POW3(L))
+ + CBZ_T_MUL (b->Lb, CBZ_T_POW2(L))
+ + CBZ_T_MUL (b->Lc, L);
+
+ if (t > CBZ_T_ONE)
+ t = CBZ_T_ONE;
+ else if (t < 0)
+ t = 0;
+
+ return t;
+}
+#endif
+
+static gint
+_clutter_bezier_t2x (const ClutterBezier * b, _FixedT t)
+{
+ /*
+ * NB -- the int coefficients can be at most 8192 for the multiplication
+ * to work in this fashion due to the limits of the 14.18 fixed.
+ */
+ return ((b->ax*CBZ_T_POW3(t) + b->bx*CBZ_T_POW2(t) + b->cx*t) >> CBZ_T_Q)
+ + b->dx;
+}
+
+gint
+_clutter_bezier_t2y (const ClutterBezier * b, _FixedT t)
+{
+ /*
+ * NB -- the int coefficients can be at most 8192 for the multiplication
+ * to work in this fashion due to the limits of the 14.18 fixed.
+ */
+ return ((b->ay*CBZ_T_POW3(t) + b->by*CBZ_T_POW2(t) + b->cy*t) >> CBZ_T_Q)
+ + b->dy;
+}
+
+/*
+ * Advances along the bezier to relative length L and returns the coordinances
+ * in knot
+ */
+void
+_clutter_bezier_advance (const ClutterBezier *b, gint L, ClutterKnot * knot)
+{
+#ifdef CBZ_L2T_INTERPOLATION
+ _FixedT t = clutter_bezier_L2t (b, L);
+#else
+ _FixedT t = L;
+#endif
+
+ knot->x = _clutter_bezier_t2x (b, t);
+ knot->y = _clutter_bezier_t2y (b, t);
+
+ CLUTTER_NOTE (BEHAVIOUR, "advancing to relative pt %f: t %f, {%d,%d}",
+ (double) L / (double) CBZ_T_ONE,
+ (double) t / (double) CBZ_T_ONE,
+ knot->x, knot->y);
+}
+
+void
+_clutter_bezier_init (ClutterBezier *b,
+ gint x_0, gint y_0,
+ gint x_1, gint y_1,
+ gint x_2, gint y_2,
+ gint x_3, gint y_3)
+{
+ _FixedT t;
+ int i;
+ int xp = x_0;
+ int yp = y_0;
+ _FixedT length [CBZ_T_SAMPLES + 1];
+
+#ifdef CBZ_L2T_INTERPOLATION
+ int j, k;
+ _FixedT L;
+ _FixedT t_equalized [CBZ_T_SAMPLES + 1];
+#endif
+
+#if 0
+ g_debug ("Initializing bezier at {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}",
+ x0, y0, x1, y1, x2, y2, x3, y3);
+#endif
+
+ b->dx = x_0;
+ b->dy = y_0;
+
+ b->cx = 3 * (x_1 - x_0);
+ b->cy = 3 * (y_1 - y_0);
+
+ b->bx = 3 * (x_2 - x_1) - b->cx;
+ b->by = 3 * (y_2 - y_1) - b->cy;
+
+ b->ax = x_3 - 3 * x_2 + 3 * x_1 - x_0;
+ b->ay = y_3 - 3 * y_2 + 3 * y_1 - y_0;
+
+#if 0
+ g_debug ("Cooeficients {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}",
+ b->ax, b->ay, b->bx, b->by, b->cx, b->cy, b->dx, b->dy);
+#endif
+
+ /*
+ * Because of the way we do the multiplication in bezeir_t2x,y
+ * these coefficients need to be at most 0x1fff; this should be the case,
+ * I think, but have added this warning to catch any problems -- if it
+ * triggers, we need to change those two functions a bit.
+ */
+ if (b->ax > 0x1fff || b->bx > 0x1fff || b->cx > 0x1fff)
+ g_warning ("Calculated coefficents will result in multiplication "
+ "overflow in clutter_bezier_t2x and clutter_bezier_t2y.");
+
+ /*
+ * Sample the bezier with CBZ_T_SAMPLES and calculate length at
+ * each point.
+ *
+ * We are working with integers here, so we use the fast sqrti function.
+ */
+ length[0] = 0;
+
+ for (t = CBZ_T_STEP, i = 1; i <= CBZ_T_SAMPLES; ++i, t += CBZ_T_STEP)
+ {
+ int x = _clutter_bezier_t2x (b, t);
+ int y = _clutter_bezier_t2y (b, t);
+
+ guint l = clutter_sqrti ((y - yp)*(y - yp) + (x - xp)*(x - xp));
+
+ l += length[i-1];
+
+ length[i] = l;
+
+ xp = x;
+ yp = y;
+ }
+
+ b->length = length[CBZ_T_SAMPLES];
+
+#if 0
+ g_debug ("length %d", b->length);
+#endif
+
+#ifdef CBZ_L2T_INTERPOLATION
+ /*
+ * Now normalize the length values, converting them into _FixedT
+ */
+ for (i = 0; i <= CBZ_T_SAMPLES; ++i)
+ {
+ length[i] = (length[i] << CBZ_T_Q) / b->length;
+ }
+
+ /*
+ * Now generate a L -> t table such that the L will equidistant
+ * over <0,1>
+ */
+ t_equalized[0] = 0;
+
+ for (i = 1, j = 1, L = CBZ_L_STEP; i < CBZ_T_SAMPLES; ++i, L += CBZ_L_STEP)
+ {
+ _FixedT l1, l2;
+ _FixedT d1, d2, d;
+ _FixedT t1, t2;
+
+ /* find the band for our L */
+ for (k = j; k < CBZ_T_SAMPLES; ++k)
+ {
+ if (L < length[k])
+ break;
+ }
+
+ /*
+ * Now we know that L is from (length[k-1],length[k]>
+ * We remember k-1 in order not to have to iterate over the
+ * whole length array in the next iteration of the main loop
+ */
+ j = k - 1;
+
+ /*
+ * Now interpolate equlised t as a weighted average
+ */
+ l1 = length[k-1];
+ l2 = length[k];
+ d1 = l2 - L;
+ d2 = L - l1;
+ d = l2 - l1;
+ t1 = (k - 1) * CBZ_T_STEP;
+ t2 = k * CBZ_T_STEP;
+
+ t_equalized[i] = (t1*d1 + t2*d2)/d;
+
+ if (t_equalized[i] < t_equalized[i-1])
+ g_debug ("wrong t: L %f, l1 %f, l2 %f, t1 %f, t2 %f",
+ (double) (L)/(double)CBZ_T_ONE,
+ (double) (l1)/(double)CBZ_T_ONE,
+ (double) (l2)/(double)CBZ_T_ONE,
+ (double) (t1)/(double)CBZ_T_ONE,
+ (double) (t2)/(double)CBZ_T_ONE);
+
+ }
+
+ t_equalized[CBZ_T_SAMPLES] = CBZ_T_ONE;
+
+ /* We now fit a bezier -- at this stage, do a single fit through our values
+ * at 0, 1/3, 2/3 and 1
+ *
+ * FIXME -- do we need to use a better fitting approach to choose the best
+ * beziere. The actual curve we acquire this way is not too bad shapwise,
+ * but (probably due to rounding errors) the resulting curve no longer
+ * satisfies the necessary condition that for L2 > L1, t2 > t1, which
+ * causes oscilation.
+ */
+
+#if 0
+ /*
+ * These are the control points we use to calculate the curve coefficients
+ * for bezier t(L); these are not needed directly, but are implied in the
+ * calculations below.
+ *
+ * (p0 is 0,0, and p3 is 1,1)
+ */
+ p1 = (18 * t_equalized[CBZ_T_SAMPLES/3] -
+ 9 * t_equalized[2*CBZ_T_SAMPLES/3] +
+ 2 << CBZ_T_Q) / 6;
+
+ p2 = (18 * t_equalized[2*CBZ_T_SAMPLES/3] -
+ 9 * t_equalized[CBZ_T_SAMPLES/3] -
+ (5 << CBZ_T_Q)) / 6;
+#endif
+
+ b->Lc = (18 * t_equalized[CBZ_T_SAMPLES/3] -
+ 9 * t_equalized[2*CBZ_T_SAMPLES/3] +
+ (2 << CBZ_T_Q)) >> 1;
+
+ b->Lb = (36 * t_equalized[2*CBZ_T_SAMPLES/3] -
+ 45 * t_equalized[CBZ_T_SAMPLES/3] -
+ (9 << CBZ_T_Q)) >> 1;
+
+ b->La = ((27 * (t_equalized[CBZ_T_SAMPLES/3] -
+ t_equalized[2*CBZ_T_SAMPLES/3]) +
+ (7 << CBZ_T_Q)) >> 1) + CBZ_T_ONE;
+
+ g_debug ("t(1/3) %f, t(2/3) %f",
+ (double)t_equalized[CBZ_T_SAMPLES/3]/(double)CBZ_T_ONE,
+ (double)t_equalized[2*CBZ_T_SAMPLES/3]/(double)CBZ_T_ONE);
+
+ g_debug ("L -> t coefficients: %f, %f, %f",
+ (double)b->La/(double)CBZ_T_ONE,
+ (double)b->Lb/(double)CBZ_T_ONE,
+ (double)b->Lc/(double)CBZ_T_ONE);
+
+
+ /*
+ * For debugging, you can load these values into a spreadsheet and graph
+ * them to see how well the approximation matches the data
+ */
+ for (i = 0; i < CBZ_T_SAMPLES; ++i)
+ {
+ g_print ("%f, %f, %f\n",
+ (double)(i*CBZ_T_STEP)/(double)CBZ_T_ONE,
+ (double)(t_equalized[i])/(double)CBZ_T_ONE,
+ (double)(clutter_bezier_L2t(b,i*CBZ_T_STEP))/(double)CBZ_T_ONE);
+ }
+#endif
+}
+
+/*
+ * Moves a control point at indx to location represented by knot
+ */
+void
+_clutter_bezier_adjust (ClutterBezier * b, ClutterKnot * knot, guint indx)
+{
+ guint x[4], y[4];
+
+ g_assert (indx < 4);
+
+ x[0] = b->dx;
+ y[0] = b->dy;
+
+ x[1] = b->cx / 3 + x[0];
+ y[1] = b->cy / 3 + y[0];
+
+ x[2] = b->bx / 3 + b->cx + x[1];
+ y[2] = b->by / 3 + b->cy + y[1];
+
+ x[3] = b->ax + x[0] + b->cx + b->bx;
+ y[3] = b->ay + y[0] + b->cy + b->by;
+
+ x[indx] = knot->x;
+ y[indx] = knot->y;
+
+ _clutter_bezier_init (b, x[0], y[0], x[1], y[1], x[2], y[2], x[3], y[3]);
+}
+
+guint
+_clutter_bezier_get_length (const ClutterBezier *b)
+{
+ return b->length;
+}
--- /dev/null
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Tomas Frydrych <tf@openedhand.com>
+ *
+ * Copyright (C) 2006, 2007 OpenedHand
+ *
+ * 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/>.
+ */
+
+#ifndef __CLUTTER_BEZIER_H__
+#define __CLUTTER_BEZIER_H__
+
+#include <glib.h>
+#include "clutter-types.h"
+
+G_BEGIN_DECLS
+
+/* This is used in _clutter_bezier_advance to represent the full
+ length of the bezier curve. Anything less than that represents a
+ fraction of the length */
+#define CLUTTER_BEZIER_MAX_LENGTH (1 << 18)
+
+typedef struct _ClutterBezier ClutterBezier;
+
+ClutterBezier *_clutter_bezier_new ();
+
+void _clutter_bezier_free (ClutterBezier * b);
+
+ClutterBezier *_clutter_bezier_clone_and_move (const ClutterBezier *b,
+ gint x,
+ gint y);
+
+void _clutter_bezier_advance (const ClutterBezier *b,
+ gint L,
+ ClutterKnot *knot);
+
+void _clutter_bezier_init (ClutterBezier *b,
+ gint x_0, gint y_0,
+ gint x_1, gint y_1,
+ gint x_2, gint y_2,
+ gint x_3, gint y_3);
+
+void _clutter_bezier_adjust (ClutterBezier *b,
+ ClutterKnot *knot,
+ guint indx);
+
+guint _clutter_bezier_get_length (const ClutterBezier *b);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_BEZIER_H__ */
#include "clutter-marshal.h"
#include "clutter-private.h"
#include "clutter-debug.h"
-#include "clutter-behaviour-bspline.h"
#include "clutter-behaviour-depth.h"
#include "clutter-behaviour-ellipse.h"
#include "clutter-behaviour-opacity.h"
gpointer data)
{
ClutterEffectClosure *c;
- ClutterKnot knots[2];
+ ClutterPath *path;
c = clutter_effect_closure_new (template_,
actor,
c->completed_func = func;
c->completed_data = data;
- knots[0].x = clutter_actor_get_x (actor);
- knots[0].y = clutter_actor_get_y (actor);
+ path = clutter_path_new ();
- knots[1].x = x;
- knots[1].y = y;
+ clutter_path_add_move_to (path,
+ clutter_actor_get_x (actor),
+ clutter_actor_get_y (actor));
+ clutter_path_add_line_to (path, x, y);
- c->behave = clutter_behaviour_path_new (c->alpha, knots, 2);
+ c->behave = clutter_behaviour_path_new (c->alpha, path);
clutter_behaviour_apply (c->behave, actor);
clutter_timeline_start (c->timeline);
gpointer data)
{
ClutterEffectClosure *c;
+ ClutterPath *path;
+ guint i;
c = clutter_effect_closure_new (template_,
actor,
G_CALLBACK (on_effect_complete));
+ path = clutter_path_new ();
+
c->completed_func = func;
c->completed_data = data;
+ path = clutter_path_new ();
+
if (n_knots)
- clutter_actor_set_position (actor, knots[0].x, knots[0].y);
+ {
+ clutter_actor_set_position (actor, knots[0].x, knots[0].y);
+ clutter_path_add_move_to (path, knots[0].x, knots[0].y);
+
+ for (i = 1; i < n_knots; i++)
+ clutter_path_add_line_to (path, knots[i].x, knots[i].y);
+ }
- c->behave = clutter_behaviour_path_new (c->alpha, knots, n_knots);
+ c->behave = clutter_behaviour_path_new (c->alpha, path);
clutter_behaviour_apply (c->behave, actor);
clutter_timeline_start (c->timeline);
VOID:OBJECT,POINTER
VOID:STRING,BOOLEAN,BOOLEAN
VOID:STRING,INT
+VOID:UINT
VOID:VOID
--- /dev/null
+/*
+ * 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 <glib-object.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "clutter-path.h"
+#include "clutter-types.h"
+#include "clutter-bezier.h"
+#include "clutter-private.h"
+#include "clutter-alpha.h"
+
+G_DEFINE_TYPE (ClutterPath, clutter_path, G_TYPE_INITIALLY_UNOWNED);
+
+#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
+};
+
+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_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);
+ 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);
+ 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_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;
+
+ g_return_if_fail (CLUTTER_IS_PATH (path));
+
+ 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)
+{
+ 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)
+{
+ 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)
+{
+ 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)
+{
+ clutter_path_add_node_helper (path, CLUTTER_PATH_REL_LINE_TO, 1, x, y);
+}
+
+/**
+ * clutter_path_add_curve_to:
+ * @path: a #ClutterPath
+ * @x1: the x coordinate of the first control point
+ * @y1: the y coordinate of the first control point
+ * @x2: the x coordinate of the second control point
+ * @y2: the y coordinate of the second control point
+ * @x3: the x coordinate of the third control point
+ * @y3: 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 (x3,y3) using
+ * (x1,y1) and (x2,y2) as control points.
+ *
+ * Since: 1.0
+ */
+void
+clutter_path_add_curve_to (ClutterPath *path,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gint x3,
+ gint y3)
+{
+ clutter_path_add_node_helper (path, CLUTTER_PATH_CURVE_TO, 3,
+ x1, y1, x2, y2, x3, y3);
+}
+
+/**
+ * clutter_path_add_rel_curve_to:
+ * @path: a #ClutterPath
+ * @x1: the x coordinate of the first control point
+ * @y1: the y coordinate of the first control point
+ * @x2: the x coordinate of the second control point
+ * @y2: the y coordinate of the second control point
+ * @x3: the x coordinate of the third control point
+ * @y3: 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 x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gint x3,
+ gint y3)
+{
+ clutter_path_add_node_helper (path, CLUTTER_PATH_REL_CURVE_TO, 3,
+ x1, y1, x2, y2, x3, y3);
+}
+
+/**
+ * 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)
+{
+ 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)
+{
+ GSList *nodes = NULL;
+ ClutterPathNodeFull *node;
+
+ 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);
+
+ 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_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: 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: 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: 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);
+
+ 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), FALSE);
+
+ 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 gint
+clutter_path_node_distance (const ClutterKnot *start,
+ const ClutterKnot *end)
+{
+ gint 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;
+
+ t = (end->x - start->x) * (end->x - start->x) +
+ (end->y - start->y) * (end->y - start->y);
+
+ /*
+ * If we are using limited precision sqrti implementation, fallback on
+ * clib sqrt if the precission would be less than 10%
+ */
+#if INT_MAX > CLUTTER_SQRTI_ARG_10_PERCENT
+ if (t <= COGL_SQRTI_ARG_10_PERCENT)
+ return cogl_sqrti (t);
+ else
+ return COGL_FLOAT_TO_INT (sqrt(t));
+#else
+ return cogl_sqrti (t);
+#endif
+}
+
+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
+ * @alpha: an alpha value
+ * @position: location to store the position
+ *
+ * The value in @alpha represents a position along the path where 0 is
+ * the beginning and %CLUTTER_ALPHA_MAX_ALPHA 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,
+ guint alpha,
+ ClutterKnot *position)
+{
+ ClutterPathPrivate *priv;
+ GSList *l;
+ guint length = 0, node_num = 0;
+ ClutterPathNodeFull *node;
+
+ g_return_val_if_fail (CLUTTER_IS_PATH (path), 0);
+ g_return_val_if_fail (alpha <= CLUTTER_ALPHA_MAX_ALPHA, 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 alpha fraction to a length along the path */
+ alpha = (alpha * priv->total_length) / CLUTTER_ALPHA_MAX_ALPHA;
+
+ /* Find the node that covers this point */
+ for (l = priv->nodes;
+ l->next && alpha >= (((ClutterPathNodeFull *) l->data)->length
+ + length);
+ l = l->next)
+ {
+ length += ((ClutterPathNodeFull *) l->data)->length;
+ node_num++;
+ }
+
+ node = l->data;
+
+ /* Convert the alpha to a distance along the node */
+ alpha -= length;
+ if (alpha > node->length)
+ alpha = 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) alpha / (gint) node->length));
+ position->y = (node->k.points[1].y
+ + ((node->k.points[2].y - node->k.points[1].y)
+ * (gint) alpha / (gint) node->length));
+ }
+ break;
+
+ case CLUTTER_PATH_CURVE_TO:
+ _clutter_bezier_advance (node->bezier,
+ alpha * 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;
+}
+
+GType
+clutter_path_node_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (G_UNLIKELY (!our_type))
+ {
+ our_type =
+ g_boxed_type_register_static (I_("ClutterPathNode"),
+ (GBoxedCopyFunc) clutter_path_node_copy,
+ (GBoxedFreeFunc) clutter_path_node_free);
+ }
+
+ return our_type;
+}
--- /dev/null
+/*
+ * 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/>.
+ */
+
+#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
+#error "Only <clutter/clutter.h> can be included directly."
+#endif
+
+#ifndef __CLUTTER_PATH_H__
+#define __CLUTTER_PATH_H__
+
+#include <glib-object.h>
+#include <clutter/clutter-types.h>
+
+G_BEGIN_DECLS
+
+#define CLUTTER_TYPE_PATH \
+ (clutter_path_get_type())
+#define CLUTTER_PATH(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ CLUTTER_TYPE_PATH, \
+ ClutterPath))
+#define CLUTTER_PATH_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ CLUTTER_TYPE_PATH, \
+ ClutterPathClass))
+#define CLUTTER_IS_PATH(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ CLUTTER_TYPE_PATH))
+#define CLUTTER_IS_PATH_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ CLUTTER_TYPE_PATH))
+#define CLUTTER_PATH_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ CLUTTER_TYPE_PATH, \
+ ClutterPathClass))
+
+#define CLUTTER_TYPE_PATH_NODE (clutter_path_node_get_type ())
+
+#define CLUTTER_PATH_RELATIVE 32
+
+/**
+ * ClutterPathNodeType:
+ * @CLUTTER_PATH_MOVE_TO: jump to the given position
+ * @CLUTTER_PATH_LINE_TO: create a line from the last node to the
+ * given position
+ * @CLUTTER_PATH_CURVE_TO: bezier curve using the last position and
+ * three control points.
+ * @CLUTTER_PATH_CLOSE: create a line from the last node to the last
+ * %CLUTTER_PATH_MOVE_TO node.
+ * @CLUTTER_PATH_REL_MOVE_TO: same as %CLUTTER_PATH_MOVE_TO but with
+ * coordinates relative to the last node.
+ * @CLUTTER_PATH_REL_LINE_TO: same as %CLUTTER_PATH_LINE_TO but with
+ * coordinates relative to the last node.
+ * @CLUTTER_PATH_REL_CURVE_TO: same as %CLUTTER_PATH_CURVE_TO but with
+ * coordinates relative to the last node.
+ */
+typedef enum {
+ CLUTTER_PATH_MOVE_TO = 0,
+ CLUTTER_PATH_LINE_TO = 1,
+ CLUTTER_PATH_CURVE_TO = 2,
+ CLUTTER_PATH_CLOSE = 3,
+
+ CLUTTER_PATH_REL_MOVE_TO = CLUTTER_PATH_MOVE_TO | CLUTTER_PATH_RELATIVE,
+ CLUTTER_PATH_REL_LINE_TO = CLUTTER_PATH_LINE_TO | CLUTTER_PATH_RELATIVE,
+ CLUTTER_PATH_REL_CURVE_TO = CLUTTER_PATH_CURVE_TO | CLUTTER_PATH_RELATIVE
+} ClutterPathNodeType;
+
+typedef struct _ClutterPath ClutterPath;
+typedef struct _ClutterPathClass ClutterPathClass;
+typedef struct _ClutterPathPrivate ClutterPathPrivate;
+typedef struct _ClutterPathNode ClutterPathNode;
+
+/**
+ * ClutterPathCallback:
+ * @node: the node
+ * @data: optional data passed to the function
+ *
+ * This function is passed to clutter_path_foreach() and will be
+ * called for each node contained in the path.
+ *
+ * Since: 1.0
+ */
+typedef void (* ClutterPathCallback) (const ClutterPathNode *node,
+ gpointer data);
+
+/**
+ * ClutterPathClass:
+ *
+ * The #ClutterPathClass struct contains only private data.
+ */
+struct _ClutterPathClass
+{
+ /*< private >*/
+ GInitiallyUnownedClass parent_class;
+};
+
+/**
+ * ClutterPath:
+ *
+ * The #ClutterPath struct contains only private data and should
+ * be accessed with the functions below.
+ */
+struct _ClutterPath
+{
+ /*< private >*/
+ GInitiallyUnowned parent;
+
+ ClutterPathPrivate *priv;
+};
+
+/**
+ * ClutterPathNode:
+ * @type: the node's type
+ * @points: the coordinates of the node
+ *
+ * Represents a single node of a #ClutterPath.
+ *
+ * Some of the coordinates in @points may be unused for some node
+ * types. %CLUTTER_PATH_MOVE_TO and %CLUTTER_PATH_LINE_TO use only two
+ * pairs of coordinates, %CLUTTER_PATH_CURVE_TO uses all three and
+ * %CLUTTER_PATH_CLOSE uses none.
+ *
+ * Since: 1.0
+ */
+struct _ClutterPathNode
+{
+ ClutterPathNodeType type;
+
+ ClutterKnot points[3];
+};
+
+GType clutter_path_get_type (void) G_GNUC_CONST;
+
+ClutterPath *clutter_path_new (void);
+
+ClutterPath *clutter_path_new_with_description (const gchar *desc);
+
+void clutter_path_add_move_to (ClutterPath *path,
+ gint x,
+ gint y);
+
+void clutter_path_add_rel_move_to (ClutterPath *path,
+ gint x,
+ gint y);
+
+void clutter_path_add_line_to (ClutterPath *path,
+ gint x,
+ gint y);
+
+void clutter_path_add_rel_line_to (ClutterPath *path,
+ gint x,
+ gint y);
+
+void clutter_path_add_curve_to (ClutterPath *path,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gint x3,
+ gint y3);
+
+void clutter_path_add_rel_curve_to (ClutterPath *path,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gint x3,
+ gint y3);
+
+void clutter_path_add_close (ClutterPath *path);
+
+gboolean clutter_path_add_string (ClutterPath *path,
+ const gchar *str);
+
+void clutter_path_add_node (ClutterPath *path,
+ const ClutterPathNode *node);
+
+guint clutter_path_get_n_nodes (ClutterPath *path);
+
+void clutter_path_get_node (ClutterPath *path,
+ guint index,
+ ClutterPathNode *node);
+
+GSList *clutter_path_get_nodes (ClutterPath *path);
+
+void clutter_path_foreach (ClutterPath *path,
+ ClutterPathCallback callback,
+ gpointer user_data);
+
+void clutter_path_insert_node (ClutterPath *path,
+ gint index,
+ const ClutterPathNode *node);
+
+void clutter_path_remove_node (ClutterPath *path,
+ guint index);
+
+void clutter_path_replace_node (ClutterPath *path,
+ guint index,
+ const ClutterPathNode *node);
+
+gchar *clutter_path_get_description (ClutterPath *path);
+
+gboolean clutter_path_set_description (ClutterPath *path,
+ const gchar *str);
+
+void clutter_path_clear (ClutterPath *path);
+
+guint clutter_path_get_position (ClutterPath *path,
+ guint alpha,
+ ClutterKnot *position);
+
+guint clutter_path_get_length (ClutterPath *path);
+
+ClutterPathNode *clutter_path_node_copy (const ClutterPathNode *node);
+
+void clutter_path_node_free (ClutterPathNode *node);
+
+gboolean clutter_path_node_equal (const ClutterPathNode *node_a,
+ const ClutterPathNode *node_b);
+
+GType clutter_path_node_get_type (void);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_PATH_H__ */
#include "clutter-color.h"
#include "clutter-container.h"
#include "clutter-behaviour.h"
-#include "clutter-behaviour-bspline.h"
#include "clutter-behaviour-depth.h"
#include "clutter-behaviour-ellipse.h"
#include "clutter-behaviour-opacity.h"
#include "clutter-main.h"
#include "clutter-media.h"
#include "clutter-model.h"
+#include "clutter-path.h"
#include "clutter-stage.h"
#include "clutter-stage-manager.h"
#include "clutter-texture.h"
<variablelist>
<varlistentry>
- <term>#ClutterBehaviourBspline</term>
- <listitem><simpara>Moves actors along a B-spline path</simpara></listitem>
- </varlistentry>
- <varlistentry>
<term>#ClutterBehaviourDepth</term>
<listitem><simpara>Changes the depth of actors</simpara></listitem>
</varlistentry>
<chapter>
<title>Behaviours</title>
- <xi:include href="xml/clutter-behaviour-bspline.xml"/>
<xi:include href="xml/clutter-behaviour-depth.xml"/>
<xi:include href="xml/clutter-behaviour-ellipse.xml"/>
<xi:include href="xml/clutter-behaviour-opacity.xml"/>
<xi:include href="xml/clutter-behaviour-path.xml"/>
+ <xi:include href="xml/clutter-path.xml"/>
<xi:include href="xml/clutter-behaviour-rotate.xml"/>
<xi:include href="xml/clutter-behaviour-scale.xml"/>
</chapter>
ClutterBehaviourPath
ClutterBehaviourPathClass
clutter_behaviour_path_new
-clutter_behaviour_path_get_knots
-clutter_behaviour_path_append_knot
-clutter_behaviour_path_append_knots
-clutter_behaviour_path_insert_knot
-clutter_behaviour_path_remove_knot
-clutter_behaviour_path_clear
-
+clutter_behaviour_path_new_with_description
+clutter_behaviour_path_new_with_knots
+clutter_behaviour_path_set_path
+clutter_behaviour_path_get_path
<SUBSECTION>
ClutterKnot
clutter_knot_copy
</SECTION>
<SECTION>
+<FILE>clutter-path</FILE>
+<TITLE>ClutterPath</TITLE>
+ClutterPath
+ClutterPathClass
+ClutterPathCallback
+ClutterPathNodeType
+clutter_path_new
+clutter_path_new_with_description
+clutter_path_add_move_to
+clutter_path_add_rel_move_to
+clutter_path_add_line_to
+clutter_path_add_rel_line_to
+clutter_path_add_curve_to
+clutter_path_add_rel_curve_to
+clutter_path_add_close
+clutter_path_add_string
+clutter_path_add_node
+clutter_path_get_n_nodes
+clutter_path_get_node
+clutter_path_get_nodes
+clutter_path_foreach
+clutter_path_insert_node
+clutter_path_remove_node
+clutter_path_replace_node
+clutter_path_get_description
+clutter_path_set_description
+clutter_path_clear
+clutter_path_get_position
+clutter_path_get_length
+
+<SUBSECTION>
+ClutterPathNode
+clutter_path_node_copy
+clutter_path_node_free
+clutter_path_node_equal
+</SECTION>
+
+<SECTION>
<FILE>clutter-behaviour-opacity</FILE>
<TITLE>ClutterBehaviourOpacity</TITLE>
ClutterBehaviourOpacity
</SECTION>
<SECTION>
-<FILE>clutter-behaviour-bspline</FILE>
-<TITLE>ClutterBehaviourBspline</TITLE>
-ClutterBehaviourBspline
-ClutterBehaviourBsplineClass
-clutter_behaviour_bspline_new
-clutter_behaviour_bspline_append_knot
-clutter_behaviour_bspline_append_knots
-clutter_behaviour_bspline_truncate
-clutter_behaviour_bspline_join
-clutter_behaviour_bspline_split
-clutter_behaviour_bspline_clear
-clutter_behaviour_bspline_adjust
-clutter_behaviour_bspline_set_origin
-clutter_behaviour_bspline_get_origin
-<SUBSECTION Standard>
-CLUTTER_TYPE_BEHAVIOUR_BSPLINE
-CLUTTER_BEHAVIOUR_BSPLINE
-CLUTTER_BEHAVIOUR_BSPLINE_CLASS
-CLUTTER_IS_BEHAVIOUR_BSPLINE
-CLUTTER_IS_BEHAVIOUR_BSPLINE_CLASS
-CLUTTER_BEHAVIOUR_BSPLINE_GET_CLASS
-<SUBSECTION Private>
-ClutterBehaviourBsplinePrivate
-clutter_behaviour_bspline_get_type
-</SECTION>
-
-<SECTION>
<FILE>clutter-behaviour-ellipse</FILE>
<TITLE>ClutterBehaviourEllipse</TITLE>
ClutterBehaviourEllipse
clutter_media_get_type
clutter_behaviour_get_type
clutter_alpha_get_type
-clutter_behaviour_bspline_get_type
clutter_behaviour_depth_get_type
clutter_behaviour_ellipse_get_type
clutter_behaviour_opacity_get_type
clutter_behaviour_path_get_type
+clutter_path_get_type
clutter_behaviour_rotate_get_type
clutter_behaviour_scale_get_type
clutter_backend_get_type
test-mesh-contiguous.c \
test-mesh-interleved.c \
test-mesh-mutability.c \
+ test-path.c \
test-pick.c \
test-label-cache.c \
test-clutter-entry.c \
TEST_CONFORM_SIMPLE ("/texture", test_backface_culling);
+ TEST_CONFORM_SIMPLE ("/path", test_path);
+
return g_test_run ();
}
--- /dev/null
+#include <clutter/clutter.h>
+#include <string.h>
+#include <math.h>
+
+#include "test-conform-common.h"
+
+#define MAX_NODES 128
+
+#define FLOAT_FUZZ_AMOUNT 5.0f
+
+typedef struct _CallbackData CallbackData;
+
+typedef gboolean (* PathTestFunc) (CallbackData *data);
+
+static void compare_node (const ClutterPathNode *node, gpointer data_p);
+
+struct _CallbackData
+{
+ ClutterPath *path;
+
+ guint n_nodes;
+ ClutterPathNode nodes[MAX_NODES];
+
+ gboolean nodes_different;
+ guint nodes_found;
+};
+
+static const char path_desc[] =
+ "M 21 22 m 23 24 "
+ "L 25 26 l 27 28 "
+ "C 29 30 31 32 33 34 c 35 36 37 38 39 40 "
+ "z";
+static const ClutterPathNode path_nodes[] =
+ { { CLUTTER_PATH_MOVE_TO, { { 21, 22 }, { 0, 0 }, { 0, 0 } } },
+ { CLUTTER_PATH_REL_MOVE_TO, { { 23, 24 }, { 0, 0 }, { 0, 0 } } },
+ { CLUTTER_PATH_LINE_TO, { { 25, 26 }, { 0, 0 }, { 0, 0 } } },
+ { CLUTTER_PATH_REL_LINE_TO, { { 27, 28 }, { 0, 0 }, { 0, 0 } } },
+ { CLUTTER_PATH_CURVE_TO, { { 29, 30 }, { 31, 32 }, { 33, 34 } } },
+ { CLUTTER_PATH_REL_CURVE_TO, { { 35, 36 }, { 37, 38 }, { 39, 40 } } },
+ { CLUTTER_PATH_CLOSE, { { 0, 0 }, { 0, 0 }, { 0, 0 } } } };
+
+static gboolean
+path_test_add_move_to (CallbackData *data)
+{
+ ClutterPathNode node;
+
+ node.type = CLUTTER_PATH_MOVE_TO;
+ node.points[0].x = 1;
+ node.points[0].y = 2;
+
+ clutter_path_add_move_to (data->path, node.points[0].x, node.points[0].y);
+
+ data->nodes[data->n_nodes++] = node;
+
+ return TRUE;
+}
+
+static gboolean
+path_test_add_line_to (CallbackData *data)
+{
+ ClutterPathNode node;
+
+ node.type = CLUTTER_PATH_LINE_TO;
+ node.points[0].x = 3;
+ node.points[0].y = 4;
+
+ clutter_path_add_line_to (data->path, node.points[0].x, node.points[0].y);
+
+ data->nodes[data->n_nodes++] = node;
+
+ return TRUE;
+}
+
+static gboolean
+path_test_add_curve_to (CallbackData *data)
+{
+ ClutterPathNode node;
+
+ node.type = CLUTTER_PATH_CURVE_TO;
+ node.points[0].x = 5;
+ node.points[0].y = 6;
+ node.points[1].x = 7;
+ node.points[1].y = 8;
+ node.points[2].x = 9;
+ node.points[2].y = 10;
+
+ clutter_path_add_curve_to (data->path,
+ node.points[0].x, node.points[0].y,
+ node.points[1].x, node.points[1].y,
+ node.points[2].x, node.points[2].y);
+
+ data->nodes[data->n_nodes++] = node;
+
+ return TRUE;
+}
+
+static gboolean
+path_test_add_close (CallbackData *data)
+{
+ ClutterPathNode node;
+
+ node.type = CLUTTER_PATH_CLOSE;
+
+ clutter_path_add_close (data->path);
+
+ data->nodes[data->n_nodes++] = node;
+
+ return TRUE;
+}
+
+static gboolean
+path_test_add_rel_move_to (CallbackData *data)
+{
+ ClutterPathNode node;
+
+ node.type = CLUTTER_PATH_REL_MOVE_TO;
+ node.points[0].x = 11;
+ node.points[0].y = 12;
+
+ clutter_path_add_rel_move_to (data->path, node.points[0].x, node.points[0].y);
+
+ data->nodes[data->n_nodes++] = node;
+
+ return TRUE;
+}
+
+static gboolean
+path_test_add_rel_line_to (CallbackData *data)
+{
+ ClutterPathNode node;
+
+ node.type = CLUTTER_PATH_REL_LINE_TO;
+ node.points[0].x = 13;
+ node.points[0].y = 14;
+
+ clutter_path_add_rel_line_to (data->path, node.points[0].x, node.points[0].y);
+
+ data->nodes[data->n_nodes++] = node;
+
+ return TRUE;
+}
+
+static gboolean
+path_test_add_rel_curve_to (CallbackData *data)
+{
+ ClutterPathNode node;
+
+ node.type = CLUTTER_PATH_REL_CURVE_TO;
+ node.points[0].x = 15;
+ node.points[0].y = 16;
+ node.points[1].x = 17;
+ node.points[1].y = 18;
+ node.points[2].x = 19;
+ node.points[2].y = 20;
+
+ clutter_path_add_rel_curve_to (data->path,
+ node.points[0].x, node.points[0].y,
+ node.points[1].x, node.points[1].y,
+ node.points[2].x, node.points[2].y);
+
+ data->nodes[data->n_nodes++] = node;
+
+ return TRUE;
+}
+
+static gboolean
+path_test_add_string (CallbackData *data)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (path_nodes); i++)
+ data->nodes[data->n_nodes++] = path_nodes[i];
+
+ clutter_path_add_string (data->path, path_desc);
+
+ return TRUE;
+}
+
+static gboolean
+path_test_add_node_by_struct (CallbackData *data)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (path_nodes); i++)
+ {
+ data->nodes[data->n_nodes++] = path_nodes[i];
+ clutter_path_add_node (data->path, path_nodes + i);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+path_test_get_n_nodes (CallbackData *data)
+{
+ return clutter_path_get_n_nodes (data->path) == data->n_nodes;
+}
+
+static gboolean
+path_test_get_node (CallbackData *data)
+{
+ int i;
+
+ data->nodes_found = 0;
+ data->nodes_different = FALSE;
+
+ for (i = 0; i < data->n_nodes; i++)
+ {
+ ClutterPathNode node;
+
+ clutter_path_get_node (data->path, i, &node);
+
+ compare_node (&node, data);
+ }
+
+ return !data->nodes_different;
+}
+
+static gboolean
+path_test_get_nodes (CallbackData *data)
+{
+ GSList *list, *node;
+
+ data->nodes_found = 0;
+ data->nodes_different = FALSE;
+
+ list = clutter_path_get_nodes (data->path);
+
+ for (node = list; node; node = node->next)
+ compare_node (node->data, data);
+
+ g_slist_free (list);
+
+ return !data->nodes_different && data->nodes_found == data->n_nodes;
+}
+
+static gboolean
+path_test_insert_beginning (CallbackData *data)
+{
+ ClutterPathNode node;
+
+ node.type = CLUTTER_PATH_LINE_TO;
+ node.points[0].x = 41;
+ node.points[0].y = 42;
+
+ memmove (data->nodes + 1, data->nodes,
+ data->n_nodes++ * sizeof (ClutterPathNode));
+ data->nodes[0] = node;
+
+ clutter_path_insert_node (data->path, 0, &node);
+
+ return TRUE;
+}
+
+static gboolean
+path_test_insert_end (CallbackData *data)
+{
+ ClutterPathNode node;
+
+ node.type = CLUTTER_PATH_LINE_TO;
+ node.points[0].x = 43;
+ node.points[0].y = 44;
+
+ data->nodes[data->n_nodes++] = node;
+
+ clutter_path_insert_node (data->path, -1, &node);
+
+ return TRUE;
+}
+
+static gboolean
+path_test_insert_middle (CallbackData *data)
+{
+ ClutterPathNode node;
+ int pos = data->n_nodes / 2;
+
+ node.type = CLUTTER_PATH_LINE_TO;
+ node.points[0].x = 45;
+ node.points[0].y = 46;
+
+ memmove (data->nodes + pos + 1, data->nodes + pos,
+ (data->n_nodes - pos) * sizeof (ClutterPathNode));
+ data->nodes[pos] = node;
+ data->n_nodes++;
+
+ clutter_path_insert_node (data->path, pos, &node);
+
+ return TRUE;
+}
+
+static gboolean
+path_test_clear (CallbackData *data)
+{
+ clutter_path_clear (data->path);
+
+ data->n_nodes = 0;
+
+ return TRUE;
+}
+
+static gboolean
+path_test_clear_insert (CallbackData *data)
+{
+ return path_test_clear (data) && path_test_insert_middle (data);
+}
+
+static gboolean
+path_test_remove_beginning (CallbackData *data)
+{
+ memmove (data->nodes, data->nodes + 1,
+ --data->n_nodes * sizeof (ClutterPathNode));
+
+ clutter_path_remove_node (data->path, 0);
+
+ return TRUE;
+}
+
+static gboolean
+path_test_remove_end (CallbackData *data)
+{
+ clutter_path_remove_node (data->path, --data->n_nodes);
+
+ return TRUE;
+}
+
+static gboolean
+path_test_remove_middle (CallbackData *data)
+{
+ int pos = data->n_nodes / 2;
+
+ memmove (data->nodes + pos, data->nodes + pos + 1,
+ (--data->n_nodes - pos) * sizeof (ClutterPathNode));
+
+ clutter_path_remove_node (data->path, pos);
+
+ return TRUE;
+}
+
+static gboolean
+path_test_remove_only (CallbackData *data)
+{
+ return path_test_clear (data)
+ && path_test_add_line_to (data)
+ && path_test_remove_beginning (data);
+}
+
+static gboolean
+path_test_replace (CallbackData *data)
+{
+ ClutterPathNode node;
+ int pos = data->n_nodes / 2;
+
+ node.type = CLUTTER_PATH_LINE_TO;
+ node.points[0].x = 47;
+ node.points[0].y = 48;
+
+ data->nodes[pos] = node;
+
+ clutter_path_replace_node (data->path, pos, &node);
+
+ return TRUE;
+}
+
+static gboolean
+path_test_set_description (CallbackData *data)
+{
+ data->n_nodes = G_N_ELEMENTS (path_nodes);
+ memcpy (data->nodes, path_nodes, sizeof (path_nodes));
+
+ return clutter_path_set_description (data->path, path_desc);
+}
+
+static gboolean
+path_test_get_description (CallbackData *data)
+{
+ char *desc1, *desc2;
+ gboolean ret = TRUE;
+
+ desc1 = clutter_path_get_description (data->path);
+ clutter_path_clear (data->path);
+ if (!clutter_path_set_description (data->path, desc1))
+ ret = FALSE;
+ desc2 = clutter_path_get_description (data->path);
+
+ if (strcmp (desc1, desc2))
+ ret = FALSE;
+
+ g_free (desc1);
+ g_free (desc2);
+
+ return ret;
+}
+
+static gboolean
+float_fuzzy_equals (float fa, float fb)
+{
+ return fabs (fa - fb) <= FLOAT_FUZZ_AMOUNT;
+}
+
+static void
+set_triangle_path (CallbackData *data)
+{
+ /* Triangular shaped path hitting (0,0), (64,64) and (128,0) in four
+ parts. The two curves are actually straight lines */
+ static const ClutterPathNode nodes[] =
+ { { CLUTTER_PATH_MOVE_TO, { { 0, 0 } } },
+ { CLUTTER_PATH_LINE_TO, { { 32, 32 } } },
+ { CLUTTER_PATH_CURVE_TO, { { 40, 40 }, { 56, 56 }, { 64, 64 } } },
+ { CLUTTER_PATH_REL_CURVE_TO, { { 8, -8 }, { 24, -24 }, { 32, -32 } } },
+ { CLUTTER_PATH_REL_LINE_TO, { { 32, -32 } } } };
+ gint i;
+
+ clutter_path_clear (data->path);
+
+ for (i = 0; i < G_N_ELEMENTS (nodes); i++)
+ clutter_path_add_node (data->path, nodes + i);
+
+ memcpy (data->nodes, nodes, sizeof (nodes));
+ data->n_nodes = G_N_ELEMENTS (nodes);
+}
+
+static gboolean
+path_test_get_position (CallbackData *data)
+{
+ static const float values[] = { 0.125f, 16.0f, 16.0f,
+ 0.375f, 48.0f, 48.0f,
+ 0.625f, 80.0f, 48.0f,
+ 0.875f, 112.0f, 16.0f };
+ gint i;
+
+ set_triangle_path (data);
+
+ for (i = 0; i < G_N_ELEMENTS (values); i += 3)
+ {
+ ClutterKnot pos;
+
+ clutter_path_get_position (data->path,
+ values[i] * CLUTTER_ALPHA_MAX_ALPHA, &pos);
+
+ if (!float_fuzzy_equals (values[i + 1], pos.x)
+ || !float_fuzzy_equals (values[i + 2], pos.y))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+path_test_get_length (CallbackData *data)
+{
+ const float actual_length /* sqrt(64**2 + 64**2) * 2 */ = 181.019336f;
+ guint approx_length;
+
+ set_triangle_path (data);
+
+ g_object_get (data->path, "length", &approx_length, NULL);
+
+ /* Allow 15% margin of error */
+ return fabs (approx_length - actual_length) / actual_length <= 0.15f;
+}
+
+static gboolean
+path_test_boxed_type (CallbackData *data)
+{
+ gboolean ret = TRUE;
+ GSList *nodes, *l;
+ GValue value;
+
+ nodes = clutter_path_get_nodes (data->path);
+
+ memset (&value, 0, sizeof (value));
+
+ for (l = nodes; l; l = l->next)
+ {
+ g_value_init (&value, CLUTTER_TYPE_PATH_NODE);
+
+ g_value_set_boxed (&value, l->data);
+
+ if (!clutter_path_node_equal (l->data,
+ g_value_get_boxed (&value)))
+ ret = FALSE;
+
+ g_value_unset (&value);
+ }
+
+ g_slist_free (nodes);
+
+ return ret;
+}
+
+static const struct
+{
+ const char *desc;
+ PathTestFunc func;
+}
+path_tests[] =
+ {
+ { "Add line to", path_test_add_line_to },
+ { "Add move to", path_test_add_move_to },
+ { "Add curve to", path_test_add_curve_to },
+ { "Add close", path_test_add_close },
+ { "Add relative line to", path_test_add_rel_line_to },
+ { "Add relative move to", path_test_add_rel_move_to },
+ { "Add relative curve to", path_test_add_rel_curve_to },
+ { "Add string", path_test_add_string },
+ { "Add node by struct", path_test_add_node_by_struct },
+ { "Get number of nodes", path_test_get_n_nodes },
+ { "Get a node", path_test_get_node },
+ { "Get all nodes", path_test_get_nodes },
+ { "Insert at beginning", path_test_insert_beginning },
+ { "Insert at end", path_test_insert_end },
+ { "Insert at middle", path_test_insert_middle },
+ { "Add after insert", path_test_add_line_to },
+ { "Clear then insert", path_test_clear_insert },
+ { "Add string again", path_test_add_string },
+ { "Remove from beginning", path_test_remove_beginning },
+ { "Remove from end", path_test_remove_end },
+ { "Remove from middle", path_test_remove_middle },
+ { "Add after remove", path_test_add_line_to },
+ { "Remove only node", path_test_remove_only },
+ { "Add after remove again", path_test_add_line_to },
+ { "Replace a node", path_test_replace },
+ { "Set description", path_test_set_description },
+ { "Get description", path_test_get_description },
+ { "Clear", path_test_clear },
+ { "Get position", path_test_get_position },
+ { "Check node boxed type", path_test_boxed_type },
+ { "Get length", path_test_get_length }
+ };
+
+static void
+compare_node (const ClutterPathNode *node, gpointer data_p)
+{
+ CallbackData *data = data_p;
+
+ if (data->nodes_found >= data->n_nodes)
+ data->nodes_different = TRUE;
+ else
+ {
+ guint n_points = 0, i;
+ const ClutterPathNode *onode = data->nodes + data->nodes_found;
+
+ if (node->type != onode->type)
+ data->nodes_different = TRUE;
+
+ switch (node->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:
+ data->nodes_different = TRUE;
+ break;
+ }
+
+ for (i = 0; i < n_points; i++)
+ if (node->points[i].x != onode->points[i].x
+ || node->points[i].y != onode->points[i].y)
+ {
+ data->nodes_different = TRUE;
+ break;
+ }
+ }
+
+ data->nodes_found++;
+}
+
+static gboolean
+compare_nodes (CallbackData *data)
+{
+ data->nodes_different = FALSE;
+ data->nodes_found = 0;
+
+ clutter_path_foreach (data->path, compare_node, data);
+
+ return !data->nodes_different && data->nodes_found == data->n_nodes;
+}
+
+void
+test_path (TestConformSimpleFixture *fixture,
+ gconstpointer _data)
+{
+ CallbackData data;
+ gint i;
+
+ memset (&data, 0, sizeof (data));
+
+ data.path = clutter_path_new ();
+
+ for (i = 0; i < G_N_ELEMENTS (path_tests); i++)
+ {
+ gboolean succeeded;
+
+ if (g_test_verbose ())
+ g_print ("%s... ", path_tests[i].desc);
+
+ succeeded = path_tests[i].func (&data) && compare_nodes (&data);
+
+ if (g_test_verbose ())
+ g_print ("%s\n", succeeded ? "ok" : "FAIL");
+
+ g_assert (succeeded);
+ }
+
+ g_object_unref (data.path);
+}
+
PATH_BSPLINE
} path_t;
-#define MAGIC 0.551784
-#define RADIUS 200
-
G_MODULE_EXPORT int
test_behave_main (int argc, char *argv[])
{
ClutterColor rect_border_color = { 0, 0, 0, 0 };
int i;
path_t path_type = PATH_POLY;
-
- ClutterKnot knots_poly[] = {{ 0, 0 }, { 0, 300 }, { 300, 300 },
- { 300, 0 }, {0, 0 }};
-
- ClutterKnot origin = { 200, 200 };
-
- ClutterKnot knots_bspline[] = {{ -RADIUS, 0 },
- { -RADIUS, RADIUS*MAGIC },
- { -RADIUS*MAGIC, RADIUS },
- { 0, RADIUS },
- { RADIUS*MAGIC, RADIUS },
- { RADIUS, RADIUS*MAGIC },
- { RADIUS, 0 },
- { RADIUS, -RADIUS*MAGIC },
- { RADIUS*MAGIC, -RADIUS },
- { 0, -RADIUS },
- { -RADIUS*MAGIC, -RADIUS },
- { -RADIUS, -RADIUS*MAGIC },
- { -RADIUS, 0}};
+
+ const char *knots_poly = ("M 0, 0 L 0, 300 L 300, 300 "
+ "L 300, 0 L 0, 0");
+
+ /* A spiral created with inkscake */
+ const char *knots_bspline =
+ "M 34.285713,35.219326 "
+ "C 44.026891,43.384723 28.084874,52.378758 20.714286,51.409804 "
+ "C 0.7404474,48.783999 -4.6171866,23.967448 1.904757,8.0764719 "
+ "C 13.570984,-20.348756 49.798303,-26.746504 74.999994,-13.352108 "
+ "C 111.98449,6.3047056 119.56591,55.259271 99.047626,89.505034 "
+ "C 71.699974,135.14925 9.6251774,143.91924 -33.571422,116.17172 "
+ "C -87.929934,81.254291 -97.88804,5.8941057 -62.857155,-46.209236 "
+ "C -20.430061,-109.31336 68.300385,-120.45954 129.2857,-78.114021 "
+ "C 201.15479,-28.21129 213.48932,73.938876 163.80954,143.79074 "
+ "C 106.45226,224.43749 -9.1490153,237.96076 -87.85713,180.93363 "
+ "C -177.29029,116.13577 -192.00272,-12.937817 -127.61907,-100.49494 "
+ "C -55.390344,-198.72081 87.170553,-214.62275 183.57141,-142.87593 "
+ "C 290.59464,-63.223369 307.68641,92.835839 228.57145,198.07645";
for (i = 0; i < argc; ++i)
{
switch (path_type)
{
case PATH_POLY:
- p_behave = clutter_behaviour_path_new (alpha, knots_poly, 5);
+ {
+ ClutterPath *path = clutter_path_new ();
+ clutter_path_set_description (path, knots_poly);
+ p_behave = clutter_behaviour_path_new (alpha, path);
+ }
break;
case PATH_ELLIPSE:
p_behave =
break;
case PATH_BSPLINE:
- origin.x = 0;
- origin.y = RADIUS;
- p_behave =
- clutter_behaviour_bspline_new (alpha, knots_bspline,
- sizeof (knots_bspline)/sizeof(ClutterKnot));
-
- clutter_behaviour_bspline_set_origin (
- CLUTTER_BEHAVIOUR_BSPLINE (p_behave),
- &origin);
+ {
+ ClutterPath *path = clutter_path_new ();
+ clutter_path_set_description (path, knots_bspline);
+ p_behave = clutter_behaviour_path_new (alpha, path);
+ }
break;
}
" {"
" \"id\" : \"path-behaviour\","
" \"type\" : \"ClutterBehaviourPath\","
-" \"knots\" : [ [ 50, 50 ], { \"x\" : 100, \"y\" : 100 } ],"
+" \"path\" : \"M 50 50 L 100 100\","
" \"alpha\" : {"
" \"timeline\" : \"main-timeline\","
" \"function\" : \"ramp\""
ClutterColor rect_color = { 0xee, 0x55, 0x55, 0x99 };
ClutterColor progress_color = { 0x55, 0xee, 0x55, 0xbb };
ClutterBehaviour *r_behaviour, *p_behaviour;
+ ClutterAlpha *alpha;
const ClutterKnot knots[] = {
{ 75, 150 },
{ 400, 150 }
0.0, 360.0);
clutter_behaviour_apply (r_behaviour, rect);
- p_behaviour = clutter_behaviour_path_new (clutter_alpha_new_full (timeline,
- clutter_ramp_inc_func,
- NULL, NULL),
- knots,
- G_N_ELEMENTS (knots));
+ alpha = clutter_alpha_new_full (timeline, clutter_ramp_inc_func,
+ NULL, NULL);
+ p_behaviour = clutter_behaviour_path_new_with_knots (alpha,
+ knots,
+ G_N_ELEMENTS (knots));
clutter_behaviour_apply (p_behaviour, rect);
g_signal_connect (stage,