View difference between Paste ID: aDj3RwQk and VnWvfj5T
SHOW: | | - or go back to the newest paste.
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics.CodeAnalysis;
4
using System.Linq;
5
using System.Text;
6
using System.Threading.Tasks;
7
using Windows.Foundation;
8
using Windows.UI.Input;
9
using Windows.UI.Xaml;
10
using Windows.UI.Xaml.Controls;
11
using Windows.UI.Xaml.Controls.Primitives;
12
using Windows.UI.Xaml.Input;
13
using Windows.UI.Xaml.Media;
14
using Windows.UI.Xaml.Media.Animation;
15
16
namespace SharpGIS.Metro.Controls
17
{
18
	/// <summary>
19
	/// This code provides attached properties for adding a 'tilt' effect to all 
20
	/// controls within a container.
21
	/// </summary>
22
	/// <QualityBand>Preview</QualityBand>
23
	[SuppressMessage("Microsoft.Design", "CA1052:StaticHolderTypesShouldBeSealed", Justification = "Cannot be static and derive from DependencyObject.")]
24
	public partial class TiltEffect : DependencyObject
25
	{
26
		/// <summary>
27
		/// Cache of previous cache modes. Not using weak references for now.
28
		/// </summary>
29
		private static Dictionary<DependencyObject, CacheMode> _originalCacheMode = new Dictionary<DependencyObject, CacheMode>();
30
31
		/// <summary>
32
		/// Maximum amount of tilt, in radians.
33
		/// </summary>
34
		private const double MaxAngle = 0.3;
35
36
		/// <summary>
37
		/// Maximum amount of depression, in pixels
38
		/// </summary>
39
		private const double MaxDepression = 25;
40
41
		/// <summary>
42
		/// Delay between releasing an element and the tilt release animation 
43
		/// playing.
44
		/// </summary>
45
		private static readonly TimeSpan TiltReturnAnimationDelay = TimeSpan.FromMilliseconds(200);
46
47
		/// <summary>
48
		/// Duration of tilt release animation.
49
		/// </summary>
50
		private static readonly TimeSpan TiltReturnAnimationDuration = TimeSpan.FromMilliseconds(100);
51
52
		/// <summary>
53
		/// The control that is currently being tilted.
54
		/// </summary>
55
		private static FrameworkElement currentTiltElement;
56
57
		/// <summary>
58
		/// The single instance of a storyboard used for all tilts.
59
		/// </summary>
60
		private static Storyboard tiltReturnStoryboard;
61
62
		/// <summary>
63
		/// The single instance of an X rotation used for all tilts.
64
		/// </summary>
65
		private static DoubleAnimation tiltReturnXAnimation;
66
67
		/// <summary>
68
		/// The single instance of a Y rotation used for all tilts.
69
		/// </summary>
70
		private static DoubleAnimation tiltReturnYAnimation;
71
72
		/// <summary>
73
		/// The single instance of a Z depression used for all tilts.
74
		/// </summary>
75
		private static DoubleAnimation tiltReturnZAnimation;
76
77
		/// <summary>
78
		/// The center of the tilt element.
79
		/// </summary>
80
		private static Point currentTiltElementCenter;
81
82
		/// <summary>
83
		/// Whether the animation just completed was for a 'pause' or not.
84
		/// </summary>
85
		private static bool wasPauseAnimation = false;
86
87
		/// <summary>
88
		/// Default list of items that are tiltable.
89
		/// </summary>
90
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tiltable", Justification = "By design.")]
91
		[SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "Keeping it simple.")]
92
		public static List<Type> TiltableItems { get; private set; }
93
94
		#region Constructor and Static Constructor
95
		/// <summary>
96
		/// This is not a constructable class, but it cannot be static because 
97
		/// it derives from DependencyObject.
98
		/// </summary>
99
		private TiltEffect()
100
		{
101
		}
102
103
		/// <summary>
104
		/// Initialize the static properties
105
		/// </summary>
106
		[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Need to initialize the tiltable items property.")]
107
		static TiltEffect()
108
		{
109
			// The tiltable items list.
110
			TiltableItems = new List<Type>() 
111
            { 
112
                typeof(ButtonBase), 
113
                typeof(ListBoxItem), 
114
                //typeof(MenuItem),
115
            };
116
		}
117
118
		#endregion
119
120
		#region Dependency properties
121
122
		/// <summary>
123
		/// Whether the tilt effect is enabled on a container (and all its 
124
		/// children).
125
		/// </summary>
126
		public static readonly DependencyProperty IsTiltEnabledProperty = DependencyProperty.RegisterAttached(
127
		  "IsTiltEnabled",
128
		  typeof(bool),
129
		  typeof(TiltEffect),
130
		  new PropertyMetadata(false, OnIsTiltEnabledChanged)
131
		  );
132
133
		/// <summary>
134
		/// Gets the IsTiltEnabled dependency property from an object.
135
		/// </summary>
136
		/// <param name="source">The object to get the property from.</param>
137
		/// <returns>The property's value.</returns>
138
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
139
		public static bool GetIsTiltEnabled(DependencyObject source)
140
		{
141
			return (bool)source.GetValue(IsTiltEnabledProperty);
142
		}
143
144
		/// <summary>
145
		/// Sets the IsTiltEnabled dependency property on an object.
146
		/// </summary>
147
		/// <param name="source">The object to set the property on.</param>
148
		/// <param name="value">The value to set.</param>
149
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
150
		public static void SetIsTiltEnabled(DependencyObject source, bool value)
151
		{
152
			source.SetValue(IsTiltEnabledProperty, value);
153
		}
154
155
		/// <summary>
156
		/// Suppresses the tilt effect on a single control that would otherwise 
157
		/// be tilted.
158
		/// </summary>
159
		public static readonly DependencyProperty SuppressTiltProperty = DependencyProperty.RegisterAttached(
160
		  "SuppressTilt",
161
		  typeof(bool),
162
		  typeof(TiltEffect),
163
		  null
164
		  );
165
166
		/// <summary>
167
		/// Gets the SuppressTilt dependency property from an object.
168
		/// </summary>
169
		/// <param name="source">The object to get the property from.</param>
170
		/// <returns>The property's value.</returns>
171
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
172
		public static bool GetSuppressTilt(DependencyObject source)
173
		{
174
			return (bool)source.GetValue(SuppressTiltProperty);
175
		}
176
177
		/// <summary>
178
		/// Sets the SuppressTilt dependency property from an object.
179
		/// </summary>
180
		/// <param name="source">The object to get the property from.</param>
181
		/// <param name="value">The property's value.</param>
182
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
183
		public static void SetSuppressTilt(DependencyObject source, bool value)
184
		{
185
			source.SetValue(SuppressTiltProperty, value);
186
		}
187
188
		/// <summary>
189
		/// Property change handler for the IsTiltEnabled dependency property.
190
		/// </summary>
191
		/// <param name="target">The element that the property is atteched to.</param>
192
		/// <param name="args">Event arguments.</param>
193
		/// <remarks>
194
		/// Adds or removes event handlers from the element that has been 
195
		/// (un)registered for tilting.
196
		/// </remarks>
197
		static void OnIsTiltEnabledChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
198
		{
199
			FrameworkElement fe = target as FrameworkElement;
200
			if (fe != null)
201
			{
202
				// Add / remove the event handler if necessary
203
				if ((bool)args.NewValue == true)
204
				{
205
					fe.PointerPressed += TiltEffect_PointerPressed;
206
				}
207
				else
208
				{
209
					fe.PointerPressed -= TiltEffect_PointerPressed;
210
				}
211
			}
212
		}
213
214
215
		#endregion
216
217
		#region Top-level manipulation event handlers
218
219
		/// <summary>
220
		/// Event handler for ManipulationStarted.
221
		/// </summary>
222
		/// <param name="sender">sender of the event - this will be the tilt 
223
		/// container (eg, entire page).</param>
224
		/// <param name="e">Event arguments.</param>
225
		private static void TiltEffect_PointerPressed(object sender, PointerEventArgs e)
226
		{
227
			TryStartTiltEffect(sender as FrameworkElement, e);
228
		}
229
230
		/// <summary>
231
		/// Event handler for ManipulationDelta
232
		/// </summary>
233
		/// <param name="sender">sender of the event - this will be the tilting 
234
		/// object (eg a button).</param>
235
		/// <param name="e">Event arguments.</param>
236
		private static void TiltEffect_PointerMoved(object sender, PointerEventArgs e)
237
		{
238
			ContinueTiltEffect(sender as FrameworkElement, e);
239
		}
240
241
		/// <summary>
242
		/// Event handler for ManipulationCompleted.
243
		/// </summary>
244
		/// <param name="sender">sender of the event - this will be the tilting 
245
		/// object (eg a button).</param>
246
		/// <param name="e">Event arguments.</param>
247
		static void TiltEffect_PointerReleased(object sender, PointerEventArgs e)
248
		{
249
			EndTiltEffect(currentTiltElement);
250
		}
251
252
		#endregion
253
254
		#region Core tilt logic
255
256
		/// <summary>
257
		/// Checks if the manipulation should cause a tilt, and if so starts the 
258
		/// tilt effect.
259
		/// </summary>
260
		/// <param name="source">The source of the manipulation (the tilt 
261
		/// container, eg entire page).</param>
262
		/// <param name="e">The args from the ManipulationStarted event.</param>
263
		static void TryStartTiltEffect(FrameworkElement source, PointerEventArgs e)
264
		{
265
			FrameworkElement element = source; // VisualTreeHelper.GetChild(ancestor, 0) as FrameworkElement;
266
			FrameworkElement container = source;// e.Container as FrameworkElement;
267
268
			if (element == null || container == null)
269
				return;
270
271
			// Touch point relative to the element being tilted.
272
			Point tiltTouchPoint = e.GetCurrentPoint(element).Position; // container.TransformToVisual(element).TransformPoint(e.GetCurrentPoint(element));
273
274
			// Center of the element being tilted.
275
			Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
276
277
			// Camera adjustment.
278
			Point centerToCenterDelta = GetCenterToCenterDelta(element, source);
279
280-
			BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta);
280+
			BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta, e.Pointer);
281
			return;
282
283
284
		}
285
286
		/// <summary>
287
		/// Computes the delta between the centre of an element and its 
288
		/// container.
289
		/// </summary>
290
		/// <param name="element">The element to compare.</param>
291
		/// <param name="container">The element to compare against.</param>
292
		/// <returns>A point that represents the delta between the two centers.</returns>
293
		static Point GetCenterToCenterDelta(FrameworkElement element, FrameworkElement container)
294
		{
295
			Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
296
			Point containerCenter;
297
298
#if WINDOWS_PHONE
299
300
            // Need to special-case the frame to handle different orientations.
301
            PhoneApplicationFrame frame = container as PhoneApplicationFrame;
302
            if (frame != null)
303
            {
304
                // Switch width and height in landscape mode
305
                if ((frame.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
306
                {
307
                    containerCenter = new Point(container.ActualHeight / 2, container.ActualWidth / 2);
308
                }
309
                else
310
                {
311
                    containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
312
                }
313
            }
314
            else
315
            {
316
                containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
317
            }
318
#else
319
			containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
320
#endif
321
322
			Point transformedElementCenter = element.TransformToVisual(container).TransformPoint(elementCenter);
323
			Point result = new Point(containerCenter.X - transformedElementCenter.X, containerCenter.Y - transformedElementCenter.Y);
324
325
			return result;
326
		}
327
328
		/// <summary>
329
		/// Begins the tilt effect by preparing the control and doing the 
330
		/// initial animation.
331
		/// </summary>
332
		/// <param name="element">The element to tilt.</param>
333
		/// <param name="touchPoint">The touch point, in element coordinates.</param>
334
		/// <param name="centerPoint">The center point of the element in element
335
		/// coordinates.</param>
336
		/// <param name="centerDelta">The delta between the 
337
		/// <paramref name="element"/>'s center and the container's center.</param>
338-
		static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta)
338+
		static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta, Pointer p)
339
		{
340
			if (tiltReturnStoryboard != null)
341
			{
342
				StopTiltReturnStoryboardAndCleanup();
343
			}
344
345-
			if (PrepareControlForTilt(element, centerDelta) == false)
345+
			if (PrepareControlForTilt(element, centerDelta, p) == false)
346
			{
347
				return;
348
			}
349
350
			currentTiltElement = element;
351
			currentTiltElementCenter = centerPoint;
352
			PrepareTiltReturnStoryboard(element);
353
354
			ApplyTiltEffect(currentTiltElement, touchPoint, currentTiltElementCenter);
355
		}
356
357
		/// <summary>
358
		/// Prepares a control to be tilted by setting up a plane projection and
359
		/// some event handlers.
360
		/// </summary>
361
		/// <param name="element">The control that is to be tilted.</param>
362
		/// <param name="centerDelta">Delta between the element's center and the
363
		/// tilt container's.</param>
364
		/// <returns>true if successful; false otherwise.</returns>
365
		/// <remarks>
366
		/// This method is conservative; it will fail any attempt to tilt a 
367
		/// control that already has a projection on it.
368
		/// </remarks>
369-
		static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta)
369+
		static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta, Pointer p)
370
		{
371
			// Prevents interference with any existing transforms
372
			if (element.Projection != null || (element.RenderTransform != null && element.RenderTransform.GetType() != typeof(MatrixTransform)))
373
			{
374
				return false;
375
			}
376
377
			_originalCacheMode[element] = element.CacheMode;
378
			element.CacheMode = new BitmapCache();
379
380
			TranslateTransform transform = new TranslateTransform();
381
			transform.X = centerDelta.X;
382
			transform.Y = centerDelta.Y;
383
			element.RenderTransform = transform;
384
385
			PlaneProjection projection = new PlaneProjection();
386
			projection.GlobalOffsetX = -1 * centerDelta.X;
387
			projection.GlobalOffsetY = -1 * centerDelta.Y;
388
			element.Projection = projection;
389
390
			element.PointerMoved += TiltEffect_PointerMoved;
391
			element.PointerReleased += TiltEffect_PointerReleased;
392
			element.CapturePointer(p);
393
			return true;
394
		}
395
396
		/// <summary>
397
		/// Removes modifications made by PrepareControlForTilt.
398
		/// </summary>
399
		/// <param name="element">THe control to be un-prepared.</param>
400
		/// <remarks>
401
		/// This method is basic; it does not do anything to detect if the 
402
		/// control being un-prepared was previously prepared.
403
		/// </remarks>
404
		private static void RevertPrepareControlForTilt(FrameworkElement element)
405
		{
406
			element.PointerMoved -= TiltEffect_PointerMoved;
407
			element.PointerReleased -= TiltEffect_PointerReleased;
408
			element.Projection = null;
409
			element.RenderTransform = null;
410
411
			CacheMode original;
412
			if (_originalCacheMode.TryGetValue(element, out original))
413
			{
414
				element.CacheMode = original;
415
				_originalCacheMode.Remove(element);
416
			}
417
			else
418
			{
419
				element.CacheMode = null;
420
			}
421
		}
422
423
		/// <summary>
424
		/// Creates the tilt return storyboard (if not already created) and 
425
		/// targets it to the projection.
426
		/// </summary>
427
		/// <param name="element">The framework element to prepare for
428
		/// projection.</param>
429
		static void PrepareTiltReturnStoryboard(FrameworkElement element)
430
		{
431
			if (tiltReturnStoryboard == null)
432
			{
433
				tiltReturnStoryboard = new Storyboard();
434
				tiltReturnStoryboard.Completed += TiltReturnStoryboard_Completed;
435
				tiltReturnXAnimation = new DoubleAnimation();
436
				Storyboard.SetTargetProperty(tiltReturnXAnimation, "RotationX");
437
				tiltReturnXAnimation.BeginTime = TiltReturnAnimationDelay;
438
				tiltReturnXAnimation.To = 0;
439
				tiltReturnXAnimation.Duration = TiltReturnAnimationDuration;
440
441
				tiltReturnYAnimation = new DoubleAnimation();
442
				Storyboard.SetTargetProperty(tiltReturnYAnimation, "RotationY");
443
				tiltReturnYAnimation.BeginTime = TiltReturnAnimationDelay;
444
				tiltReturnYAnimation.To = 0;
445
				tiltReturnYAnimation.Duration = TiltReturnAnimationDuration;
446
447
				tiltReturnZAnimation = new DoubleAnimation();
448
				Storyboard.SetTargetProperty(tiltReturnZAnimation, "GlobalOffsetZ");
449
				tiltReturnZAnimation.BeginTime = TiltReturnAnimationDelay;
450
				tiltReturnZAnimation.To = 0;
451
				tiltReturnZAnimation.Duration = TiltReturnAnimationDuration;
452
453
				tiltReturnStoryboard.Children.Add(tiltReturnXAnimation);
454
				tiltReturnStoryboard.Children.Add(tiltReturnYAnimation);
455
				tiltReturnStoryboard.Children.Add(tiltReturnZAnimation);
456
			}
457
458
			Storyboard.SetTarget(tiltReturnXAnimation, element.Projection);
459
			Storyboard.SetTarget(tiltReturnYAnimation, element.Projection);
460
			Storyboard.SetTarget(tiltReturnZAnimation, element.Projection);
461
		}
462
463
		/// <summary>
464
		/// Continues a tilt effect that is currently applied to an element, 
465
		/// presumably because the user moved their finger.
466
		/// </summary>
467
		/// <param name="element">The element being tilted.</param>
468
		/// <param name="e">The manipulation event args.</param>
469
		static void ContinueTiltEffect(FrameworkElement element, PointerEventArgs e)
470
		{
471
			FrameworkElement container = element;
472
			if (container == null || element == null)
473
			{
474
				return;
475
			}
476
477
			Point tiltTouchPoint =  e.GetCurrentPoint(element).Position;
478
479
			// If touch moved outside bounds of element, then pause the tilt 
480
			// (but don't cancel it)
481
			if (new Rect(0, 0, currentTiltElement.ActualWidth, currentTiltElement.ActualHeight).Contains(tiltTouchPoint) != true)
482
			{
483
				PauseTiltEffect();
484
			}
485
			else
486
			{
487
				// Apply the updated tilt effect
488
				ApplyTiltEffect(currentTiltElement, tiltTouchPoint, currentTiltElementCenter);
489
			}
490
		}
491
492
		/// <summary>
493
		/// Ends the tilt effect by playing the animation.
494
		/// </summary>
495
		/// <param name="element">The element being tilted.</param>
496
		private static void EndTiltEffect(FrameworkElement element)
497
		{
498
			if (element != null)
499
			{
500
				element.PointerReleased -= TiltEffect_PointerPressed;
501
				element.PointerMoved -= TiltEffect_PointerMoved;
502
			}
503
504
			if (tiltReturnStoryboard != null)
505
			{
506
				wasPauseAnimation = false;
507
				if (tiltReturnStoryboard.GetCurrentState() != ClockState.Active)
508
				{
509
					tiltReturnStoryboard.Begin();
510
				}
511
			}
512
			else
513
			{
514
				StopTiltReturnStoryboardAndCleanup();
515
			}
516
		}
517
518
		/// <summary>
519
		/// Handler for the storyboard complete event.
520
		/// </summary>
521
		/// <param name="sender">sender of the event.</param>
522
		/// <param name="e">event args.</param>
523
		private static void TiltReturnStoryboard_Completed(object sender, object e)
524
		{
525
			if (wasPauseAnimation)
526
			{
527
				ResetTiltEffect(currentTiltElement);
528
			}
529
			else
530
			{
531
				StopTiltReturnStoryboardAndCleanup();
532
			}
533
		}
534
535
		/// <summary>
536
		/// Resets the tilt effect on the control, making it appear 'normal'
537
		/// again.
538
		/// </summary>
539
		/// <param name="element">The element to reset the tilt on.</param>
540
		/// <remarks>
541
		/// This method doesn't turn off the tilt effect or cancel any current
542
		/// manipulation; it just temporarily cancels the effect.
543
		/// </remarks>
544
		private static void ResetTiltEffect(FrameworkElement element)
545
		{
546
			PlaneProjection projection = element.Projection as PlaneProjection;
547
			projection.RotationY = 0;
548
			projection.RotationX = 0;
549
			projection.GlobalOffsetZ = 0;
550
		}
551
552
		/// <summary>
553
		/// Stops the tilt effect and release resources applied to the currently
554
		/// tilted control.
555
		/// </summary>
556
		private static void StopTiltReturnStoryboardAndCleanup()
557
		{
558
			if (tiltReturnStoryboard != null)
559
			{
560
				tiltReturnStoryboard.Stop();
561
			}
562
563
			RevertPrepareControlForTilt(currentTiltElement);
564
		}
565
566
		/// <summary>
567
		/// Pauses the tilt effect so that the control returns to the 'at rest'
568
		/// position, but doesn't stop the tilt effect (handlers are still 
569
		/// attached).
570
		/// </summary>
571
		private static void PauseTiltEffect()
572
		{
573
			if ((tiltReturnStoryboard != null) && !wasPauseAnimation)
574
			{
575
				tiltReturnStoryboard.Stop();
576
				wasPauseAnimation = true;
577
				tiltReturnStoryboard.Begin();
578
			}
579
		}
580
581
		/// <summary>
582
		/// Resets the storyboard to not running.
583
		/// </summary>
584
		private static void ResetTiltReturnStoryboard()
585
		{
586
			tiltReturnStoryboard.Stop();
587
			wasPauseAnimation = false;
588
		}
589
590
		/// <summary>
591
		/// Applies the tilt effect to the control.
592
		/// </summary>
593
		/// <param name="element">the control to tilt.</param>
594
		/// <param name="touchPoint">The touch point, in the container's 
595
		/// coordinates.</param>
596
		/// <param name="centerPoint">The center point of the container.</param>
597
		private static void ApplyTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint)
598
		{
599
			// Stop any active animation
600
			ResetTiltReturnStoryboard();
601
602
			// Get relative point of the touch in percentage of container size
603
			Point normalizedPoint = new Point(
604
				Math.Min(Math.Max(touchPoint.X / (centerPoint.X * 2), 0), 1),
605
				Math.Min(Math.Max(touchPoint.Y / (centerPoint.Y * 2), 0), 1));
606
607
			if (double.IsNaN(normalizedPoint.X) || double.IsNaN(normalizedPoint.Y))
608
			{
609
				return;
610
			}
611
612
			// Shell values
613
			double xMagnitude = Math.Abs(normalizedPoint.X - 0.5);
614
			double yMagnitude = Math.Abs(normalizedPoint.Y - 0.5);
615
			double xDirection = -Math.Sign(normalizedPoint.X - 0.5);
616
			double yDirection = Math.Sign(normalizedPoint.Y - 0.5);
617
			double angleMagnitude = xMagnitude + yMagnitude;
618
			double xAngleContribution = xMagnitude + yMagnitude > 0 ? xMagnitude / (xMagnitude + yMagnitude) : 0;
619
620
			double angle = angleMagnitude * MaxAngle * 180 / Math.PI;
621
			double depression = (1 - angleMagnitude) * MaxDepression;
622
623
			// RotationX and RotationY are the angles of rotations about the x- 
624
			// or y-*axis*; to achieve a rotation in the x- or y-*direction*, we 
625
			// need to swap the two. That is, a rotation to the left about the 
626
			// y-axis is a rotation to the left in the x-direction, and a 
627
			// rotation up about the x-axis is a rotation up in the y-direction.
628
			PlaneProjection projection = element.Projection as PlaneProjection;
629
			projection.RotationY = angle * xAngleContribution * xDirection;
630
			projection.RotationX = angle * (1 - xAngleContribution) * yDirection;
631
			projection.GlobalOffsetZ = -depression;
632
		}
633
634
		#endregion
635
	}
636
637
	/// <summary>
638
	/// Couple of simple helpers for walking the visual tree.
639
	/// </summary>
640
	static class TreeHelpers
641
	{
642
		/// <summary>
643
		/// Gets the ancestors of the element, up to the root.
644
		/// </summary>
645
		/// <param name="node">The element to start from.</param>
646
		/// <returns>An enumerator of the ancestors.</returns>
647
		public static IEnumerable<FrameworkElement> GetVisualAncestors(this FrameworkElement node)
648
		{
649
			FrameworkElement parent = node.GetVisualParent();
650
			while (parent != null)
651
			{
652
				yield return parent;
653
				parent = parent.GetVisualParent();
654
			}
655
		}
656
657
		/// <summary>
658
		/// Gets the visual parent of the element.
659
		/// </summary>
660
		/// <param name="node">The element to check.</param>
661
		/// <returns>The visual parent.</returns>
662
		public static FrameworkElement GetVisualParent(this FrameworkElement node)
663
		{
664
			return VisualTreeHelper.GetParent(node) as FrameworkElement;
665
		}
666
	}
667
}