|
@@ -1,7 +1,10 @@
|
|
using System.Windows.Input;
|
|
using System.Windows.Input;
|
|
using Avalonia;
|
|
using Avalonia;
|
|
using Avalonia.Input;
|
|
using Avalonia.Input;
|
|
|
|
+using Drawie.Backend.Core.ColorsImpl;
|
|
using Drawie.Backend.Core.Numerics;
|
|
using Drawie.Backend.Core.Numerics;
|
|
|
|
+using Drawie.Backend.Core.Surfaces.PaintImpl;
|
|
|
|
+using Drawie.Backend.Core.Text;
|
|
using Drawie.Backend.Core.Vector;
|
|
using Drawie.Backend.Core.Vector;
|
|
using Drawie.Numerics;
|
|
using Drawie.Numerics;
|
|
using PixiEditor.Extensions.UI.Overlays;
|
|
using PixiEditor.Extensions.UI.Overlays;
|
|
@@ -68,7 +71,7 @@ public class VectorPathOverlay : Overlay
|
|
|
|
|
|
AddHandle(transformHandle);
|
|
AddHandle(transformHandle);
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
protected override void ZoomChanged(double newZoom)
|
|
protected override void ZoomChanged(double newZoom)
|
|
{
|
|
{
|
|
dashedStroke.UpdateZoom((float)newZoom);
|
|
dashedStroke.UpdateZoom((float)newZoom);
|
|
@@ -100,17 +103,21 @@ public class VectorPathOverlay : Overlay
|
|
private void RenderHandles(Canvas context)
|
|
private void RenderHandles(Canvas context)
|
|
{
|
|
{
|
|
bool anySelected = false;
|
|
bool anySelected = false;
|
|
- int anchor = 0;
|
|
|
|
|
|
+ int globalAnchor = 0;
|
|
int controlPoint = 0;
|
|
int controlPoint = 0;
|
|
|
|
+ int subPath = 1;
|
|
int anchorCount = GetAnchorCount();
|
|
int anchorCount = GetAnchorCount();
|
|
|
|
+ int closeAtIndexInSubPath = GetCloseIndexInSubPath(Path, subPath);
|
|
|
|
+ int anchorCountInSubPath = CountAnchorsInSubPath(Path, subPath);
|
|
|
|
+
|
|
foreach (var verb in Path)
|
|
foreach (var verb in Path)
|
|
{
|
|
{
|
|
- if (anchor == anchorCount - 1 && !anySelected)
|
|
|
|
|
|
+ if (globalAnchor == anchorCount - 1 && !anySelected)
|
|
{
|
|
{
|
|
- GetHandleAt(anchor).IsSelected = true;
|
|
|
|
|
|
+ GetHandleAt(globalAnchor).IsSelected = true;
|
|
}
|
|
}
|
|
|
|
|
|
- anySelected = anySelected || GetHandleAt(anchor).IsSelected;
|
|
|
|
|
|
+ anySelected = anySelected || GetHandleAt(globalAnchor).IsSelected;
|
|
|
|
|
|
VecF verbPointPos = GetVerbPointPos(verb);
|
|
VecF verbPointPos = GetVerbPointPos(verb);
|
|
|
|
|
|
@@ -143,18 +150,35 @@ public class VectorPathOverlay : Overlay
|
|
}
|
|
}
|
|
else if (verb.verb == PathVerb.Close)
|
|
else if (verb.verb == PathVerb.Close)
|
|
{
|
|
{
|
|
|
|
+ subPath++;
|
|
|
|
+ closeAtIndexInSubPath = GetCloseIndexInSubPath(Path, subPath);
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
- if (anchor == anchorCount)
|
|
|
|
|
|
+ if (globalAnchor == closeAtIndexInSubPath - 1)
|
|
{
|
|
{
|
|
|
|
+ //globalAnchor++;
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
- anchorHandles[anchor].Position = new VecD(verbPointPos.X, verbPointPos.Y);
|
|
|
|
- anchorHandles[anchor].Draw(context);
|
|
|
|
|
|
+ if (globalAnchor == anchorCount)
|
|
|
|
+ {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ using Font font = Font.CreateDefault();
|
|
|
|
+ font.FontSize = 24 / ZoomScale;
|
|
|
|
+
|
|
|
|
+ using Paint paint = new Paint();
|
|
|
|
+ paint.Color = Colors.White;
|
|
|
|
+
|
|
|
|
+ anchorHandles[globalAnchor].Position = new VecD(verbPointPos.X, verbPointPos.Y);
|
|
|
|
+ anchorHandles[globalAnchor].Draw(context);
|
|
|
|
+
|
|
|
|
+ context.DrawText($"i: {globalAnchor}, sub: {subPath}", new VecD(verbPointPos.X, verbPointPos.Y), font,
|
|
|
|
+ paint);
|
|
|
|
|
|
- anchor++;
|
|
|
|
|
|
+ globalAnchor++;
|
|
}
|
|
}
|
|
|
|
|
|
transformHandle.Position = Path.TightBounds.BottomRight + new VecD(1, 1);
|
|
transformHandle.Position = Path.TightBounds.BottomRight + new VecD(1, 1);
|
|
@@ -163,11 +187,38 @@ public class VectorPathOverlay : Overlay
|
|
|
|
|
|
private int GetAnchorCount()
|
|
private int GetAnchorCount()
|
|
{
|
|
{
|
|
- return Path.VerbCount - (Path.IsClosed ? 2 : 0);
|
|
|
|
|
|
+ int closeVerbs = Path.Count(x => x.verb == PathVerb.Close);
|
|
|
|
+ return Path.VerbCount - closeVerbs * 2;
|
|
}
|
|
}
|
|
|
|
|
|
- private void AdjustHandles(int pointsCount)
|
|
|
|
|
|
+ private int GetCloseIndexInSubPath(VectorPath path, int subPath)
|
|
{
|
|
{
|
|
|
|
+ int closeIndex = 0;
|
|
|
|
+ int subPathIndex = 1;
|
|
|
|
+
|
|
|
|
+ foreach (var data in path)
|
|
|
|
+ {
|
|
|
|
+ if (data.verb == PathVerb.Close)
|
|
|
|
+ {
|
|
|
|
+ if (subPathIndex == subPath)
|
|
|
|
+ {
|
|
|
|
+ return closeIndex;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ subPathIndex++;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ closeIndex++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void AdjustHandles(VectorPath path)
|
|
|
|
+ {
|
|
|
|
+ int pointsCount = GetPointCount(path);
|
|
int anchorCount = anchorHandles.Count;
|
|
int anchorCount = anchorHandles.Count;
|
|
int totalHandles = anchorCount + controlPointHandles.Count;
|
|
int totalHandles = anchorCount + controlPointHandles.Count;
|
|
if (totalHandles != pointsCount)
|
|
if (totalHandles != pointsCount)
|
|
@@ -194,6 +245,7 @@ public class VectorPathOverlay : Overlay
|
|
|
|
|
|
ConnectControlPointsToAnchors();
|
|
ConnectControlPointsToAnchors();
|
|
}
|
|
}
|
|
|
|
+
|
|
Refresh();
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
|
|
@@ -234,6 +286,11 @@ public class VectorPathOverlay : Overlay
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private int GetPointCount(VectorPath path)
|
|
|
|
+ {
|
|
|
|
+ return path.PointCount - path.Count(x => x.verb == PathVerb.Close);
|
|
|
|
+ }
|
|
|
|
+
|
|
private int CalculateMissingControlPoints(int handleCount)
|
|
private int CalculateMissingControlPoints(int handleCount)
|
|
{
|
|
{
|
|
int totalControlPoints = 0;
|
|
int totalControlPoints = 0;
|
|
@@ -370,6 +427,32 @@ public class VectorPathOverlay : Overlay
|
|
Path = newPath;
|
|
Path = newPath;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private int CountAnchorsInSubPath(VectorPath path, int subPath)
|
|
|
|
+ {
|
|
|
|
+ int anchorCount = 0;
|
|
|
|
+ int subPathIndex = 1;
|
|
|
|
+
|
|
|
|
+ foreach (var data in path)
|
|
|
|
+ {
|
|
|
|
+ if (data.verb == PathVerb.Close)
|
|
|
|
+ {
|
|
|
|
+ if (subPathIndex == subPath)
|
|
|
|
+ {
|
|
|
|
+ return anchorCount - 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ subPathIndex++;
|
|
|
|
+ anchorCount = 0;
|
|
|
|
+ }
|
|
|
|
+ else if (data.verb != PathVerb.Done)
|
|
|
|
+ {
|
|
|
|
+ anchorCount++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return anchorCount;
|
|
|
|
+ }
|
|
|
|
+
|
|
private bool IsFirstHandle(AnchorHandle handle)
|
|
private bool IsFirstHandle(AnchorHandle handle)
|
|
{
|
|
{
|
|
return anchorHandles.IndexOf(handle) == 0;
|
|
return anchorHandles.IndexOf(handle) == 0;
|
|
@@ -476,6 +559,11 @@ public class VectorPathOverlay : Overlay
|
|
VecD targetSymmetryPos = GetMirroredControlPoint((VecF)targetPos, (VecF)handle.Position);
|
|
VecD targetSymmetryPos = GetMirroredControlPoint((VecF)targetPos, (VecF)handle.Position);
|
|
|
|
|
|
bool ctrlPressed = args.Modifiers.HasFlag(KeyModifiers.Control);
|
|
bool ctrlPressed = args.Modifiers.HasFlag(KeyModifiers.Control);
|
|
|
|
+ int subPathNum = 1;
|
|
|
|
+
|
|
|
|
+ VecF firstSubPathPoint = Path.Points.First();
|
|
|
|
+ bool isSubPathClosed = IsSubPathClosed(subPathNum, Path);
|
|
|
|
+ int lastAnchorAt = GetCloseAnchorIndexInSubPath(Path, subPathNum);
|
|
foreach (var data in Path)
|
|
foreach (var data in Path)
|
|
{
|
|
{
|
|
VecF point;
|
|
VecF point;
|
|
@@ -489,9 +577,10 @@ public class VectorPathOverlay : Overlay
|
|
}
|
|
}
|
|
|
|
|
|
point = data.points[0];
|
|
point = data.points[0];
|
|
- point = TryApplyNewPos(args, i, index, point, Path.IsClosed, data.points[0]);
|
|
|
|
|
|
+ point = TryApplyNewPos(args, i, index, point, isSubPathClosed, data.points[0], lastAnchorAt);
|
|
|
|
|
|
newPath.MoveTo(point);
|
|
newPath.MoveTo(point);
|
|
|
|
+ firstSubPathPoint = point;
|
|
previousDelta = point - data.points[0];
|
|
previousDelta = point - data.points[0];
|
|
break;
|
|
break;
|
|
case PathVerb.Line:
|
|
case PathVerb.Line:
|
|
@@ -502,7 +591,7 @@ public class VectorPathOverlay : Overlay
|
|
}
|
|
}
|
|
|
|
|
|
point = data.points[1];
|
|
point = data.points[1];
|
|
- point = TryApplyNewPos(args, i, index, point, Path.IsClosed, newPath.Points[0]);
|
|
|
|
|
|
+ point = TryApplyNewPos(args, i, index, point, isSubPathClosed, firstSubPathPoint, lastAnchorAt);
|
|
|
|
|
|
newPath.LineTo(point);
|
|
newPath.LineTo(point);
|
|
break;
|
|
break;
|
|
@@ -517,7 +606,7 @@ public class VectorPathOverlay : Overlay
|
|
else
|
|
else
|
|
{
|
|
{
|
|
point = data.points[3];
|
|
point = data.points[3];
|
|
- point = TryApplyNewPos(args, i, index, point, Path.IsClosed, newPath.Points[0]);
|
|
|
|
|
|
+ point = TryApplyNewPos(args, i, index, point, isSubPathClosed, firstSubPathPoint, lastAnchorAt);
|
|
|
|
|
|
VecF mid1Delta = previousDelta;
|
|
VecF mid1Delta = previousDelta;
|
|
|
|
|
|
@@ -534,11 +623,47 @@ public class VectorPathOverlay : Overlay
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
- i++;
|
|
|
|
|
|
+ if (data.verb == PathVerb.Close)
|
|
|
|
+ {
|
|
|
|
+ subPathNum++;
|
|
|
|
+ isSubPathClosed = IsSubPathClosed(subPathNum, Path);
|
|
|
|
+ lastAnchorAt = GetCloseAnchorIndexInSubPath(Path, subPathNum);
|
|
|
|
+ i--;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ i++;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
Path = newPath;
|
|
Path = newPath;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ private int GetCloseAnchorIndexInSubPath(VectorPath path, int subPath)
|
|
|
|
+ {
|
|
|
|
+ int anchorIndex = 0;
|
|
|
|
+ int subPathIndex = 1;
|
|
|
|
+
|
|
|
|
+ foreach (var data in path)
|
|
|
|
+ {
|
|
|
|
+ if (data.verb == PathVerb.Close)
|
|
|
|
+ {
|
|
|
|
+ if (subPathIndex == subPath)
|
|
|
|
+ {
|
|
|
|
+ return anchorIndex - 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ subPathIndex++;
|
|
|
|
+ anchorIndex--;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ anchorIndex++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
|
|
private void OnControlPointDrag(Handle source, OverlayPointerArgs args)
|
|
private void OnControlPointDrag(Handle source, OverlayPointerArgs args)
|
|
{
|
|
{
|
|
@@ -624,6 +749,26 @@ public class VectorPathOverlay : Overlay
|
|
newPath.CubicTo(controlPoint1, controlPoint2, endPoint);
|
|
newPath.CubicTo(controlPoint1, controlPoint2, endPoint);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private bool IsSubPathClosed(int subPathIndex, VectorPath path)
|
|
|
|
+ {
|
|
|
|
+ int subPath = 1;
|
|
|
|
+
|
|
|
|
+ foreach (var data in path)
|
|
|
|
+ {
|
|
|
|
+ if (data.verb == PathVerb.Close)
|
|
|
|
+ {
|
|
|
|
+ if (subPathIndex == subPath)
|
|
|
|
+ {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ subPath++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
private VecD GetMirroredControlPoint(VecF controlPoint, VecF anchor)
|
|
private VecD GetMirroredControlPoint(VecF controlPoint, VecF anchor)
|
|
{
|
|
{
|
|
return new VecD(2 * anchor.X - controlPoint.X, 2 * anchor.Y - controlPoint.Y);
|
|
return new VecD(2 * anchor.X - controlPoint.X, 2 * anchor.Y - controlPoint.Y);
|
|
@@ -653,13 +798,13 @@ public class VectorPathOverlay : Overlay
|
|
}
|
|
}
|
|
|
|
|
|
private VecF TryApplyNewPos(OverlayPointerArgs args, int i, int index, VecF point, bool firstIsLast,
|
|
private VecF TryApplyNewPos(OverlayPointerArgs args, int i, int index, VecF point, bool firstIsLast,
|
|
- VecF firstPoint)
|
|
|
|
|
|
+ VecF firstPoint, int lastAnchorAt)
|
|
{
|
|
{
|
|
- if (i == index)
|
|
|
|
|
|
+ if (i == index && i != lastAnchorAt)
|
|
{
|
|
{
|
|
point = (VecF)ApplySymmetry(args.Point);
|
|
point = (VecF)ApplySymmetry(args.Point);
|
|
}
|
|
}
|
|
- else if (firstIsLast && i == GetAnchorCount())
|
|
|
|
|
|
+ else if (firstIsLast && i == lastAnchorAt)
|
|
{
|
|
{
|
|
point = firstPoint;
|
|
point = firstPoint;
|
|
}
|
|
}
|
|
@@ -770,7 +915,7 @@ public class VectorPathOverlay : Overlay
|
|
|
|
|
|
private void PathChanged(VectorPath newPath)
|
|
private void PathChanged(VectorPath newPath)
|
|
{
|
|
{
|
|
- AdjustHandles(newPath.PointCount - (newPath.IsClosed ? 1 : 0));
|
|
|
|
|
|
+ AdjustHandles(newPath);
|
|
}
|
|
}
|
|
|
|
|
|
private static void DefaultPathVerb((PathVerb verb, VecF[] points) data, VectorPath newPath)
|
|
private static void DefaultPathVerb((PathVerb verb, VecF[] points) data, VectorPath newPath)
|
|
@@ -814,7 +959,7 @@ public class VectorPathOverlay : Overlay
|
|
else
|
|
else
|
|
{
|
|
{
|
|
var path = args.NewValue.Value;
|
|
var path = args.NewValue.Value;
|
|
- overlay.AdjustHandles(path.PointCount - (path.IsClosed ? 1 : 0));
|
|
|
|
|
|
+ overlay.AdjustHandles(path);
|
|
overlay.IsVisible = true;
|
|
overlay.IsVisible = true;
|
|
}
|
|
}
|
|
|
|
|