diff options
| author | Victoria Plitt <Victoria.Plitt@twine-s.com> | 2019-04-08 13:49:55 +0300 |
|---|---|---|
| committer | Victoria Plitt <Victoria.Plitt@twine-s.com> | 2019-04-08 13:49:55 +0300 |
| commit | fc8a05358a92cc3c77c5f1e30d536807ef0614fd (patch) | |
| tree | c65f696ebd60f3790145721307c255e5a339923f /Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs | |
| parent | b4a71931ea52636c6b36376aa9d71697ccf73524 (diff) | |
| download | Tango-fc8a05358a92cc3c77c5f1e30d536807ef0614fd.tar.gz Tango-fc8a05358a92cc3c77c5f1e30d536807ef0614fd.zip | |
were added scripting projects
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs')
| -rw-r--r-- | Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs new file mode 100644 index 000000000..fb21448e5 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Editing/Caret.cs @@ -0,0 +1,472 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using System.Windows.Threading; + +using Tango.Scripting.Editors.Document; +using Tango.Scripting.Editors.Rendering; +using Tango.Scripting.Editors.Utils; + +namespace Tango.Scripting.Editors.Editing +{ + /// <summary> + /// Helper class with caret-related methods. + /// </summary> + public sealed class Caret + { + readonly TextArea textArea; + readonly TextView textView; + readonly CaretLayer caretAdorner; + bool visible; + + internal Caret(TextArea textArea) + { + this.textArea = textArea; + this.textView = textArea.TextView; + position = new TextViewPosition(1, 1, 0); + + caretAdorner = new CaretLayer(textView); + textView.InsertLayer(caretAdorner, KnownLayer.Caret, LayerInsertionPosition.Replace); + textView.VisualLinesChanged += TextView_VisualLinesChanged; + textView.ScrollOffsetChanged += TextView_ScrollOffsetChanged; + } + + void TextView_VisualLinesChanged(object sender, EventArgs e) + { + if (visible) { + Show(); + } + // required because the visual columns might have changed if the + // element generators did something differently than on the last run + // (e.g. a FoldingSection was collapsed) + InvalidateVisualColumn(); + } + + void TextView_ScrollOffsetChanged(object sender, EventArgs e) + { + if (caretAdorner != null) { + caretAdorner.InvalidateVisual(); + } + } + + double desiredXPos = double.NaN; + TextViewPosition position; + + /// <summary> + /// Gets/Sets the position of the caret. + /// Retrieving this property will validate the visual column (which can be expensive). + /// Use the <see cref="Location"/> property instead if you don't need the visual column. + /// </summary> + public TextViewPosition Position { + get { + ValidateVisualColumn(); + return position; + } + set { + if (position != value) { + position = value; + + storedCaretOffset = -1; + + //Debug.WriteLine("Caret position changing to " + value); + + ValidatePosition(); + InvalidateVisualColumn(); + RaisePositionChanged(); + Log("Caret position changed to " + value); + if (visible) + Show(); + } + } + } + + /// <summary> + /// Gets the caret position without validating it. + /// </summary> + internal TextViewPosition NonValidatedPosition { + get { + return position; + } + } + + /// <summary> + /// Gets/Sets the location of the caret. + /// The getter of this property is faster than <see cref="Position"/> because it doesn't have + /// to validate the visual column. + /// </summary> + public TextLocation Location { + get { + return position.Location; + } + set { + this.Position = new TextViewPosition(value); + } + } + + /// <summary> + /// Gets/Sets the caret line. + /// </summary> + public int Line { + get { return position.Line; } + set { + this.Position = new TextViewPosition(value, position.Column); + } + } + + /// <summary> + /// Gets/Sets the caret column. + /// </summary> + public int Column { + get { return position.Column; } + set { + this.Position = new TextViewPosition(position.Line, value); + } + } + + /// <summary> + /// Gets/Sets the caret visual column. + /// </summary> + public int VisualColumn { + get { + ValidateVisualColumn(); + return position.VisualColumn; + } + set { + this.Position = new TextViewPosition(position.Line, position.Column, value); + } + } + + bool isInVirtualSpace; + + /// <summary> + /// Gets whether the caret is in virtual space. + /// </summary> + public bool IsInVirtualSpace { + get { + ValidateVisualColumn(); + return isInVirtualSpace; + } + } + + int storedCaretOffset; + + internal void OnDocumentChanging() + { + storedCaretOffset = this.Offset; + InvalidateVisualColumn(); + } + + internal void OnDocumentChanged(DocumentChangeEventArgs e) + { + InvalidateVisualColumn(); + if (storedCaretOffset >= 0) { + int newCaretOffset = e.GetNewOffset(storedCaretOffset, AnchorMovementType.Default); + TextDocument document = textArea.Document; + if (document != null) { + // keep visual column + this.Position = new TextViewPosition(document.GetLocation(newCaretOffset), position.VisualColumn); + } + } + storedCaretOffset = -1; + } + + /// <summary> + /// Gets/Sets the caret offset. + /// Setting the caret offset has the side effect of setting the <see cref="DesiredXPos"/> to NaN. + /// </summary> + public int Offset { + get { + TextDocument document = textArea.Document; + if (document == null) { + return 0; + } else { + return document.GetOffset(position.Location); + } + } + set { + TextDocument document = textArea.Document; + if (document != null) { + this.Position = new TextViewPosition(document.GetLocation(value)); + this.DesiredXPos = double.NaN; + } + } + } + + /// <summary> + /// Gets/Sets the desired x-position of the caret, in device-independent pixels. + /// This property is NaN if the caret has no desired position. + /// </summary> + public double DesiredXPos { + get { return desiredXPos; } + set { desiredXPos = value; } + } + + void ValidatePosition() + { + if (position.Line < 1) + position.Line = 1; + if (position.Column < 1) + position.Column = 1; + if (position.VisualColumn < -1) + position.VisualColumn = -1; + TextDocument document = textArea.Document; + if (document != null) { + if (position.Line > document.LineCount) { + position.Line = document.LineCount; + position.Column = document.GetLineByNumber(position.Line).Length + 1; + position.VisualColumn = -1; + } else { + DocumentLine line = document.GetLineByNumber(position.Line); + if (position.Column > line.Length + 1) { + position.Column = line.Length + 1; + position.VisualColumn = -1; + } + } + } + } + + /// <summary> + /// Event raised when the caret position has changed. + /// If the caret position is changed inside a document update (between BeginUpdate/EndUpdate calls), + /// the PositionChanged event is raised only once at the end of the document update. + /// </summary> + public event EventHandler PositionChanged; + + bool raisePositionChangedOnUpdateFinished; + + void RaisePositionChanged() + { + if (textArea.Document != null && textArea.Document.IsInUpdate) { + raisePositionChangedOnUpdateFinished = true; + } else { + if (PositionChanged != null) { + PositionChanged(this, EventArgs.Empty); + } + } + } + + internal void OnDocumentUpdateFinished() + { + if (raisePositionChangedOnUpdateFinished) { + if (PositionChanged != null) { + PositionChanged(this, EventArgs.Empty); + } + } + } + + bool visualColumnValid; + + void ValidateVisualColumn() + { + if (!visualColumnValid) { + TextDocument document = textArea.Document; + if (document != null) { + //Debug.WriteLine("Explicit validation of caret column"); + var documentLine = document.GetLineByNumber(position.Line); + RevalidateVisualColumn(textView.GetOrConstructVisualLine(documentLine)); + } + } + } + + void InvalidateVisualColumn() + { + visualColumnValid = false; + } + + /// <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> + /// Returns the caret rectangle. The coordinate system is in device-independent pixels from the top of the document. + /// </summary> + public Rect CalculateCaretRectangle() + { + if (textView != null && textView.Document != null) { + VisualLine visualLine = textView.GetOrConstructVisualLine(textView.Document.GetLineByNumber(position.Line)); + return CalcCaretRectangle(visualLine); + } else { + return Rect.Empty; + } + } + + /// <summary> + /// Minimum distance of the caret to the view border. + /// </summary> + internal const double MinimumDistanceToViewBorder = 30; + + /// <summary> + /// Scrolls the text view so that the caret is visible. + /// </summary> + public void BringCaretToView() + { + BringCaretToView(MinimumDistanceToViewBorder); + } + + internal void BringCaretToView(double border) + { + Rect caretRectangle = CalculateCaretRectangle(); + if (!caretRectangle.IsEmpty) { + caretRectangle.Inflate(border, border); + textView.MakeVisible(caretRectangle); + } + } + + /// <summary> + /// Makes the caret visible and updates its on-screen position. + /// </summary> + public void Show() + { + Log("Caret.Show()"); + visible = true; + if (!showScheduled) { + showScheduled = true; + textArea.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(ShowInternal)); + } + } + + bool showScheduled; + bool hasWin32Caret; + + void ShowInternal() + { + showScheduled = false; + + // if show was scheduled but caret hidden in the meantime + if (!visible) + return; + + if (caretAdorner != null && textView != null) { + VisualLine visualLine = textView.GetVisualLine(position.Line); + if (visualLine != null) { + Rect caretRect = CalcCaretRectangle(visualLine); + // Create Win32 caret so that Windows knows where our managed caret is. This is necessary for + // features like 'Follow text editing' in the Windows Magnifier. + if (!hasWin32Caret) { + hasWin32Caret = Win32.CreateCaret(textView, caretRect.Size); + } + if (hasWin32Caret) { + Win32.SetCaretPosition(textView, caretRect.Location - textView.ScrollOffset); + } + caretAdorner.Show(caretRect); + textArea.ime.UpdateCompositionWindow(); + } else { + caretAdorner.Hide(); + } + } + } + + /// <summary> + /// Makes the caret invisible. + /// </summary> + public void Hide() + { + Log("Caret.Hide()"); + visible = false; + if (hasWin32Caret) { + Win32.DestroyCaret(); + hasWin32Caret = false; + } + if (caretAdorner != null) { + caretAdorner.Hide(); + } + } + + [Conditional("DEBUG")] + static void Log(string text) + { + // commented out to make debug output less noisy - add back if there are any problems with the caret + //Debug.WriteLine(text); + } + + /// <summary> + /// Gets/Sets the color of the caret. + /// </summary> + public Brush CaretBrush { + get { return caretAdorner.CaretBrush; } + set { caretAdorner.CaretBrush = value; } + } + } +} |
