public InertiaProcessor(Manipulation owner, Point position, ManipulationDelta cumulative, ManipulationVelocities velocities) { _owner = owner; _position0 = position; _cumulative0 = cumulative; _velocities0 = velocities; _isTranslateInertiaXEnabled = _owner._isTranslateXEnabled && _owner._settings.HasFlag(Input.GestureSettings.ManipulationTranslateInertia) && Abs(velocities.Linear.X) > _owner._inertiaThresholds.TranslateX; _isTranslateInertiaYEnabled = _owner._isTranslateYEnabled && _owner._settings.HasFlag(Input.GestureSettings.ManipulationTranslateInertia) && Abs(velocities.Linear.Y) > _owner._inertiaThresholds.TranslateY; _isRotateInertiaEnabled = _owner._isRotateEnabled && _owner._settings.HasFlag(Input.GestureSettings.ManipulationRotateInertia) && Abs(velocities.Angular) > _owner._inertiaThresholds.Rotate; _isScaleInertiaEnabled = _owner._isScaleEnabled && _owner._settings.HasFlag(Input.GestureSettings.ManipulationScaleInertia) && Abs(velocities.Expansion) > _owner._inertiaThresholds.Expansion; global::System.Diagnostics.Debug.Assert(_isTranslateInertiaXEnabled || _isTranslateInertiaYEnabled || _isRotateInertiaEnabled || _isScaleInertiaEnabled); // For better experience, as soon inertia kicked-in on an axis, we bypass threshold on the second axis. _isTranslateInertiaXEnabled |= _isTranslateInertiaYEnabled && _owner._isTranslateXEnabled; _isTranslateInertiaYEnabled |= _isTranslateInertiaXEnabled && _owner._isTranslateYEnabled; _timer = DispatcherQueue.GetForCurrentThread().CreateTimer(); _timer.Interval = TimeSpan.FromMilliseconds(1000d / _framesPerSecond); _timer.IsRepeating = true; _timer.Tick += (snd, e) => Process(snd.LastTickElapsed.TotalMilliseconds); }
internal ManipulationInertiaStartingEventArgs(PointerDeviceType pointerDeviceType, Point position, ManipulationDelta delta, ManipulationDelta cumulative) { PointerDeviceType = pointerDeviceType; Position = position; Delta = delta; Cumulative = cumulative; }
private void NotifyUpdate(bool pointerAdded = false) { // Note: Make sure to update the _sumOfPublishedDelta before raising the event, so if an exception is raised // or if the manipulation is Completed, the Complete event args can use the updated _sumOfPublishedDelta. var cumulative = GetCumulative(); switch (_state) { case ManipulationState.Starting when pointerAdded: _state = ManipulationState.Started; _sumOfPublishedDelta = cumulative; _recognizer.ManipulationStarted?.Invoke( _recognizer, new ManipulationStartedEventArgs(_deviceType, _currents.Center, cumulative)); // No needs to publish an update when we start the manipulation due to an additional pointer as cumulative will be empty. break; case ManipulationState.Starting when cumulative.IsSignificant(_startThresholds): _state = ManipulationState.Started; _sumOfPublishedDelta = cumulative; // Note: We first start with an empty delta, then invoke Update. // This is required to patch a common issue in applications that are using only the // ManipulationUpdated.Delta property to track the pointer (like the WCT GridSplitter). // UWP seems to do that only for Touch and Pen (i.e. the Delta is not empty on start with a mouse), // but there is no side effect to use the same behavior for all pointer types. _recognizer.ManipulationStarted?.Invoke( _recognizer, new ManipulationStartedEventArgs(_deviceType, _origins.Center, ManipulationDelta.Empty)); _recognizer.ManipulationUpdated?.Invoke( _recognizer, new ManipulationUpdatedEventArgs(_deviceType, _currents.Center, cumulative, cumulative, isInertial: false)); break; case ManipulationState.Started: // Even if Scale and Angle are expected to be default when we add a pointer (i.e. forceUpdate == true), // the 'delta' and 'cumulative' might still contains some TranslateX|Y compared to the previous Pointer1 location. var delta = GetDelta(cumulative); if (pointerAdded || delta.IsSignificant(_deltaThresholds)) { _sumOfPublishedDelta = _sumOfPublishedDelta.Add(delta); _recognizer.ManipulationUpdated?.Invoke( _recognizer, new ManipulationUpdatedEventArgs(_deviceType, _currents.Center, delta, cumulative, isInertial: false)); } break; } }
internal static ManipulationDelta Add(ManipulationDelta left, ManipulationDelta right) => new ManipulationDelta { Translation = new Point( left.Translation.X + right.Translation.X, left.Translation.Y + right.Translation.Y), Rotation = left.Rotation + right.Rotation, Scale = left.Scale * right.Scale, Expansion = left.Expansion + right.Expansion };
internal ManipulationCompletedEventArgs( PointerDeviceType pointerDeviceType, Point position, ManipulationDelta cumulative, bool isInertial) { PointerDeviceType = pointerDeviceType; Position = position; Cumulative = cumulative; IsInertial = isInertial; }
internal ManipulationStartedEventArgs( PointerIdentifier[] pointers, Point position, ManipulationDelta cumulative, uint contactCount) { global::System.Diagnostics.Debug.Assert(contactCount == pointers.Length, "We should have the same number of pointers for the manip start."); global::System.Diagnostics.Debug.Assert(pointers.Length > 0 && pointers.All(p => p.Type == pointers[0].Type)); Pointers = pointers; PointerDeviceType = pointers[0].Type; Position = position; Cumulative = cumulative; ContactCount = contactCount; }
private ManipulationDelta GetDelta(ManipulationDelta cumulative) { var deltaSum = _sumOfPublishedDelta; var translateX = _isTranslateXEnabled ? cumulative.Translation.X - deltaSum.Translation.X : 0; var translateY = _isTranslateYEnabled ? cumulative.Translation.Y - deltaSum.Translation.Y : 0; var rotate = _isRotateEnabled ? cumulative.Rotation - deltaSum.Rotation : 0; var scale = _isScaleEnabled ? cumulative.Scale / deltaSum.Scale : 1; var expansion = _isScaleEnabled ? cumulative.Expansion - deltaSum.Expansion : 0; return(new ManipulationDelta { Translation = new Point(translateX, translateY), Rotation = (float)MathEx.NormalizeDegree(rotate), Scale = scale, Expansion = expansion }); }
private ManipulationDelta GetInertiaCumulative(double t, ManipulationDelta previousCumulative) { var linearX = GetValue(_isTranslateInertiaXEnabled, _velocities0.Linear.X, DesiredDisplacementDeceleration, t, (float)previousCumulative.Translation.X); var linearY = GetValue(_isTranslateInertiaYEnabled, _velocities0.Linear.Y, DesiredDisplacementDeceleration, t, (float)previousCumulative.Translation.Y); var angular = GetValue(_isRotateInertiaEnabled, _velocities0.Angular, DesiredRotationDeceleration, t, previousCumulative.Rotation); var expansion = GetValue(_isScaleInertiaEnabled, _velocities0.Expansion, DesiredExpansionDeceleration, t, previousCumulative.Expansion); var scale = _isScaleInertiaEnabled ? (_owner._origins.Distance + expansion) / _owner._origins.Distance : 1; var delta = new ManipulationDelta { Translation = new Point(linearX, linearY), Rotation = angular, Expansion = expansion, Scale = scale }; return(delta); }
internal ManipulationCompletedEventArgs( PointerIdentifier[] pointers, Point position, ManipulationDelta cumulative, ManipulationVelocities velocities, bool isInertial, uint contactCount, uint currentContactCount) { global::System.Diagnostics.Debug.Assert(pointers.Length > 0 && pointers.All(p => p.Type == pointers[0].Type)); Pointers = pointers; Position = position; Cumulative = cumulative; Velocities = velocities; IsInertial = isInertial; ContactCount = contactCount; CurrentContactCount = currentContactCount; }
internal ManipulationInertiaStartingEventArgs( PointerIdentifier[] pointers, Point position, ManipulationDelta delta, ManipulationDelta cumulative, ManipulationVelocities velocities, uint contactCount, GestureRecognizer.Manipulation.InertiaProcessor processor) { global::System.Diagnostics.Debug.Assert(pointers.Length > 0 && pointers.All(p => p.Type == pointers[0].Type)); Pointers = pointers; PointerDeviceType = pointers[0].Type; Position = position; Delta = delta; Cumulative = cumulative; Velocities = velocities; ContactCount = contactCount; Processor = processor; }
private void Process(double t) { // First we update the internal state (i.e. the current cumulative manip delta for the current time) var previous = _inertiaCumulative; var current = GetInertiaCumulative(t, previous); _inertiaCumulative = current; // Then we request to the owner to raise its events (will cause the GetCumulative()) // We notify in any cases in order to make sure to raise at least one ManipDelta (even if Delta.IsEmpty ^^) before stop the processor _owner.NotifyUpdate(); if (previous.Translation.X == current.Translation.X && previous.Translation.Y == current.Translation.Y && previous.Rotation == current.Rotation && previous.Expansion == current.Expansion) // Note: we DO NOT compare the scaling, expansion is enough here! { _timer.Stop(); _owner.NotifyUpdate(); } }
public FlickingEventArgs(FlickDirection direction, ManipulationDelta delta) { Direction = direction; Delta = delta; CanContinue = true; }
private void NotifyUpdate(bool pointerAdded = false) { // Note: Make sure to update the _sumOfPublishedDelta before raising the event, so if an exception is raised // or if the manipulation is Completed, the Complete event args can use the updated _sumOfPublishedDelta. var cumulative = GetCumulative(); switch (_state) { case ManipulationState.Starting when IsBeginningOfDragManipulation(): // On UWP if the element was configured to allow both Drag and Manipulations, // both events are going to be raised (... until the drag "content" is being render an captures all pointers). // This results as a manipulation started which is never completed. // If user uses double touch the manipulation will however start and complete when user adds / remove the 2nd finger. // On Uno, as allowing both Manipulations and drop on the same element is really a stretch case (and is bugish on UWP), // we accept as a known limitation that once dragging started no manipulation event would be fired. _state = ManipulationState.Started; IsDragManipulation = true; _recognizer.Dragging?.Invoke( _recognizer, new DraggingEventArgs(_currents.Pointer1, DraggingState.Started)); break; case ManipulationState.Starting when pointerAdded: _state = ManipulationState.Started; _sumOfPublishedDelta = cumulative; _recognizer.ManipulationStarted?.Invoke( _recognizer, new ManipulationStartedEventArgs(_deviceType, _currents.Center, cumulative)); // No needs to publish an update when we start the manipulation due to an additional pointer as cumulative will be empty. break; case ManipulationState.Starting when cumulative.IsSignificant(_startThresholds): _state = ManipulationState.Started; _sumOfPublishedDelta = cumulative; // Note: We first start with an empty delta, then invoke Update. // This is required to patch a common issue in applications that are using only the // ManipulationUpdated.Delta property to track the pointer (like the WCT GridSplitter). // UWP seems to do that only for Touch and Pen (i.e. the Delta is not empty on start with a mouse), // but there is no side effect to use the same behavior for all pointer types. _recognizer.ManipulationStarted?.Invoke( _recognizer, new ManipulationStartedEventArgs(_deviceType, _origins.Center, ManipulationDelta.Empty)); _recognizer.ManipulationUpdated?.Invoke( _recognizer, new ManipulationUpdatedEventArgs(_deviceType, _currents.Center, cumulative, cumulative, isInertial: false)); break; case ManipulationState.Started when IsDragManipulation: _recognizer.Dragging?.Invoke( _recognizer, new DraggingEventArgs(_currents.Pointer1, DraggingState.Continuing)); break; case ManipulationState.Started: // Even if Scale and Angle are expected to be default when we add a pointer (i.e. forceUpdate == true), // the 'delta' and 'cumulative' might still contains some TranslateX|Y compared to the previous Pointer1 location. var delta = GetDelta(cumulative); if (pointerAdded || delta.IsSignificant(_deltaThresholds)) { _sumOfPublishedDelta = _sumOfPublishedDelta.Add(delta); _recognizer.ManipulationUpdated?.Invoke( _recognizer, new ManipulationUpdatedEventArgs(_deviceType, _currents.Center, delta, cumulative, isInertial: false)); } break; } }
private bool IsSignificant(ManipulationDelta delta) => Math.Abs(delta.Translation.X) >= MinManipulationDeltaTranslateX || Math.Abs(delta.Translation.Y) >= MinManipulationDeltaTranslateY || delta.Rotation >= MinManipulationDeltaRotate || // We used the ToDegreeNormalized, no need to check for negative angles Math.Abs(delta.Expansion) >= MinManipulationDeltaExpansion;
internal ManipulationStartedEventArgs(PointerDeviceType pointerDeviceType, Point position, ManipulationDelta cumulative) { PointerDeviceType = pointerDeviceType; Position = position; Cumulative = cumulative; }
private DragLock GetDragLockForMove(ManipulationDelta cumulative) { if (!this.dragging) { this.ReleaseMouseCaptureAtGestureOrigin(); } this.dragging = true; if (this.dragLock == DragLock.Unset) { double num = GestureService.AngleFromVector(cumulative.Translation.X, cumulative.Translation.Y) % 180.0; this.dragLock = num <= 45.0 || num >= 135.0 ? DragLock.Horizontal : DragLock.Vertical; } return this.dragLock; }
protected virtual GestureEnd EndMove(bool isInertial, ManipulationDelta cumulative, ManipulationVelocities velocities, Point position) { this.dragLock = DragLock.Unset; this.dragging = false; double angle = 0.0; if (isInertial) { angle = GestureService.AngleFromVector(velocities.Linear.X, velocities.Linear.Y); if (angle <= 45.0 || angle >= 315.0) { angle = 0.0; } else if (angle >= 135.0 && angle <= 225.0) { angle = 180.0; } this.ReleaseMouseCaptureAtGestureOrigin(); } return new GestureEnd(angle, isInertial, cumulative.Translation, position); }
internal ManipulationDelta Add(ManipulationDelta right) => Add(this, right);