private static Line ClosestLine(Rect rect, Point p) { var angle = new Vector(1, 1).AngleTo(p.VectorTo(rect.CenterPoint())); if (angle >= 0 && angle <= 90) { return rect.TopLine(); } if (angle >= 90 && angle <= 180) { return rect.RightLine(); } if (angle >= -90 && angle <= 0) { return rect.LeftLine(); } if (angle >= -180 && angle <= -90) { return rect.BottomLine(); } throw new InvalidOperationException("Could not find closest line"); }
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; }
protected virtual 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 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 || selfRect.Contains(tp.Value)) { this.InvalidateProperty(ConnectorOffsetProperty); return; } var mp = selfRect.CenterPoint(); var ip = new Line(mp, tp.Value).ClosestIntersection(selfRect); 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 Point? GetPointOnTarget(Point sourceMidPoint, Rect target) { switch (this.Vertical) { case VerticalPlacement.Auto: switch (this.Horizontal) { case HorizontalPlacement.Auto: var closestLine = ClosestLine(target, sourceMidPoint); return AutoPoint(sourceMidPoint.LineTo(target.CenterPoint()), closestLine); case HorizontalPlacement.Left: return AutoPoint(sourceMidPoint.LineTo(target.CenterPoint()), target.LeftLine()); case HorizontalPlacement.Center: return sourceMidPoint.Closest(target.BottomLine(), target.TopLine()) .MidPoint; case HorizontalPlacement.Right: return AutoPoint(sourceMidPoint.LineTo(target.CenterPoint()), target.RightLine()); default: throw new ArgumentOutOfRangeException(); } case VerticalPlacement.Top: switch (this.Horizontal) { case HorizontalPlacement.Auto: return AutoPoint(sourceMidPoint.LineTo(target.CenterPoint()), target.TopLine()); case HorizontalPlacement.Left: return target.TopLeft; case HorizontalPlacement.Center: return PointExt.MidPoint(target.TopLeft, target.TopRight); case HorizontalPlacement.Right: return target.TopRight; default: throw new ArgumentOutOfRangeException(); } case VerticalPlacement.Center: switch (this.Horizontal) { case HorizontalPlacement.Auto: return sourceMidPoint.Closest(target.LeftLine(), target.RightLine()) .MidPoint; case HorizontalPlacement.Left: return PointExt.MidPoint(target.BottomLeft, target.TopLeft); case HorizontalPlacement.Center: return PointExt.MidPoint(target.TopLeft, target.BottomRight); case HorizontalPlacement.Right: return PointExt.MidPoint(target.BottomRight, target.TopRight); default: throw new ArgumentOutOfRangeException(); } case VerticalPlacement.Bottom: switch (this.Horizontal) { case HorizontalPlacement.Auto: return AutoPoint(sourceMidPoint.LineTo(target.CenterPoint()), target.BottomLine()); case HorizontalPlacement.Left: return target.BottomLeft; case HorizontalPlacement.Center: return PointExt.MidPoint(target.BottomLeft, target.BottomRight); case HorizontalPlacement.Right: return target.BottomRight; default: throw new ArgumentOutOfRangeException(); } default: throw new ArgumentOutOfRangeException(); } }
public Point? GetPointOnTarget(Rect placed, Rect target) { var p = this.GetPointOnTarget(placed.CenterPoint(), target); return p; }