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;
        }