/// <summary> /// Starts the specified <paramref name="board"/> in the context of the specified /// <paramref name="element"/>. /// </summary> /// <remarks> /// Depending on the parameter <paramref name="handoffBehavior"/>, the new storyboard will /// be started when the last other storyboard, which occupies a conflicting property, /// has finished. /// </remarks> /// <param name="board">The storyboard to start.</param> /// <param name="element">Context element which will be used as /// <see cref="TimelineContext.VisualParent"/> for the new <paramref name="board"/>.</param> /// <param name="handoffBehavior">Controls how the new storyboard animation will be /// attached to already running animations, if there are conflicting properties animated /// by an already running anmiation an by the new <paramref name="board"/>.</param> public void StartStoryboard(Storyboard board, UIElement element, HandoffBehavior handoffBehavior) { lock (_syncObject) { AnimationContext context = new AnimationContext { Timeline = board, TimelineContext = board.CreateTimelineContext(element) }; IDictionary <IDataDescriptor, object> conflictingProperties; ICollection <AnimationContext> conflictingAnimations; FindConflicts(context, out conflictingAnimations, out conflictingProperties); ExecuteHandoff(context, conflictingAnimations, handoffBehavior); try { board.Setup(context.TimelineContext, conflictingProperties); _scheduledAnimations.Add(context); board.Start(context.TimelineContext, SkinContext.SystemTickCount); } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Error("Animator: Error initializing StoryBoard", ex); } // No animation here - has to be done in the Animate method } }
protected void ResetAllValues(AnimationContext ac) { IDictionary <IDataDescriptor, object> animProperties = new Dictionary <IDataDescriptor, object>(); ac.Timeline.AddAllAnimatedProperties(ac.TimelineContext, animProperties); foreach (KeyValuePair <IDataDescriptor, object> animProperty in animProperties) { SetValue(animProperty.Key, animProperty.Value); } }
/// <summary> /// Stops the specified <paramref name="board"/> which runs in the context of the /// given <paramref name="element"/>. /// </summary> /// <param name="board">The storyboard to stop.</param> /// <param name="element">Context element on which the <paramref name="board"/> runs.</param> public void StopStoryboard(Storyboard board, UIElement element) { lock (_syncObject) { AnimationContext context = GetContext(board, element); if (context == null) { return; } ResetAllValues(context); _scheduledAnimations.Remove(context); } }
/// <summary> /// Checks the state of all wait dependencies for the specified animation /// <paramref name="context"/> and tidies up the wait hierarchy, if appropriate. /// </summary> /// <returns><c>true</c>, if the specified animation is ready to be animated, else <c>false</c>.</returns> protected bool IsWaiting(AnimationContext context) { // Tidy up wait dependencies if (context.WaitingFor.Count == 0) { return(false); } bool allEndedOrStopped = true; foreach (AnimationContext waitForAc in context.WaitingFor) { int index = _scheduledAnimations.IndexOf(waitForAc); AnimationContext ac; if (index != -1) { if ((ac = _scheduledAnimations[index]).Timeline.HasEnded(ac.TimelineContext)) { endedWaitForAnimations.Add(waitForAc); } else { allEndedOrStopped = false; break; } } } try { if (allEndedOrStopped) { // Stop all parent animations at once via the ExecuteHandoff method, when the last // one ended. This will preserve all animations with FillBehavior.HoldEnd until // the new animation starts. context.WaitingFor.Clear(); ExecuteHandoff(context, endedWaitForAnimations, HandoffBehavior.SnapshotAndReplace); return(false); } else { // Animation isn't ready yet. return(true); } } finally { endedWaitForAnimations.Clear(); } }
/// <summary> /// Will check the specified <paramref name="animationContext"/> for conflicts with already /// scheduled animations and returns those conflicts. /// </summary> /// <param name="animationContext">The new animation context to check against the running /// animations.</param> /// <param name="conflictingAnimations">Returns all already running or sleeping animations with /// conflicting properties.</param> /// <param name="conflictingProperties">Conflicting data descriptors mapped to their original /// values. This returned value can be used to initialize the original values of the new animation.</param> protected void FindConflicts( AnimationContext animationContext, out ICollection <AnimationContext> conflictingAnimations, out IDictionary <IDataDescriptor, object> conflictingProperties) { Timeline newTL = animationContext.Timeline; TimelineContext context = animationContext.TimelineContext; IDictionary <IDataDescriptor, object> newProperties = new Dictionary <IDataDescriptor, object>(); newTL.AddAllAnimatedProperties(context, newProperties); ICollection <IDataDescriptor> newPDs = newProperties.Keys; conflictingAnimations = new List <AnimationContext>(); conflictingProperties = new Dictionary <IDataDescriptor, object>(); lock (_syncObject) { // Find conflicting properties in the values to be set foreach (KeyValuePair <IDataDescriptor, object> property in new Dictionary <IDataDescriptor, object>(_valuesToSet)) { if (!newPDs.Contains(property.Key)) { continue; } conflictingProperties[property.Key] = property.Value; _valuesToSet.Remove(property.Key); } // Find conflicting animations and conflicting animated properties foreach (AnimationContext ac in _scheduledAnimations) { IDictionary <IDataDescriptor, object> animProperties = new Dictionary <IDataDescriptor, object>(); ac.Timeline.AddAllAnimatedProperties(ac.TimelineContext, animProperties); bool isConflict = false; foreach (KeyValuePair <IDataDescriptor, object> animProperty in animProperties) { if (!newPDs.Contains(animProperty.Key)) { continue; } isConflict = true; conflictingProperties[animProperty.Key] = animProperty.Value; } if (isConflict) { conflictingAnimations.Add(ac); } } } }
/// <summary> /// Handles the handoff between conflicting animations. /// This method will, depending on the specified <paramref name="handoffBehavior"/>, stop conflicting /// animations (in case see cref="HandoffBehavior.SnapshotAndReplace"/>) /// or add them to the wait set for the given <paramref name="animationContext"/> /// (in case <see cref="HandoffBehavior.Compose"/>). /// The handoff behavior <see cref="HandoffBehavior.TemporaryReplace"/> will stop the conflicting /// animations, let the new animation run, and re-schedule the conflicting animations after the new animation. /// </summary> protected void ExecuteHandoff(AnimationContext animationContext, ICollection <AnimationContext> conflictingAnimations, HandoffBehavior handoffBehavior) { // Do the handoff depending on HandoffBehavior switch (handoffBehavior) { case HandoffBehavior.Compose: foreach (AnimationContext ac in conflictingAnimations) { animationContext.WaitingFor.Add(ac); } break; case HandoffBehavior.TemporaryReplace: foreach (AnimationContext ac in conflictingAnimations) { ac.WaitingFor.Add(animationContext); } break; case HandoffBehavior.SnapshotAndReplace: // Reset values of conflicting animations foreach (AnimationContext ac in conflictingAnimations) { ResetAllValues(ac); } // And remove those values which are handled by the new animation - // avoids flickering IDictionary <IDataDescriptor, object> animProperties = new Dictionary <IDataDescriptor, object>(); animationContext.Timeline.AddAllAnimatedProperties(animationContext.TimelineContext, animProperties); foreach (IDataDescriptor dd in animProperties.Keys) { _valuesToSet.Remove(dd); } break; default: throw new NotImplementedException("Animator.HandleConflicts: HandoffBehavior '" + handoffBehavior + "' is not implemented"); } }
/// <summary> /// Starts the specified <paramref name="board"/> in the context of the specified /// <paramref name="element"/>. /// </summary> /// <remarks> /// Depending on the parameter <paramref name="handoffBehavior"/>, the new storyboard will /// be started when the last other storyboard, which occupies a conflicting property, /// has finished. /// </remarks> /// <param name="board">The storyboard to start.</param> /// <param name="element">Context element which will be used as /// <see cref="TimelineContext.VisualParent"/> for the new <paramref name="board"/>.</param> /// <param name="handoffBehavior">Controls how the new storyboard animation will be /// attached to already running animations, if there are conflicting properties animated /// by an already running anmiation an by the new <paramref name="board"/>.</param> public void StartStoryboard(Storyboard board, UIElement element, HandoffBehavior handoffBehavior) { lock (_syncObject) { AnimationContext context = new AnimationContext { Timeline = board, TimelineContext = board.CreateTimelineContext(element) }; IDictionary <IDataDescriptor, object> conflictingProperties; ICollection <AnimationContext> conflictingAnimations; FindConflicts(context, out conflictingAnimations, out conflictingProperties); ExecuteHandoff(context, conflictingAnimations, handoffBehavior); board.Setup(context.TimelineContext, conflictingProperties); _scheduledAnimations.Add(context); board.Start(context.TimelineContext, SkinContext.SystemTickCount); // No animation here - has to be done in the Animate method } }
/// <summary> /// Handles the handoff between conflicting animations. /// This method will, depending on the specified <paramref name="handoffBehavior"/>, stop conflicting /// animations (in case see cref="HandoffBehavior.SnapshotAndReplace"/>) /// or add them to the wait set for the given <paramref name="animationContext"/> /// (in case <see cref="HandoffBehavior.Compose"/>). /// The handoff behavior <see cref="HandoffBehavior.TemporaryReplace"/> will stop the conflicting /// animations, let the new animation run, and re-schedule the conflicting animations after the new animation. /// </summary> protected void ExecuteHandoff(AnimationContext animationContext, ICollection<AnimationContext> conflictingAnimations, HandoffBehavior handoffBehavior) { // Do the handoff depending on HandoffBehavior switch (handoffBehavior) { case HandoffBehavior.Compose: foreach (AnimationContext ac in conflictingAnimations) animationContext.WaitingFor.Add(ac); break; case HandoffBehavior.TemporaryReplace: foreach (AnimationContext ac in conflictingAnimations) ac.WaitingFor.Add(animationContext); break; case HandoffBehavior.SnapshotAndReplace: // Reset values of conflicting animations foreach (AnimationContext ac in conflictingAnimations) ResetAllValues(ac); // And remove those values which are handled by the new animation - // avoids flickering IDictionary<IDataDescriptor, object> animProperties = new Dictionary<IDataDescriptor, object>(); animationContext.Timeline.AddAllAnimatedProperties(animationContext.TimelineContext, animProperties); foreach (IDataDescriptor dd in animProperties.Keys) _valuesToSet.Remove(dd); break; default: throw new NotImplementedException("Animator.HandleConflicts: HandoffBehavior '" + handoffBehavior + "' is not implemented"); } }
/// <summary> /// Will check the specified <paramref name="animationContext"/> for conflicts with already /// scheduled animations and returns those conflicts. /// </summary> /// <param name="animationContext">The new animation context to check against the running /// animations.</param> /// <param name="conflictingAnimations">Returns all already running or sleeping animations with /// conflicting properties.</param> /// <param name="conflictingProperties">Conflicting data descriptors mapped to their original /// values. This returned value can be used to initialize the original values of the new animation.</param> protected void FindConflicts( AnimationContext animationContext, out ICollection<AnimationContext> conflictingAnimations, out IDictionary<IDataDescriptor, object> conflictingProperties) { Timeline newTL = animationContext.Timeline; TimelineContext context = animationContext.TimelineContext; IDictionary<IDataDescriptor, object> newProperties = new Dictionary<IDataDescriptor, object>(); newTL.AddAllAnimatedProperties(context, newProperties); ICollection<IDataDescriptor> newPDs = newProperties.Keys; conflictingAnimations = new List<AnimationContext>(); conflictingProperties = new Dictionary<IDataDescriptor, object>(); lock (_syncObject) { // Find conflicting properties in the values to be set foreach (KeyValuePair<IDataDescriptor, object> property in new Dictionary<IDataDescriptor, object>(_valuesToSet)) { if (!newPDs.Contains(property.Key)) continue; conflictingProperties[property.Key] = property.Value; _valuesToSet.Remove(property.Key); } // Find conflicting animations and conflicting animated properties foreach (AnimationContext ac in _scheduledAnimations) { IDictionary<IDataDescriptor, object> animProperties = new Dictionary<IDataDescriptor, object>(); ac.Timeline.AddAllAnimatedProperties(ac.TimelineContext, animProperties); bool isConflict = false; foreach (KeyValuePair<IDataDescriptor, object> animProperty in animProperties) { if (!newPDs.Contains(animProperty.Key)) continue; isConflict = true; conflictingProperties[animProperty.Key] = animProperty.Value; } if (isConflict) conflictingAnimations.Add(ac); } } }
/// <summary> /// Checks the state of all wait dependencies for the specified animation /// <paramref name="context"/> and tidies up the wait hierarchy, if appropriate. /// </summary> /// <returns><c>true</c>, if the specified animation is ready to be animated, else <c>false</c>.</returns> protected bool IsWaiting(AnimationContext context) { // Tidy up wait dependencies if (context.WaitingFor.Count == 0) return false; bool allEndedOrStopped = true; foreach (AnimationContext waitForAc in context.WaitingFor) { int index = _scheduledAnimations.IndexOf(waitForAc); AnimationContext ac; if (index != -1) if ((ac = _scheduledAnimations[index]).Timeline.HasEnded(ac.TimelineContext)) endedWaitForAnimations.Add(waitForAc); else { allEndedOrStopped = false; break; } } try { if (allEndedOrStopped) { // Stop all parent animations at once via the ExecuteHandoff method, when the last // one ended. This will preserve all animations with FillBehavior.HoldEnd until // the new animation starts. context.WaitingFor.Clear(); ExecuteHandoff(context, endedWaitForAnimations, HandoffBehavior.SnapshotAndReplace); return false; } else // Animation isn't ready yet. return true; } finally { endedWaitForAnimations.Clear(); } }
protected void ResetAllValues(AnimationContext ac) { IDictionary<IDataDescriptor, object> animProperties = new Dictionary<IDataDescriptor, object>(); ac.Timeline.AddAllAnimatedProperties(ac.TimelineContext, animProperties); foreach (KeyValuePair<IDataDescriptor, object> animProperty in animProperties) SetValue(animProperty.Key, animProperty.Value); }
/// <summary> /// Starts the specified <paramref name="board"/> in the context of the specified /// <paramref name="element"/>. /// </summary> /// <remarks> /// Depending on the parameter <paramref name="handoffBehavior"/>, the new storyboard will /// be started when the last other storyboard, which occupies a conflicting property, /// has finished. /// </remarks> /// <param name="board">The storyboard to start.</param> /// <param name="element">Context element which will be used as /// <see cref="TimelineContext.VisualParent"/> for the new <paramref name="board"/>.</param> /// <param name="handoffBehavior">Controls how the new storyboard animation will be /// attached to already running animations, if there are conflicting properties animated /// by an already running anmiation an by the new <paramref name="board"/>.</param> public void StartStoryboard(Storyboard board, UIElement element, HandoffBehavior handoffBehavior) { lock (_syncObject) { AnimationContext context = new AnimationContext { Timeline = board, TimelineContext = board.CreateTimelineContext(element) }; IDictionary<IDataDescriptor, object> conflictingProperties; ICollection<AnimationContext> conflictingAnimations; FindConflicts(context, out conflictingAnimations, out conflictingProperties); ExecuteHandoff(context, conflictingAnimations, handoffBehavior); try { board.Setup(context.TimelineContext, conflictingProperties); _scheduledAnimations.Add(context); board.Start(context.TimelineContext, SkinContext.SystemTickCount); } catch (Exception ex) { ServiceRegistration.Get<ILogger>().Error("Animator: Error initializing StoryBoard", ex); } // No animation here - has to be done in the Animate method } }
/// <summary> /// Starts the specified <paramref name="board"/> in the context of the specified /// <paramref name="element"/>. /// </summary> /// <remarks> /// Depending on the parameter <paramref name="handoffBehavior"/>, the new storyboard will /// be started when the last other storyboard, which occupies a conflicting property, /// has finished. /// </remarks> /// <param name="board">The storyboard to start.</param> /// <param name="element">Context element which will be used as /// <see cref="TimelineContext.VisualParent"/> for the new <paramref name="board"/>.</param> /// <param name="handoffBehavior">Controls how the new storyboard animation will be /// attached to already running animations, if there are conflicting properties animated /// by an already running anmiation an by the new <paramref name="board"/>.</param> public void StartStoryboard(Storyboard board, UIElement element, HandoffBehavior handoffBehavior) { lock (_syncObject) { AnimationContext context = new AnimationContext { Timeline = board, TimelineContext = board.CreateTimelineContext(element) }; IDictionary<IDataDescriptor, object> conflictingProperties; ICollection<AnimationContext> conflictingAnimations; FindConflicts(context, out conflictingAnimations, out conflictingProperties); ExecuteHandoff(context, conflictingAnimations, handoffBehavior); board.Setup(context.TimelineContext, conflictingProperties); _scheduledAnimations.Add(context); board.Start(context.TimelineContext, SkinContext.SystemTickCount); // No animation here - has to be done in the Animate method } }