protected override Geometry GetOrCreateConnectorGeometry(Size renderSize) { var ellipse = Ellipse.CreateFromSize(renderSize); this.SetCurrentValue(EllipseProperty, ellipse); if (ellipse.IsZero) { return Geometry.Empty; } var direction = this.ConnectorOffset; var ip = ellipse.PointOnCircumference(direction); var vertexPoint = ip + this.ConnectorOffset; var ray = new Ray(vertexPoint, this.ConnectorOffset.Negated()); var p1 = ConnectorPoint.Find(ray, this.ConnectorAngle / 2, this.StrokeThickness, ellipse); var p2 = ConnectorPoint.Find(ray, -this.ConnectorAngle / 2, this.StrokeThickness, ellipse); this.SetCurrentValue(ConnectorVertexPointProperty, vertexPoint); this.SetCurrentValue(ConnectorPoint1Property, p1); this.SetCurrentValue(ConnectorPoint2Property, p2); if (this.ConnectorGeometry is PathGeometry) { return this.ConnectorGeometry; } var figure = this.CreatePathFigureStartingAt(ConnectorPoint1Property); figure.Segments.Add(this.CreateLineSegmentTo(ConnectorVertexPointProperty)); figure.Segments.Add(this.CreateLineSegmentTo(ConnectorPoint2Property)); var geometry = new PathGeometry(); geometry.Figures.Add(figure); return geometry; }
protected override void UpdateConnectorOffset() { if (this.IsVisible && this.RenderSize.Width > 0 && this.PlacementTarget?.IsVisible == true) { if (!this.IsLoaded) { this.Dispatcher.Invoke(this.UpdateConnectorOffset, DispatcherPriority.Loaded); return; } var selfRect = new Rect(new Point(0, 0).ToScreen(this), this.RenderSize).ToScreen(this); var ellipse = new Ellipse(selfRect); var targetRect = new Rect(new Point(0, 0).ToScreen(this.PlacementTarget), this.PlacementTarget.RenderSize).ToScreen(this); var tp = this.PlacementOptions?.GetPointOnTarget(selfRect, targetRect); if (tp == null || ellipse.Contains(tp.Value)) { this.InvalidateProperty(ConnectorOffsetProperty); return; } if (ellipse.Contains(tp.Value)) { this.SetCurrentValue(ConnectorOffsetProperty, new Vector(0, 0)); return; } var mp = ellipse.CenterPoint; var ip = new Ray(mp, mp.VectorTo(tp.Value)).FirstIntersectionWith(ellipse); Debug.Assert(ip != null, "Did not find an intersection, bug in the library"); if (ip == null) { // failing silently in release this.InvalidateProperty(ConnectorOffsetProperty); } else { var v = tp.Value - ip.Value; // ReSharper disable once CompareOfFloatsByEqualityOperator if (this.PlacementOptions != null && v.Length > 0 && this.PlacementOptions.Offset != 0) { v = v - this.PlacementOptions.Offset * v.Normalized(); } this.SetCurrentValue(ConnectorOffsetProperty, v); } } else { this.InvalidateProperty(ConnectorOffsetProperty); } }
private static Point FindTangentPoint(Ray toCenter, Ellipse ellipse) { var toEllipseCenter = toCenter.PerpendicularLineTo(ellipse.CenterPoint); Debug.Assert(toEllipseCenter != null, "Ray should not go through ellipse center here"); if (toEllipseCenter == null) { // this should never happen but failing silently // the balloons should not throw much returning random point. return ellipse.CenterPoint; } return ellipse.PointOnCircumference(toEllipseCenter.Value.Direction.Negated()); }
internal static Point Find(Ray ray, double angle, double strokeThickness, Ellipse ellipse) { return Find(ray.Rotate(angle), strokeThickness, ellipse); }
private static Point Find(Ray ray, double strokeThickness, Ellipse ellipse) { var ip = ray.FirstIntersectionWith(ellipse); if (ip != null) { return ip.Value + strokeThickness * ray.Direction; } return FindTangentPoint(ray, ellipse); }
protected override Geometry GetOrCreateConnectorGeometry(Size renderSize) { var rectangle = new Rect(new Point(0, 0), renderSize); if (rectangle.Width <= 0 || rectangle.Height <= 0) { return Geometry.Empty; } var fromCenter = new Ray(rectangle.CenterPoint(), this.ConnectorOffset); var ip = fromCenter.FirstIntersectionWith(rectangle); if (ip == null) { Debug.Assert(false, $"Line {fromCenter} does not intersect rectangle {rectangle}"); // ReSharper disable once HeuristicUnreachableCode return Geometry.Empty; } var cr = this.AdjustedCornerRadius(); var vertexPoint = ip.Value + this.ConnectorOffset; var toCenter = new Ray(vertexPoint, this.ConnectorOffset.Negated()); var p1 = ConnectorPoint.Find(toCenter, this.ConnectorAngle / 2, this.StrokeThickness, rectangle, cr); var p2 = ConnectorPoint.Find(toCenter, -this.ConnectorAngle / 2, this.StrokeThickness, rectangle, cr); this.SetCurrentValue(ConnectorVertexPointProperty, vertexPoint); this.SetCurrentValue(ConnectorPoint1Property, p1); this.SetCurrentValue(ConnectorPoint2Property, p2); if (this.ConnectorGeometry is PathGeometry) { return this.ConnectorGeometry; } var figure = this.CreatePathFigureStartingAt(ConnectorPoint1Property); figure.Segments.Add(this.CreateLineSegmentTo(ConnectorVertexPointProperty)); figure.Segments.Add(this.CreateLineSegmentTo(ConnectorPoint2Property)); var geometry = new PathGeometry(); geometry.Figures.Add(figure); return geometry; }
private static Point FindTangentPoint(Ray ray, Rect rectangle, CornerRadius cornerRadius) { var toMid = ray.PerpendicularLineTo(rectangle.CenterPoint()); Debug.Assert(toMid != null, "Cannot find tangent if line goes through center"); if (toMid == null) { // failing silently in release return rectangle.CenterPoint(); } // Debug.Assert(!rectangle.Contains(toMid.Value.StartPoint), "Cannot find tangent if line intersects rectangle"); if (toMid.Value.Direction.Axis() != null) { return ray.Point.Closest(rectangle.TopLeft, rectangle.TopRight, rectangle.BottomRight, rectangle.BottomLeft); } Circle corner; switch (toMid.Value.Direction.Quadrant()) { case Quadrant.NegativeXPositiveY: corner = CreateTopRight(rectangle.TopRight, cornerRadius.TopRight); break; case Quadrant.PositiveXPositiveY: corner = CreateTopLeft(rectangle.TopLeft, cornerRadius.TopLeft); break; case Quadrant.PositiveXNegativeY: corner = CreateBottomLeft(rectangle.BottomLeft, cornerRadius.BottomLeft); break; case Quadrant.NegativeXNegativeY: corner = CreateBottomRight(rectangle.BottomRight, cornerRadius.BottomRight); break; default: throw new ArgumentOutOfRangeException(); } // ReSharper disable once CompareOfFloatsByEqualityOperator if (corner.Radius == 0) { return corner.Center; } var lineToCenter = ray.PerpendicularLineTo(corner.Center); Debug.Assert(lineToCenter != null, "Ray cannot go through center here"); if (lineToCenter == null) { // this should never happen but failing silently // the balloons should not throw much. return corner.Center; } return corner.Center - corner.Radius * lineToCenter.Value.Direction; }
private static Point FindForRotated(Ray ray, double strokeThickness, Rect rectangle, CornerRadius cornerRadius) { var ip = ray.FirstIntersectionWith(rectangle); if (ip == null) { return FindTangentPoint(ray, rectangle, cornerRadius); } Circle corner; if (TryGetCorner(ip.Value, rectangle, cornerRadius, out corner)) { ip = ray.FirstIntersectionWith(corner); if (ip == null) { return FindTangentPoint(ray, rectangle, cornerRadius); } return ip.Value + strokeThickness * ray.Direction; } return ip.Value + strokeThickness * ray.Direction; }
internal static Point Find(Ray toCenter, double angle, double strokeThickness, Rect rectangle, CornerRadius cornerRadius) { var rotated = toCenter.Rotate(angle); return FindForRotated(rotated, strokeThickness, rectangle, cornerRadius); }
// http://geomalgorithms.com/a05-_intersect-1.html#intersect2D_2Segments() private static Point? IntersectionPoint(Ray ray, Line l2, bool mustBeBetweenStartAndEnd) { var u = ray.Direction; var v = l2.Direction; var w = ray.Point - l2.StartPoint; var d = Perp(u, v); if (Math.Abs(d) < Constants.Tolerance) { // parallel lines return null; } var sI = Perp(v, w) / d; var p = ray.Point + sI * u; if (mustBeBetweenStartAndEnd) { if (ray.IsPointOn(p) && l2.IsPointOnLine(p)) { return p; } return null; } return p; }