// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Unity.CodeEditor.Document; using Unity.CodeEditor.Text; using UnityEngine; using LogicalDirection = Unity.CodeEditor.Document.LogicalDirection; namespace Unity.CodeEditor.Rendering { /// /// Represents a visual element in the document. /// internal abstract class VisualLineElement { /// /// Creates a new VisualLineElement. /// /// The length of the element in VisualLine coordinates. Must be positive. /// The length of the element in the document. Must be non-negative. protected VisualLineElement(int visualLength, int documentLength) { if (visualLength < 1) throw new ArgumentOutOfRangeException(nameof(visualLength), visualLength, "Value must be at least 1"); if (documentLength < 0) throw new ArgumentOutOfRangeException(nameof(documentLength), documentLength, "Value must be at least 0"); VisualLength = visualLength; DocumentLength = documentLength; } /// /// Gets the length of this element in visual columns. /// internal int VisualLength { get; private set; } /// /// Gets the length of this element in the text document. /// internal int DocumentLength { get; private set; } /// /// Gets the visual column where this element starts. /// [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This property holds the start visual column, use GetVisualColumn to get inner visual columns.")] internal int VisualColumn { get; set; } /// /// Gets the text offset where this element starts, relative to the start text offset of the visual line. /// internal int RelativeTextOffset { get; set; } /// /// Gets the text run properties. /// A unique instance is used for each /// ; colorizing code may assume that modifying the /// will affect only this /// . /// internal TextParagraphProperties TextParagraphProperties { get; private set; } /// /// Gets/sets the brush used for the background of this . /// internal Color BackgroundColor { get; set; } internal void SetTextParagraphProperties(TextParagraphProperties p) { TextParagraphProperties = p; } /// /// Creates the TextRun for this line element. /// /// /// The visual column from which the run should be constructed. /// Normally the same value as the property is used to construct the full run; /// but when word-wrapping is active, partial runs might be created. /// /// /// Context object that contains information relevant for text run creation. /// internal abstract TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context); /// /// Retrieves the text span immediately before the visual column. /// /// This method is used for word-wrapping in bidirectional text. internal virtual ReadOnlyMemory GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context) { return ReadOnlyMemory.Empty; } /// /// Gets if this VisualLineElement can be split. /// internal virtual bool CanSplit => false; /// /// Splits the element. /// /// Position inside this element at which it should be broken /// The collection of line elements /// The index at which this element is in the elements list. internal virtual void Split(int splitVisualColumn, IList elements, int elementIndex) { throw new NotSupportedException(); } /// /// Helper method for splitting this line element into two, correctly updating the /// , , /// and properties. /// /// The element before the split position. /// The element after the split position. /// The split position as visual column. /// The split position as text offset. protected void SplitHelper(VisualLineElement firstPart, VisualLineElement secondPart, int splitVisualColumn, int splitRelativeTextOffset) { if (firstPart == null) throw new ArgumentNullException(nameof(firstPart)); if (secondPart == null) throw new ArgumentNullException(nameof(secondPart)); var relativeSplitVisualColumn = splitVisualColumn - VisualColumn; var relativeSplitRelativeTextOffset = splitRelativeTextOffset - RelativeTextOffset; if (relativeSplitVisualColumn <= 0 || relativeSplitVisualColumn >= VisualLength) throw new ArgumentOutOfRangeException(nameof(splitVisualColumn), splitVisualColumn, "Value must be between " + (VisualColumn + 1) + " and " + (VisualColumn + VisualLength - 1)); if (relativeSplitRelativeTextOffset < 0 || relativeSplitRelativeTextOffset > DocumentLength) throw new ArgumentOutOfRangeException(nameof(splitRelativeTextOffset), splitRelativeTextOffset, "Value must be between " + (RelativeTextOffset) + " and " + (RelativeTextOffset + DocumentLength)); var oldVisualLength = VisualLength; var oldDocumentLength = DocumentLength; var oldVisualColumn = VisualColumn; var oldRelativeTextOffset = RelativeTextOffset; firstPart.VisualColumn = oldVisualColumn; secondPart.VisualColumn = oldVisualColumn + relativeSplitVisualColumn; firstPart.RelativeTextOffset = oldRelativeTextOffset; secondPart.RelativeTextOffset = oldRelativeTextOffset + relativeSplitRelativeTextOffset; firstPart.VisualLength = relativeSplitVisualColumn; secondPart.VisualLength = oldVisualLength - relativeSplitVisualColumn; firstPart.DocumentLength = relativeSplitRelativeTextOffset; secondPart.DocumentLength = oldDocumentLength - relativeSplitRelativeTextOffset; if (firstPart.TextParagraphProperties == null) firstPart.TextParagraphProperties = TextParagraphProperties.Clone(); if (secondPart.TextParagraphProperties == null) secondPart.TextParagraphProperties = TextParagraphProperties.Clone(); firstPart.BackgroundColor = BackgroundColor; secondPart.BackgroundColor = BackgroundColor; } /// /// Gets the visual column of a text location inside this element. /// The text offset is given relative to the visual line start. /// internal virtual int GetVisualColumn(int relativeTextOffset) { if (relativeTextOffset >= RelativeTextOffset + DocumentLength) return VisualColumn + VisualLength; return VisualColumn; } /// /// Gets the text offset of a visual column inside this element. /// /// A text offset relative to the visual line start. internal virtual int GetRelativeOffset(int visualColumn) { if (visualColumn >= VisualColumn + VisualLength) return RelativeTextOffset + DocumentLength; return RelativeTextOffset; } /// /// Gets the next caret position inside this element. /// /// The visual column from which the search should be started. /// The search direction (forwards or backwards). /// Whether to stop only at word borders. /// The visual column of the next caret position, or -1 if there is no next caret position. /// /// In the space between two line elements, it is sufficient that one of them contains a caret position; /// though in many cases, both of them contain one. /// internal virtual int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode) { var stop1 = VisualColumn; var stop2 = VisualColumn + VisualLength; if (direction == LogicalDirection.Backward) { if (visualColumn > stop2 && mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol) return stop2; if (visualColumn > stop1) return stop1; } else { if (visualColumn < stop1) return stop1; if (visualColumn < stop2 && mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol) return stop2; } return -1; } /// /// Gets whether the specified offset in this element is considered whitespace. /// internal virtual bool IsWhitespace(int visualColumn) { return false; } /// /// Gets whether the implementation handles line borders. /// If this property returns false, the caller of GetNextCaretPosition should handle the line /// borders (i.e. place caret stops at the start and end of the line). /// This property has an effect only for VisualLineElements that are at the start or end of a /// . /// internal virtual bool HandlesLineBorders => false; /* /// /// Queries the cursor over the visual line element. /// protected internal virtual void OnQueryCursor(PointerEventArgs e) { }*/ /// /// Allows the visual line element to handle a pointer event. /// protected internal virtual void OnPointerPressed(Vector2 localPosition) { } /// /// Allows the visual line element to handle a pointer event. /// protected internal virtual void OnPointerReleased(Vector2 localPosition) { } } }