1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * clock.cpp: Clock management
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
20 #ifdef HAVE_SYS_TIME_H
27 #include "timemanager.h"
30 #include "deployment.h"
34 RepeatBehavior
RepeatBehavior::Forever (RepeatBehavior::FOREVER
);
35 Duration
Duration::Automatic (Duration::AUTOMATIC
);
36 Duration
Duration::Forever (Duration::FOREVER
);
44 typedef void (*ClockFunc
)(Clock
*);
47 clock_list_foreach (GList
*clock_list
, ClockFunc func
)
49 GList
*list
= NULL
, *tail
= NULL
;
50 for (GList
*l
= clock_list
; l
; l
= l
->next
) {
51 list
= g_list_prepend (list
, l
->data
);
52 if (!tail
) tail
= list
;
53 ((Clock
*)l
->data
)->ref();
55 for (GList
*node
= tail
;node
;node
= node
->prev
) {
56 func ((Clock
*)node
->data
);
57 ((Clock
*)node
->data
)->unref ();
63 CallRaiseAccumulatedEvents (Clock
*clock
)
65 clock
->RaiseAccumulatedEvents ();
69 CallRaiseAccumulatedCompleted (Clock
*clock
)
71 clock
->RaiseAccumulatedCompleted ();
75 Clock::Clock (Timeline
*tl
)
76 : natural_duration (Duration::Automatic
)
78 SetObjectType (Type::CLOCK
);
80 calculated_natural_duration
= false;
81 state
= Clock::Stopped
;
90 accumulated_pause_time
= 0;
99 begin_on_tick
= false;
100 emit_completed
= false;
101 has_completed
= false;
112 if (!IsDisposed ()) {
113 DependencyObject::Dispose ();
114 GetTimeline()->TeardownClock ();
119 Clock::GetNaturalDuration ()
121 if (!calculated_natural_duration
) {
122 calculated_natural_duration
= true;
124 Duration
*duration
= timeline
->GetDuration ();
125 if (duration
->HasTimeSpan ()) {
126 natural_duration
= *duration
;
129 natural_duration
= timeline
->GetNaturalDuration (this);
133 return natural_duration
;
137 Clock::UpdateFromParentTime (TimeSpan parentTime
)
139 #define CLAMP_NORMALIZED_TIME do { \
140 if (normalizedTime < 0.0) normalizedTime = 0.0; \
141 if (normalizedTime > 1.0) normalizedTime = 1.0; \
145 // The idea behind this method is that it is possible (and
146 // easier, and clearer) to apply a simple function to our
147 // parent clock's time to calculate our own time.
149 // We also calculate our progress (MS uses the term
150 // "normalized time"), a value in the range [0-1] at the same
153 // This clock's localTime runs from the range
154 // [0-natural_duration] for natural_durations with timespans,
155 // and [0-$forever] for Forever durations. Automatic
156 // durations are translated into timespans.
158 if (!GetHasStarted() && !GetWasStopped() && (GetBeginOnTick() || timeline
->GetBeginTime () <= parentTime
)) {
159 if (GetBeginOnTick())
164 // root_parent_time is the time we were added to our parent clock.
165 // timeline->GetBeginTime() is expressed in the time-space of the parent clock.
167 // subtracting those two translates our start time to 0
169 // we then have to account for our accumulated pause time, and
170 // scale the whole thing by our speed ratio.
172 // the result is a timespan unaffected by repeatbehavior or
173 // autoreverse. it is simple the timespan our clock has been
175 TimeSpan localTime
= (parentTime
- root_parent_time
- timeline
->GetBeginTime() - accumulated_pause_time
) * timeline
->GetSpeedRatio();
177 bool seek_completed
= false;
180 // if we're seeking, we need to arrange for the above
181 // localTime formula to keep time correctly. we clear
182 // accumulated_pause_time, and adjust root_parent_time
183 // such that we can re-evaluate localTime and have
184 // localTime = seek_time.
186 begin_pause_time
= 0;
187 accumulated_pause_time
= 0;
189 /* seek_time = localTime
191 seek_time = (parentTime - root_parent_time - timeline->BeginTime() - 0) * timeline->GetSpeedRatio ()
194 ------------------------- = parentTime - root_parent_time - timeline->BeginTime();
195 timeline->GetSpeedRatio()
197 root_parent_time = parentTime - timeline->BeginTime() - -------------------------
198 timeline->GetSpeedRatio()
200 root_parent_time
= parentTime
- (timeline
->GetBeginTime () - seek_time
) / timeline
->GetSpeedRatio ();
201 localTime
= (seek_time
- timeline
->GetBeginTime()) * timeline
->GetSpeedRatio();
203 seek_completed
= true;
205 if (!GetHasStarted())
206 CalculateFillTime ();
208 else if (is_paused
) {
209 // if we're paused and not seeking, we don't update
214 // the clock doesn't update and we don't progress if the
215 // translated local time is before our begin time. Keep in
216 // mind that this can happen *after* a clock has started,
217 // since parentTime isn't strictly increasing. It can
218 // decrease and represent a time before our start time.
222 if (GetClockState () == Clock::Stopped
) {
226 // even for stopped clocks we update their position if they're seeked.
229 double normalizedTime
= 0.0;
232 // we only do the bulk of the work if the duration has a
233 // timespan. if we're automatic/forever, our normalizedTime
234 // stays pegged at 0.0, and our localTime progresses
235 // undisturbed. i.e. a RepeatBehavior="2x" means nothing if
236 // the Duration of the animation is forever.
237 if (GetNaturalDuration().HasTimeSpan()) {
238 TimeSpan natural_duration_timespan
= GetNaturalDuration().GetTimeSpan();
240 if (natural_duration_timespan
<= 0) {
241 // for clocks with instantaneous begin times/durations, expressable like so:
242 // <DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" BeginTime="00:00:00" Duration="00:00:00" />
243 // we keep our localtime pegged at 0 (FIXME:
244 // without filling?) and our normalizedTime at
245 // 1. The latter makes sure that value is applied in full.
247 normalizedTime
= 1.0;
248 if (GetClockState () == Clock::Active
) {
253 else if (natural_duration_timespan
> 0) {
254 RepeatBehavior
*repeat
= timeline
->GetRepeatBehavior ();
256 if (!repeat
->IsForever() && localTime
>= fillTime
) {
257 // fillTime represents the local time
258 // at which the number of repeats
259 // (expressed either as a timespan or
260 // e.g. "2x") and autoreverses have
261 // completed. i.e. it's the
262 // $natural_duration * $repeat_count
263 // for repeat behaviors with counts,
264 // and $repeat_duration for repeat
265 // behaviors with timespans.
267 // if the timeline is auto-reversible,
268 // we always end at localTime = 0.
269 // Otherwise we know it's fillTime.
270 localTime
= timeline
->GetAutoReverse () ? 0 : fillTime
;
271 normalizedTime
= (double)localTime
/ natural_duration_timespan
;
272 CLAMP_NORMALIZED_TIME
;
273 if (GetClockState () == Clock::Active
) {
279 if (GetClockState () != Clock::Active
)
280 SetClockState (Clock::Active
);
283 double t
= (double)localTime
/ natural_duration_timespan
;
285 double fract
= t
- ti
;
287 // This block of code is the first time where localTime is translated
288 // into per-repeat/per-autoreverse segments. We do it here because it
289 // allows us to use a cute hack for determining if we're ascending or
294 // <storyboard duration="00:00:12">
295 // <doubleanimation begintime="00:00:00" repeatbehavior="2x" autoreverse="<below>" duration="00:00:03" />
298 // autoreverse = true autoreverse = false
299 // 0 / 3 = 0 = 0 0 / 3 = 0 = 0
300 // 1 / 3 = .333 > 0.333 1 / 3 = .333 > 0.333
301 // 2 / 3 = .666 > 0.666 2 / 3 = .666 > 0.666
302 // 3 / 3 = 1 = 1 3 / 3 = 1 = 1
303 // 4 / 3 = 1.33 < 0.666 4 / 3 = 1.33 > 0.333
304 // 5 / 3 = 1.66 < 0.333 5 / 3 = 1.66 > 0.666
305 // 6 / 3 = 2 = 0 6 / 3 = 2 = 1
306 // 7 / 3 = 2.33 > 0.333
307 // 8 / 3 = 2.66 > 0.666
309 // 10 / 3 = 3.33 < 0.666
310 // 11 / 3 = 3.66 < 0.333
314 // a little explanation: the $localtime / $natural_duration = $foo is done
315 // to factor out the repeat count. we know that the time within a given repeated
316 // run is just the fractional part of that (if the result has a fraction), or 0 or 1.
318 // the >,<,= column above represents whether we're increasing, decreasing, or at an
319 // end-point, respectively.
321 if (timeline
->GetAutoReverse()) {
326 // 4 / 3 = 1.33 < 0.666
327 // 5 / 3 = 1.66 < 0.333
329 // we know we're either at normalized time 1 (at our duration), or we're descending,
330 // based on if there's a fractional component.
332 normalizedTime
= 1.0;
333 localTime
= natural_duration_timespan
;
336 /* we're descending */
337 normalizedTime
= 1.0 - fract
;
338 CLAMP_NORMALIZED_TIME
;
339 localTime
= normalizedTime
* natural_duration_timespan
;
345 // 7 / 3 = 2.33 > 0.333
346 // 8 / 3 = 2.66 > 0.666
348 // we know we're either at normalizd time 0 (at our start time), or we're ascending,
349 // based on if there's a fractional component.
351 normalizedTime
= 0.0;
355 /* we're ascending */
356 normalizedTime
= fract
;
357 CLAMP_NORMALIZED_TIME
;
358 localTime
= normalizedTime
* natural_duration_timespan
;
365 // 1 / 3 = .333 > 0.333
366 // 2 / 3 = .666 > 0.666
368 // 4 / 3 = 1.33 > 0.333
369 // 5 / 3 = 1.66 > 0.666
372 // we're always ascending here (since autoreverse is off), and we know we're > 0,
373 // so we don't need to concern ourselves with that case. At the integer points we're
374 // at our duration, and otherwise we're at the fractional value.
376 normalizedTime
= 1.0;
377 localTime
= natural_duration_timespan
;
380 /* we're ascending */
381 normalizedTime
= fract
;
382 CLAMP_NORMALIZED_TIME
;
383 localTime
= normalizedTime
* natural_duration_timespan
;
391 SetCurrentTime (localTime
);
392 progress
= normalizedTime
;
397 Clock::BeginOnTick (bool begin
)
399 begin_on_tick
= begin
;
401 // tell the time manager that we need another tick
402 time_manager
->NeedClockTick ();
406 Clock::SetClockState (ClockState state
)
409 const char *states
[] = { "Active", "Filling", "Stopped" };
410 printf ("Setting clock %p state to %s\n", this, states
[state
]);
413 QueueEvent (CURRENT_STATE_INVALIDATED
);
417 Clock::SetCurrentTime (TimeSpan ts
)
420 QueueEvent (CURRENT_TIME_INVALIDATED
);
424 Clock::QueueEvent (int event
)
426 queued_events
|= event
;
430 Clock::RaiseAccumulatedEvents ()
432 if ((queued_events
& CURRENT_TIME_INVALIDATED
) != 0) {
433 Emit (CurrentTimeInvalidatedEvent
);
436 if ((queued_events
& CURRENT_STATE_INVALIDATED
) != 0) {
437 if (state
!= Clock::Stopped
)
439 Emit (CurrentStateInvalidatedEvent
);
446 Clock::RaiseAccumulatedCompleted ()
448 if (emit_completed
) {
449 emit_completed
= false;
450 // printf ("clock %p (%s) completed\n", this, GetName ());
451 Emit (CompletedEvent
);
452 has_completed
= true;
457 Clock::SetRootParentTime (TimeSpan parentTime
)
459 root_parent_time
= parentTime
;
463 Clock::CalculateFillTime ()
465 if (GetNaturalDuration().HasTimeSpan()) {
466 RepeatBehavior
*repeat
= timeline
->GetRepeatBehavior ();
467 if (repeat
->HasDuration ()) {
468 fillTime
= (repeat
->GetDuration() * timeline
->GetSpeedRatio ());
470 else if (repeat
->HasCount ()) {
471 fillTime
= repeat
->GetCount() * GetNaturalDuration().GetTimeSpan() * (timeline
->GetAutoReverse() ? 2 : 1);
474 fillTime
= GetNaturalDuration().GetTimeSpan() * (timeline
->GetAutoReverse() ? 2 : 1);
480 Clock::Begin (TimeSpan parentTime
)
482 //printf ("clock %p (%s) begin\n", this, GetName ());
483 emit_completed
= false;
484 has_completed
= false;
487 /* we're starting. initialize our current_time field */
488 SetCurrentTime ((parentTime
- root_parent_time
- timeline
->GetBeginTime ()) * timeline
->GetSpeedRatio());
490 Duration d
= GetNaturalDuration ();
491 if (d
.HasTimeSpan ()) {
492 if (d
.GetTimeSpan() == 0) {
496 progress
= (double)current_time
/ d
.GetTimeSpan();
504 CalculateFillTime ();
506 SetClockState (Clock::Active
);
508 // force the time manager to tick the clock hierarchy to wake it up
509 time_manager
->NeedClockTick ();
515 //printf ("clock %p (%s) paused\n", this, GetName ());
521 begin_pause_time
= GetCurrentTime();
532 accumulated_pause_time
+= GetCurrentTime() - begin_pause_time
;
536 Clock::Seek (TimeSpan timespan
)
538 //printf ("clock %p (%s) seek to timespan %" G_GINT64_FORMAT "\n", this, GetName (), timespan);
540 seek_time
= timespan
;
546 Clock::SeekAlignedToLastTick (TimeSpan timespan
)
551 UpdateFromParentTime (parent_clock
->GetCurrentTime());
555 Clock::FillOnNextTick ()
557 switch (timeline
->GetFillBehavior()) {
558 case FillBehaviorHoldEnd
:
559 SetClockState (Clock::Filling
);
562 case FillBehaviorStop
:
571 // FIXME: this only works on clocks that have a duration
573 printf ("filling clock %p after this tick\n", this);
584 SetClockState (Clock::Stopped
);
591 // printf ("clock %p (%s) reset\n", this, GetName());
592 calculated_natural_duration
= false;
593 state
= Clock::Stopped
;
598 begin_on_tick
= false;
602 accumulated_pause_time
= 0;
605 emit_completed
= false;
606 has_completed
= false;
613 emit_completed
= true;
616 ClockGroup::ClockGroup (TimelineGroup
*timeline
, bool timemanager_clockgroup
)
619 SetObjectType (Type::CLOCKGROUP
);
621 this->timemanager_clockgroup
= timemanager_clockgroup
;
627 ClockGroup::AddChild (Clock
*clock
)
629 clock
->SetRootParentTime (GetCurrentTime ());
630 clock
->SetParentClock (this);
632 child_clocks
= g_list_append (child_clocks
, clock
);
634 clock
->SetTimeManager (GetTimeManager());
638 ClockGroup::SetTimeManager (TimeManager
*manager
)
640 Clock::SetTimeManager (manager
);
641 for (GList
*l
= child_clocks
; l
; l
= l
->next
) {
642 Clock
*c
= (Clock
*)l
->data
;
643 c
->SetTimeManager (manager
);
648 ClockGroup::RemoveChild (Clock
*clock
)
650 child_clocks
= g_list_remove (child_clocks
, clock
);
651 clock
->SetTimeManager (NULL
);
652 clock
->SetParentClock (NULL
);
657 ClockGroup::Begin (TimeSpan parentTime
)
659 Clock::Begin (parentTime
);
661 for (GList
*l
= child_clocks
; l
; l
= l
->next
) {
662 Clock
*c
= (Clock
*)l
->data
;
663 c
->ClearHasStarted ();
665 /* start any clocks that need starting immediately */
666 if (c
->GetTimeline()->GetBeginTime() <= current_time
) {
667 c
->Begin (current_time
);
673 ClockGroup::SkipToFill ()
675 if (child_clocks
== NULL
)
678 Clock::SkipToFill ();
684 for (GList
*l
= child_clocks
; l
; l
= l
->next
) {
685 Clock
*clock
= (Clock
*)l
->data
;
687 if (timemanager_clockgroup
|| !clock
->Is(Type::CLOCKGROUP
)) {
688 // we don't stop sub-clock groups, since if we
689 // nest storyboards under one another they
690 // seem to behave independent of each other
691 // from this perspective.
692 ((Clock
*)l
->data
)->Stop ();
700 ClockGroup::UpdateFromParentTime (TimeSpan parentTime
)
702 // we need to cache this here because
703 // Clock::UpdateFromParentTime will be updating it for the
705 ClockState current_state
= GetClockState();
707 /* likewise, we need to cache this here since
708 Clock::UpdateFromParentTime will clear it */
709 bool seeking
= GetIsSeeking();
711 bool rv
= Clock::UpdateFromParentTime (parentTime
);
713 // ClockGroups (which correspond to storyboards generally)
714 // only cause their children to update (and therefore for
715 // animations to hold/progress their value) if they are
716 // active, or if they've had Seek called on them.
718 // but it also happens when the clockgroup is in the Filling
719 // state. This means that you can attach a handler to
720 // Storyboard.Completed and inside the handler modify a
721 // property that an animation under that storyboard was
722 // targetting. and the new setting isnt clobbered by the
723 // animation like it would be if the storyboard was active.
725 bool update_child_clocks
= (current_state
== Clock::Active
|| seeking
);
727 for (GList
*l
= child_clocks
; l
; l
= l
->next
) {
728 Clock
*clock
= (Clock
*)l
->data
;
729 if (update_child_clocks
|| clock
->Is(Type::CLOCKGROUP
))
730 rv
= clock
->UpdateFromParentTime (current_time
) || rv
;
737 ClockGroup::RaiseAccumulatedEvents ()
739 /* raise our events */
740 Clock::RaiseAccumulatedEvents ();
742 /* now cause our children to raise theirs */
743 clock_list_foreach (child_clocks
, CallRaiseAccumulatedEvents
);
747 ClockGroup::RaiseAccumulatedCompleted ()
749 Clock::RaiseAccumulatedCompleted ();
750 clock_list_foreach (child_clocks
, CallRaiseAccumulatedCompleted
);
758 for (GList
*l
= child_clocks
; l
; l
= l
->next
)
759 ((Clock
*)l
->data
)->Reset();
762 ClockGroup::~ClockGroup ()
767 ClockGroup::Dispose ()
769 GList
*node
= child_clocks
;
771 Clock
*clock
= (Clock
*)node
->data
;