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 | } |