Пример #1
0
        private Rect?GetViewSpanRect(ITrackingSpan viewSpan)
        {
            var view = WpfTextView;

            if (view == null || view.TextViewLines == null || view.IsClosed)
            {
                return(null);
            }

            SnapshotSpan visualSpan = viewSpan.GetSpan(view.TextSnapshot);

            Rect?spanRectangle = null;

            if (visualSpan.Length > 0)
            {
                double left   = double.MaxValue;
                double top    = double.MaxValue;
                double right  = double.MinValue;
                double bottom = double.MinValue;

                var bounds = view.TextViewLines.GetNormalizedTextBounds(visualSpan);
                foreach (var bound in bounds)
                {
                    left   = Math.Min(left, bound.Left);
                    top    = Math.Min(top, bound.TextTop);
                    right  = Math.Max(right, bound.Right);
                    bottom = Math.Max(bottom, bound.TextBottom + ToolTipVerticalOffset);
                }

                // If the start of the span lies within the view, use that instead of the left-bound of the span as a whole.
                // This will cause popups to be left-aligned with the start of their span, if at all possible.
                var startLine = view.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.Start);
                if (startLine != null)
                {
                    var startPointBounds = startLine.GetExtendedCharacterBounds(visualSpan.Start);
                    if ((startPointBounds.Left < right) &&
                        (startPointBounds.Left >= view.ViewportLeft) &&
                        (startPointBounds.Left < view.ViewportRight))
                    {
                        left = startPointBounds.Left;
                    }
                }

                //Special case handling for when the end of the span is at the start of a line.
                ITextViewLine line = view.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.End);
                if ((line != null) && (line.Start == visualSpan.End))
                {
                    bottom = Math.Max(bottom, line.TextBottom + ToolTipVerticalOffset);
                }

                if (left < right)
                {
                    spanRectangle = new Rect(left, top, right - left, bottom - top);
                }
            }
            else
            {
                // visualSpan is zero length so the default MarkerGeometry will be null. Create a custom marker geometry based on
                // the location.
                ITextViewLine line = view.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.Start);
                if (line != null)
                {
                    TextBounds bounds = line.GetCharacterBounds(visualSpan.Start);
                    spanRectangle = new Rect(bounds.Left, bounds.TextTop, 0.0, bounds.TextHeight + ToolTipVerticalOffset);
                }
            }

            if (spanRectangle.HasValue && !spanRectangle.Value.IsEmpty)
            {
                //Get the portion of the span geometry that is inside the view.
                Rect viewRect = new Rect(view.ViewportLeft,
                                         view.ViewportTop,
                                         view.ViewportWidth,
                                         view.ViewportHeight);

                Rect spanRect = spanRectangle.Value;
                spanRect.Intersect(viewRect);

                Rect spanRectInScreenCoordinates =
                    new Rect(this.GetScreenPointFromTextXY(spanRect.Left, spanRect.Top),
                             this.GetScreenPointFromTextXY(spanRect.Right, spanRect.Bottom));

                return(spanRectInScreenCoordinates);
            }

            return(null);
        }
Пример #2
0
        public Geometry PositionAndDisplay(Geometry reservedSpace)
        {
            //This method should only be called from the popup manager (which should never call it when the containing
            //view is hidden or in the middle of a layout).

            //This method does not support virtual whitespace positioning.  An attempt to support it by using caret position was introduced, but
            //regressed the behavior that popup should stay in place as the caret moves.  If in the future we need to support virtual whitespace,
            //consider using virtual span instead.

            //Update the visual span to the current snapshot.
            SnapshotSpan visualSpan = _visualSpan.GetSpan(_textView.TextSnapshot);

            // If the style indicates that we should dismiss when the mouse leaves the span of the text, then we should make an
            // initial check to make sure the mouse starts-off in the span.  If not, we should fail to position.
            if ((_style & (PopupStyles.DismissOnMouseLeaveText | PopupStyles.DismissOnMouseLeaveTextOrContent)) != 0)
            {
                _textView.VisualElement.GetPointer(out int x, out int y);
                if (this.ShouldClearToolTipOnMouseMove(new Point(x, y)))
                {
                    return(null);
                }
            }

            Rect?spanRectangle = null;

            if (visualSpan.Length > 0)
            {
                double left   = double.MaxValue;
                double top    = double.MaxValue;
                double right  = double.MinValue;
                double bottom = double.MinValue;

                var bounds = _textView.TextViewLines.GetNormalizedTextBounds(visualSpan);
                foreach (var bound in bounds)
                {
                    left   = Math.Min(left, bound.Left);
                    top    = Math.Min(top, bound.TextTop);
                    right  = Math.Max(right, bound.Right);
                    bottom = Math.Max(bottom, bound.TextBottom);
                }

                // If the start of the span lies within the view, use that instead of the left-bound of the span as a whole.
                // This will cause popups to be left-aligned with the start of their span, if at all possible.
                var startLine = _textView.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.Start);
                if (startLine != null)
                {
                    var startPointBounds = startLine.GetExtendedCharacterBounds(visualSpan.Start);
                    if ((startPointBounds.Left < right) && (startPointBounds.Left >= _textView.ViewportLeft) && (startPointBounds.Left < _textView.ViewportRight))
                    {
                        left = startPointBounds.Left;
                    }
                }

                //Special case handling for when the end of the span is at the start of a line.
                ITextViewLine line = _textView.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.End);
                if ((line != null) && (line.Start == visualSpan.End))
                {
                    bottom = Math.Max(bottom, line.TextBottom);
                }

                if (left < right)
                {
                    spanRectangle = new Rect(left, top, right - left, bottom - top);
                }
            }
            else
            {
                //visualSpan is zero length so the default MarkerGeometry will be null. Create a custom marker geometry based on the location.
                ITextViewLine line = _textView.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.Start);
                if (line != null)
                {
                    TextBounds bounds = line.GetCharacterBounds(visualSpan.Start);
                    spanRectangle = new Rect(bounds.Left, bounds.TextTop, 0.0, bounds.TextHeight);
                }
            }

            if (spanRectangle.HasValue)
            {
                //Get the portion of the span geometry that is inside the view.
                Rect viewRect = new Rect(_textView.ViewportLeft, _textView.ViewportTop, _textView.ViewportWidth, _textView.ViewportHeight);

                Rect spanRect = spanRectangle.Value;
                spanRect = spanRect.Intersect(viewRect);

                if (spanRect != default(Rect))
                {
                    // Determine two different rectangles for the span.  One is the span in its raw form.  The other is a "guess" at
                    // what the already-reserved space around the span will be.  We have a very-prevalent space reservation agent (the
                    // current line agent) that reserves the current line plus a 3-pixel buffer below the line.  We'll optimistically
                    // guess that this agent might be in-play and attempt to avoid it.
                    Rect spanRectWithBuffer = new Rect(spanRect.Left, spanRect.Top, spanRect.Right - spanRect.Left, spanRect.Bottom - spanRect.Top + BelowTheLineBufferHint);

                    //Some of the text associated with the popup is visible, show the popup.
                    Rect spanRectInScreenCoordinates = new Rect(this.GetScreenPointFromTextXY(spanRect.Left, spanRect.Top),
                                                                this.GetScreenPointFromTextXY(spanRect.Right, spanRect.Bottom));
                    Rect spanRectWithBufferInScreenCoordinates = new Rect(this.GetScreenPointFromTextXY(spanRectWithBuffer.Left, spanRectWithBuffer.Top),
                                                                          this.GetScreenPointFromTextXY(spanRectWithBuffer.Right, spanRectWithBuffer.Bottom));
                    Rect screenRect = Xwt.Desktop.GetScreenAtLocation(spanRectInScreenCoordinates.TopLeft).Bounds;//TODO: Check if we should use VisualBounds

                    Size desiredSize = _popup.Size;
                    //The popup size specified in deivice pixels. Convert these to logical
                    //pixels for purposes of calculating the actual size of the popup.
                    //TODO desiredSize = new Size (desiredSize.Width / WpfHelper.DeviceScaleX, desiredSize.Height / WpfHelper.DeviceScaleY);
                    desiredSize = new Size(desiredSize.Width, desiredSize.Height);

                    PopupStyles alternateStyle = _style ^ PopupStyles.PreferLeftOrTopPosition;

                    var   tr      = reservedSpace.Bounds;
                    Point topLeft = new Point(Math.Min(spanRectInScreenCoordinates.Left, tr.Left),
                                              Math.Min(spanRectInScreenCoordinates.Top, tr.Top));
                    Point bottomRight = new Point(Math.Max(spanRectInScreenCoordinates.Right, tr.Right),
                                                  Math.Max(spanRectInScreenCoordinates.Bottom, tr.Bottom));
                    var reservedRect = new Rect(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);

                    //There are 6 possible locations for the popup.  The order of preference is determined by the presence of the
                    //'PositionClosest' PopupStyle.
                    //
                    //  Without 'PositionClosest':
                    //  1 .. On the desired side of the span.
                    //  2 .. On the desired side of the span with a bit of buffer.
                    //  3 .. on the desired side of the reserved rectangle.
                    //  4 .. On the alternate side of the span.
                    //  5 .. On the alternate side of the span with a bit of buffer.
                    //  6 .. on the alternate side of the reserved rectangle.
                    //
                    //  With 'PositionClosest':
                    //  1 .. On the desired side of the span.
                    //  2 .. On the desired side of the span with a bit of buffer.
                    //  3 .. On the alternate side of the span.
                    //  4 .. On the alternate side of the span with a bit of buffer.
                    //  5 .. on the desired side of the reserved rectangle.
                    //  6 .. on the alternate side of the reserved rectangle.
                    //
                    //Try each location till we get a winner.
                    //  A location is a winner if it is disjoint from the original reserved rect and
                    //  the edges of the screen.

                    Tuple <PopupStyles, Rect>[] positionChoices;
                    if (reservedRect != default(Rect))
                    {
                        if ((_style & PopupStyles.PositionClosest) == 0)
                        {
                            positionChoices = new Tuple <PopupStyles, Rect>[]
                            {
                                new Tuple <PopupStyles, Rect>(_style, spanRectInScreenCoordinates),
                                new Tuple <PopupStyles, Rect>(_style, spanRectWithBufferInScreenCoordinates),
                                new Tuple <PopupStyles, Rect>(_style, reservedRect),
                                new Tuple <PopupStyles, Rect>(alternateStyle, spanRectInScreenCoordinates),
                                new Tuple <PopupStyles, Rect>(alternateStyle, spanRectWithBufferInScreenCoordinates),
                                new Tuple <PopupStyles, Rect>(alternateStyle, reservedRect),
                            };
                        }
                        else
                        {
                            positionChoices = new Tuple <PopupStyles, Rect>[]
                            {
                                new Tuple <PopupStyles, Rect>(_style, spanRectInScreenCoordinates),
                                new Tuple <PopupStyles, Rect>(_style, spanRectWithBufferInScreenCoordinates),
                                new Tuple <PopupStyles, Rect>(alternateStyle, spanRectInScreenCoordinates),
                                new Tuple <PopupStyles, Rect>(alternateStyle, spanRectWithBufferInScreenCoordinates),
                                new Tuple <PopupStyles, Rect>(_style, reservedRect),
                                new Tuple <PopupStyles, Rect>(alternateStyle, reservedRect),
                            };
                        }
                    }
                    else
                    {
                        positionChoices = new Tuple <PopupStyles, Rect>[]
                        {
                            new Tuple <PopupStyles, Rect>(_style, spanRectInScreenCoordinates),
                            new Tuple <PopupStyles, Rect>(_style, spanRectWithBufferInScreenCoordinates),
                            new Tuple <PopupStyles, Rect>(alternateStyle, spanRectInScreenCoordinates),
                            new Tuple <PopupStyles, Rect>(alternateStyle, spanRectWithBufferInScreenCoordinates),
                        };
                    }

                    Rect location = default;
                    foreach (var choice in positionChoices)
                    {
                        Rect locationToTry = GetLocation(choice.Item1, desiredSize, spanRectInScreenCoordinates, choice.Item2, screenRect);
                        if (DisjointWithPadding(reservedSpace, locationToTry) && ContainsWithPadding(screenRect, locationToTry))
                        {
                            location = locationToTry;
                            _style   = choice.Item1;
                            break;
                        }
                    }

                    // If we couldn't locate a place to live, tell the manager we want to go away.
                    if (location == default)
                    {
                        return(null);
                    }

                    if (!_popup.IsVisible)
                    {
                        this.RegisterForEvents();
                    }

                    _popup.DisplayAt(location.TopLeft);

                    GeometryGroup requestedSpace = new GeometryGroup();
                    requestedSpace.Children.Add(new RectangleGeometry(FromXwtRect(spanRectInScreenCoordinates)));
                    requestedSpace.Children.Add(new RectangleGeometry(FromXwtRect(location)));

                    return(requestedSpace);
                }
            }

            //The text associated with the popup visualSpan is not visible: tell the manager we want to go away.
            return(null);
        }