/// <summary> /// Creates a visual line text element with the specified length. /// It uses the <see cref="ITextRunConstructionContext.VisualLine"/> and its /// <see cref="VisualLineElement.RelativeTextOffset"/> to find the actual text string. /// </summary> public VisualLineText(VisualLine parentVisualLine, int length) : base(length, length) { if (parentVisualLine == null) throw new ArgumentNullException("parentVisualLine"); this.parentVisualLine = parentVisualLine; }
public VisualLineDrawingVisual(VisualLine visualLine) { this.VisualLine = visualLine; var drawingContext = RenderOpen(); double pos = 0; foreach (TextLine textLine in visualLine.TextLines) { textLine.Draw(drawingContext, new Point(0, pos), InvertAxes.None); pos += textLine.Height; } this.Height = pos; drawingContext.Close(); }
/// <summary> /// Validates the visual column of the caret using the specified visual line. /// The visual line must contain the caret offset. /// </summary> void RevalidateVisualColumn(VisualLine visualLine) { if (visualLine == null) throw new ArgumentNullException("visualLine"); // mark column as validated visualColumnValid = true; int caretOffset = textView.Document.GetOffset(position.Location); int firstDocumentLineOffset = visualLine.FirstDocumentLine.Offset; position.VisualColumn = visualLine.ValidateVisualColumn(position, textArea.Selection.EnableVirtualSpace); // search possible caret positions int newVisualColumnForwards = visualLine.GetNextCaretPosition(position.VisualColumn - 1, LogicalDirection.Forward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace); // If position.VisualColumn was valid, we're done with validation. if (newVisualColumnForwards != position.VisualColumn) { // also search backwards so that we can pick the better match int newVisualColumnBackwards = visualLine.GetNextCaretPosition(position.VisualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace); if (newVisualColumnForwards < 0 && newVisualColumnBackwards < 0) throw ThrowUtil.NoValidCaretPosition(); // determine offsets for new visual column positions int newOffsetForwards; if (newVisualColumnForwards >= 0) newOffsetForwards = visualLine.GetRelativeOffset(newVisualColumnForwards) + firstDocumentLineOffset; else newOffsetForwards = -1; int newOffsetBackwards; if (newVisualColumnBackwards >= 0) newOffsetBackwards = visualLine.GetRelativeOffset(newVisualColumnBackwards) + firstDocumentLineOffset; else newOffsetBackwards = -1; int newVisualColumn, newOffset; // if there's only one valid position, use it if (newVisualColumnForwards < 0) { newVisualColumn = newVisualColumnBackwards; newOffset = newOffsetBackwards; } else if (newVisualColumnBackwards < 0) { newVisualColumn = newVisualColumnForwards; newOffset = newOffsetForwards; } else { // two valid positions: find the better match if (Math.Abs(newOffsetBackwards - caretOffset) < Math.Abs(newOffsetForwards - caretOffset)) { // backwards is better newVisualColumn = newVisualColumnBackwards; newOffset = newOffsetBackwards; } else { // forwards is better newVisualColumn = newVisualColumnForwards; newOffset = newOffsetForwards; } } this.Position = new TextViewPosition(textView.Document.GetLocation(newOffset), newVisualColumn); } isInVirtualSpace = (position.VisualColumn > visualLine.VisualLength); }
Rect CalcCaretRectangle(VisualLine visualLine) { if (!visualColumnValid) { RevalidateVisualColumn(visualLine); } TextLine textLine = visualLine.GetTextLine(position.VisualColumn); double xPos = visualLine.GetTextLineVisualXPosition(textLine, position.VisualColumn); double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop); double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom); return new Rect(xPos, lineTop, SystemParameters.CaretWidth, lineBottom - lineTop); }
/// <summary> /// Creates a visual line text element with the specified length. /// It uses the <see cref="ITextRunConstructionContext.VisualLine"/> and its /// <see cref="VisualLineElement.RelativeTextOffset"/> to find the actual text string. /// </summary> public VisualLineLinkText(VisualLine parentVisualLine, int length) : base(parentVisualLine, length) { this.RequireControlModifierForClick = true; }
static IEnumerable<Rect> ProcessTextLines(TextView textView, VisualLine visualLine, int segmentStartVC, int segmentEndVC) { TextLine lastTextLine = visualLine.TextLines.Last(); Vector scrollOffset = textView.ScrollOffset; for (int i = 0; i < visualLine.TextLines.Count; i++) { TextLine line = visualLine.TextLines[i]; double y = visualLine.GetTextLineVisualYPosition(line, VisualYPosition.LineTop); int visualStartCol = visualLine.GetTextLineVisualStartColumn(line); int visualEndCol = visualStartCol + line.Length; if (line != lastTextLine) visualEndCol -= line.TrailingWhitespaceLength; if (segmentEndVC < visualStartCol) break; if (lastTextLine != line && segmentStartVC > visualEndCol) continue; int segmentStartVCInLine = Math.Max(segmentStartVC, visualStartCol); int segmentEndVCInLine = Math.Min(segmentEndVC, visualEndCol); y -= scrollOffset.Y; if (segmentStartVCInLine == segmentEndVCInLine) { // GetTextBounds crashes for length=0, so we'll handle this case with GetDistanceFromCharacterHit // We need to return a rectangle to ensure empty lines are still visible double pos = visualLine.GetTextLineVisualXPosition(line, segmentStartVCInLine); pos -= scrollOffset.X; // The following special cases are necessary to get rid of empty rectangles at the end of a TextLine if "Show Spaces" is active. // If not excluded once, the same rectangle is calculated (and added) twice (since the offset could be mapped to two visual positions; end/start of line), if there is no trailing whitespace. // Skip this TextLine segment, if it is at the end of this line and this line is not the last line of the VisualLine and the selection continues and there is no trailing whitespace. if (segmentEndVCInLine == visualEndCol && i < visualLine.TextLines.Count - 1 && segmentEndVC > segmentEndVCInLine && line.TrailingWhitespaceLength == 0) continue; if (segmentStartVCInLine == visualStartCol && i > 0 && segmentStartVC < segmentStartVCInLine && visualLine.TextLines[i - 1].TrailingWhitespaceLength == 0) continue; yield return new Rect(pos, y, 1, line.Height); } else { Rect lastRect = Rect.Empty; if (segmentStartVCInLine <= visualEndCol) { foreach (TextBounds b in line.GetTextBounds(segmentStartVCInLine, segmentEndVCInLine - segmentStartVCInLine)) { double left = b.Rectangle.Left - scrollOffset.X; double right = b.Rectangle.Right - scrollOffset.X; if (!lastRect.IsEmpty) yield return lastRect; // left>right is possible in RTL languages lastRect = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height); } } if (segmentEndVC >= visualLine.VisualLengthWithEndOfLineMarker) { double left = (segmentStartVC > visualLine.VisualLengthWithEndOfLineMarker ? visualLine.GetTextLineVisualXPosition(lastTextLine, segmentStartVC) : line.Width) - scrollOffset.X; double right = ((segmentEndVC == int.MaxValue || line != lastTextLine) ? Math.Max(((IScrollInfo)textView).ExtentWidth, ((IScrollInfo)textView).ViewportWidth) : visualLine.GetTextLineVisualXPosition(lastTextLine, segmentEndVC)) - scrollOffset.X; Rect extendSelection = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height); if (!lastRect.IsEmpty) { if (extendSelection.IntersectsWith(lastRect)) { lastRect.Union(extendSelection); yield return lastRect; } else { yield return lastRect; yield return extendSelection; } } else yield return extendSelection; } else yield return lastRect; } } }
static void SetCaretPosition(TextArea textArea, VisualLine targetVisualLine, TextLine targetLine, int newVisualColumn, bool allowWrapToNextLine) { int targetLineStartCol = targetVisualLine.GetTextLineVisualStartColumn(targetLine); if (!allowWrapToNextLine && newVisualColumn >= targetLineStartCol + targetLine.Length) { if (newVisualColumn <= targetVisualLine.VisualLength) newVisualColumn = targetLineStartCol + targetLine.Length - 1; } int newOffset = targetVisualLine.GetRelativeOffset(newVisualColumn) + targetVisualLine.FirstDocumentLine.Offset; SetCaretPosition(textArea, newVisualColumn, newOffset); }
/// <summary> /// Calculates the rectangles for the visual column segment. /// This returns one rectangle for each line inside the segment. /// </summary> public static IEnumerable<Rect> GetRectsFromVisualSegment(TextView textView, VisualLine line, int startVC, int endVC) { if (textView == null) throw new ArgumentNullException("textView"); if (line == null) throw new ArgumentNullException("line"); return ProcessTextLines(textView, line, startVC, endVC); }
static void MoveCaretUpDown(TextArea textArea, CaretMovementType direction, VisualLine visualLine, TextLine textLine, int caretVisualColumn) { // moving up/down happens using the desired visual X position double xPos = textArea.Caret.DesiredXPos; if (double.IsNaN(xPos)) xPos = visualLine.GetTextLineVisualXPosition(textLine, caretVisualColumn); // now find the TextLine+VisualLine where the caret will end up in VisualLine targetVisualLine = visualLine; TextLine targetLine; int textLineIndex = visualLine.TextLines.IndexOf(textLine); switch (direction) { case CaretMovementType.LineUp: { // Move up: move to the previous TextLine in the same visual line // or move to the last TextLine of the previous visual line int prevLineNumber = visualLine.FirstDocumentLine.LineNumber - 1; if (textLineIndex > 0) { targetLine = visualLine.TextLines[textLineIndex - 1]; } else if (prevLineNumber >= 1) { DocumentLine prevLine = textArea.Document.GetLineByNumber(prevLineNumber); targetVisualLine = textArea.TextView.GetOrConstructVisualLine(prevLine); targetLine = targetVisualLine.TextLines[targetVisualLine.TextLines.Count - 1]; } else { targetLine = null; } break; } case CaretMovementType.LineDown: { // Move down: move to the next TextLine in the same visual line // or move to the first TextLine of the next visual line int nextLineNumber = visualLine.LastDocumentLine.LineNumber + 1; if (textLineIndex < visualLine.TextLines.Count - 1) { targetLine = visualLine.TextLines[textLineIndex + 1]; } else if (nextLineNumber <= textArea.Document.LineCount) { DocumentLine nextLine = textArea.Document.GetLineByNumber(nextLineNumber); targetVisualLine = textArea.TextView.GetOrConstructVisualLine(nextLine); targetLine = targetVisualLine.TextLines[0]; } else { targetLine = null; } break; } case CaretMovementType.PageUp: case CaretMovementType.PageDown: { // Page up/down: find the target line using its visual position double yPos = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineMiddle); if (direction == CaretMovementType.PageUp) yPos -= textArea.TextView.RenderSize.Height; else yPos += textArea.TextView.RenderSize.Height; DocumentLine newLine = textArea.TextView.GetDocumentLineByVisualTop(yPos); targetVisualLine = textArea.TextView.GetOrConstructVisualLine(newLine); targetLine = targetVisualLine.GetTextLineByVisualYPosition(yPos); break; } default: throw new NotSupportedException(direction.ToString()); } if (targetLine != null) { double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle); int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos), textArea.Selection.EnableVirtualSpace); SetCaretPosition(textArea, targetVisualLine, targetLine, newVisualColumn, false); textArea.Caret.DesiredXPos = xPos; } }
static void MoveCaretToStartOfLine(TextArea textArea, VisualLine visualLine) { int newVC = visualLine.GetNextCaretPosition(-1, LogicalDirection.Forward, CaretPositioningMode.WordStart, textArea.Selection.EnableVirtualSpace); if (newVC < 0) throw ThrowUtil.NoValidCaretPosition(); // when the caret is already at the start of the text, jump to start before whitespace if (newVC == textArea.Caret.VisualColumn) newVC = 0; int offset = visualLine.FirstDocumentLine.Offset + visualLine.GetRelativeOffset(newVC); SetCaretPosition(textArea, newVC, offset); }
static void MoveCaretToEndOfLine(TextArea textArea, VisualLine visualLine) { int newVC = visualLine.VisualLength; int offset = visualLine.FirstDocumentLine.Offset + visualLine.GetRelativeOffset(newVC); SetCaretPosition(textArea, newVC, offset); }
static void MoveCaretRight(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode) { int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace); if (pos >= 0) { SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset); } else { // move to start of next line DocumentLine nextDocumentLine = visualLine.LastDocumentLine.NextLine; if (nextDocumentLine != null) { VisualLine nextLine = textArea.TextView.GetOrConstructVisualLine(nextDocumentLine); pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace); if (pos < 0) throw ThrowUtil.NoValidCaretPosition(); SetCaretPosition(textArea, pos, nextLine.GetRelativeOffset(pos) + nextLine.FirstDocumentLine.Offset); } else { // at end of document Debug.Assert(visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength == textArea.Document.TextLength); SetCaretPosition(textArea, -1, textArea.Document.TextLength); } } }
static void MoveCaretLeft(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode) { int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace); if (pos >= 0) { SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset); } else { // move to end of previous line DocumentLine previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine; if (previousDocumentLine != null) { VisualLine previousLine = textArea.TextView.GetOrConstructVisualLine(previousDocumentLine); pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace); if (pos < 0) throw ThrowUtil.NoValidCaretPosition(); SetCaretPosition(textArea, pos, previousLine.GetRelativeOffset(pos) + previousLine.FirstDocumentLine.Offset); } else { // at start of document Debug.Assert(visualLine.FirstDocumentLine.Offset == 0); SetCaretPosition(textArea, 0, 0); } } }