/* The *_Pinch methods are separate from the standard event methods * because they have to deal with multiple touches. It gets really * messy and ugly if single-touch and multi-touch detection is all * intermingled in the same methods. */ private static void OnReleased_Pinch(int fingerId, Vector2 touchPosition) { // We don't care about fingers that aren't part of the pinch if (fingerId != activeFingerId && fingerId != secondFingerId) { return; } if (IsGestureEnabled(GestureType.PinchComplete)) { // Pinch Complete! TouchPanel.EnqueueGesture(new GestureSample( GestureType.PinchComplete, GetGestureTimestamp(), Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero, activeFingerId, secondFingerId )); } // If we lost the active finger if (fingerId == activeFingerId) { // Then the second finger becomes the active finger activeFingerId = secondFingerId; activeFingerPosition = secondFingerPosition; } // Regardless, we no longer have a second finger secondFingerId = TouchPanel.NO_FINGER; // Attempt to replace our fallen comrade bool replacedSecondFinger = false; foreach (int id in fingerIds) { // Find a finger that's not already spoken for if (id != activeFingerId) { secondFingerId = id; replacedSecondFinger = true; break; } } if (!replacedSecondFinger) { // Aaaand we're back to a single touch state = GestureState.HELD; } }
private static void OnMoved_Pinch(int fingerId, Vector2 touchPosition, Vector2 delta) { // We only care if the finger moved is involved in the pinch if (fingerId != activeFingerId && fingerId != secondFingerId) { return; } /* In XNA, each Pinch gesture sample contained a delta * for both fingers. It was somehow able to detect * simultaneous deltas at an OS level. We don't have that * luxury, so instead, each Pinch gesture will contain the * delta information for just _one_ of the fingers. * * In practice what this means is that you'll get twice as * many Pinch gestures added to the queue (one sample for * each finger). This doesn't matter too much, though, * since the resulting behavior is identical to XNA. * * -caleb */ if (fingerId == activeFingerId) { activeFingerPosition = touchPosition; TouchPanel.EnqueueGesture(new GestureSample( GestureType.Pinch, GetGestureTimestamp(), activeFingerPosition, secondFingerPosition, delta, Vector2.Zero, activeFingerId, secondFingerId )); } else { secondFingerPosition = touchPosition; TouchPanel.EnqueueGesture(new GestureSample( GestureType.Pinch, GetGestureTimestamp(), activeFingerPosition, secondFingerPosition, Vector2.Zero, delta, activeFingerId, secondFingerId )); } }
internal static void OnUpdate() { if (state == GestureState.PINCHING) { /* Handle the case where the Pinch gesture * was disabled *while* the user was pinching. */ if (!IsGestureEnabled(GestureType.Pinch)) { state = GestureState.HELD; secondFingerId = TouchPanel.NO_FINGER; // Still might need to trigger a PinchComplete callBelatedPinchComplete = true; } // No pinches allowed in the rest of this method! return; } // Must have an active finger to proceed if (activeFingerId == TouchPanel.NO_FINGER) { return; } #region Flick Velocity Calculation if (IsGestureEnabled(GestureType.Flick)) { // We need one frame to pass so we can calculate delta time if (updateTimestamp != DateTime.MinValue) { /* The calculation below is mostly taken from MonoGame. * It accumulates velocity after running it through * a low-pass filter to mitigate the effect of * acceleration spikes. This works pretty well, * but on rare occasions the velocity will still * spike by an order of magnitude. * * In practice this tends to be a non-issue, but * if you *really* need to avoid any spikes, you * may want to consider normalizing the delta * reported in the GestureSample and then scaling it * to min(actualVectorLength, preferredMaxLength). * * -caleb */ float dt = (float)(DateTime.Now - updateTimestamp).TotalSeconds; Vector2 delta = activeFingerPosition - lastUpdatePosition; Vector2 instVelocity = delta / (0.001f + dt); velocity += (instVelocity - velocity) * 0.45f; } lastUpdatePosition = activeFingerPosition; updateTimestamp = DateTime.Now; } #endregion #region Hold Detection if (IsGestureEnabled(GestureType.Hold) && state == GestureState.HOLDING) { TimeSpan timeSincePress = DateTime.Now - eventTimestamp; if (timeSincePress >= TimeSpan.FromSeconds(1)) { // Hold! TouchPanel.EnqueueGesture(new GestureSample( GestureType.Hold, GetGestureTimestamp(), activeFingerPosition, Vector2.Zero, Vector2.Zero, Vector2.Zero, activeFingerId, TouchPanel.NO_FINGER )); state = GestureState.HELD; } } #endregion }
internal static void OnMoved(int fingerId, Vector2 touchPosition, Vector2 delta) { // Handle move events separately for Pinch gestures if (state == GestureState.PINCHING) { OnMoved_Pinch(fingerId, touchPosition, delta); return; } // Replace the active finger if we lost it if (activeFingerId == TouchPanel.NO_FINGER) { activeFingerId = fingerId; } // If this finger isn't the active finger if (fingerId != activeFingerId) { // We don't care about it return; } // Update the position activeFingerPosition = touchPosition; #region Prepare for Dragging // Determine which drag gestures are enabled bool hdrag = IsGestureEnabled(GestureType.HorizontalDrag); bool vdrag = IsGestureEnabled(GestureType.VerticalDrag); bool fdrag = IsGestureEnabled(GestureType.FreeDrag); if (state == GestureState.HOLDING || state == GestureState.HELD) { // Prevent accidental drags float distanceFromPress = (touchPosition - pressPosition).Length(); if (distanceFromPress > MOVE_THRESHOLD) { if (hdrag && (Math.Abs(delta.X) > Math.Abs(delta.Y))) { // Horizontal Drag! state = GestureState.DRAGGING_H; } else if (vdrag && (Math.Abs(delta.Y) > Math.Abs(delta.X))) { // Vertical Drag! state = GestureState.DRAGGING_V; } else if (fdrag) { // Free Drag! state = GestureState.DRAGGING_FREE; } else { // No drag... state = GestureState.NONE; } } } #endregion #region Drag Detection if (state == GestureState.DRAGGING_H && hdrag) { // Horizontal Dragging! TouchPanel.EnqueueGesture(new GestureSample( GestureType.HorizontalDrag, GetGestureTimestamp(), touchPosition, Vector2.Zero, new Vector2(delta.X, 0), Vector2.Zero, fingerId, TouchPanel.NO_FINGER )); } else if (state == GestureState.DRAGGING_V && vdrag) { // Vertical Dragging! TouchPanel.EnqueueGesture(new GestureSample( GestureType.VerticalDrag, GetGestureTimestamp(), touchPosition, Vector2.Zero, new Vector2(0, delta.Y), Vector2.Zero, fingerId, TouchPanel.NO_FINGER )); } else if (state == GestureState.DRAGGING_FREE && fdrag) { // Free Dragging! TouchPanel.EnqueueGesture(new GestureSample( GestureType.FreeDrag, GetGestureTimestamp(), touchPosition, Vector2.Zero, delta, Vector2.Zero, fingerId, TouchPanel.NO_FINGER )); } #endregion #region Handle Disabled Drags /* Handle the case where the current drag type * was disabled *while* the user was dragging. */ if ((state == GestureState.DRAGGING_H && !hdrag) || (state == GestureState.DRAGGING_V && !vdrag) || (state == GestureState.DRAGGING_FREE && !fdrag)) { // Reset the state state = GestureState.HELD; } #endregion }
internal static void OnReleased(int fingerId, Vector2 touchPosition) { fingerIds.Remove(fingerId); // Handle release events seperately for Pinch gestures if (state == GestureState.PINCHING) { OnReleased_Pinch(fingerId, touchPosition); return; } // Did the user lift the active finger? if (fingerId == activeFingerId) { activeFingerId = TouchPanel.NO_FINGER; } // We're only interested in the very last finger to leave if (FNAPlatform.GetNumTouchFingers() > 0) { return; } #region Tap Detection if (state == GestureState.HOLDING) { // Which Tap gestures are enabled? bool tapEnabled = IsGestureEnabled(GestureType.Tap); bool dtapEnabled = IsGestureEnabled(GestureType.DoubleTap); if (tapEnabled || dtapEnabled) { // How long did the user hold the touch? TimeSpan timeHeld = DateTime.Now - eventTimestamp; if (timeHeld < TimeSpan.FromSeconds(1)) { // Don't register a Tap immediately after a Double Tap if (!justDoubleTapped) { if (tapEnabled) { // Tap! TouchPanel.EnqueueGesture(new GestureSample( GestureType.Tap, GetGestureTimestamp(), touchPosition, Vector2.Zero, Vector2.Zero, Vector2.Zero, fingerId, TouchPanel.NO_FINGER )); } /* Even if Tap isn't enabled, we still * need this for Double Tap detection. */ state = GestureState.JUST_TAPPED; } } } } // Reset this flag so we can catch Taps in the future justDoubleTapped = false; #endregion #region Flick Detection if (IsGestureEnabled(GestureType.Flick)) { // Only flick if the finger is outside the threshold and moving fast float distanceFromPress = (touchPosition - pressPosition).Length(); if (distanceFromPress > MOVE_THRESHOLD && velocity.Length() >= MIN_FLICK_VELOCITY) { // Flick! TouchPanel.EnqueueGesture(new GestureSample( GestureType.Flick, GetGestureTimestamp(), Vector2.Zero, Vector2.Zero, velocity, Vector2.Zero, fingerId, TouchPanel.NO_FINGER )); } // Reset velocity calculation variables velocity = Vector2.Zero; lastUpdatePosition = Vector2.Zero; updateTimestamp = DateTime.MinValue; } #endregion #region Drag Complete Detection if (IsGestureEnabled(GestureType.DragComplete)) { bool wasDragging = (state == GestureState.DRAGGING_H || state == GestureState.DRAGGING_V || state == GestureState.DRAGGING_FREE); if (wasDragging) { // Drag Complete! TouchPanel.EnqueueGesture(new GestureSample( GestureType.DragComplete, GetGestureTimestamp(), Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero, fingerId, TouchPanel.NO_FINGER )); } } #endregion #region Belated Pinch Complete Detection if (callBelatedPinchComplete && IsGestureEnabled(GestureType.PinchComplete)) { TouchPanel.EnqueueGesture(new GestureSample( GestureType.PinchComplete, GetGestureTimestamp(), Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero, TouchPanel.NO_FINGER, TouchPanel.NO_FINGER )); } callBelatedPinchComplete = false; #endregion // Reset the state if we're not anticipating a Double Tap if (state != GestureState.JUST_TAPPED) { state = GestureState.NONE; } eventTimestamp = DateTime.Now; }
internal static void OnPressed(int fingerId, Vector2 touchPosition) { fingerIds.Add(fingerId); if (state == GestureState.PINCHING) { // None of this method applies to active pinches return; } // Set the active finger if there isn't one already if (activeFingerId == TouchPanel.NO_FINGER) { activeFingerId = fingerId; activeFingerPosition = touchPosition; } else { #region Pinch Initialization if (IsGestureEnabled(GestureType.Pinch)) { // Initialize a Pinch secondFingerId = fingerId; secondFingerPosition = touchPosition; state = GestureState.PINCHING; } #endregion // No need to do anything more return; } #region Double Tap Detection if (state == GestureState.JUST_TAPPED) { if (IsGestureEnabled(GestureType.DoubleTap)) { // Must tap again within 300ms of original tap's release TimeSpan timeSinceRelease = DateTime.Now - eventTimestamp; if (timeSinceRelease <= TimeSpan.FromMilliseconds(300)) { // If the new tap is close to the original tap float distance = (touchPosition - pressPosition).Length(); if (distance <= MOVE_THRESHOLD) { // Double Tap! TouchPanel.EnqueueGesture(new GestureSample( GestureType.DoubleTap, GetGestureTimestamp(), touchPosition, Vector2.Zero, Vector2.Zero, Vector2.Zero, fingerId, TouchPanel.NO_FINGER )); justDoubleTapped = true; } } } } #endregion state = GestureState.HOLDING; pressPosition = touchPosition; eventTimestamp = DateTime.Now; }