27
27
* SECTION:clutter-timeline
28
28
* @short_description: A class for time-based events
30
* #ClutterTimeline is a base class for managing time based events such
29
* @see_also: #ClutterAnimation, #ClutterAnimator, #ClutterState
31
* #ClutterTimeline is a base class for managing time-based event that cause
32
* Clutter to redraw a stage, such as animations.
34
* Each #ClutterTimeline instance has a duration: once a timeline has been
35
* started, using clutter_timeline_start(), it will emit a signal that can
36
* be used to update the state of the actors.
38
* It is important to note that #ClutterTimeline is not a generic API for
39
* calling closures after an interval; each Timeline is tied into the master
40
* clock used to drive the frame cycle. If you need to schedule a closure
41
* after an interval, see clutter_threads_add_timeout() instead.
43
* Users of #ClutterTimeline should connect to the #ClutterTimeline::new-frame
44
* signal, which is emitted each time a timeline is advanced during the maste
45
* clock iteration. The #ClutterTimeline::new-frame signal provides the time
46
* elapsed since the beginning of the timeline, in milliseconds. A normalized
47
* progress value can be obtained by calling clutter_timeline_get_progress().
48
* By using clutter_timeline_get_delta() it is possible to obtain the wallclock
49
* time elapsed since the last emission of the #ClutterTimeline::new-frame
52
* Initial state can be set up by using the #ClutterTimeline::started signal,
53
* while final state can be set up by using the #ClutterTimeline::completed
54
* signal. The #ClutterTimeline guarantees the emission of at least a single
55
* #ClutterTimeline::new-frame signal, as well as the emission of the
56
* #ClutterTimeline::completed signal.
58
* It is possible to connect to specific points in the timeline progress by
59
* adding <emphasis>markers</emphasis> using clutter_timeline_add_marker_at_time()
60
* and connecting to the #ClutterTimeline::marker-reached signal.
62
* Timelines can be made to loop once they reach the end of their duration, by
63
* using clutter_timeline_set_repeat_count(); a looping timeline will still
64
* emit the #ClutterTimeline::completed signal once it reaches the end of its
67
* Timelines have a #ClutterTimeline:direction: the default direction is
68
* %CLUTTER_TIMELINE_FORWARD, and goes from 0 to the duration; it is possible
69
* to change the direction to %CLUTTER_TIMELINE_BACKWARD, and have the timeline
70
* go from the duration to 0. The direction can be automatically reversed
71
* when reaching completion by using the #ClutterTimeline:auto-reverse property.
73
* Timelines are used in the Clutter animation framework by classes like
74
* #ClutterAnimation, #ClutterAnimator, and #ClutterState.
76
* <refsect2 id="timeline-script">
77
* <title>Defining Timelines in ClutterScript</title>
78
* <para>A #ClutterTimeline can be described in #ClutterScript like any
79
* other object. Additionally, it is possible to define markers directly
80
* inside the JSON definition by using the <emphasis>markers</emphasis>
81
* JSON object member, such as:</para>
82
* <informalexample><programlisting><![CDATA[
84
"type" : "ClutterTimeline",
87
{ "name" : "quarter", "time" : 250 },
88
{ "name" : "half-time", "time" : 500 },
89
{ "name" : "three-quarters", "time" : 750 }
92
* ]]></programlisting></informalexample>
34
96
#ifdef HAVE_CONFIG_H
35
97
#include "config.h"
100
#include "clutter-timeline.h"
38
102
#include "clutter-debug.h"
103
#include "clutter-easing.h"
39
104
#include "clutter-enum-types.h"
40
105
#include "clutter-main.h"
41
106
#include "clutter-marshal.h"
42
107
#include "clutter-master-clock.h"
43
108
#include "clutter-private.h"
44
#include "clutter-timeline.h"
46
G_DEFINE_TYPE (ClutterTimeline, clutter_timeline, G_TYPE_OBJECT);
109
#include "clutter-scriptable.h"
111
#include "deprecated/clutter-timeline.h"
113
static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);
115
G_DEFINE_TYPE_WITH_CODE (ClutterTimeline, clutter_timeline, G_TYPE_OBJECT,
116
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
117
clutter_scriptable_iface_init));
48
119
struct _ClutterTimelinePrivate
222
* clutter_timeline_add_marker_internal:
223
* @timeline: a #ClutterTimeline
224
* @marker: a TimelineMarker
226
* Adds @marker into the hash table of markers for @timeline.
228
* The TimelineMarker will either be added or, in case of collisions
229
* with another existing marker, freed. In any case, this function
230
* assumes the ownership of the passed @marker.
233
clutter_timeline_add_marker_internal (ClutterTimeline *timeline,
234
TimelineMarker *marker)
236
ClutterTimelinePrivate *priv = timeline->priv;
237
TimelineMarker *old_marker;
239
/* create the hash table that will hold the markers */
240
if (G_UNLIKELY (priv->markers_by_name == NULL))
241
priv->markers_by_name = g_hash_table_new_full (g_str_hash, g_str_equal,
243
timeline_marker_free);
245
old_marker = g_hash_table_lookup (priv->markers_by_name, marker->name);
246
if (old_marker != NULL)
248
g_warning ("A marker named '%s' already exists at time %d",
251
timeline_marker_free (marker);
255
g_hash_table_insert (priv->markers_by_name, marker->name, marker);
259
clutter_timeline_set_loop_internal (ClutterTimeline *timeline,
262
gint old_repeat_count;
264
old_repeat_count = timeline->priv->repeat_count;
267
clutter_timeline_set_repeat_count (timeline, -1);
269
clutter_timeline_set_repeat_count (timeline, 0);
271
if (old_repeat_count != timeline->priv->repeat_count)
272
g_object_notify_by_pspec (G_OBJECT (timeline), obj_props[PROP_LOOP]);
276
typedef struct _ParseClosure {
277
ClutterTimeline *timeline;
278
ClutterScript *script;
284
parse_timeline_markers (JsonArray *array,
289
ParseClosure *clos = data;
291
TimelineMarker *marker;
294
if (JSON_NODE_TYPE (element) != JSON_NODE_OBJECT)
296
g_warning ("The 'markers' member of a ClutterTimeline description "
297
"should be an array of objects, but the element %d of the "
298
"array is of type '%s'. The element will be ignored.",
300
json_node_type_name (element));
304
object = json_node_get_object (element);
306
if (!(json_object_has_member (object, "name") &&
307
json_object_has_member (object, "time")))
309
g_warning ("The marker definition in a ClutterTimeline description "
310
"must be an object with the 'name' and 'time' members, "
311
"but the element %d of the 'markers' array does not have "
317
if (G_IS_VALUE (clos->value))
318
markers = g_value_get_pointer (clos->value);
321
g_value_init (clos->value, G_TYPE_POINTER);
325
marker = timeline_marker_new (json_object_get_string_member (object, "name"),
326
json_object_get_int_member (object, "time"));
328
markers = g_list_prepend (markers, marker);
330
g_value_set_pointer (clos->value, markers);
336
clutter_timeline_parse_custom_node (ClutterScriptable *scriptable,
337
ClutterScript *script,
344
if (strcmp (name, "markers") != 0)
347
if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
350
clos.timeline = CLUTTER_TIMELINE (scriptable);
351
clos.script = script;
355
json_array_foreach_element (json_node_get_array (node),
356
parse_timeline_markers,
363
clutter_timeline_set_custom_property (ClutterScriptable *scriptable,
364
ClutterScript *script,
368
if (strcmp (name, "markers") == 0)
370
ClutterTimeline *timeline = CLUTTER_TIMELINE (scriptable);
371
GList *markers = g_value_get_pointer (value);
374
/* the list was created through prepend() */
375
markers = g_list_reverse (markers);
377
for (m = markers; m != NULL; m = m->next)
378
clutter_timeline_add_marker_internal (timeline, m->data);
380
g_list_free (markers);
383
g_object_set_property (G_OBJECT (scriptable), name, value);
388
clutter_scriptable_iface_init (ClutterScriptableIface *iface)
390
iface->parse_custom_node = clutter_timeline_parse_custom_node;
391
iface->set_custom_property = clutter_timeline_set_custom_property;
1631
1949
return timeline->priv->auto_reverse;
1953
* clutter_timeline_set_repeat_count:
1954
* @timeline: a #ClutterTimeline
1955
* @count: the number of times the timeline should repeat
1957
* Sets the number of times the @timeline should repeat.
1959
* If @count is 0, the timeline never repeats.
1961
* If @count is -1, the timeline will always repeat until
1967
clutter_timeline_set_repeat_count (ClutterTimeline *timeline,
1970
ClutterTimelinePrivate *priv;
1972
g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
1973
g_return_if_fail (count >= -1);
1975
priv = timeline->priv;
1977
if (priv->repeat_count != count)
1979
priv->repeat_count = count;
1981
g_object_notify_by_pspec (G_OBJECT (timeline),
1982
obj_props[PROP_REPEAT_COUNT]);
1987
* clutter_timeline_get_repeat_count:
1988
* @timeline: a #ClutterTimeline
1990
* Retrieves the number set using clutter_timeline_set_repeat_count().
1992
* Return value: the number of repeats
1997
clutter_timeline_get_repeat_count (ClutterTimeline *timeline)
1999
g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
2001
return timeline->priv->repeat_count;
2005
* clutter_timeline_set_progress_func:
2006
* @timeline: a #ClutterTimeline
2007
* @func: (scope notified) (allow-none): a progress function, or %NULL
2008
* @data: (closure): data to pass to @func
2009
* @notify: a function to be called when the progress function is removed
2010
* or the timeline is disposed
2012
* Sets a custom progress function for @timeline. The progress function will
2013
* be called by clutter_timeline_get_progress() and will be used to compute
2014
* the progress value based on the elapsed time and the total duration of the
2017
* If @func is not %NULL, the #ClutterTimeline:progress-mode property will
2018
* be set to %CLUTTER_CUSTOM_MODE.
2020
* If @func is %NULL, any previously set progress function will be unset, and
2021
* the #ClutterTimeline:progress-mode property will be set to %CLUTTER_LINEAR.
2026
clutter_timeline_set_progress_func (ClutterTimeline *timeline,
2027
ClutterTimelineProgressFunc func,
2029
GDestroyNotify notify)
2031
ClutterTimelinePrivate *priv;
2033
g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
2035
priv = timeline->priv;
2037
if (priv->progress_notify != NULL)
2038
priv->progress_notify (priv->progress_data);
2040
priv->progress_func = func;
2041
priv->progress_data = data;
2042
priv->progress_notify = notify;
2044
if (priv->progress_func != NULL)
2045
priv->progress_mode = CLUTTER_CUSTOM_MODE;
2047
priv->progress_mode = CLUTTER_LINEAR;
2049
g_object_notify_by_pspec (G_OBJECT (timeline), obj_props[PROP_PROGRESS_MODE]);
2053
* _clutter_animation_modes:
2055
* A mapping of animation modes and easing functions.
2057
static const struct {
2058
ClutterAnimationMode mode;
2059
ClutterEasingFunc func;
2061
} _clutter_animation_modes[] = {
2062
{ CLUTTER_CUSTOM_MODE, NULL, "custom" },
2064
{ CLUTTER_LINEAR, clutter_linear, "linear" },
2065
{ CLUTTER_EASE_IN_QUAD, clutter_ease_in_quad, "easeInQuad" },
2066
{ CLUTTER_EASE_OUT_QUAD, clutter_ease_out_quad, "easeOutQuad" },
2067
{ CLUTTER_EASE_IN_OUT_QUAD, clutter_ease_in_out_quad, "easeInOutQuad" },
2068
{ CLUTTER_EASE_IN_CUBIC, clutter_ease_in_cubic, "easeInCubic" },
2069
{ CLUTTER_EASE_OUT_CUBIC, clutter_ease_out_cubic, "easeOutCubic" },
2070
{ CLUTTER_EASE_IN_OUT_CUBIC, clutter_ease_in_out_cubic, "easeInOutCubic" },
2071
{ CLUTTER_EASE_IN_QUART, clutter_ease_in_quart, "easeInQuart" },
2072
{ CLUTTER_EASE_OUT_QUART, clutter_ease_out_quart, "easeOutQuart" },
2073
{ CLUTTER_EASE_IN_OUT_QUART, clutter_ease_in_out_quart, "easeInOutQuart" },
2074
{ CLUTTER_EASE_IN_QUINT, clutter_ease_in_quint, "easeInQuint" },
2075
{ CLUTTER_EASE_OUT_QUINT, clutter_ease_out_quint, "easeOutQuint" },
2076
{ CLUTTER_EASE_IN_OUT_QUINT, clutter_ease_in_out_quint, "easeInOutQuint" },
2077
{ CLUTTER_EASE_IN_SINE, clutter_ease_in_sine, "easeInSine" },
2078
{ CLUTTER_EASE_OUT_SINE, clutter_ease_out_sine, "easeOutSine" },
2079
{ CLUTTER_EASE_IN_OUT_SINE, clutter_ease_in_out_sine, "easeInOutSine" },
2080
{ CLUTTER_EASE_IN_EXPO, clutter_ease_in_expo, "easeInExpo" },
2081
{ CLUTTER_EASE_OUT_EXPO, clutter_ease_out_expo, "easeOutExpo" },
2082
{ CLUTTER_EASE_IN_OUT_EXPO, clutter_ease_in_out_expo, "easeInOutExpo" },
2083
{ CLUTTER_EASE_IN_CIRC, clutter_ease_in_circ, "easeInCirc" },
2084
{ CLUTTER_EASE_OUT_CIRC, clutter_ease_out_circ, "easeOutCirc" },
2085
{ CLUTTER_EASE_IN_OUT_CIRC, clutter_ease_in_out_circ, "easeInOutCirc" },
2086
{ CLUTTER_EASE_IN_ELASTIC, clutter_ease_in_elastic, "easeInElastic" },
2087
{ CLUTTER_EASE_OUT_ELASTIC, clutter_ease_out_elastic, "easeOutElastic" },
2088
{ CLUTTER_EASE_IN_OUT_ELASTIC, clutter_ease_in_out_elastic, "easeInOutElastic" },
2089
{ CLUTTER_EASE_IN_BACK, clutter_ease_in_back, "easeInBack" },
2090
{ CLUTTER_EASE_OUT_BACK, clutter_ease_out_back, "easeOutBack" },
2091
{ CLUTTER_EASE_IN_OUT_BACK, clutter_ease_in_out_back, "easeInOutBack" },
2092
{ CLUTTER_EASE_IN_BOUNCE, clutter_ease_in_bounce, "easeInBounce" },
2093
{ CLUTTER_EASE_OUT_BOUNCE, clutter_ease_out_bounce, "easeOutBounce" },
2094
{ CLUTTER_EASE_IN_OUT_BOUNCE, clutter_ease_in_out_bounce, "easeInOutBounce" },
2096
{ CLUTTER_ANIMATION_LAST, NULL, "sentinel" },
2100
clutter_timeline_progress_func (ClutterTimeline *timeline,
2103
gpointer user_data G_GNUC_UNUSED)
2105
ClutterTimelinePrivate *priv = timeline->priv;
2106
ClutterEasingFunc easing_func;
2108
g_assert (_clutter_animation_modes[priv->progress_mode].mode == priv->progress_mode);
2109
g_assert (_clutter_animation_modes[priv->progress_mode].func != NULL);
2111
easing_func = _clutter_animation_modes[priv->progress_mode].func;
2113
return easing_func (elapsed, duration);
2117
* clutter_timeline_set_progress_mode:
2118
* @timeline: a #ClutterTimeline
2119
* @mode: the progress mode, as a #ClutterAnimationMode
2121
* Sets the progress function using a value from the #ClutterAnimationMode
2122
* enumeration. The @mode cannot be %CLUTTER_CUSTOM_MODE or bigger than
2123
* %CLUTTER_ANIMATION_LAST.
2128
clutter_timeline_set_progress_mode (ClutterTimeline *timeline,
2129
ClutterAnimationMode mode)
2131
ClutterTimelinePrivate *priv;
2133
g_return_if_fail (CLUTTER_IS_TIMELINE (timeline));
2134
g_return_if_fail (mode < CLUTTER_ANIMATION_LAST);
2135
g_return_if_fail (mode != CLUTTER_CUSTOM_MODE);
2137
priv = timeline->priv;
2139
if (priv->progress_mode == mode)
2142
if (priv->progress_notify != NULL)
2143
priv->progress_notify (priv->progress_data);
2145
priv->progress_mode = mode;
2147
/* short-circuit linear progress */
2148
if (priv->progress_mode != CLUTTER_LINEAR)
2149
priv->progress_func = clutter_timeline_progress_func;
2151
priv->progress_func = NULL;
2153
priv->progress_data = NULL;
2154
priv->progress_notify = NULL;
2156
g_object_notify_by_pspec (G_OBJECT (timeline), obj_props[PROP_PROGRESS_MODE]);
2160
* clutter_timeline_get_progress_mode:
2161
* @timeline: a #ClutterTimeline
2163
* Retrieves the progress mode set using clutter_timeline_set_progress_mode()
2164
* or clutter_timeline_set_progress_func().
2166
* Return value: a #ClutterAnimationMode
2170
ClutterAnimationMode
2171
clutter_timeline_get_progress_mode (ClutterTimeline *timeline)
2173
g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), CLUTTER_LINEAR);
2175
return timeline->priv->progress_mode;
2179
* clutter_timeline_get_duration_hint:
2180
* @timeline: a #ClutterTimeline
2182
* Retrieves the full duration of the @timeline, taking into account the
2183
* current value of the #ClutterTimeline:repeat-count property.
2185
* If the #ClutterTimeline:repeat-count property is set to -1, this function
2186
* will return %G_MAXINT64.
2188
* The returned value is to be considered a hint, and it's only valid
2189
* as long as the @timeline hasn't been changed.
2191
* Return value: the full duration of the #ClutterTimeline
2196
clutter_timeline_get_duration_hint (ClutterTimeline *timeline)
2198
ClutterTimelinePrivate *priv;
2200
g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
2202
priv = timeline->priv;
2204
if (priv->repeat_count == 0)
2205
return priv->duration;
2206
else if (priv->repeat_count < 0)
2209
return priv->repeat_count * priv->duration;
2213
* clutter_timeline_get_current_repeat:
2214
* @timeline: a #ClutterTimeline
2216
* Retrieves the current repeat for a timeline.
2218
* Repeats start at 0.
2220
* Return value: the current repeat
2225
clutter_timeline_get_current_repeat (ClutterTimeline *timeline)
2227
g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), 0);
2229
return timeline->priv->current_repeat;