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