/// <summary> /// Perform animation effects on the set of children. /// </summary> /// <param name="animateId">Identifier of the animate to be used.</param> /// <param name="metaPanel">Reference to owning panel instance.</param> /// <param name="stateDict">Dictionary of per-element state.</param> /// <param name="elements">Collection of elements to be animated.</param> /// <param name="elapsedMilliseconds">Elapsed milliseconds since last animation cycle.</param> public override void ApplyAnimation(string animateId, MetaPanelBase metaPanel, MetaElementStateDict stateDict, ICollection elements, double elapsedMilliseconds) { // Only apply if we match the incoming animate identifier if (string.IsNullOrEmpty(Id) || Id.Equals(animateId)) { foreach (UIElement element in elements) { MetaElementState elementState = stateDict[element]; // Only interested in elements being removed... if (elementState.Status == MetaElementStatus.Removing) { // ...and having the final target rectangle calculated just the once... if (!elementState.RemoveCalculated) { // Calculate the correct target rectangle Rect positionRect = RectFromSize(Size, RectFromLocation(Location, metaPanel, elementState), elementState); // Update the final target value elementState.TargetRect = positionRect; elementState.TargetChanged = true; elementState.AnimateComplete = false; } } } } // Let base class take care of easing animations base.ApplyAnimation(animateId, metaPanel, stateDict, elements, elapsedMilliseconds); }
/// <summary> /// Perform animation effects on the set of children. /// </summary> /// <param name="animateId">Identifier of the animate to be used.</param> /// <param name="metaPanel">Reference to owning panel instance.</param> /// <param name="stateDict">Dictionary of per-element state.</param> /// <param name="elements">Collection of elements to be animated.</param> /// <param name="elapsedMilliseconds">Elapsed milliseconds since last animation cycle.</param> public override void ApplyAnimation(string animateId, MetaPanelBase metaPanel, MetaElementStateDict stateDict, ICollection elements, double elapsedMilliseconds) { // Only apply if we match the incoming animate identifier if (string.IsNullOrEmpty(Id) || Id.Equals(animateId)) { foreach (UIElement element in elements) { MetaElementState elementState = stateDict[element]; // If this is a new element that has not had its current rectangle set if ((elementState.Status == MetaElementStatus.New) && elementState.NewCalculating) { elementState.CurrentRect = RectFromSize(Size, RectFromLocation(Location, metaPanel, elementState), elementState); elementState.TargetChanged = true; elementState.AnimateComplete = false; } } } // Let base class take care of easing animations base.ApplyAnimation(animateId, metaPanel, stateDict, elements, elapsedMilliseconds); }
/// <summary> /// Perform animation effects on the set of children. /// </summary> /// <param name="animateId">Identifier of the animate to be used.</param> /// <param name="metaPanel">Reference to owning panel instance.</param> /// <param name="stateDict">Dictionary of per-element state.</param> /// <param name="elements">Collection of elements to be animated.</param> /// <param name="elapsedMilliseconds">Elapsed milliseconds since last animation cycle.</param> /// <param name="startOpacity">Opacity for start of animation.</param> /// <param name="endOpacity">Opacity for end of animation.</param> public void ApplyAnimation(string animateId, MetaPanelBase metaPanel, MetaElementStateDict stateDict, ICollection elements, double elapsedMilliseconds, double startOpacity, double endOpacity) { // Only apply if we match the incoming animate identifier if (string.IsNullOrEmpty(Id) || Id.Equals(animateId)) { // Only grab the dependancy properties once, for improved perf double duration = Duration; foreach (UIElement element in elements) { // Only apply animation to element of required target state MetaElementState elementState = stateDict[element]; if (elementState.Status == Target) { // If the element is the correct target and correct status for starting animation if ((!elementState.RemoveCalculated && (Target == MetaElementStatus.Removing)) || (elementState.NewCalculating && (Target == MetaElementStatus.New))) { // Use the starting opacity element.Opacity = startOpacity; elementState.ElapsedOpacityTime = 0; } else { // Only perform animation if some time has actually ellapsed and not already at target if ((elapsedMilliseconds > 0) && (element.Opacity != (double)endOpacity)) { // Add new elapsed time to the animation running time elementState.ElapsedOpacityTime += elapsedMilliseconds; // Does elapsed time indicate animation should have completed? if (elementState.ElapsedOpacityTime >= duration) { element.Opacity = endOpacity; } else { element.Opacity = EasingCalculation.Calculate(elementState.ElapsedOpacityTime, startOpacity, endOpacity - startOpacity, duration); } } } // If not yet at target opacity then not finished with animating elementState.AnimateComplete &= (element.Opacity == (double)endOpacity); } } } }
private void OnUIElementsRemove(object sender, UIElementsEventArgs e) { foreach (UIElement element in e.Elements) { MetaElementState elementState = _stateDict[element]; elementState.Status = MetaElementStatus.Removing; elementState.TargetChanged = true; } // If we are animating the removal of an element then it might not cause a // measure to occur because we do not actually remove it from the visual // collection. So force measure here so animation will be started. InvalidateMeasure(); }
/// <summary> /// Calculate appropriate rectangle from given current state and target size. /// </summary> /// <param name="size">Size enumeration.</param> /// <param name="rect">Rectangle to modify.</param> /// <param name="elementState">Animation state of element.</param> /// <returns>Calculated rectangle using provided size.</returns> protected Rect RectFromSize(AnimateSize size, Rect rect, MetaElementState elementState) { Size minSize = elementState.Element.DesiredSize; switch (size) { case AnimateSize.Original: return(rect); case AnimateSize.ZeroWidthLeft: return(new Rect(rect.X, rect.Y, minSize.Width, rect.Height)); case AnimateSize.ZeroWidthCenter: return(new Rect(rect.X + ((rect.Width - minSize.Width) / 2), rect.Y, minSize.Width, rect.Height)); case AnimateSize.ZeroWidthRight: return(new Rect(rect.Right - minSize.Width, rect.Y, minSize.Width, rect.Height)); case AnimateSize.ZeroHeightTop: return(new Rect(rect.X, rect.Y, rect.Width, minSize.Height)); case AnimateSize.ZeroHeightCenter: return(new Rect(rect.X, rect.Y + (rect.Height - minSize.Height) / 2, rect.Width, minSize.Height)); case AnimateSize.ZeroHeightBottom: return(new Rect(rect.X, rect.Bottom - minSize.Height, rect.Width, minSize.Height)); case AnimateSize.ZeroZeroCenter: return(new Rect(rect.X + (rect.Width - minSize.Width) / 2, rect.Y + (rect.Height - minSize.Height) / 2, minSize.Width, minSize.Height)); case AnimateSize.ZeroZeroTopLeft: return(new Rect(rect.X, rect.Y, minSize.Width, minSize.Height)); case AnimateSize.ZeroZeroTopRight: return(new Rect(rect.Right - minSize.Width, rect.Y, minSize.Width, minSize.Height)); case AnimateSize.ZeroZeroBottomLeft: return(new Rect(rect.X, rect.Bottom - minSize.Height, minSize.Width, minSize.Height)); case AnimateSize.ZeroZeroBottomRight: return(new Rect(rect.Right - minSize.Width, rect.Bottom - minSize.Height, minSize.Width, minSize.Height)); default: // Should never happen! Debug.Assert(false); return(new Rect()); } }
/// <summary> /// Position child elements according to already calculated target state. /// </summary> /// <param name="layoutId">Identifier of the layout to be used.</param> /// <param name="metaPanel">Reference to owning panel instance.</param> /// <param name="stateDict">Dictionary of per-element state.</param> /// <param name="elements">Collection of elements to be arranged.</param> /// <param name="finalSize">Size that layout should use to arrange child elements.</param> /// <returns>Size used by the layout panel.</returns> public Size ArrangeChildren(string layoutId, MetaPanelBase metaPanel, MetaElementStateDict stateDict, ICollection elements, Size finalSize) { foreach (UIElement element in elements) { MetaElementState elementState = stateDict[element]; elementState.Element.Arrange(elementState.CurrentRect); } // Layout panel takes all the provided size return(finalSize); }
/// <summary> /// Perform animation effects on the set of children. /// </summary> /// <param name="animateId">Identifier of the animate to be used.</param> /// <param name="metaPanel">Reference to owning panel instance.</param> /// <param name="stateDict">Dictionary of per-element state.</param> /// <param name="elements">Collection of elements to be animated.</param> /// <param name="elapsedMilliseconds">Elapsed milliseconds since last animation cycle.</param> public override void ApplyAnimation(string animateId, MetaPanelBase metaPanel, MetaElementStateDict stateDict, ICollection elements, double elapsedMilliseconds) { // Only apply if we match the incoming animate identifier if (string.IsNullOrEmpty(Id) || Id.Equals(animateId)) { foreach (UIElement element in elements) { // Immediately move to the target rectangle MetaElementState elementState = stateDict[element]; elementState.CurrentRect = elementState.TargetRect; } } }
/// <summary> /// Invoked when the VisualCollection of a visual object is modified. /// </summary> /// <param name="visualAdded">The Visual that was added to the collection.</param> /// <param name="visualRemoved">The Visual that was removed from the collection.</param> protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) { if (!_ignoreVisualChange) { // Let base class do its own stuff base.OnVisualChildrenChanged(visualAdded, visualRemoved); // When an items host we do not get the UIElementAdded/Removed events // for changing the element state dictionary. So we do it here instead. if (IsItemsHost) { if (visualAdded is UIElement) { UIElement elementAdded = (UIElement)visualAdded; _stateDict.Add(elementAdded, new MetaElementState(elementAdded)); } if (visualRemoved is UIElement) { UIElement elementRemoved = (UIElement)visualRemoved; MetaElementState elementState = _stateDict[elementRemoved]; // If item has finished its removal if (elementState.Status == MetaElementStatus.Removing) { // Base class already removed it as a visual/logical child so just remove dictionary entry _stateDict.Remove(elementRemoved); } else { // Item needs marking so it removal animates elementState.Status = MetaElementStatus.Removing; elementState.TargetChanged = true; // Prevent reentrancy from trying to process the element being added back again _ignoreVisualChange = true; // Add into the internal collection and add back as a visual child _children.InternalAdd(elementRemoved); _ignoreVisualChange = false; } } } } }
/// <summary> /// Perform remove animation for the child and then remove from the children. /// </summary> /// <param name="element">Element to be removed.</param> public void RemoveChild(UIElement element) { // Check the element is in the collection if (_stateDict.ContainsKey(element)) { // If the element is not already marked to be removed... MetaElementState elementState = _stateDict[element]; if (elementState.Status != MetaElementStatus.Removing) { // Mark element to be removed elementState.Status = MetaElementStatus.Removing; elementState.TargetChanged = true; // Need to measure to force remove animation InvalidateMeasure(); } } }
/// <summary> /// Position child elements according to already calculated target state. /// </summary> /// <param name="layoutId">Identifier of the layout to be used.</param> /// <param name="metaPanel">Reference to owning panel instance.</param> /// <param name="stateDict">Dictionary of per-element state.</param> /// <param name="elements">Collection of elements to be arranged.</param> /// <param name="finalSize">Size that layout should use to arrange child elements.</param> /// <returns>Size used by the layout panel.</returns> public virtual Size ArrangeChildren(string layoutId, MetaPanelBase metaPanel, MetaElementStateDict stateDict, ICollection elements, Size finalSize) { // Only apply if we match the incoming layout identifier if (string.IsNullOrEmpty(Id) || Id.Equals(layoutId)) { // Position each element foreach (UIElement element in elements) { MetaElementState elementState = stateDict[element]; elementState.Element.Arrange(elementState.CurrentRect); } } // Layout panel takes all the provided size return(finalSize); }
/// <summary> /// Perform animation effects on the set of children. /// </summary> /// <param name="animateId">Identifier of the animate to be used.</param> /// <param name="metaPanel">Reference to owning panel instance.</param> /// <param name="stateDict">Dictionary of per-element state.</param> /// <param name="elements">Collection of elements to be animated.</param> /// <param name="elapsedMilliseconds">Elapsed milliseconds since last animation cycle.</param> public override void ApplyAnimation(string animateId, MetaPanelBase metaPanel, MetaElementStateDict stateDict, ICollection elements, double elapsedMilliseconds) { // Only apply if we match the incoming animate identifier if (string.IsNullOrEmpty(Id) || Id.Equals(animateId)) { // Update the easing equation with latest value EasingCalculation.Easing = Easing; // Cache dependancy properties for faster perf double duration = Math.Max((double)1, Duration); foreach (UIElement element in elements) { // Only apply animation to element of required target state MetaElementState elementState = stateDict[element]; if (elementState.Status == Target) { Rect targetRect = elementState.TargetRect; Rect currentRect = (elementState.CurrentRect.IsEmpty ? targetRect : elementState.CurrentRect); // If start of animation.... if (elementState.TargetChanged) { // Cache starting information elementState.StartRect = currentRect; elementState.ElapsedBoundsTime = 0; } else { // Only perform animation if some time has actually ellapsed and not already at target if ((elapsedMilliseconds > 0) && !currentRect.Equals(targetRect)) { // Add new elapsed time to the animation running time elementState.ElapsedBoundsTime += elapsedMilliseconds; // Does elapsed time indicate animation should have completed? if (elementState.ElapsedBoundsTime >= duration) { currentRect = targetRect; } else { Rect startRect = elementState.StartRect; double elapsedTime = elementState.ElapsedBoundsTime; // Using animation easing to discover new target rectangle corners double left = EasingCalculation.Calculate(elapsedTime, startRect.X, targetRect.X - startRect.X, duration); double top = EasingCalculation.Calculate(elapsedTime, startRect.Y, targetRect.Y - startRect.Y, duration); double bottom = EasingCalculation.Calculate(elapsedTime, startRect.Bottom, targetRect.Bottom - startRect.Bottom, duration); double right = EasingCalculation.Calculate(elapsedTime, startRect.Right, targetRect.Right - startRect.Right, duration); // Normalize edges left/right edges if (left > right) { elapsedTime = left; left = right; right = elapsedTime; } // Normalize edges top/bottom edges if (top > bottom) { elapsedTime = top; top = bottom; bottom = elapsedTime; } currentRect = new Rect(left, top, right - left, bottom - top); } } } // Put back the updated rectangle and decide if more animation is needed elementState.CurrentRect = currentRect; elementState.AnimateComplete &= (currentRect.Equals(targetRect)); } } } }
/// <summary> /// Calculate appropriate rectangle from given current state and target location. /// </summary> /// <param name="location">Location enumeration.</param> /// <param name="metaPanel">Reference to owning panel instance.</param> /// <param name="elementState">Animation state of element.</param> /// <returns>Calculated rectangle using provided location.</returns> protected Rect RectFromLocation(AnimateLocation location, MetaPanelBase metaPanel, MetaElementState elementState) { // Nearest edge is converted into a particular edge switch (location) { case AnimateLocation.NearestEdge: case AnimateLocation.NearestEdgePaged: bool paged = (location == AnimateLocation.NearestEdgePaged); // Find distance from each edge double left = Math.Abs(elementState.TargetRect.Left); double top = Math.Abs(elementState.TargetRect.Top); double right = Math.Abs(metaPanel.ActualWidth - elementState.TargetRect.Right); double bottom = Math.Abs(metaPanel.ActualHeight - elementState.TargetRect.Bottom); // Find nearest distance for vertical and horizontal double horz = (left < right ? left : right); double vert = (top < bottom ? top : bottom); // Is horizontal nearest? if (horz <= vert) { // Is the left the nearest? if (horz == left) { location = (paged ? AnimateLocation.LeftPaged : AnimateLocation.Left); } else { location = (paged ? AnimateLocation.RightPaged : AnimateLocation.Right); } } else { // Is the top the nearest? if (vert == top) { location = (paged ? AnimateLocation.TopPaged : AnimateLocation.Top); } else { location = (paged ? AnimateLocation.BottomPaged : AnimateLocation.Bottom); } } break; } switch (location) { case AnimateLocation.Target: return(elementState.TargetRect); case AnimateLocation.Center: return(new Rect((metaPanel.ActualWidth / 2) - (elementState.TargetRect.Width - 2), (metaPanel.ActualHeight / 2) - (elementState.TargetRect.Height - 2), elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.Top: return(new Rect(elementState.TargetRect.X, -elementState.TargetRect.Height, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.TopPaged: return(new Rect(elementState.TargetRect.X, -metaPanel.ActualHeight + elementState.TargetRect.Y, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.Bottom: return(new Rect(elementState.TargetRect.X, metaPanel.ActualHeight, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.BottomPaged: return(new Rect(elementState.TargetRect.X, metaPanel.ActualHeight + elementState.TargetRect.Y, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.Left: return(new Rect(-elementState.TargetRect.Width, elementState.TargetRect.Y, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.LeftPaged: return(new Rect(-metaPanel.ActualWidth + elementState.TargetRect.X, elementState.TargetRect.Y, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.Right: return(new Rect(metaPanel.ActualWidth, elementState.TargetRect.Y, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.RightPaged: return(new Rect(metaPanel.ActualWidth + elementState.TargetRect.X, elementState.TargetRect.Y, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.TopLeft: return(new Rect(-elementState.TargetRect.Width, -elementState.TargetRect.Height, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.TopRight: return(new Rect(metaPanel.ActualWidth, -elementState.TargetRect.Height, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.BottomLeft: return(new Rect(-elementState.TargetRect.Width, metaPanel.ActualHeight, elementState.TargetRect.Width, elementState.TargetRect.Height)); case AnimateLocation.BottomRight: return(new Rect(metaPanel.ActualWidth, metaPanel.ActualHeight, elementState.TargetRect.Width, elementState.TargetRect.Height)); default: // Should never happen! Debug.Assert(false); return(new Rect()); } }