Ejemplo n.º 1
0
        public void AdvanceWithSample(StrokeSample incomingSample)
        {
            var sampleAfter = SampleAfter;

            if (sampleAfter != null)
            {
                SampleBefore     = FromSample;
                FromSample       = ToSample;
                ToSample         = SampleAfter;
                SampleAfter      = incomingSample;
                FromSampleIndex += 1;
            }
        }
Ejemplo n.º 2
0
        public void Update(StrokeSample sample, int index)
        {
            if (index == 0)
            {
                hasUpdatesFromStartTo = 0;
            }
            else if (hasUpdatesFromStartTo.HasValue && index == hasUpdatesFromStartTo.Value + 1)
            {
                hasUpdatesFromStartTo = index;
            }
            else if (!hasUpdatesAtEndFrom.HasValue || hasUpdatesAtEndFrom.Value > index)
            {
                hasUpdatesAtEndFrom = index;
            }

            Samples [index] = sample;

            sampleIndicesExpectingUpdates.Remove(index);
            if (sampleIndicesExpectingUpdates.Count == 0)
            {
                ReceivedAllNeededUpdatesBlock?.Invoke();
                ReceivedAllNeededUpdatesBlock = null;
            }
        }
Ejemplo n.º 3
0
        public int Add(StrokeSample sample)
        {
            var resultIndex = Samples.Count;

            if (!hasUpdatesAtEndFrom.HasValue)
            {
                hasUpdatesAtEndFrom = resultIndex;
            }

            Samples.Add(sample);

            if (PreviousPredictedSamples.Count == 0)
            {
                PreviousPredictedSamples.AddRange(PredictedSamples);
            }

            if (sample.EstimatedPropertiesExpectingUpdates != 0)
            {
                sampleIndicesExpectingUpdates.Add(resultIndex);
            }

            PredictedSamples.Clear();
            return(resultIndex);
        }
Ejemplo n.º 4
0
 public void AddPredicted(StrokeSample sample)
 {
     PredictedSamples.Add(sample);
 }
Ejemplo n.º 5
0
 public StrokeSegment(StrokeSample sample)
 {
     SampleAfter     = sample;
     FromSampleIndex = -2;
 }
Ejemplo n.º 6
0
        // Note: this is not a particularily efficient way to draw a great stroke path
        // with CoreGraphics.It is just a way to produce an interesting looking result.
        // For a real world example you would reuse and cache CGPaths and draw longer
        // paths instead of an aweful lot of tiny ones, etc.You would also respect the
        // draw rect to cull your draw requests.And you would use bezier paths to
        // interpolate between the points to get a smooother curve.
        void Draw(Stroke stroke)
        {
            var updateRanges = stroke.UpdatedRanges();

            if (DisplayOptions == StrokeViewDisplayOptions.Debug)
            {
                for (int index = 0; index < DirtyRectViews.Count; index++)
                {
                    var dirtyRectView = DirtyRectViews [index];
                    dirtyRectView.Alpha = 0;
                    if (index < updateRanges.Length)
                    {
                        dirtyRectView.Alpha = 1;
                        var range   = updateRanges [index];
                        var strokes = stroke.Samples.Skip(range.LowerBound)
                                      .Take(range.UpperBound - range.LowerBound + 1);
                        dirtyRectView.Frame = DirtyRectForSampleStride(strokes);
                    }
                }
            }

            lastEstimatedSample = null;
            stroke.ClearUpdateInfo();
            var sampleCount = stroke.Samples.Count;

            if (sampleCount <= 0)
            {
                return;
            }
            var context = UIGraphics.GetCurrentContext();

            if (context == null)
            {
                return;
            }
            var strokeColor = UIColor.Black;

            Action lineSettings;
            Action forceEstimatedLineSettings;

            if (displayOptions == StrokeViewDisplayOptions.Debug)
            {
                lineSettings = () => {
                    context.SetLineWidth(0.5f);
                    context.SetStrokeColor(UIColor.White.CGColor);
                };
                forceEstimatedLineSettings = () => {
                    context.SetLineWidth(0.5f);
                    context.SetStrokeColor(UIColor.Blue.CGColor);
                };
            }
            else
            {
                lineSettings = () => {
                    context.SetLineWidth(0.25f);
                    context.SetStrokeColor(strokeColor.CGColor);
                };
                forceEstimatedLineSettings = lineSettings;
            }

            Action azimuthSettings = () => {
                context.SetLineWidth(1.5f);
                context.SetStrokeColor(UIColor.Orange.CGColor);
            };

            Action altitudeSettings = () => {
                context.SetLineWidth(0.5f);
                context.SetStrokeColor(strokeColor.CGColor);
            };
            var forceMultiplier = 2f;
            var forceOffset     = 0.1f;

            var fillColorRegular   = UIColor.Black.CGColor;
            var fillColorCoalesced = UIColor.LightGray.CGColor;
            var fillColorPredicted = UIColor.Red.CGColor;

            CGVector?lockedAzimuthUnitVector      = null;
            var      azimuthLockAltitudeThreshold = NMath.PI / 2 * 0.8f;        // locking azimuth at 80% altitude

            lineSettings();

            Func <StrokeSample, nfloat> forceAccessBlock = sample => {
                return(sample.ForceWithDefault());
            };

            if (DisplayOptions == StrokeViewDisplayOptions.Ink)
            {
                forceAccessBlock = sample => {
                    return(sample.PerpendicularForce());
                };
            }

            // Make the force influence less pronounced for the calligraphy pen.
            if (DisplayOptions == StrokeViewDisplayOptions.Calligraphy)
            {
                var prevGetter = forceAccessBlock;
                forceAccessBlock = sample => {
                    return(NMath.Max(prevGetter(sample), 1));
                };
                // make force value less pronounced
                forceMultiplier = 1;
                forceOffset     = 10;
            }

            var previousGetter = forceAccessBlock;

            forceAccessBlock = sample => {
                return(previousGetter(sample) * forceMultiplier + forceOffset);
            };

            StrokeSample heldFromSample           = null;
            CGVector?    heldFromSampleUnitVector = null;

            Action <StrokeSegment> draw = segment => {
                var toSample = segment.ToSample;
                if (toSample != null)
                {
                    StrokeSample fromSample = heldFromSample ?? segment.FromSample;

                    // Skip line segments that are too short.
                    var dist = Vector(fromSample.Location, toSample.Location).Quadrance();
                    if (dist < 0.003f)
                    {
                        if (heldFromSample == null)
                        {
                            heldFromSample           = fromSample;
                            heldFromSampleUnitVector = segment.FromSampleUnitNormal;
                        }
                        return;
                    }

                    if (toSample.Predicted)
                    {
                        if (displayOptions == StrokeViewDisplayOptions.Debug)
                        {
                            context.SetFillColor(fillColorPredicted);
                        }
                    }
                    else
                    {
                        bool coalesced = displayOptions == StrokeViewDisplayOptions.Debug && fromSample.Coalesced;
                        context.SetFillColor(coalesced ? fillColorCoalesced : fillColorRegular);
                    }

                    if (displayOptions == StrokeViewDisplayOptions.Calligraphy)
                    {
                        var fromAzimuthUnitVector = Stroke.CalligraphyFallbackAzimuthUnitVector;
                        var toAzimuthUnitVector   = Stroke.CalligraphyFallbackAzimuthUnitVector;

                        if (fromSample.Azimuth.HasValue)
                        {
                            if (!lockedAzimuthUnitVector.HasValue)
                            {
                                lockedAzimuthUnitVector = fromSample.GetAzimuthUnitVector();
                            }
                            fromAzimuthUnitVector = fromSample.GetAzimuthUnitVector();
                            toAzimuthUnitVector   = toSample.GetAzimuthUnitVector();

                            if (fromSample.Altitude.Value > azimuthLockAltitudeThreshold)
                            {
                                fromAzimuthUnitVector = lockedAzimuthUnitVector.Value;
                            }

                            if (toSample.Altitude.Value > azimuthLockAltitudeThreshold)
                            {
                                toAzimuthUnitVector = lockedAzimuthUnitVector.Value;
                            }
                            else
                            {
                                lockedAzimuthUnitVector = toAzimuthUnitVector;
                            }
                        }

                        // Rotate 90 degrees
                        var calligraphyTransform = CGAffineTransform.MakeRotation(NMath.PI / 2);
                        fromAzimuthUnitVector = fromAzimuthUnitVector.Apply(calligraphyTransform);
                        toAzimuthUnitVector   = toAzimuthUnitVector.Apply(calligraphyTransform);

                        var fromUnitVector = fromAzimuthUnitVector.Mult(forceAccessBlock(fromSample));
                        var toUnitVector   = toAzimuthUnitVector.Mult(forceAccessBlock(toSample));

                        context.BeginPath();
                        context.Move(fromSample.Location.Add(fromUnitVector));
                        context.AddLine(toSample.Location.Add(toUnitVector));
                        context.AddLine(toSample.Location.Sub(toUnitVector));
                        context.AddLine(fromSample.Location.Sub(fromUnitVector));
                        context.ClosePath();

                        context.DrawPath(CGPathDrawingMode.FillStroke);
                    }
                    else
                    {
                        var fromUnitVector = (heldFromSampleUnitVector.HasValue ? heldFromSampleUnitVector.Value : segment.FromSampleUnitNormal).Mult(forceAccessBlock(fromSample));

                        var toUnitVector     = segment.ToSampleUnitNormal.Mult(forceAccessBlock(toSample));
                        var isForceEstimated = fromSample.EstimatedProperties.HasFlag(UITouchProperties.Force) ||
                                               toSample.EstimatedProperties.HasFlag(UITouchProperties.Force);

                        if (isForceEstimated)
                        {
                            if (lastEstimatedSample == null)
                            {
                                lastEstimatedSample = new EstimatedSample {
                                    Index = segment.FromSampleIndex + 1, Sample = toSample
                                }
                            }
                            ;
                            forceEstimatedLineSettings();
                        }
                        else
                        {
                            lineSettings();
                        }

                        context.BeginPath();
                        context.Move(fromSample.Location.Add(fromUnitVector));
                        context.AddLine(toSample.Location.Add(toUnitVector));
                        context.AddLine(toSample.Location.Sub(toUnitVector));
                        context.AddLine(fromSample.Location.Sub(fromUnitVector));
                        context.ClosePath();
                        context.DrawPath(CGPathDrawingMode.FillStroke);
                    }

                    var isEstimated = fromSample.EstimatedProperties.HasFlag(UITouchProperties.Azimuth);
                    if (fromSample.Azimuth.HasValue && (!fromSample.Coalesced || isEstimated) && !fromSample.Predicted && displayOptions == StrokeViewDisplayOptions.Debug)
                    {
                        var length            = 20f;
                        var azimuthUnitVector = fromSample.GetAzimuthUnitVector();
                        var azimuthTarget     = fromSample.Location.Add(azimuthUnitVector.Mult(length));

                        var altitudeStart  = azimuthTarget.Add(azimuthUnitVector.Mult(length / -2));
                        var altitudeTarget = altitudeStart.Add((azimuthUnitVector.Mult(length / 2)).Apply(CGAffineTransform.MakeRotation(fromSample.Altitude.Value)));

                        // Draw altitude as black line coming from the center of the azimuth.
                        altitudeSettings();

                        context.BeginPath();
                        context.Move(altitudeStart);
                        context.AddLine(altitudeTarget);
                        context.StrokePath();

                        // Draw azimuth as orange (or blue if estimated) line.
                        azimuthSettings();

                        if (isEstimated)
                        {
                            context.SetStrokeColor(UIColor.Blue.CGColor);
                        }
                        context.BeginPath();
                        context.Move(fromSample.Location);
                        context.AddLine(azimuthTarget);
                        context.StrokePath();
                    }

                    heldFromSample           = null;
                    heldFromSampleUnitVector = null;
                }
            };

            if (stroke.Samples.Count == 1)
            {
                // Construct a face segment to draw for a stroke that is only one point.
                var sample = stroke.Samples [0];

                var tempSampleFrom = new StrokeSample {
                    Timestamp           = sample.Timestamp,
                    Location            = sample.Location.Add(new CGVector(-0.5f, 0)),
                    Coalesced           = false,
                    Predicted           = false,
                    Force               = sample.Force,
                    Azimuth             = sample.Azimuth,
                    Altitude            = sample.Altitude,
                    EstimatedProperties = sample.EstimatedProperties
                };

                var tempSampleTo = new StrokeSample {
                    Timestamp           = sample.Timestamp,
                    Location            = sample.Location.Add(new CGVector(0.5f, 0)),
                    Coalesced           = false,
                    Predicted           = false,
                    Force               = sample.Force,
                    Azimuth             = sample.Azimuth,
                    Altitude            = sample.Altitude,
                    EstimatedProperties = sample.EstimatedProperties
                };

                var segment = new StrokeSegment(tempSampleFrom);
                segment.AdvanceWithSample(tempSampleTo);
                segment.AdvanceWithSample(null);

                draw(segment);
            }
            else
            {
                foreach (var segment in stroke)
                {
                    draw(segment);
                }
            }
        }
Ejemplo n.º 7
0
        void Collect(Stroke stroke, UITouch touch, UIView view, bool coalesced, bool predicted)
        {
            if (view == null)
            {
                throw new ArgumentNullException();
            }

            // Only collect samples that actually moved in 2D space.
            var location       = touch.GetPreciseLocation(view);
            var previousSample = stroke.Samples.LastOrDefault();

            if (Distance(previousSample?.Location, location) < 0.003)
            {
                return;
            }

            var sample = new StrokeSample {
                Timestamp = touch.Timestamp,
                Location  = location,
                Coalesced = coalesced,
                Predicted = predicted
            };
            bool collectForce = touch.Type == UITouchType.Stylus || view.TraitCollection.ForceTouchCapability == UIForceTouchCapability.Available;

            if (collectForce)
            {
                sample.Force = touch.Force;
            }

            if (touch.Type == UITouchType.Stylus)
            {
                var estimatedProperties = touch.EstimatedProperties;
                sample.EstimatedProperties = estimatedProperties;
                sample.EstimatedPropertiesExpectingUpdates = touch.EstimatedPropertiesExpectingUpdates;
                sample.Altitude = touch.AltitudeAngle;
                sample.Azimuth  = touch.GetAzimuthAngle(view);

                if (stroke.Samples.Count == 0 && estimatedProperties.HasFlag(UITouchProperties.Azimuth))
                {
                    stroke.ExpectsAltitudeAzimuthBackfill = true;
                }
                else if (stroke.ExpectsAltitudeAzimuthBackfill &&
                         !estimatedProperties.HasFlag(UITouchProperties.Azimuth))
                {
                    for (int index = 0; index < stroke.Samples.Count; index++)
                    {
                        var priorSample   = stroke.Samples [index];
                        var updatedSample = priorSample;

                        if (updatedSample.EstimatedProperties.HasFlag(UITouchProperties.Altitude))
                        {
                            updatedSample.EstimatedProperties &= ~UITouchProperties.Altitude;
                            updatedSample.Altitude             = sample.Altitude;
                        }
                        if (updatedSample.EstimatedProperties.HasFlag(UITouchProperties.Azimuth))
                        {
                            updatedSample.EstimatedProperties &= ~UITouchProperties.Azimuth;
                            updatedSample.Azimuth              = sample.Azimuth;
                        }
                        stroke.Update(updatedSample, index);
                    }
                    stroke.ExpectsAltitudeAzimuthBackfill = false;
                }
            }

            if (predicted)
            {
                stroke.AddPredicted(sample);
            }
            else
            {
                var index = stroke.Add(sample);
                if (touch.EstimatedPropertiesExpectingUpdates != 0)
                {
                    outstandingUpdateIndexes [touch.EstimationUpdateIndex] = new StrokeIndex {
                        Stroke = stroke,
                        Index  = index
                    };
                }
            }
        }