file360

Log | Files | Refs

MetroInMotion.cs (17077B)


      1 using System;
      2 using System.Net;
      3 using System.Linq;
      4 using System.Windows;
      5 using System.Windows.Controls;
      6 using System.Windows.Documents;
      7 using System.Windows.Ink;
      8 using System.Windows.Input;
      9 using System.Windows.Media;
     10 using System.Windows.Media.Animation;
     11 using System.Windows.Shapes;
     12 using Microsoft.Phone.Controls;
     13 using LinqToVisualTree;
     14 using System.Diagnostics;
     15 using System.Collections.Generic;
     16 using System.Windows.Controls.Primitives;
     17 using System.Windows.Media.Imaging;
     18 
     19 namespace MetroInMotionUtils
     20 {
     21   public static class MetroInMotion
     22   {
     23     #region AnimationLevel
     24 
     25     public static int GetAnimationLevel(DependencyObject obj)
     26     {
     27       return (int)obj.GetValue(AnimationLevelProperty);
     28     }
     29 
     30     public static void SetAnimationLevel(DependencyObject obj, int value)
     31     {
     32       obj.SetValue(AnimationLevelProperty, value);
     33     }
     34 
     35 
     36     public static readonly DependencyProperty AnimationLevelProperty =
     37         DependencyProperty.RegisterAttached("AnimationLevel", typeof(int),
     38         typeof(MetroInMotion), new PropertyMetadata(-1));
     39 
     40     #endregion
     41 
     42     #region Tilt
     43 
     44     public static double GetTilt(DependencyObject obj)
     45     {
     46       return (double)obj.GetValue(TiltProperty);
     47     }
     48 
     49     public static void SetTilt(DependencyObject obj, double value)
     50     {
     51       obj.SetValue(TiltProperty, value);
     52     }
     53 
     54 
     55     public static readonly DependencyProperty TiltProperty =
     56         DependencyProperty.RegisterAttached("Tilt", typeof(double),
     57         typeof(MetroInMotion), new PropertyMetadata(2.0, OnTiltChanged));
     58 
     59     /// <summary>
     60     /// The extent of the tilt action, the larger the number, the bigger the tilt
     61     /// </summary>
     62     private static double TiltAngleFactor = 4;
     63 
     64     /// <summary>
     65     /// The extent of the scaling action, the smaller the number, the greater the scaling.
     66     /// </summary>
     67     private static double ScaleFactor = 100;
     68 
     69     private static void OnTiltChanged(DependencyObject d,
     70       DependencyPropertyChangedEventArgs args)
     71     {
     72       FrameworkElement targetElement = d as FrameworkElement;
     73       
     74       double tiltFactor = GetTilt(d);
     75 
     76       // create the required transformations
     77       var projection = new PlaneProjection();
     78       var scale = new ScaleTransform();
     79       var translate = new TranslateTransform();
     80 
     81       var transGroup = new TransformGroup();
     82       transGroup.Children.Add(scale);
     83       transGroup.Children.Add(translate);
     84 
     85       // associate with the target element
     86       targetElement.Projection = projection;
     87       targetElement.RenderTransform = transGroup;
     88       targetElement.RenderTransformOrigin = new Point(0.5, 0.5);
     89 
     90       targetElement.MouseLeftButtonDown += (s, e) =>
     91         {
     92           var clickPosition = e.GetPosition(targetElement);
     93 
     94           // find the maximum of width / height
     95           double maxDimension = Math.Max(targetElement.ActualWidth, targetElement.ActualHeight);
     96 
     97           // compute the normalised horizontal distance from the centre
     98           double distanceFromCenterX = targetElement.ActualWidth / 2 - clickPosition.X;
     99           double normalisedDistanceX = 2 * distanceFromCenterX / maxDimension; 
    100 
    101           // rotate around the Y axis 
    102           projection.RotationY = normalisedDistanceX * TiltAngleFactor * tiltFactor;
    103 
    104           // compute the normalised vertical distance from the centre
    105           double distanceFromCenterY = targetElement.ActualHeight / 2 - clickPosition.Y;
    106           double normalisedDistanceY = 2 * distanceFromCenterY / maxDimension;
    107 
    108           // rotate around the X axis, 
    109           projection.RotationX = -normalisedDistanceY * TiltAngleFactor * tiltFactor;
    110 
    111           // find the distance to centre
    112           double distanceToCentre = Math.Sqrt(normalisedDistanceX * normalisedDistanceX
    113             + normalisedDistanceY * normalisedDistanceY);
    114 
    115           // scale accordingly
    116           double scaleVal = tiltFactor * (1 - distanceToCentre) / ScaleFactor;
    117           scale.ScaleX = 1 - scaleVal;
    118           scale.ScaleY = 1 - scaleVal;
    119 
    120           // offset the plane transform
    121           var rootElement = Application.Current.RootVisual as FrameworkElement;
    122           var relativeToCentre = (targetElement.GetRelativePosition(rootElement).Y - rootElement.ActualHeight / 2) / 2;
    123           translate.Y = -relativeToCentre;
    124           projection.LocalOffsetY = +relativeToCentre;
    125 
    126         };
    127 
    128       targetElement.ManipulationCompleted += (s, e) =>
    129         {
    130           var sb = new Storyboard();
    131           sb.Children.Add(CreateAnimation(null, 0, 0.1, "RotationY", projection));
    132           sb.Children.Add(CreateAnimation(null, 0, 0.1, "RotationX", projection));
    133           sb.Children.Add(CreateAnimation(null, 1, 0.1, "ScaleX", scale));
    134           sb.Children.Add(CreateAnimation(null, 1, 0.1, "ScaleY", scale));
    135           sb.Begin();
    136 
    137           translate.Y = 0;
    138           projection.LocalOffsetY = 0;
    139         };
    140 
    141     }
    142 
    143 
    144     #endregion
    145 
    146     #region IsPivotAnimated
    147 
    148     public static bool GetIsPivotAnimated(DependencyObject obj)
    149     {
    150       return (bool)obj.GetValue(IsPivotAnimatedProperty);
    151     }
    152 
    153     public static void SetIsPivotAnimated(DependencyObject obj, bool value)
    154     {
    155       obj.SetValue(IsPivotAnimatedProperty, value);
    156     }
    157 
    158     public static readonly DependencyProperty IsPivotAnimatedProperty =
    159         DependencyProperty.RegisterAttached("IsPivotAnimated", typeof(bool),
    160         typeof(MetroInMotion), new PropertyMetadata(false, OnIsPivotAnimatedChanged));
    161 
    162     private static void OnIsPivotAnimatedChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
    163     {
    164       ItemsControl list = d as ItemsControl;
    165 
    166       list.Loaded += (s2, e2) =>
    167         {
    168           // locate the pivot control that this list is within
    169           Pivot pivot = list.Ancestors<Pivot>().Single() as Pivot;
    170 
    171           // and its index within the pivot
    172           int pivotIndex = pivot.Items.IndexOf(list.Ancestors<PivotItem>().Single());
    173 
    174           bool selectionChanged = false;
    175 
    176           pivot.SelectionChanged += (s3, e3) =>
    177             {
    178               selectionChanged = true;
    179             };
    180 
    181           // handle manipulation events which occur when the user
    182           // moves between pivot items
    183           pivot.ManipulationCompleted += (s, e) =>
    184             {
    185               if (!selectionChanged)
    186                 return;
    187 
    188               selectionChanged = false;
    189 
    190               if (pivotIndex != pivot.SelectedIndex)
    191                 return;
    192               
    193               // determine which direction this tab will be scrolling in from
    194               bool fromRight = e.TotalManipulation.Translation.X <= 0;
    195 
    196                             
    197               // iterate over each of the items in view
    198               var items = list.GetItemsInView().ToList();
    199               for (int index = 0; index < items.Count; index++ )
    200               {
    201                 var lbi = items[index];
    202 
    203                 list.Dispatcher.BeginInvoke(() =>
    204                 {
    205                   var animationTargets = lbi.Descendants()
    206                                          .Where(p => MetroInMotion.GetAnimationLevel(p) > -1);
    207                   foreach (FrameworkElement target in animationTargets)
    208                   {
    209                     // trigger the required animation
    210                     GetSlideAnimation(target, fromRight).Begin();
    211                   }
    212                 });
    213               };
    214              
    215             };
    216         };
    217     }
    218 
    219 
    220     #endregion
    221 
    222     /// <summary>
    223     /// Animates each element in order, creating a 'peel' effect. The supplied action
    224     /// is invoked when the animation ends.
    225     /// </summary>
    226     public static void Peel(this IEnumerable<FrameworkElement> elements, Action endAction)
    227     {
    228       var elementList = elements.ToList();
    229       var lastElement = elementList.Last();
    230 
    231       // iterate over all the elements, animating each of them
    232       double delay = 0;
    233       foreach (FrameworkElement element in elementList)
    234       {
    235         var sb = GetPeelAnimation(element, delay);
    236 
    237         // add a Completed event handler to the last element
    238         if (element.Equals(lastElement))
    239         {
    240           sb.Completed += (s, e) =>
    241             {
    242               endAction();
    243             };
    244         }
    245 
    246         sb.Begin();
    247         delay += 50;
    248       }
    249     }
    250 
    251 
    252     /// <summary>
    253     /// Enumerates all the items that are currently visible in am ItemsControl. This implementation assumes
    254     /// that a VirtualizingStackPanel is being used as the ItemsPanel.
    255     /// </summary>
    256     public static IEnumerable<FrameworkElement> GetItemsInView(this ItemsControl itemsControl)
    257     {
    258        // locate the stack panel that hosts the items
    259       VirtualizingStackPanel vsp = itemsControl.Descendants<VirtualizingStackPanel>().First() as VirtualizingStackPanel;
    260 
    261       // iterate over each of the items in view
    262       int firstVisibleItem = (int)vsp.VerticalOffset;
    263       int visibleItemCount = (int)vsp.ViewportHeight;
    264       for (int index = firstVisibleItem; index <= firstVisibleItem + visibleItemCount + 1; index++)
    265       {
    266         var item = itemsControl.ItemContainerGenerator.ContainerFromIndex(index) as FrameworkElement;
    267         if (item == null)
    268           continue;
    269 
    270         yield return item;
    271       }
    272     }
    273 
    274     /// <summary>
    275     /// Creates a PlaneProjection and associates it with the given element, returning
    276     /// a Storyboard which will animate the PlaneProjection to 'peel' the item
    277     /// from the screen.
    278     /// </summary>
    279     private static Storyboard GetPeelAnimation(FrameworkElement element, double delay)
    280     {
    281       Storyboard sb;
    282 
    283       var projection = new PlaneProjection()
    284       {
    285         CenterOfRotationX = -0.1
    286       };
    287       element.Projection = projection;
    288 
    289       // compute the angle of rotation required to make this element appear
    290       // at a 90 degree angle at the edge of the screen.
    291       var width = element.ActualWidth;
    292       var targetAngle = Math.Atan(1000 / (width / 2));
    293       targetAngle = targetAngle * 180 / Math.PI;
    294 
    295       // animate the projection
    296       sb = new Storyboard();
    297       sb.BeginTime = TimeSpan.FromMilliseconds(delay);      
    298       sb.Children.Add(CreateAnimation(0, -(180 - targetAngle), 0.3, "RotationY", projection));
    299       sb.Children.Add(CreateAnimation(0, 23, 0.3, "RotationZ", projection));
    300       sb.Children.Add(CreateAnimation(0, -23, 0.3, "GlobalOffsetZ", projection));      
    301       return sb;
    302     }
    303 
    304     private static DoubleAnimation CreateAnimation(double? from, double? to, double duration,
    305       string targetProperty, DependencyObject target)
    306     {
    307       var db = new DoubleAnimation();
    308       db.To = to;
    309       db.From = from;
    310       db.EasingFunction = new SineEase();
    311       db.Duration = TimeSpan.FromSeconds(duration);
    312       Storyboard.SetTarget(db, target);
    313       Storyboard.SetTargetProperty(db, new PropertyPath(targetProperty));
    314       return db;
    315     }
    316 
    317     /// <summary>
    318     /// Creates a TranslateTransform and associates it with the given element, returning
    319     /// a Storyboard which will animate the TranslateTransform with a SineEase function
    320     /// </summary>
    321     private static Storyboard  GetSlideAnimation(FrameworkElement element, bool fromRight)
    322     {
    323       double from = fromRight ? 80 : -80;
    324       
    325       Storyboard sb;
    326       double delay = (MetroInMotion.GetAnimationLevel(element)) * 0.1 + 0.1;
    327 
    328       TranslateTransform trans = new TranslateTransform() { X = from };
    329       element.RenderTransform = trans;
    330 
    331       sb = new Storyboard();
    332       sb.BeginTime = TimeSpan.FromSeconds(delay);
    333       sb.Children.Add(CreateAnimation(from, 0, 0.8, "X", trans));      
    334       return sb;
    335     }
    336 
    337   }
    338 
    339   public static class ExtensionMethods
    340   {
    341     public static Point GetRelativePosition(this UIElement element, UIElement other)
    342     {
    343       return element.TransformToVisual(other)
    344         .Transform(new Point(0, 0));
    345     }
    346   }
    347 
    348   /// <summary>
    349   /// Animates an element so that it flies out and flies in!
    350   /// </summary>
    351   public class ItemFlyInAndOutAnimations
    352   {
    353     private Popup _popup;
    354 
    355     private Canvas _popupCanvas;
    356 
    357     private FrameworkElement _targetElement;
    358 
    359     private Point _targetElementPosition;
    360 
    361     private Image _targetElementClone;
    362 
    363     private Rectangle _backgroundMask;
    364 
    365     private static TimeSpan _flyInSpeed = TimeSpan.FromMilliseconds(200);
    366 
    367     private static TimeSpan _flyOutSpeed = TimeSpan.FromMilliseconds(300);
    368 
    369     public ItemFlyInAndOutAnimations()
    370     {
    371       // construct a popup, with a Canvas as its child
    372       _popup = new Popup();
    373       _popupCanvas = new Canvas();
    374       _popup.Child = _popupCanvas;
    375     }
    376 
    377     public static void TitleFlyIn(FrameworkElement title)
    378     {
    379       TranslateTransform trans = new TranslateTransform();
    380       trans.X = 300;
    381       trans.Y = -50;
    382       title.RenderTransform = trans;      
    383 
    384       var sb = new Storyboard();
    385 
    386       // animate the X position
    387       var db = CreateDoubleAnimation(300, 0,
    388           new SineEase(), trans, TranslateTransform.XProperty, _flyInSpeed);
    389       sb.Children.Add(db);
    390 
    391       // animate the Y position
    392       db = CreateDoubleAnimation(-100, 0,
    393           new SineEase(), trans, TranslateTransform.YProperty, _flyInSpeed);
    394       sb.Children.Add(db);
    395 
    396       sb.Begin();
    397     }
    398 
    399     /// <summary>
    400     /// Animate the previously 'flown-out' element back to its original location.
    401     /// </summary>
    402     public void ItemFlyIn()
    403     {
    404       if (_popupCanvas.Children.Count != 2)
    405         return;
    406 
    407       _popup.IsOpen = true;
    408       _backgroundMask.Opacity = 0.0;
    409 
    410       Image animatedImage = _popupCanvas.Children[1] as Image;
    411 
    412       var sb = new Storyboard();
    413 
    414       // animate the X position
    415       var db = CreateDoubleAnimation(_targetElementPosition.X - 100, _targetElementPosition.X,
    416           new SineEase(),
    417           _targetElementClone, Canvas.LeftProperty, _flyInSpeed);
    418       sb.Children.Add(db);
    419 
    420       // animate the Y position
    421       db = CreateDoubleAnimation(_targetElementPosition.Y - 50, _targetElementPosition.Y,
    422           new SineEase(),
    423           _targetElementClone, Canvas.TopProperty, _flyInSpeed);
    424       sb.Children.Add(db);
    425 
    426       sb.Completed += (s, e) =>
    427         {
    428           // when the animation has finished, hide the popup once more
    429           _popup.IsOpen = false;
    430 
    431           // restore the element we have animated
    432           _targetElement.Opacity = 1.0;
    433 
    434           // and get rid of our clone
    435           _popupCanvas.Children.Clear();
    436         };
    437 
    438       sb.Begin();
    439     }
    440 
    441 
    442     /// <summary>
    443     /// Animate the given element so that it flies off screen, fading 
    444     /// everything else that is on screen.
    445     /// </summary>
    446     public void ItemFlyOut(FrameworkElement element, Action action)
    447     {
    448       _targetElement = element;
    449       var rootElement = Application.Current.RootVisual as FrameworkElement;
    450 
    451       _backgroundMask = new Rectangle()
    452       {
    453         Fill = new SolidColorBrush(Colors.Black),
    454         Opacity = 0.0,
    455         Width = rootElement.ActualWidth,
    456         Height = rootElement.ActualHeight
    457       };
    458       _popupCanvas.Children.Add(_backgroundMask);
    459 
    460       _targetElementClone = new Image()
    461       {
    462         Source = new WriteableBitmap(element, null)
    463       };
    464       _popupCanvas.Children.Add(_targetElementClone);
    465 
    466       _targetElementPosition = element.GetRelativePosition(rootElement);
    467       Canvas.SetTop(_targetElementClone, _targetElementPosition.Y);
    468       Canvas.SetLeft(_targetElementClone, _targetElementPosition.X);
    469 
    470       var sb = new Storyboard();
    471 
    472       // animate the X position
    473       var db = CreateDoubleAnimation(_targetElementPosition.X, _targetElementPosition.X + 500,
    474           new SineEase() { EasingMode = EasingMode.EaseIn },
    475           _targetElementClone, Canvas.LeftProperty, _flyOutSpeed);
    476       sb.Children.Add(db);
    477 
    478       // animate the Y position
    479       db = CreateDoubleAnimation(_targetElementPosition.Y, _targetElementPosition.Y + 50,
    480           new SineEase() { EasingMode = EasingMode.EaseOut },
    481           _targetElementClone, Canvas.TopProperty, _flyOutSpeed);
    482       sb.Children.Add(db);     
    483       
    484       // fade out the other elements
    485       db = CreateDoubleAnimation(0, 1,
    486           null, _backgroundMask, UIElement.OpacityProperty, _flyOutSpeed);
    487       sb.Children.Add(db);
    488 
    489       sb.Completed += (s, e2) =>
    490         {
    491           action();
    492 
    493           // hide the popup, by placing a task on the dispatcher queue, this
    494           // should be executed after the navigation has occurred
    495           element.Dispatcher.BeginInvoke(() =>
    496           {
    497             _popup.IsOpen = false;
    498           });
    499         };
    500 
    501       // hide the element we have 'cloned' into the popup
    502       element.Opacity = 0.0;
    503 
    504       // open the popup
    505       _popup.IsOpen = true;
    506 
    507       // begin the animation
    508       sb.Begin();
    509     }
    510 
    511     public static DoubleAnimation CreateDoubleAnimation(double from, double to, IEasingFunction easing,
    512       DependencyObject target, object propertyPath, TimeSpan duration)
    513     {
    514       var db = new DoubleAnimation();
    515       db.To = to;
    516       db.From = from;
    517       db.EasingFunction = easing;
    518       db.Duration = duration;
    519       Storyboard.SetTarget(db, target);
    520       Storyboard.SetTargetProperty(db, new PropertyPath(propertyPath));
    521       return db;
    522     }
    523   }
    524 }