protected virtual bool VerifyPathGestures(KSPictureLoginPathGesture verifyGesture, KSPictureLoginPathGesture storedGesture, RectangleF drawingViewRect) { var numHitScreenPoints = 0; //var hitScreenPointIndexes = new List<int>(); // Number of locations in the gesture to verify. int numMaxGesturePoints = 0; // Number of locations of the current recorded gesture. int numRecordedGestureLocations = 0; numRecordedGestureLocations = storedGesture.Path.Count; // Convert the drawn locations of the gesture into validation rects. IEnumerable<KSPictureLoginValidationRect> storedValidationRects = storedGesture.Path.Select (p => p.ScaleLocation(drawingViewRect.Size).GetValidationRect(KSPictureLoginController.GridRectSize)); // Convert gesture points into scaled points. IEnumerable<PointF> verifyScreenPoints = verifyGesture.Path.Select (p => p.ScaleLocation(drawingViewRect.Size)); // Get how many points this gesture has. numMaxGesturePoints = verifyScreenPoints.Count (); // Loop all the currently drawn points and compare if they are close to a recorded point. foreach (var validationRect in storedValidationRects) { //int index = 0; foreach (var verifyPoint in verifyScreenPoints) { // Remember the amount of hit points of the gesture. if (validationRect.Contains (verifyPoint)) { numHitScreenPoints++; } // TODO: Add indexed gesture verification as additional feature. // If the rectangle around the just drawn point contains a point of the gesture to verify, remember the index // of the point. The user has to hit the points in the correct order. // Only add if not already previously hit. // if(validationRect.Contains(verifyPoint) && !hitScreenPointIndexes.Contains(index)) // { // hitScreenPointIndexes.Add(index); // } // index++; } } // Get the number of gesture points that were hit in ascending order. // int compareIndex = -1; // int ascendingIndexesHit = 0; // foreach(int index in hitScreenPointIndexes) // { // if(index > compareIndex) // { // ascendingIndexesHit++; // compareIndex = index; // } // } if(numHitScreenPoints > numMaxGesturePoints) { numHitScreenPoints = numMaxGesturePoints; } // Get a percentage: how many percent of the possible gesture points were hit? var hitRatioPercentage = Convert.ToSingle (numHitScreenPoints) / Convert.ToSingle (numMaxGesturePoints); if (hitRatioPercentage < KSPictureLoginGestureVerifier.VerifyGestureRequiredAccuracyPercentage) { KSPictureLoginGlobal.Log ("Required accuracy of {0:.00}% not met. Gesture {1} was not matched by gesture {2}.", KSPictureLoginGestureVerifier.VerifyGestureRequiredAccuracyPercentage * 100f, verifyGesture, storedGesture); return false; } else { // The required amount of points of the gesture was hit. // Check if the user matched the gesture by drawing a reasonable amount of points or if he just painted all // across the screen and made a lucky guess. var overflowPointsPercentage = Convert.ToSingle (numRecordedGestureLocations) / Convert.ToSingle (numMaxGesturePoints) - 1f; if (overflowPointsPercentage > KSPictureLoginGestureVerifier.VerifyGestureMaxLocationOverflowPercentage) { KSPictureLoginGlobal.Log ("Maximum overflow of {0:.00}% not met (was {1:.00}%). Gesture {2} was not matched by gesture {3}.", KSPictureLoginGestureVerifier.VerifyGestureMaxLocationOverflowPercentage * 100f, overflowPointsPercentage * 100f, verifyGesture, storedGesture); return false; } else { KSPictureLoginGlobal.Log ("Gesture {0} was matched by gesture {1} with {2:.00}% accuracy and an overflow of {3:.00}%.", verifyGesture, storedGesture, hitRatioPercentage * 100f, overflowPointsPercentage * 100f); } } return true; }
/// <summary> /// Gets called if touches ended or were cancelled. This adds the last drawn gesture to the list of gestures to be saved. /// </summary> /// <param name="touches">Touches.</param> /// <param name="evt">Evt.</param> public virtual void HandleTouchesEnded(NSSet touches, UIEvent evt) { this.PictureLoginView.DrawingAreaView.EnableGestureVisualizationLayer = false; TimeSpan touchDuration = DateTime.Now - this.touchStartTime; // Check if the touch should be recorded as a tap. if(touchDuration.TotalSeconds < KSPictureLoginController.TapTouchThresholdSeconds) { var tapGesture = this.Factory.CreateGesture<KSPictureLoginTapGesture> (); tapGesture.Location = this.touchStartLocation.NormalizePoint (this.PictureLoginView.DrawingAreaView.Bounds.Size); this.gestureCollection.Add (tapGesture); this.PictureLoginView.DrawingAreaView.AddTapGesture (this.touchStartLocation); KSPictureLoginGlobal.Log ("Adding tap gesture to list: {0}", tapGesture); } else if (this.currentPathGesture != null) { KSPictureLoginGlobal.Log ("Adding path gesture to list: {0}", this.currentPathGesture); this.gestureCollection.Add (this.currentPathGesture); this.currentPathGesture = null; } // If a gesture ended and we're in verification mode, check if the gesture to be verfied was matched. if (this.GesturesToVerify != null) { bool gestureVerified = this.VerifyGesture (); KSPictureLoginGlobal.Log ("Gesture verified: {0}", gestureVerified); // Let the user know that the gesture was verified. var gestureVerifiedCallback = this.GestureVerified; if (gestureVerifiedCallback != null) { gestureVerifiedCallback (gestureVerified); } } // Update the view. this.PictureLoginView.DrawingAreaView.SetNeedsDisplay(); }
public override void TouchesMoved(NSSet touches, UIEvent evt) { base.TouchesMoved (touches, evt); // If there isn't an active path gesture, create one now. if(this.currentPathGesture == null) { this.currentPathGesture = this.Factory.CreateGesture<KSPictureLoginPathGesture> (); this.currentPathGesture.Path.Add (this.touchStartLocation.NormalizePoint(this.PictureLoginView.DrawingAreaView.Bounds.Size)); } UITouch touch = touches.AnyObject as UITouch; var viewLocation = touch.LocationInView (this.PictureLoginView.DrawingAreaView); // Don't allow drawing outside of the image. viewLocation = viewLocation.LimitValues (this.PictureLoginView.DrawingAreaView.Bounds); this.PictureLoginView.DrawingAreaView.SetGestureVisualizationLayerPosition (viewLocation); this.PictureLoginView.DrawingAreaView.EnableGestureVisualizationLayer = true; // We only store the point if it does not collide with the previous validation rectangle. // This reduces the amount of points being stored. var currentRasterizationRect = viewLocation.GetValidationRect (KSPictureLoginController.GridRectSize); if (previousRasterizationRect == null || !currentRasterizationRect.IntersectsWith (this.previousRasterizationRect)) { // The current rect is different from the previous validation rect. Store the center of the rect as a new location. var normalizedPoint = currentRasterizationRect.Center.NormalizePoint (this.PictureLoginView.DrawingAreaView.Bounds.Size); this.currentPathGesture.Path.Add (normalizedPoint); this.PictureLoginView.DrawingAreaView.ContinuePathGesture (currentRasterizationRect.Center); // Remember the current validation rect for the next iteration. this.previousRasterizationRect = currentRasterizationRect; // Update the view. this.PictureLoginView.DrawingAreaView.SetNeedsDisplay(); } }
/// <summary> /// Clears the gestures recorded so far. Triggers the user's callback if registered. /// </summary> public void ClearGestures() { this.gestureCollection = new KSPictureLoginGestureCollection (); this.currentPathGesture = null; this.PictureLoginView.DrawingAreaView.ClearGestures (); }