void EditPath(Shape shape) { // get the existing verts PathSegment[] oldSegments = shape.GetPathWorldSegments(); List <PathSegment> segments = new List <PathSegment>(oldSegments); bool hasMaxSegments = segments.Count == Shape.MaxPathSegments; // are we in delete mode? what color should handles be? Color pink = new Color(1, 0, 0.75f); bool deleteMode = false; if ((Event.current.control || Event.current.command) && segments.Count > 1) { Handles.color = Color.red; deleteMode = true; } else { Handles.color = pink; } bool splitMode = Event.current.shift; Vector3 mouseWorldPos = GetMouseWorldPos(); bool isWithinBounds = shape.PointIsWithinShapeBounds(mouseWorldPos); // drag handle result for getting info from our handles CustomHandles.DragHandleResult dhResult; // draw handles for each existing point and check if they've been moved or clicked bool changed = false; for (int i = segments.Count * 3 - 1; i >= 0; i--) { PathSegment segment = segments[i / 3]; Vector3 p = segment.p0; bool isInfluencePoint = false; if (i % 3 == 1) { p = segment.p1; isInfluencePoint = true; } else if (i % 3 == 2) { p = segment.p2; } if (deleteMode && isInfluencePoint) { continue; } float size = 0.04f * HandleUtility.GetHandleSize(p); #if UNITY_5_6_OR_NEWER Handles.CapFunction cap = Handles.RectangleHandleCap; #else Handles.DrawCapFunction cap = Handles.RectangleCap; #endif if (isInfluencePoint) { #if UNITY_5_6_OR_NEWER cap = Handles.CircleHandleCap; #else cap = Handles.CircleCap; #endif size = 0.05f * HandleUtility.GetHandleSize(p); } if (isInfluencePoint) { Color oldColor = Handles.color; Handles.color = Color.grey; Handles.DrawDottedLine(p, segment.p0, HandleUtility.GetHandleSize(p)); Handles.DrawDottedLine(p, segment.p2, HandleUtility.GetHandleSize(p)); Handles.color = oldColor; } Vector3 newPos = CustomHandles.DragHandle(p, size, cap, pink, out dhResult); if (deleteMode && !isInfluencePoint && dhResult == CustomHandles.DragHandleResult.LMBPress) { // the user clicked on the handle while in delete mode, so delete the segment segments.RemoveAt(i / 3); changed = true; break; } else if (!deleteMode && isInfluencePoint && dhResult == CustomHandles.DragHandleResult.LMBDoubleClick) { segment.MakeLinear(); segments[i / 3] = segment; changed = true; } else if (!deleteMode && newPos != p) { // the handle has been dragged, so move the point to the new position if (isInfluencePoint || isWithinBounds) { MovePathPoint(segments, i, newPos, moveConnected: !splitMode); changed = true; } } else if (!splitMode && !deleteMode && !isInfluencePoint && dhResult == CustomHandles.DragHandleResult.LMBRelease) { // the handle has been released. snap it to any nearby points. for (int c = 0; c < segments.Count; c++) { PathSegment seg2 = segments[c]; if (seg2.p0 != newPos && Vector2.Distance(seg2.p0, newPos) < HandleUtility.GetHandleSize(newPos) * 0.25f) { newPos = seg2.p0; break; } if (seg2.p2 != newPos && Vector2.Distance(seg2.p2, newPos) < HandleUtility.GetHandleSize(newPos) * 0.25f) { newPos = seg2.p2; break; } } MovePathPoint(segments, i, newPos, moveConnected: true); changed = true; } } // check if the mouse is hovering over a space where we could add a new point, // and draw it if so bool closeToExistingPoint = false; if (!changed && !hasMaxSegments && !deleteMode) { foreach (PathSegment s in segments) { // if close to an existing point, we don't want to add a new one if (Vector2.Distance(HandleUtility.WorldToGUIPoint(mouseWorldPos), HandleUtility.WorldToGUIPoint(s.p0)) < 15) { closeToExistingPoint = true; break; } if (Vector2.Distance(HandleUtility.WorldToGUIPoint(mouseWorldPos), HandleUtility.WorldToGUIPoint(s.p1)) < 15) { closeToExistingPoint = true; break; } if (Vector2.Distance(HandleUtility.WorldToGUIPoint(mouseWorldPos), HandleUtility.WorldToGUIPoint(s.p2)) < 15) { closeToExistingPoint = true; break; } } if (!closeToExistingPoint && isWithinBounds) { // not too close to an existing vert, so draw a new one // find the closest point float closestDistance = 99999; Vector2 closestPoint = Vector2.zero; for (int i = 0; i < segments.Count; i++) { float dist = Vector2.Distance(segments[i].p0, (Vector2)mouseWorldPos); if (dist < closestDistance) { closestPoint = segments[i].p0; closestDistance = dist; } dist = Vector2.Distance(segments[i].p2, (Vector2)mouseWorldPos); if (dist < closestDistance) { closestPoint = segments[i].p2; closestDistance = dist; } } // don't use an actual handle cause we want to intercept nearby clicks // and not just clicks directly on the handle. Rect rect = new Rect(); float dim = 0.05f * HandleUtility.GetHandleSize(mouseWorldPos); rect.center = mouseWorldPos - new Vector3(dim, dim, 0); rect.size = new Vector2(dim * 2, dim * 2); Handles.color = Color.white; // remove the weird tint it does Handles.DrawSolidRectangleWithOutline(rect, Color.green, Color.clear); Color oldColor = Handles.color; Handles.color = Color.grey; Handles.DrawDottedLine(rect.center, closestPoint, HandleUtility.GetHandleSize(closestPoint)); Handles.color = oldColor; if (Event.current.type == EventType.MouseDown && !Event.current.alt && !Event.current.shift && !Event.current.command && !Event.current.control) { // the user has clicked to add a new segment, so add it for real segments.Add(new PathSegment(closestPoint, mouseWorldPos)); changed = true; } } } // something has been changed, so apply the new points back to the shape if (changed) { Undo.RecordObject(shape, "Edit Shapes2D Path Points"); shape.SetPathWorldSegments(segments.GetRange(0, segments.Count).ToArray()); EditorUtility.SetDirty(target); } else { HandleUtility.Repaint(); // to draw the new point placeholder handle if (Event.current.type == EventType.MouseDown && !isWithinBounds) { StopEditingShape(true); } } }