1
1
using System ;
2
2
using System . Collections . Generic ;
3
3
using System . Diagnostics ;
4
+ using System . Linq ;
4
5
using System . Numerics ;
5
6
using System . Runtime . CompilerServices ;
6
7
using System . Text ;
@@ -57,13 +58,13 @@ private void InitializeInteractionTracker()
57
58
58
59
private void UnoAttachEventHandlers ( )
59
60
{
60
- m_content . ManipulationMode = ManipulationModes . TranslateX /*| ManipulationModes.TranslateInertia*/ ;
61
+ m_content . ManipulationMode = m_isHorizontal ? ManipulationModes . TranslateX : ManipulationModes . TranslateY /*| ManipulationModes.TranslateInertia*/ ;
61
62
m_content . ManipulationStarting += OnSwipeManipulationStarting ;
62
63
m_content . ManipulationStarted += OnSwipeManipulationStarted ;
63
64
m_content . ManipulationDelta += OnSwipeManipulationDelta ;
64
65
//m_content.ManipulationInertiaStarting += OnSwipeManipulationInertiaStarting;
65
66
m_content . ManipulationCompleted += OnSwipeManipulationCompleted ;
66
- }
67
+ }
67
68
68
69
private void UnoDetachEventHandlers ( )
69
70
{
@@ -102,6 +103,7 @@ private void OnSwipeManipulationStarted(object sender, ManipulationStartedRouted
102
103
103
104
_positionWhenCaptured = new Vector2 ( ( float ) _transform . X , ( float ) _transform . Y ) ;
104
105
_grabbedTimer . Start ( ) ;
106
+ _lastMoves . Clear ( ) ;
105
107
}
106
108
107
109
private void OnSwipeManipulationDelta ( object sender , ManipulationDeltaRoutedEventArgs e )
@@ -179,16 +181,21 @@ void RecordMovements(ManipulationDeltaRoutedEventArgs e)
179
181
private void UpdateDesiredPosition ( ManipulationDeltaRoutedEventArgs e )
180
182
{
181
183
var rawDesiredPosition = _positionWhenCaptured + e . Cumulative . Translation . ToVector2 ( ) ;
184
+ var clampedPosition = GetClampedPosition ( rawDesiredPosition ) ;
185
+ var attenuatedPosition = GetAttenuatedPosition ( clampedPosition ) ;
186
+
187
+ _desiredPosition = attenuatedPosition ;
188
+ }
182
189
190
+ private Vector2 GetClampedPosition ( Vector2 rawDesiredPosition )
191
+ {
183
192
var harNearContent = _hasLeftContent || _hasTopContent ;
184
193
var hasFarContent = _hasRightContent || _hasBottomContent ;
185
194
var min = _isNearOpen || ! hasFarContent ? 0 : - 10000 ;
186
195
var max = _isFarOpen || ! harNearContent ? 0 : 10000 ;
187
196
188
197
var clampedPosition = Vector2 . Max ( Vector2 . Min ( rawDesiredPosition , Vector2 . One * max ) , Vector2 . One * min ) ;
189
- var attenuatedPosition = GetAttenuatedPosition ( clampedPosition ) ;
190
-
191
- _desiredPosition = attenuatedPosition ;
198
+ return clampedPosition ;
192
199
}
193
200
194
201
private void UpdateStackPanelDesiredPosition ( )
@@ -279,12 +286,37 @@ private async void OnSwipeManipulationCompleted(object sender, ManipulationCompl
279
286
280
287
private async Task SimulateInertia ( )
281
288
{
282
- // TODO: Use the recorded speeds to ajust the _desiredPosition.
289
+ const float speedThreshold = 500f ; // pixel/s
290
+ const float simulatedInertiaDuration = 0.5f ; // in seconds.
291
+
292
+ var unit = m_isHorizontal ? Vector2 . UnitX : Vector2 . UnitY ;
293
+ var estimatedSpeed = m_isHorizontal ? GetSpeed ( ) . X : GetSpeed ( ) . Y ;
294
+ var useAfterInertiaPosition = false ;
295
+ var estimatedPositionAfterInertia = _desiredPosition ;
296
+
297
+ if ( Math . Abs ( estimatedSpeed ) > speedThreshold )
298
+ {
299
+ estimatedPositionAfterInertia = _desiredPosition + unit * estimatedSpeed * simulatedInertiaDuration ; // What would be the position if we let the movement go at the current speed during 'simulatedInertiaDuration'.
300
+ estimatedPositionAfterInertia = GetClampedPosition ( estimatedPositionAfterInertia ) ;
301
+
302
+ // If inertia would flip sign of the transform, we close instead.
303
+ var flickToOppositeSideCheck = _desiredPosition * estimatedPositionAfterInertia ;
304
+ // If the stackpanel IsMeasureDirty or IsArrangeDirty are true, it means we can't use its ActualSize, so we close the content.
305
+ // This solves a problem when the user swipes the content from one side to the other quickly and the stackpanel doesn't have time to measure and arrange itself before the inertia starts.
306
+ // When that happens, the content can open using the previous stackpanel size, causing an invalid behavior.
307
+ if ( m_swipeContentStackPanel . IsMeasureDirty || m_swipeContentStackPanel . IsArrangeDirty || ( m_isHorizontal ? flickToOppositeSideCheck . X < 0 : flickToOppositeSideCheck . Y < 0 ) )
308
+ {
309
+ CloseWithoutAnimation ( ) ;
310
+ return ;
311
+ }
312
+
313
+ _desiredPosition = estimatedPositionAfterInertia ;
314
+ useAfterInertiaPosition = true ;
315
+ }
283
316
284
317
var displacement = m_isHorizontal ? _desiredPosition . X : _desiredPosition . Y ;
285
318
var absoluteDisplacement = Math . Abs ( displacement ) ;
286
319
var effectiveStackSize = m_isHorizontal ? m_swipeContentStackPanel . ActualWidth : m_swipeContentStackPanel . ActualHeight ;
287
- var unit = m_isHorizontal ? Vector2 . UnitX : Vector2 . UnitY ;
288
320
289
321
if ( m_isOpen )
290
322
{
@@ -309,9 +341,17 @@ private async Task SimulateInertia()
309
341
}
310
342
}
311
343
344
+ var displacementFromInertiaVector = _desiredPosition - estimatedPositionAfterInertia ;
345
+ var displacementFromInertia = m_isHorizontal ? displacementFromInertiaVector . X : displacementFromInertiaVector . Y ;
346
+ if ( displacementFromInertia * estimatedSpeed < 0 )
347
+ {
348
+ // If the inertia speed and the direction to the final position are opposite, we don't use the inertia speed.
349
+ useAfterInertiaPosition = false ;
350
+ }
351
+
312
352
UpdateStackPanelDesiredPosition ( ) ;
313
353
314
- await AnimateTransforms ( ) ;
354
+ await AnimateTransforms ( useAfterInertiaPosition , estimatedSpeed ) ;
315
355
316
356
m_isInteracting = false ;
317
357
@@ -325,22 +365,7 @@ private async Task SimulateInertia()
325
365
}
326
366
}
327
367
328
- //It is possible that the user has flicked from a negative position to a position that would result in the interaction
329
- //tracker coming to rest at the positive open position (or vise versa). The != zero check does not account for this.
330
- //Instead we check to ensure that the current position and the ModifiedRestingPosition have the same sign (multiply to a positive number)
331
- //If they do not then we are in this situation and want the end result of the interaction to be the closed state, so close without any animation and return
332
- //to prevent further processing of this inertia state.
333
-
334
- // TODO:
335
- var positionAfterInertia = _desiredPosition ;
336
- var flickToOppositeSideCheck = _desiredPosition * positionAfterInertia ;
337
- if ( m_isHorizontal ? flickToOppositeSideCheck . X < 0 : flickToOppositeSideCheck . X < 0 )
338
- {
339
- CloseWithoutAnimation ( ) ;
340
- return ;
341
- }
342
-
343
- UpdateIsOpen ( positionAfterInertia != Vector2 . Zero ) ;
368
+ UpdateIsOpen ( _desiredPosition != Vector2 . Zero ) ;
344
369
// If the user has panned the interaction tracker past 0 in the opposite direction of the previously
345
370
// opened swipe items then when we set m_isOpen to true the animations will snap to that value.
346
371
// To avoid this we block that side of the animation until the interacting state is entered.
@@ -369,6 +394,18 @@ private async Task SimulateInertia()
369
394
}
370
395
}
371
396
397
+ private Vector2 GetSpeed ( )
398
+ {
399
+ if ( _lastMoves . Any ( ) )
400
+ {
401
+ var totalDelta = _lastMoves . Aggregate ( ( sum , item ) => sum + item ) ;
402
+ var speed = totalDelta . DeltaXY / ( float ) totalDelta . DeltaT ;
403
+ return speed ;
404
+ }
405
+
406
+ return Vector2 . Zero ;
407
+ }
408
+
372
409
private void ConfigurePositionInertiaRestingValues ( ) { }
373
410
374
411
private void IdleStateEntered ( object @null , object @also_null ) { }
@@ -395,19 +432,23 @@ private void UpdateTransforms()
395
432
}
396
433
}
397
434
398
- private async Task AnimateTransforms ( )
435
+ private async Task AnimateTransforms ( bool useInertiaSpeed , double inertiaSpeed )
399
436
{
400
437
var currentPosition = m_isHorizontal ? _transform . X : _transform . Y ;
401
438
var desiredPosition = m_isHorizontal ? _desiredPosition . X : _desiredPosition . Y ;
402
439
var distance = Math . Abs ( desiredPosition - currentPosition ) ;
403
440
var duration = Math . Min ( distance / c_MinimumCloseVelocity , 0.3 ) ;
441
+ if ( useInertiaSpeed )
442
+ {
443
+ duration = distance / inertiaSpeed ;
444
+ }
404
445
405
446
var storyboard = new Storyboard ( ) ;
406
447
var animation = new DoubleAnimation ( )
407
448
{
408
449
To = desiredPosition ,
409
450
Duration = new Duration ( TimeSpan . FromSeconds ( duration ) ) ,
410
- EasingFunction = new QuadraticEase ( )
451
+ EasingFunction = useInertiaSpeed ? ( IEasingFunction ) LinearEase . Instance : new QuadraticEase ( )
411
452
{
412
453
EasingMode = EasingMode . EaseInOut
413
454
}
@@ -424,7 +465,7 @@ private async Task AnimateTransforms()
424
465
{
425
466
To = stackDesiredPosition ,
426
467
Duration = new Duration ( TimeSpan . FromSeconds ( duration ) ) ,
427
- EasingFunction = new QuadraticEase ( )
468
+ EasingFunction = useInertiaSpeed ? ( IEasingFunction ) LinearEase . Instance : new QuadraticEase ( )
428
469
{
429
470
EasingMode = EasingMode . EaseInOut
430
471
}
@@ -465,6 +506,18 @@ private struct MoveUpdate
465
506
public double DeltaY { get ; set ; }
466
507
467
508
public double DeltaT { get ; set ; }
509
+
510
+ public Vector2 DeltaXY => new Vector2 ( ( float ) DeltaX , ( float ) DeltaY ) ;
511
+
512
+ public static MoveUpdate operator + ( MoveUpdate left , MoveUpdate right )
513
+ {
514
+ return new MoveUpdate ( )
515
+ {
516
+ DeltaX = left . DeltaX + right . DeltaX ,
517
+ DeltaY = left . DeltaY + right . DeltaY ,
518
+ DeltaT = left . DeltaT + right . DeltaT
519
+ } ;
520
+ }
468
521
}
469
522
}
470
523
0 commit comments