Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
1822 DrawingView fix freezing (#1827)
Browse files Browse the repository at this point in the history
* 1822 DrawingView fix freezing

* Fix point normalization

* Fix changing original point collection

* remove linq
  • Loading branch information
VladislavAntonyuk authored Mar 15, 2022
1 parent cb0cd8c commit a13cd36
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 182 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Collections.ObjectModel;
using Xamarin.Forms;

namespace Xamarin.CommunityToolkit.UI.Views
{
/// <summary>
/// Extension methods to support <see cref="DrawingView"/>
/// </summary>
public static class Extensions
{
/// <summary>
/// Get smoothed path.
/// </summary>
public static ObservableCollection<Point> SmoothedPathWithGranularity(this ObservableCollection<Point> currentPoints,
int granularity)
{
var currentPointsCopy = new ObservableCollection<Point>(currentPoints);

// not enough points to smooth effectively, so return the original path and points.
if (currentPointsCopy.Count < granularity + 2)
return currentPointsCopy;

var smoothedPoints = new ObservableCollection<Point>();

// duplicate the first and last points as control points.
currentPointsCopy.Insert(0, currentPointsCopy[0]);
currentPointsCopy.Add(currentPointsCopy[currentPointsCopy.Count - 1]);

// add the first point
smoothedPoints.Add(currentPointsCopy[0]);

var currentPointsCount = currentPointsCopy.Count;
for (var index = 1; index < currentPointsCount - 2; index++)
{
var p0 = currentPointsCopy[index - 1];
var p1 = currentPointsCopy[index];
var p2 = currentPointsCopy[index + 1];
var p3 = currentPointsCopy[index + 2];

// add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines
for (var i = 1; i < granularity; i++)
{
var t = i * (1f / granularity);
var tt = t * t;
var ttt = tt * t;

// intermediate point
var mid = GetIntermediatePoint(p0, p1, p2, p3, t, tt, ttt);
smoothedPoints.Add(mid);
}

// add p2
smoothedPoints.Add(p2);
}

// add the last point
var last = currentPointsCopy[currentPointsCopy.Count - 1];
smoothedPoints.Add(last);
return smoothedPoints;
}

static Point GetIntermediatePoint(Point p0, Point p1, Point p2, Point p3, in float t, in float tt, in float ttt) =>
new Point
{
X = 0.5f *
((2f * p1.X) +
((p2.X - p0.X) * t) +
(((2f * p0.X) - (5f * p1.X) + (4f * p2.X) - p3.X) * tt) +
(((3f * p1.X) - p0.X - (3f * p2.X) + p3.X) * ttt)),
Y = 0.5f *
((2 * p1.Y) +
((p2.Y - p0.Y) * t) +
(((2 * p0.Y) - (5 * p1.Y) + (4 * p2.Y) - p3.Y) * tt) +
(((3 * p1.Y) - p0.Y - (3 * p2.Y) + p3.Y) * ttt))
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ protected override void OnDraw(Canvas? canvas)

if (canvas is not null && canvasBitmap is not null)
{
Draw(Element.Lines, canvas);

canvas.DrawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.DrawPath(drawPath, drawPaint);
}
Expand Down Expand Up @@ -122,7 +120,6 @@ public override bool OnTouchEvent(MotionEvent e)
}
};

drawCanvas?.DrawColor(Element.BackgroundColor.ToAndroid(), PorterDuff.Mode.Clear!);
drawPath.MoveTo(touchX, touchY);
break;
case MotionEventActions.Move:
Expand All @@ -144,6 +141,7 @@ public override bool OnTouchEvent(MotionEvent e)
if (Element.ClearOnFinish)
Element.Lines.Clear();

currentLine = null;
break;
default:
return false;
Expand Down Expand Up @@ -216,7 +214,9 @@ void Draw(IEnumerable<Line> lines, in Canvas canvas, Path? path = null)
foreach (var line in lines)
{
path ??= new Path();
var points = NormalizePoints(line.Points);
var points = NormalizePoints(line.EnableSmoothedPath
? line.Points.SmoothedPathWithGranularity(line.Granularity)
: line.Points);
path.MoveTo((float)points[0].X, (float)points[0].Y);
foreach (var (x, y) in points)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE
}
}

void OnLinesCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) => LoadPoints(surface!);
void OnLinesCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) => LoadPoints(surface);

void OnDrawingAreaExposed(object source, ExposeEventArgs args)
{
Expand Down Expand Up @@ -170,14 +170,20 @@ void DrawPoint(Context ctx, PointD pointD)
ctx.Stroke();
}

void LoadPoints(ImageSurface imageSurface)
void LoadPoints(ImageSurface? imageSurface)
{
if (imageSurface is null)
return;

var lines = Element.Lines;
if (lines.Count > 0)
{
foreach (var line in lines)
{
var stylusPoints = line.Points.Select(stylusPoint => new PointD(stylusPoint.X, stylusPoint.Y)).ToList();
var newPointsPath = line.EnableSmoothedPath
? line.Points.SmoothedPathWithGranularity(line.Granularity)
: line.Points;
var stylusPoints = newPointsPath.Select(stylusPoint => new PointD(stylusPoint.X, stylusPoint.Y)).ToList();
if (stylusPoints is { Count: > 0 })
{
previousPoint = stylusPoints[0];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using CoreGraphics;
using Foundation;
using UIKit;
using Xamarin.CommunityToolkit.UI.Views;
using Xamarin.CommunityToolkit.UI.Views.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

Expand All @@ -23,6 +23,7 @@ public class DrawingViewRenderer : ViewRenderer<DrawingView, UIView>
UIColor? lineColor;
CGPoint previousPoint;
Line? currentLine;
readonly List<ScrollViewRenderer> scrollViewParentRenderers = new();

public DrawingViewRenderer() => currentPath = new UIBezierPath();

Expand All @@ -49,6 +50,7 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE

public override void TouchesBegan(NSSet touches, UIEvent? evt)
{
DetectScrollViews();
SetParentTouches(false);

Element.Lines.CollectionChanged -= OnLinesCollectionChanged;
Expand Down Expand Up @@ -79,27 +81,29 @@ public override void TouchesMoved(NSSet touches, UIEvent? evt)
var touch = (UITouch)touches.AnyObject;
var currentPoint = touch.LocationInView(this);
AddPointToPath(currentPoint);
SetNeedsDisplay();
currentLine?.Points.Add(currentPoint.ToPoint());
}

public override void TouchesEnded(NSSet touches, UIEvent? evt)
{
if (currentLine != null)
{
UpdatePath(currentLine);
Element.Lines.Add(currentLine);
Element.OnDrawingLineCompleted(currentLine);
}

if (Element.ClearOnFinish)
Element.Lines.Clear();

currentLine = null;
SetParentTouches(true);
}

public override void TouchesCancelled(NSSet touches, UIEvent? evt)
{
InvokeOnMainThread(SetNeedsDisplay);
currentLine = null;
SetNeedsDisplay();
SetParentTouches(true);
}

Expand All @@ -109,19 +113,17 @@ public override void Draw(CGRect rect)
currentPath.Stroke();
}

void AddPointToPath(CGPoint currentPoint)
{
currentPath.AddLineTo(currentPoint);
SetNeedsDisplay();
}
void AddPointToPath(CGPoint currentPoint) => currentPath.AddLineTo(currentPoint);

void LoadPoints()
{
currentPath.RemoveAllPoints();
foreach (var line in Element.Lines)
{
UpdatePath(line);
var stylusPoints = line.Points.Select(point => new CGPoint(point.X, point.Y)).ToList();
var newPointsPath = line.EnableSmoothedPath
? line.Points.SmoothedPathWithGranularity(line.Granularity)
: line.Points;
var stylusPoints = newPointsPath.Select(point => new CGPoint(point.X, point.Y)).ToList();
if (stylusPoints.Count > 0)
{
previousPoint = stylusPoints[0];
Expand All @@ -134,82 +136,6 @@ void LoadPoints()
SetNeedsDisplay();
}

void UpdatePath(Line line)
{
Element.Lines.CollectionChanged -= OnLinesCollectionChanged;
var smoothedPoints = line.EnableSmoothedPath
? SmoothedPathWithGranularity(line.Points, line.Granularity)
: new ObservableCollection<Point>(line.Points);

line.Points.Clear();

foreach (var point in smoothedPoints)
line.Points.Add(point);

Element.Lines.CollectionChanged += OnLinesCollectionChanged;
}

ObservableCollection<Point> SmoothedPathWithGranularity(ObservableCollection<Point> currentPoints,
int granularity)
{
// not enough points to smooth effectively, so return the original path and points.
if (currentPoints.Count < granularity + 2)
return new ObservableCollection<Point>(currentPoints);

var smoothedPoints = new ObservableCollection<Point>();

// duplicate the first and last points as control points.
currentPoints.Insert(0, currentPoints[0]);
currentPoints.Add(currentPoints[^1]);

// add the first point
smoothedPoints.Add(currentPoints[0]);

var currentPointsCount = currentPoints.Count;
for (var index = 1; index < currentPointsCount - 2; index++)
{
var p0 = currentPoints[index - 1];
var p1 = currentPoints[index];
var p2 = currentPoints[index + 1];
var p3 = currentPoints[index + 2];

// add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines
for (var i = 1; i < granularity; i++)
{
var t = i * (1f / granularity);
var tt = t * t;
var ttt = tt * t;

// intermediate point
var mid = GetIntermediatePoint(p0, p1, p2, p3, t, tt, ttt);
smoothedPoints.Add(mid);
}

// add p2
smoothedPoints.Add(p2);
}

// add the last point
var last = currentPoints[^1];
smoothedPoints.Add(last);
return smoothedPoints;
}

Point GetIntermediatePoint(Point p0, Point p1, Point p2, Point p3, in float t, in float tt, in float ttt) =>
new Point
{
X = 0.5f *
((2f * p1.X) +
((p2.X - p0.X) * t) +
(((2f * p0.X) - (5f * p1.X) + (4f * p2.X) - p3.X) * tt) +
(((3f * p1.X) - p0.X - (3f * p2.X) + p3.X) * ttt)),
Y = 0.5f *
((2 * p1.Y) +
((p2.Y - p0.Y) * t) +
(((2 * p0.Y) - (5 * p1.Y) + (4 * p2.Y) - p3.Y) * tt) +
(((3 * p1.Y) - p0.Y - (3 * p2.Y) + p3.Y) * ttt))
};

protected override void Dispose(bool disposing)
{
if (disposed)
Expand All @@ -227,17 +153,28 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}

void SetParentTouches(bool enabled)
void DetectScrollViews()
{
if (scrollViewParentRenderers.Any())
return;

var parent = Superview;

while (parent != null)
{
if (parent.GetType() == typeof(ScrollViewRenderer))
((ScrollViewRenderer)parent).ScrollEnabled = enabled;
scrollViewParentRenderers.Add((ScrollViewRenderer)parent);

parent = parent.Superview;
}
}

void SetParentTouches(bool enabled)
{
foreach (var scrollViewParentRenderer in scrollViewParentRenderers)
{
scrollViewParentRenderer.ScrollEnabled = enabled;
}
}
}
}
Loading

0 comments on commit a13cd36

Please sign in to comment.