Redo Avalonia port of AvalonEdit.Rendering

This commit is contained in:
Benedikt Stebner 2022-03-10 14:48:52 +01:00
Родитель 61526254a0
Коммит 19dc0f3bcb
35 изменённых файлов: 3053 добавлений и 2848 удалений

Просмотреть файл

@ -2,7 +2,7 @@
<PropertyGroup>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AvaloniaVersion>0.10.999-cibuild0019136-beta</AvaloniaVersion>
<AvaloniaVersion>0.10.999-cibuild0019161-beta</AvaloniaVersion>
<TextMateSharpVersion>1.0.31</TextMateSharpVersion>
<NewtonsoftJsonVersion>13.0.1</NewtonsoftJsonVersion>
<Version>0.10.12.2</Version>

Просмотреть файл

@ -156,7 +156,7 @@ namespace AvaloniaEdit.Demo
private void AddControlButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
_generator.controls.Add(new Pair(_textEditor.CaretOffset, new Button() { Content = "Click me" }));
_generator.controls.Add(new KeyValuePair<int, Control>(_textEditor.CaretOffset, new Button() { Content = "Click me" }));
_textEditor.TextArea.TextView.Redraw();
}
@ -290,9 +290,9 @@ namespace AvaloniaEdit.Demo
}
}
class ElementGenerator : VisualLineElementGenerator, IComparer<Pair>
class ElementGenerator : VisualLineElementGenerator, IComparer<KeyValuePair<int, Control>>
{
public List<Pair> controls = new List<Pair>();
public List<KeyValuePair<int, Control>> controls = new List<KeyValuePair<int, Control>>();
/// <summary>
/// Gets the first interested offset using binary search
@ -301,7 +301,7 @@ namespace AvaloniaEdit.Demo
/// <param name="startOffset">Start offset.</param>
public override int GetFirstInterestedOffset(int startOffset)
{
int pos = controls.BinarySearch(new Pair(startOffset, null), this);
int pos = controls.BinarySearch(new KeyValuePair<int, Control>(startOffset, null), this);
if (pos < 0)
pos = ~pos;
if (pos < controls.Count)
@ -312,14 +312,14 @@ namespace AvaloniaEdit.Demo
public override VisualLineElement ConstructElement(int offset)
{
int pos = controls.BinarySearch(new Pair(offset, null), this);
int pos = controls.BinarySearch(new KeyValuePair<int, Control>(offset, null), this);
if (pos >= 0)
return new InlineObjectElement(0, controls[pos].Value);
else
return null;
}
int IComparer<Pair>.Compare(Pair x, Pair y)
int IComparer<KeyValuePair<int, Control>>.Compare(KeyValuePair<int, Control> x, KeyValuePair<int, Control> y)
{
return x.Key.CompareTo(y.Key);
}

Просмотреть файл

@ -45,11 +45,11 @@ namespace AvaloniaEdit.Editing
_textView = textArea.TextView;
_position = new TextViewPosition(1, 1, 0);
_caretAdorner = new CaretLayer(textArea);
_textView.InsertLayer(_caretAdorner, KnownLayer.Caret, LayerInsertionPosition.Replace);
_textView.VisualLinesChanged += TextView_VisualLinesChanged;
_textView.ScrollOffsetChanged += TextView_ScrollOffsetChanged;
}
_caretAdorner = new CaretLayer(textArea);
_textView.InsertLayer(_caretAdorner, KnownLayer.Caret, LayerInsertionPosition.Replace);
_textView.VisualLinesChanged += TextView_VisualLinesChanged;
_textView.ScrollOffsetChanged += TextView_ScrollOffsetChanged;
}
internal void UpdateIfVisible()
{
@ -402,41 +402,39 @@ namespace AvaloniaEdit.Editing
lineBottom - lineTop);
}
private Rect CalcCaretOverstrikeRectangle(VisualLine visualLine)
{
if (!_visualColumnValid)
{
RevalidateVisualColumn(visualLine);
}
Rect CalcCaretOverstrikeRectangle(VisualLine visualLine)
{
if (!_visualColumnValid) {
RevalidateVisualColumn(visualLine);
}
var currentPos = _position.VisualColumn;
// The text being overwritten in overstrike mode is everything up to the next normal caret stop
var nextPos = visualLine.GetNextCaretPosition(currentPos, LogicalDirection.Forward, CaretPositioningMode.Normal, true);
var textLine = visualLine.GetTextLine(currentPos);
int currentPos = _position.VisualColumn;
// The text being overwritten in overstrike mode is everything up to the next normal caret stop
int nextPos = visualLine.GetNextCaretPosition(currentPos, LogicalDirection.Forward, CaretPositioningMode.Normal, true);
var textLine = visualLine.GetTextLine(currentPos);
Rect r;
if (currentPos < visualLine.VisualLength)
{
// If the caret is within the text, use GetTextBounds() for the text being overwritten.
// This is necessary to ensure the rectangle is calculated correctly in bidirectional text.
r = textLine.GetTextBounds(currentPos, nextPos - currentPos);
r = r.WithY(r.Y + visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineTop));
}
else
{
// If the caret is at the end of the line (or in virtual space),
// use the visual X position of currentPos and nextPos (one or more of which will be in virtual space)
var xPos = visualLine.GetTextLineVisualXPosition(textLine, currentPos);
var xPos2 = visualLine.GetTextLineVisualXPosition(textLine, nextPos);
var lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop);
var lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom);
r = new Rect(xPos, lineTop, xPos2 - xPos, lineBottom - lineTop);
}
// If the caret is too small (e.g. in front of zero-width character), ensure it's still visible
if (r.Width < CaretWidth)
r = r.WithWidth(CaretWidth);
return r;
}
Rect r;
if (currentPos < visualLine.VisualLength) {
// If the caret is within the text, use GetTextBounds() for the text being overwritten.
// This is necessary to ensure the rectangle is calculated correctly in bidirectional text.
var textBounds = textLine.GetTextBounds(currentPos, nextPos - currentPos)[0];
r = textBounds.Rectangle;
var y = r.Y + visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineTop);
r = r.WithY(y);
} else {
// If the caret is at the end of the line (or in virtual space),
// use the visual X position of currentPos and nextPos (one or more of which will be in virtual space)
double xPos = visualLine.GetTextLineVisualXPosition(textLine, currentPos);
double xPos2 = visualLine.GetTextLineVisualXPosition(textLine, nextPos);
double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop);
double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom);
r = new Rect(xPos, lineTop, xPos2 - xPos, lineBottom - lineTop);
}
// If the caret is too small (e.g. in front of zero-width character), ensure it's still visible
if (r.Width < CaretWidth)
r = r.WithWidth(CaretWidth);
return r;
}
/// <summary>
/// Returns the caret rectangle. The coordinate system is in device-independent pixels from the top of the document.

Просмотреть файл

@ -50,51 +50,41 @@ namespace AvaloniaEdit.Editing
/// </summary>
protected double EmSize { get; set; }
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
Typeface = new Typeface(GetValue(TextBlock.FontFamilyProperty));
EmSize = GetValue(TextBlock.FontSizeProperty);
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
Typeface = this.CreateTypeface();
EmSize = GetValue(TextBlock.FontSizeProperty);
var textLine = TextFormatterFactory.FormatLine(Enumerable.Repeat('9', MaxLineNumberLength).ToArray(),
Typeface,
EmSize,
GetValue(TemplatedControl.ForegroundProperty)
);
return new Size(textLine.WidthIncludingTrailingWhitespace, textLine.Height);
}
/// <inheritdoc/>
public override void Render(DrawingContext drawingContext)
{
var textView = TextView;
var renderSize = Bounds.Size;
var text = TextFormatterFactory.CreateFormattedText(
this,
new string('9', MaxLineNumberLength),
Typeface,
EmSize,
GetValue(TextBlock.ForegroundProperty)
);
return new Size(text.Width, 0);
}
public override void Render(DrawingContext drawingContext)
{
var textView = TextView;
var renderSize = Bounds.Size;
if (textView != null && textView.VisualLinesValid)
{
var foreground = GetValue(TemplatedControl.ForegroundProperty);
foreach (var line in textView.VisualLines)
{
var lineNumber = line.FirstDocumentLine.LineNumber;
var text = lineNumber.ToString(CultureInfo.CurrentCulture);
var textLine = TextFormatterFactory.FormatLine(text.AsMemory(),
Typeface,
EmSize,
foreground
);
var y = line.TextLines.Count > 0
? line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop)
: line.VisualTop;
textLine.Draw(drawingContext,
new Point(renderSize.Width - textLine.WidthIncludingTrailingWhitespace,
y - textView.VerticalOffset));
}
}
}
if (textView is {VisualLinesValid: true}) {
var foreground = GetValue(TextBlock.ForegroundProperty);
foreach (var line in textView.VisualLines) {
var lineNumber = line.FirstDocumentLine.LineNumber;
var text = TextFormatterFactory.CreateFormattedText(
this,
lineNumber.ToString(CultureInfo.CurrentCulture),
Typeface, EmSize, foreground
);
var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop);
drawingContext.DrawText(text, new Point(renderSize.Width - text.Width, y - textView.VerticalOffset));
}
}
}
/// <inheritdoc/>
protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView)

Просмотреть файл

@ -22,7 +22,9 @@ using Avalonia;
using AvaloniaEdit.Rendering;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Media.TextFormatting;
using AvaloniaEdit.Utils;
namespace AvaloniaEdit.Folding
{
@ -149,20 +151,18 @@ namespace AvaloniaEdit.Folding
}
} while (foundOverlappingFolding);
var title = foldingSection.Title;
if (string.IsNullOrEmpty(title))
title = "...";
var p = CurrentContext.GlobalTextRunProperties.Clone();
p.SetForegroundBrush(TextBrush);
var textFormatter = TextFormatter.Current;
var text = FormattedTextElement.PrepareText(textFormatter, title, p);
return new FoldingLineElement(foldingSection, text, foldedUntil - offset, TextBrush);
}
else
{
return null;
}
}
string title = foldingSection.Title;
if (string.IsNullOrEmpty(title))
title = "...";
var p = new VisualLineElementTextRunProperties(CurrentContext.GlobalTextRunProperties);
p.SetForegroundBrush(TextBrush);
var textFormatter = TextFormatterFactory.Create(CurrentContext.TextView);
var text = FormattedTextElement.PrepareText(textFormatter, title, p);
return new FoldingLineElement(foldingSection, text, foldedUntil - offset, TextBrush);
} else {
return null;
}
}
private sealed class FoldingLineElement : FormattedTextElement
{
@ -175,10 +175,10 @@ namespace AvaloniaEdit.Folding
_textBrush = textBrush;
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
return new FoldingLineTextRun(this, TextRunProperties, _textBrush);
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
return new FoldingLineTextRun(this, this.TextRunProperties, _textBrush);
}
//DOUBLETAP
protected internal override void OnPointerPressed(PointerPressedEventArgs e)
@ -200,9 +200,9 @@ namespace AvaloniaEdit.Folding
public override void Draw(DrawingContext drawingContext, Point origin)
{
var metrics = Size;
var r = new Rect(origin.X, origin.Y, metrics.Width, metrics.Height);
drawingContext.DrawRectangle(new Pen(_textBrush), r);
var (width, height) = Size;
var r = new Rect(origin.X, origin.Y, width, height);
drawingContext.DrawRectangle(new ImmutablePen(_textBrush.ToImmutable()), r);
base.Draw(drawingContext, origin);
}
}

Просмотреть файл

@ -255,62 +255,34 @@ namespace AvaloniaEdit.Highlighting
ApplyColorToElement(element, color, CurrentContext);
}
internal static void ApplyColorToElement(VisualLineElement element, HighlightingColor color, ITextRunConstructionContext context)
{
if (color.Foreground != null)
{
var b = color.Foreground.GetBrush(context);
if (b != null)
element.TextRunProperties.SetForegroundBrush(b);
}
if (color.Background != null)
{
var b = color.Background.GetBrush(context);
if (b != null)
element.BackgroundBrush = b;
}
if (color.FontStyle != null || color.FontWeight != null || color.FontFamily != null)
{
var tf = element.TextRunProperties.Typeface;
element.TextRunProperties.SetTypeface(new Avalonia.Media.Typeface(
color.FontFamily ?? tf.FontFamily,
color.FontStyle ?? tf.Style,
color.FontWeight ?? tf.Weight)
);
}
if (color.FontSize.HasValue)
element.TextRunProperties.SetFontSize(color.FontSize.Value);
if (color.Underline ?? false)
{
element.TextRunProperties.SetTextDecorations(new TextDecorationCollection{new()
{
Location = TextDecorationLocation.Underline
}});
}
if (color.Strikethrough ?? false)
{
if (element.TextRunProperties.TextDecorations != null)
{
element.TextRunProperties.TextDecorations.Add(new TextDecoration
{
Location = TextDecorationLocation.Strikethrough
});
}
else
{
element.TextRunProperties.SetTextDecorations(new TextDecorationCollection
{
new()
{
Location = TextDecorationLocation.Strikethrough
}
});
}
}
}
internal static void ApplyColorToElement(VisualLineElement element, HighlightingColor color, ITextRunConstructionContext context)
{
if (color.Foreground != null) {
var b = color.Foreground.GetBrush(context);
if (b != null)
element.TextRunProperties.SetForegroundBrush(b);
}
if (color.Background != null) {
var b = color.Background.GetBrush(context);
if (b != null)
element.BackgroundBrush = b;
}
if (color.FontStyle != null || color.FontWeight != null || color.FontFamily != null) {
var tf = element.TextRunProperties.Typeface;
element.TextRunProperties.SetTypeface(new Typeface(
color.FontFamily ?? tf.FontFamily,
color.FontStyle ?? tf.Style,
color.FontWeight ?? tf.Weight,
tf.Stretch
));
}
if (color.Underline ?? false)
element.TextRunProperties.SetTextDecorations(TextDecorations.Underline);
if (color.Strikethrough ?? false)
element.TextRunProperties.SetTextDecorations(TextDecorations.Strikethrough);
if (color.FontSize.HasValue)
element.TextRunProperties.SetFontRenderingEmSize(color.FontSize.Value);
}
/// <summary>
/// This method is responsible for telling the TextView to redraw lines when the highlighting state has changed.

Просмотреть файл

@ -21,381 +21,362 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Avalonia;
using Avalonia.Controls.Primitives;
using AvaloniaEdit.Document;
using AvaloniaEdit.Editing;
using AvaloniaEdit.Utils;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
namespace AvaloniaEdit.Rendering
{
/// <summary>
/// Helper for creating a PathGeometry.
/// </summary>
public sealed class BackgroundGeometryBuilder
{
/// <summary>
/// Gets/sets the radius of the rounded corners.
/// </summary>
public double CornerRadius { get; set; }
/// <summary>
/// Helper for creating a PathGeometry.
/// </summary>
public sealed class BackgroundGeometryBuilder
{
private double _cornerRadius;
/// <summary>
/// Gets/Sets whether to align to whole pixels.
///
/// If BorderThickness is set to 0, the geometry is aligned to whole pixels.
/// If BorderThickness is set to a non-zero value, the outer edge of the border is aligned
/// to whole pixels.
///
/// The default value is <c>false</c>.
/// </summary>
public bool AlignToWholePixels { get; set; }
/// <summary>
/// Gets/sets the radius of the rounded corners.
/// </summary>
public double CornerRadius {
get { return _cornerRadius; }
set { _cornerRadius = value; }
}
/// <summary>
/// Gets/sets the border thickness.
///
/// This property only has an effect if <c>AlignToWholePixels</c> is enabled.
/// When using the resulting geometry to paint a border, set this property to the border thickness.
/// Otherwise, leave the property set to the default value <c>0</c>.
/// </summary>
public double BorderThickness { get; set; }
/// <summary>
/// Gets/Sets whether to align to whole pixels.
///
/// If BorderThickness is set to 0, the geometry is aligned to whole pixels.
/// If BorderThickness is set to a non-zero value, the outer edge of the border is aligned
/// to whole pixels.
///
/// The default value is <c>false</c>.
/// </summary>
public bool AlignToWholePixels { get; set; }
/// <summary>
/// Gets/Sets whether to extend the rectangles to full width at line end.
/// </summary>
public bool ExtendToFullWidthAtLineEnd { get; set; }
/// <summary>
/// Gets/sets the border thickness.
///
/// This property only has an effect if <c>AlignToWholePixels</c> is enabled.
/// When using the resulting geometry to paint a border, set this property to the border thickness.
/// Otherwise, leave the property set to the default value <c>0</c>.
/// </summary>
public double BorderThickness { get; set; }
/// <summary>
/// Adds the specified segment to the geometry.
/// </summary>
public void AddSegment(TextView textView, ISegment segment)
{
if (textView == null)
throw new ArgumentNullException(nameof(textView));
var pixelSize = PixelSnapHelpers.GetPixelSize(textView);
foreach (var r in GetRectsForSegment(textView, segment, ExtendToFullWidthAtLineEnd))
{
AddRectangle(pixelSize, r);
}
}
/// <summary>
/// Gets/Sets whether to extend the rectangles to full width at line end.
/// </summary>
public bool ExtendToFullWidthAtLineEnd { get; set; }
/// <summary>
/// Adds a rectangle to the geometry.
/// </summary>
/// <remarks>
/// This overload will align the coordinates according to
/// <see cref="AlignToWholePixels"/>.
/// Use the <see cref="AddRectangle(double,double,double,double)"/>-overload instead if the coordinates should not be aligned.
/// </remarks>
public void AddRectangle(TextView textView, Rect rectangle)
{
AddRectangle(PixelSnapHelpers.GetPixelSize(textView), rectangle);
}
/// <summary>
/// Creates a new BackgroundGeometryBuilder instance.
/// </summary>
public BackgroundGeometryBuilder()
{
}
private void AddRectangle(Size pixelSize, Rect r)
{
if (AlignToWholePixels)
{
var halfBorder = 0.5 * BorderThickness;
AddRectangle(PixelSnapHelpers.Round(r.X - halfBorder, pixelSize.Width) + halfBorder,
PixelSnapHelpers.Round(r.Y - halfBorder, pixelSize.Height) + halfBorder,
PixelSnapHelpers.Round(r.Right + halfBorder, pixelSize.Width) - halfBorder,
PixelSnapHelpers.Round(r.Bottom + halfBorder, pixelSize.Height) - halfBorder);
}
else
{
AddRectangle(r.X, r.Y, r.Right, r.Bottom);
}
}
/// <summary>
/// Adds the specified segment to the geometry.
/// </summary>
public void AddSegment(TextView textView, ISegment segment)
{
if (textView == null)
throw new ArgumentNullException("textView");
Size pixelSize = PixelSnapHelpers.GetPixelSize(textView);
foreach (Rect r in GetRectsForSegment(textView, segment, ExtendToFullWidthAtLineEnd)) {
AddRectangle(pixelSize, r);
}
}
/// <summary>
/// Calculates the list of rectangle where the segment in shown.
/// This method usually returns one rectangle for each line inside the segment
/// (but potentially more, e.g. when bidirectional text is involved).
/// </summary>
public static IEnumerable<Rect> GetRectsForSegment(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd = false)
{
if (textView == null)
throw new ArgumentNullException(nameof(textView));
if (segment == null)
throw new ArgumentNullException(nameof(segment));
return GetRectsForSegmentImpl(textView, segment, extendToFullWidthAtLineEnd);
}
/// <summary>
/// Adds a rectangle to the geometry.
/// </summary>
/// <remarks>
/// This overload will align the coordinates according to
/// <see cref="AlignToWholePixels"/>.
/// Use the <see cref="AddRectangle(double,double,double,double)"/>-overload instead if the coordinates should not be aligned.
/// </remarks>
public void AddRectangle(TextView textView, Rect rectangle)
{
AddRectangle(PixelSnapHelpers.GetPixelSize(textView), rectangle);
}
private static IEnumerable<Rect> GetRectsForSegmentImpl(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd)
{
var segmentStart = segment.Offset;
var segmentEnd = segment.Offset + segment.Length;
private void AddRectangle(Size pixelSize, Rect r)
{
if (AlignToWholePixels) {
double halfBorder = 0.5 * BorderThickness;
AddRectangle(PixelSnapHelpers.Round(r.Left - halfBorder, pixelSize.Width) + halfBorder,
PixelSnapHelpers.Round(r.Top - halfBorder, pixelSize.Height) + halfBorder,
PixelSnapHelpers.Round(r.Right + halfBorder, pixelSize.Width) - halfBorder,
PixelSnapHelpers.Round(r.Bottom + halfBorder, pixelSize.Height) - halfBorder);
//Debug.WriteLine(r.ToString() + " -> " + new Rect(lastLeft, lastTop, lastRight-lastLeft, lastBottom-lastTop).ToString());
} else {
AddRectangle(r.Left, r.Top, r.Right, r.Bottom);
}
}
segmentStart = segmentStart.CoerceValue(0, textView.Document.TextLength);
segmentEnd = segmentEnd.CoerceValue(0, textView.Document.TextLength);
/// <summary>
/// Calculates the list of rectangle where the segment in shown.
/// This method usually returns one rectangle for each line inside the segment
/// (but potentially more, e.g. when bidirectional text is involved).
/// </summary>
public static IEnumerable<Rect> GetRectsForSegment(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd = false)
{
if (textView == null)
throw new ArgumentNullException("textView");
if (segment == null)
throw new ArgumentNullException("segment");
return GetRectsForSegmentImpl(textView, segment, extendToFullWidthAtLineEnd);
}
TextViewPosition start;
TextViewPosition end;
private static IEnumerable<Rect> GetRectsForSegmentImpl(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd)
{
int segmentStart = segment.Offset;
int segmentEnd = segment.Offset + segment.Length;
if (segment is SelectionSegment sel)
{
start = new TextViewPosition(textView.Document.GetLocation(sel.StartOffset), sel.StartVisualColumn);
end = new TextViewPosition(textView.Document.GetLocation(sel.EndOffset), sel.EndVisualColumn);
}
else
{
start = new TextViewPosition(textView.Document.GetLocation(segmentStart));
end = new TextViewPosition(textView.Document.GetLocation(segmentEnd));
}
segmentStart = segmentStart.CoerceValue(0, textView.Document.TextLength);
segmentEnd = segmentEnd.CoerceValue(0, textView.Document.TextLength);
foreach (var vl in textView.VisualLines)
{
var vlStartOffset = vl.FirstDocumentLine.Offset;
if (vlStartOffset > segmentEnd)
break;
var vlEndOffset = vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length;
if (vlEndOffset < segmentStart)
continue;
TextViewPosition start;
TextViewPosition end;
int segmentStartVc;
segmentStartVc = segmentStart < vlStartOffset ? 0 : vl.ValidateVisualColumn(start, extendToFullWidthAtLineEnd);
if (segment is SelectionSegment) {
SelectionSegment sel = (SelectionSegment)segment;
start = new TextViewPosition(textView.Document.GetLocation(sel.StartOffset), sel.StartVisualColumn);
end = new TextViewPosition(textView.Document.GetLocation(sel.EndOffset), sel.EndVisualColumn);
} else {
start = new TextViewPosition(textView.Document.GetLocation(segmentStart));
end = new TextViewPosition(textView.Document.GetLocation(segmentEnd));
}
int segmentEndVc;
if (segmentEnd > vlEndOffset)
segmentEndVc = extendToFullWidthAtLineEnd ? int.MaxValue : vl.VisualLengthWithEndOfLineMarker;
else
segmentEndVc = vl.ValidateVisualColumn(end, extendToFullWidthAtLineEnd);
foreach (VisualLine vl in textView.VisualLines) {
int vlStartOffset = vl.FirstDocumentLine.Offset;
if (vlStartOffset > segmentEnd)
break;
int vlEndOffset = vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length;
if (vlEndOffset < segmentStart)
continue;
foreach (var rect in ProcessTextLines(textView, vl, segmentStartVc, segmentEndVc))
yield return rect;
}
}
int segmentStartVc;
if (segmentStart < vlStartOffset)
segmentStartVc = 0;
else
segmentStartVc = vl.ValidateVisualColumn(start, extendToFullWidthAtLineEnd);
/// <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(nameof(textView));
if (line == null)
throw new ArgumentNullException(nameof(line));
return ProcessTextLines(textView, line, startVc, endVc);
}
int segmentEndVc;
if (segmentEnd > vlEndOffset)
segmentEndVc = extendToFullWidthAtLineEnd ? int.MaxValue : vl.VisualLengthWithEndOfLineMarker;
else
segmentEndVc = vl.ValidateVisualColumn(end, extendToFullWidthAtLineEnd);
private static IEnumerable<Rect> ProcessTextLines(TextView textView, VisualLine visualLine, int segmentStartVc, int segmentEndVc)
{
if (visualLine.TextLines.Count == 0)
{
yield break;
}
var lastTextLine = visualLine.TextLines.Last();
var scrollOffset = textView.ScrollOffset;
foreach (var rect in ProcessTextLines(textView, vl, segmentStartVc, segmentEndVc))
yield return rect;
}
}
for (var i = 0; i < visualLine.TextLines.Count; i++)
{
var line = visualLine.TextLines[i];
var y = visualLine.GetTextLineVisualYPosition(line, VisualYPosition.LineTop);
var visualStartCol = visualLine.GetTextLineVisualStartColumn(line);
var visualEndCol = visualStartCol + line.TextRange.Length;
if (line == lastTextLine)
visualEndCol -= 1; // 1 position for the TextEndOfParagraph
// TODO: ?
//else
// visualEndCol -= line.TrailingWhitespaceLength;
/// <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);
}
if (segmentEndVc < visualStartCol)
break;
if (lastTextLine != line && segmentStartVc > visualEndCol)
continue;
var segmentStartVcInLine = Math.Max(segmentStartVc, visualStartCol);
var segmentEndVcInLine = Math.Min(segmentEndVc, visualEndCol);
y -= scrollOffset.Y;
var lastRect = Rect.Empty;
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
var 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;
lastRect = new Rect(pos, y, textView.EmptyLineSelectionWidth, line.Height);
}
else
{
if (segmentStartVcInLine <= visualEndCol)
{
var b = line.GetTextBounds(segmentStartVcInLine, segmentEndVcInLine - segmentStartVcInLine);
var left = b.X - scrollOffset.X;
var right = b.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 the segment ends in virtual space, extend the last rectangle with the rectangle the portion of the selection
// after the line end.
// Also, when word-wrap is enabled and the segment continues into the next line, extend lastRect up to the end of the line.
if (segmentEndVc > visualEndCol)
{
double left, right;
if (segmentStartVc > visualLine.VisualLengthWithEndOfLineMarker)
{
// segmentStartVC is in virtual space
left = visualLine.GetTextLineVisualXPosition(lastTextLine, segmentStartVc);
}
else
{
// Otherwise, we already processed the rects from segmentStartVC up to visualEndCol,
// so we only need to do the remainder starting at visualEndCol.
// For word-wrapped lines, visualEndCol doesn't include the whitespace hidden by the wrap,
// so we'll need to include it here.
// For the last line, visualEndCol already includes the whitespace.
left = line == lastTextLine ? line.WidthIncludingTrailingWhitespace : line.Width;
}
// TODO: !!!!!!!!!!!!!!!!!! SCROLL !!!!!!!!!!!!!!!!!!
//if (line != lastTextLine || segmentEndVC == int.MaxValue) {
// // If word-wrap is enabled and the segment continues into the next line,
// // or if the extendToFullWidthAtLineEnd option is used (segmentEndVC == int.MaxValue),
// // we select the full width of the viewport.
// right = Math.Max(((IScrollInfo)textView).ExtentWidth, ((IScrollInfo)textView).ViewportWidth);
//} else {
private static IEnumerable<Rect> ProcessTextLines(TextView textView, VisualLine visualLine, int segmentStartVc, int segmentEndVc)
{
TextLine lastTextLine = visualLine.TextLines.Last();
Vector scrollOffset = textView.ScrollOffset;
right = visualLine.GetTextLineVisualXPosition(lastTextLine, segmentEndVc);
//}
var extendSelection = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height);
if (!lastRect.IsEmpty)
{
if (extendSelection.Intersects(lastRect))
{
lastRect.Union(extendSelection);
yield return lastRect;
}
else
{
// If the end of the line is in an RTL segment, keep lastRect and extendSelection separate.
yield return lastRect;
yield return extendSelection;
}
}
else
yield return extendSelection;
}
else
yield return lastRect;
}
}
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.TextRange.Length;
if (line == lastTextLine)
visualEndCol -= 1; // 1 position for the TextEndOfParagraph
else
visualEndCol -= line.TrailingWhitespaceLength;
private readonly PathFigures _figures = new PathFigures();
private PathFigure _figure;
private int _insertionIndex;
private double _lastTop, _lastBottom;
private double _lastLeft, _lastRight;
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;
Rect lastRect = Rect.Empty;
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;
lastRect = new Rect(pos, y, textView.EmptyLineSelectionWidth, line.Height);
} else {
if (segmentStartVcInLine <= visualEndCol) {
foreach (var 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 the segment ends in virtual space, extend the last rectangle with the rectangle the portion of the selection
// after the line end.
// Also, when word-wrap is enabled and the segment continues into the next line, extend lastRect up to the end of the line.
if (segmentEndVc > visualEndCol) {
double left, right;
if (segmentStartVc > visualLine.VisualLengthWithEndOfLineMarker) {
// segmentStartVC is in virtual space
left = visualLine.GetTextLineVisualXPosition(lastTextLine, segmentStartVc);
} else {
// Otherwise, we already processed the rects from segmentStartVC up to visualEndCol,
// so we only need to do the remainder starting at visualEndCol.
// For word-wrapped lines, visualEndCol doesn't include the whitespace hidden by the wrap,
// so we'll need to include it here.
// For the last line, visualEndCol already includes the whitespace.
left = (line == lastTextLine ? line.WidthIncludingTrailingWhitespace : line.Width);
}
if (line != lastTextLine || segmentEndVc == int.MaxValue) {
// If word-wrap is enabled and the segment continues into the next line,
// or if the extendToFullWidthAtLineEnd option is used (segmentEndVC == int.MaxValue),
// we select the full width of the viewport.
right = Math.Max(((ILogicalScrollable)textView).Extent.Width, ((ILogicalScrollable)textView).Viewport.Width);
} else {
right = visualLine.GetTextLineVisualXPosition(lastTextLine, segmentEndVc);
}
Rect extendSelection = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height);
if (!lastRect.IsEmpty) {
if (extendSelection.Intersects(lastRect)) {
lastRect.Union(extendSelection);
yield return lastRect;
} else {
// If the end of the line is in an RTL segment, keep lastRect and extendSelection separate.
yield return lastRect;
yield return extendSelection;
}
} else
yield return extendSelection;
} else
yield return lastRect;
}
}
/// <summary>
/// Adds a rectangle to the geometry.
/// </summary>
/// <remarks>
/// This overload assumes that the coordinates are aligned properly
/// (see <see cref="AlignToWholePixels"/>).
/// Use the <see cref="AddRectangle(TextView,Rect)"/>-overload instead if the coordinates are not yet aligned.
/// </remarks>
public void AddRectangle(double left, double top, double right, double bottom)
{
if (!top.IsClose(_lastBottom))
{
CloseFigure();
}
if (_figure == null)
{
_figure = new PathFigure { StartPoint = new Point(left, top + CornerRadius) };
if (Math.Abs(left - right) > CornerRadius)
{
_figure.Segments.Add(MakeArc(left + CornerRadius, top, SweepDirection.Clockwise));
_figure.Segments.Add(MakeLineSegment(right - CornerRadius, top));
_figure.Segments.Add(MakeArc(right, top + CornerRadius, SweepDirection.Clockwise));
}
_figure.Segments.Add(MakeLineSegment(right, bottom - CornerRadius));
_insertionIndex = _figure.Segments.Count;
//figure.Segments.Add(MakeArc(left, bottom - cornerRadius, SweepDirection.Clockwise));
}
else
{
if (!_lastRight.IsClose(right))
{
var cr = right < _lastRight ? -CornerRadius : CornerRadius;
var dir1 = right < _lastRight ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
var dir2 = right < _lastRight ? SweepDirection.CounterClockwise : SweepDirection.Clockwise;
_figure.Segments.Insert(_insertionIndex++, MakeArc(_lastRight + cr, _lastBottom, dir1));
_figure.Segments.Insert(_insertionIndex++, MakeLineSegment(right - cr, top));
_figure.Segments.Insert(_insertionIndex++, MakeArc(right, top + CornerRadius, dir2));
}
_figure.Segments.Insert(_insertionIndex++, MakeLineSegment(right, bottom - CornerRadius));
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft, _lastTop + CornerRadius));
if (!_lastLeft.IsClose(left))
{
var cr = left < _lastLeft ? CornerRadius : -CornerRadius;
var dir1 = left < _lastLeft ? SweepDirection.CounterClockwise : SweepDirection.Clockwise;
var dir2 = left < _lastLeft ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
_figure.Segments.Insert(_insertionIndex, MakeArc(_lastLeft, _lastBottom - CornerRadius, dir1));
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft - cr, _lastBottom));
_figure.Segments.Insert(_insertionIndex, MakeArc(left + cr, _lastBottom, dir2));
}
}
_lastTop = top;
_lastBottom = bottom;
_lastLeft = left;
_lastRight = right;
}
private readonly PathFigures _figures = new PathFigures();
private PathFigure _figure;
private int _insertionIndex;
private double _lastTop, _lastBottom;
private double _lastLeft, _lastRight;
private ArcSegment MakeArc(double x, double y, SweepDirection dir)
{
var arc = new ArcSegment
{
Point = new Point(x, y),
Size = new Size(CornerRadius, CornerRadius),
SweepDirection = dir
};
return arc;
}
/// <summary>
/// Adds a rectangle to the geometry.
/// </summary>
/// <remarks>
/// This overload assumes that the coordinates are aligned properly
/// (see <see cref="AlignToWholePixels"/>).
/// Use the <see cref="AddRectangle(TextView,Rect)"/>-overload instead if the coordinates are not yet aligned.
/// </remarks>
public void AddRectangle(double left, double top, double right, double bottom)
{
if (!top.IsClose(_lastBottom)) {
CloseFigure();
}
if (_figure == null) {
_figure = new PathFigure();
_figure.StartPoint = new Point(left, top + _cornerRadius);
if (Math.Abs(left - right) > _cornerRadius) {
_figure.Segments.Add(MakeArc(left + _cornerRadius, top, SweepDirection.Clockwise));
_figure.Segments.Add(MakeLineSegment(right - _cornerRadius, top));
_figure.Segments.Add(MakeArc(right, top + _cornerRadius, SweepDirection.Clockwise));
}
_figure.Segments.Add(MakeLineSegment(right, bottom - _cornerRadius));
_insertionIndex = _figure.Segments.Count;
//figure.Segments.Add(MakeArc(left, bottom - cornerRadius, SweepDirection.Clockwise));
} else {
if (!_lastRight.IsClose(right)) {
double cr = right < _lastRight ? -_cornerRadius : _cornerRadius;
SweepDirection dir1 = right < _lastRight ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
SweepDirection dir2 = right < _lastRight ? SweepDirection.CounterClockwise : SweepDirection.Clockwise;
_figure.Segments.Insert(_insertionIndex++, MakeArc(_lastRight + cr, _lastBottom, dir1));
_figure.Segments.Insert(_insertionIndex++, MakeLineSegment(right - cr, top));
_figure.Segments.Insert(_insertionIndex++, MakeArc(right, top + _cornerRadius, dir2));
}
_figure.Segments.Insert(_insertionIndex++, MakeLineSegment(right, bottom - _cornerRadius));
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft, _lastTop + _cornerRadius));
if (!_lastLeft.IsClose(left)) {
double cr = left < _lastLeft ? _cornerRadius : -_cornerRadius;
SweepDirection dir1 = left < _lastLeft ? SweepDirection.CounterClockwise : SweepDirection.Clockwise;
SweepDirection dir2 = left < _lastLeft ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
_figure.Segments.Insert(_insertionIndex, MakeArc(_lastLeft, _lastBottom - _cornerRadius, dir1));
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft - cr, _lastBottom));
_figure.Segments.Insert(_insertionIndex, MakeArc(left + cr, _lastBottom, dir2));
}
}
this._lastTop = top;
this._lastBottom = bottom;
this._lastLeft = left;
this._lastRight = right;
}
private static LineSegment MakeLineSegment(double x, double y)
{
return new LineSegment { Point = new Point(x, y) };
}
private ArcSegment MakeArc(double x, double y, SweepDirection dir)
{
var arc = new ArcSegment
{
Point = new Point(x, y),
Size = new Size(CornerRadius, CornerRadius),
SweepDirection = dir
};
return arc;
}
/// <summary>
/// Closes the current figure.
/// </summary>
public void CloseFigure()
{
if (_figure != null)
{
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft, _lastTop + CornerRadius));
if (Math.Abs(_lastLeft - _lastRight) > CornerRadius)
{
_figure.Segments.Insert(_insertionIndex, MakeArc(_lastLeft, _lastBottom - CornerRadius, SweepDirection.Clockwise));
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft + CornerRadius, _lastBottom));
_figure.Segments.Insert(_insertionIndex, MakeArc(_lastRight - CornerRadius, _lastBottom, SweepDirection.Clockwise));
}
private static LineSegment MakeLineSegment(double x, double y)
{
return new LineSegment { Point = new Point(x, y) };
}
_figure.IsClosed = true;
_figures.Add(_figure);
_figure = null;
}
}
/// <summary>
/// Closes the current figure.
/// </summary>
public void CloseFigure()
{
if (_figure != null) {
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft, _lastTop + _cornerRadius));
if (Math.Abs(_lastLeft - _lastRight) > _cornerRadius) {
_figure.Segments.Insert(_insertionIndex, MakeArc(_lastLeft, _lastBottom - _cornerRadius, SweepDirection.Clockwise));
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft + _cornerRadius, _lastBottom));
_figure.Segments.Insert(_insertionIndex, MakeArc(_lastRight - _cornerRadius, _lastBottom, SweepDirection.Clockwise));
}
/// <summary>
/// Creates the geometry.
/// Returns null when the geometry is empty!
/// </summary>
public Geometry CreateGeometry()
{
CloseFigure();
return _figures.Count != 0 ? new PathGeometry { Figures = _figures } : null;
}
}
_figure.IsClosed = true;
_figures.Add(_figure);
_figure = null;
}
}
/// <summary>
/// Creates the geometry.
/// Returns null when the geometry is empty!
/// </summary>
public Geometry CreateGeometry()
{
CloseFigure();
return _figures.Count != 0 ? new PathGeometry { Figures = _figures } : null;
}
}
}

Просмотреть файл

@ -27,55 +27,47 @@ namespace AvaloniaEdit.Rendering
/// </summary>
public sealed class CollapsedLineSection
{
private DocumentLine _start;
private DocumentLine _end;
private readonly HeightTree _heightTree;
#if DEBUG
private readonly HeightTree _heightTree;
#if DEBUG
internal string Id;
private static int _nextId;
#else
internal const string Id = "";
#endif
private static int _nextId;
#else
const string ID = "";
#endif
internal CollapsedLineSection(HeightTree heightTree, DocumentLine start, DocumentLine end)
{
_heightTree = heightTree;
_start = start;
_end = end;
#if DEBUG
Start = start;
End = end;
#if DEBUG
unchecked {
Id = " #" + (_nextId++);
}
#endif
#endif
}
/// <summary>
/// Gets if the document line is collapsed.
/// This property initially is true and turns to false when uncollapsing the section.
/// </summary>
public bool IsCollapsed => _start != null;
public bool IsCollapsed => Start != null;
/// <summary>
/// <summary>
/// Gets the start line of the section.
/// When the section is uncollapsed or the text containing it is deleted,
/// this property returns null.
/// </summary>
public DocumentLine Start {
get => _start;
internal set => _start = value;
}
public DocumentLine Start { get; internal set; }
/// <summary>
/// Gets the end line of the section.
/// When the section is uncollapsed or the text containing it is deleted,
/// this property returns null.
/// </summary>
public DocumentLine End {
get => _end;
internal set => _end = value;
}
public DocumentLine End { get; internal set; }
/// <summary>
/// Uncollapses the section.
/// This causes the Start and End properties to be set to null!
@ -83,26 +75,28 @@ namespace AvaloniaEdit.Rendering
/// </summary>
public void Uncollapse()
{
if (_start == null)
if (Start == null)
return;
_heightTree.Uncollapse(this);
#if DEBUG
//heightTree.CheckProperties();
#endif
_start = null;
_end = null;
if (!_heightTree.IsDisposed) {
_heightTree.Uncollapse(this);
#if DEBUG
_heightTree.CheckProperties();
#endif
}
Start = null;
End = null;
}
/// <summary>
/// Gets a string representation of the collapsed section.
/// </summary>
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")]
public override string ToString()
{
return "[CollapsedSection" + Id + " Start=" + (_start != null ? _start.LineNumber.ToString() : "null")
+ " End=" + (_end != null ? _end.LineNumber.ToString() : "null") + "]";
return "[CollapsedSection" + Id + " Start=" + (Start != null ? Start.LineNumber.ToString() : "null")
+ " End=" + (End != null ? End.LineNumber.ToString() : "null") + "]";
}
}
}

Просмотреть файл

@ -21,111 +21,100 @@ using System.Collections.Generic;
namespace AvaloniaEdit.Rendering
{
/// <summary>
/// Base class for <see cref="IVisualLineTransformer"/> that helps
/// splitting visual elements so that colors (and other text properties) can be easily assigned
/// to individual words/characters.
/// </summary>
public abstract class ColorizingTransformer : IVisualLineTransformer, ITextViewConnect
{
/// <summary>
/// Gets the list of elements currently being transformed.
/// </summary>
protected IList<VisualLineElement> CurrentElements { get; private set; }
/// <summary>
/// Base class for <see cref="IVisualLineTransformer"/> that helps
/// splitting visual elements so that colors (and other text properties) can be easily assigned
/// to individual words/characters.
/// </summary>
public abstract class ColorizingTransformer : IVisualLineTransformer, ITextViewConnect
{
/// <summary>
/// Gets the list of elements currently being transformed.
/// </summary>
protected IList<VisualLineElement> CurrentElements { get; private set; }
/// <summary>
/// <see cref="IVisualLineTransformer.Transform"/> implementation.
/// Sets <see cref="CurrentElements"/> and calls <see cref="Colorize"/>.
/// </summary>
public void Transform(ITextRunConstructionContext context, IList<VisualLineElement> elements)
{
if (CurrentElements != null)
throw new InvalidOperationException("Recursive Transform() call");
CurrentElements = elements ?? throw new ArgumentNullException(nameof(elements));
/// <summary>
/// <see cref="IVisualLineTransformer.Transform"/> implementation.
/// Sets <see cref="CurrentElements"/> and calls <see cref="Colorize"/>.
/// </summary>
public void Transform(ITextRunConstructionContext context, IList<VisualLineElement> elements)
{
if (CurrentElements != null)
throw new InvalidOperationException("Recursive Transform() call");
CurrentElements = elements ?? throw new ArgumentNullException(nameof(elements));
try {
Colorize(context);
} finally {
CurrentElements = null;
}
}
try
{
Colorize(context);
}
finally
{
CurrentElements = null;
}
}
/// <summary>
/// Performs the colorization.
/// </summary>
protected abstract void Colorize(ITextRunConstructionContext context);
/// <summary>
/// Performs the colorization.
/// </summary>
protected abstract void Colorize(ITextRunConstructionContext context);
/// <summary>
/// Changes visual element properties.
/// This method accesses <see cref="CurrentElements"/>, so it must be called only during
/// a <see cref="Transform"/> call.
/// This method splits <see cref="VisualLineElement"/>s as necessary to ensure that the region
/// can be colored by setting the <see cref="VisualLineElement.TextRunProperties"/> of whole elements,
/// and then calls the <paramref name="action"/> on all elements in the region.
/// </summary>
/// <param name="visualStartColumn">Start visual column of the region to change</param>
/// <param name="visualEndColumn">End visual column of the region to change</param>
/// <param name="action">Action that changes an individual <see cref="VisualLineElement"/>.</param>
protected void ChangeVisualElements(int visualStartColumn, int visualEndColumn, Action<VisualLineElement> action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
for (var i = 0; i < CurrentElements.Count; i++) {
var e = CurrentElements[i];
if (e.VisualColumn > visualEndColumn)
break;
if (e.VisualColumn < visualStartColumn &&
e.VisualColumn + e.VisualLength > visualStartColumn)
{
if (e.CanSplit) {
e.Split(visualStartColumn, CurrentElements, i--);
continue;
}
}
if (e.VisualColumn >= visualStartColumn && e.VisualColumn < visualEndColumn) {
if (e.VisualColumn + e.VisualLength > visualEndColumn) {
if (e.CanSplit) {
e.Split(visualEndColumn, CurrentElements, i--);
}
} else {
action(e);
}
}
}
}
/// <summary>
/// Changes visual element properties.
/// This method accesses <see cref="CurrentElements"/>, so it must be called only during
/// a <see cref="Transform"/> call.
/// This method splits <see cref="VisualLineElement"/>s as necessary to ensure that the region
/// can be colored by setting the <see cref="VisualLineElement.TextRunProperties"/> of whole elements,
/// and then calls the <paramref name="action"/> on all elements in the region.
/// </summary>
/// <param name="visualStartColumn">Start visual column of the region to change</param>
/// <param name="visualEndColumn">End visual column of the region to change</param>
/// <param name="action">Action that changes an individual <see cref="VisualLineElement"/>.</param>
protected void ChangeVisualElements(int visualStartColumn, int visualEndColumn, Action<VisualLineElement> action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
for (int i = 0; i < CurrentElements.Count; i++)
{
VisualLineElement e = CurrentElements[i];
if (e.VisualColumn > visualEndColumn)
break;
if (e.VisualColumn < visualStartColumn &&
e.VisualColumn + e.VisualLength > visualStartColumn)
{
if (e.CanSplit)
{
e.Split(visualStartColumn, CurrentElements, i--);
continue;
}
}
if (e.VisualColumn >= visualStartColumn && e.VisualColumn < visualEndColumn)
{
if (e.VisualColumn + e.VisualLength > visualEndColumn)
{
if (e.CanSplit)
{
e.Split(visualEndColumn, CurrentElements, i--);
}
}
else
{
action(e);
}
}
}
}
/// <summary>
/// Called when added to a text view.
/// </summary>
protected virtual void OnAddToTextView(TextView textView)
{
}
/// <summary>
/// Called when added to a text view.
/// </summary>
protected virtual void OnAddToTextView(TextView textView)
{
}
/// <summary>
/// Called when removed from a text view.
/// </summary>
protected virtual void OnRemoveFromTextView(TextView textView)
{
}
/// <summary>
/// Called when removed from a text view.
/// </summary>
protected virtual void OnRemoveFromTextView(TextView textView)
{
}
void ITextViewConnect.AddToTextView(TextView textView)
{
OnAddToTextView(textView);
}
void ITextViewConnect.AddToTextView(TextView textView)
{
OnAddToTextView(textView);
}
void ITextViewConnect.RemoveFromTextView(TextView textView)
{
OnRemoveFromTextView(textView);
}
}
void ITextViewConnect.RemoveFromTextView(TextView textView)
{
OnRemoveFromTextView(textView);
}
}
}

Просмотреть файл

@ -29,22 +29,22 @@ namespace AvaloniaEdit.Rendering
/// </summary>
internal sealed class ColumnRulerRenderer : IBackgroundRenderer
{
private Pen _pen;
private int _column;
private readonly TextView _textView;
private IPen _pen;
private int _column;
private readonly TextView _textView;
public static readonly Color DefaultForeground = Colors.LightGray;
public ColumnRulerRenderer(TextView textView)
{
_pen = new Pen(new ImmutableSolidColorBrush(DefaultForeground));
_pen = new ImmutablePen(new ImmutableSolidColorBrush(DefaultForeground), 1);
_textView = textView ?? throw new ArgumentNullException(nameof(textView));
_textView.BackgroundRenderers.Add(this);
}
public KnownLayer Layer => KnownLayer.Background;
public void SetRuler(int column, Pen pen)
public void SetRuler(int column, IPen pen)
{
if (_column != column) {
_column = column;
@ -55,17 +55,17 @@ namespace AvaloniaEdit.Rendering
_textView.InvalidateLayer(Layer);
}
}
public void Draw(TextView textView, DrawingContext drawingContext)
{
if (_column < 1) return;
double offset = textView.WideSpaceWidth * _column;
Size pixelSize = PixelSnapHelpers.GetPixelSize(textView);
double markerXPos = PixelSnapHelpers.PixelAlign(offset, pixelSize.Width);
var offset = textView.WideSpaceWidth * _column;
var pixelSize = PixelSnapHelpers.GetPixelSize(textView);
var markerXPos = PixelSnapHelpers.PixelAlign(offset, pixelSize.Width);
markerXPos -= textView.ScrollOffset.X;
Point start = new Point(markerXPos, 0);
Point end = new Point(markerXPos, Math.Max(textView.DocumentHeight, textView.Bounds.Height));
var start = new Point(markerXPos, 0);
var end = new Point(markerXPos, Math.Max(textView.DocumentHeight, textView.Bounds.Height));
drawingContext.DrawLine(_pen, start, end);
}
}

Просмотреть файл

@ -37,13 +37,10 @@ namespace AvaloniaEdit.Rendering
#region Properties
public int Line
{
public int Line {
get { return _line; }
set
{
if (_line != value)
{
set {
if (_line != value) {
_line = value;
_textView.InvalidateLayer(Layer);
}
@ -52,17 +49,21 @@ namespace AvaloniaEdit.Rendering
public KnownLayer Layer => KnownLayer.Selection;
public IBrush BackgroundBrush { get; set; }
public IBrush BackgroundBrush {
get; set;
}
public Pen BorderPen { get; set; }
public IPen BorderPen {
get; set;
}
#endregion
public CurrentLineHighlightRenderer(TextView textView)
{
BorderPen = new Pen(new ImmutableSolidColorBrush(DefaultBorder));
BorderPen = new ImmutablePen(new ImmutableSolidColorBrush(DefaultBorder), 1);
BackgroundBrush = new ImmutableSolidColorBrush(DefaultBackground);
BackgroundBrush = new ImmutableSolidColorBrush(DefaultBackground);
_textView = textView ?? throw new ArgumentNullException(nameof(textView));
_textView.BackgroundRenderers.Add(this);
@ -75,7 +76,7 @@ namespace AvaloniaEdit.Rendering
if (!_textView.Options.HighlightCurrentLine)
return;
BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder();
var builder = new BackgroundGeometryBuilder();
var visualLine = _textView.GetVisualLine(_line);
if (visualLine == null) return;
@ -84,9 +85,8 @@ namespace AvaloniaEdit.Rendering
builder.AddRectangle(textView, new Rect(0, linePosY, textView.Bounds.Width, visualLine.Height));
Geometry geometry = builder.CreateGeometry();
if (geometry != null)
{
var geometry = builder.CreateGeometry();
if (geometry != null) {
drawingContext.DrawGeometry(BackgroundBrush, BorderPen, geometry);
}
}

Просмотреть файл

@ -23,80 +23,74 @@ using AvaloniaEdit.Document;
namespace AvaloniaEdit.Rendering
{
/// <summary>
/// Base class for <see cref="IVisualLineTransformer"/> that helps
/// colorizing the document. Derived classes can work with document lines
/// and text offsets and this class takes care of the visual lines and visual columns.
/// </summary>
public abstract class DocumentColorizingTransformer : ColorizingTransformer
{
private DocumentLine _currentDocumentLine;
private int _firstLineStart;
private int _currentDocumentLineStartOffset, _currentDocumentLineEndOffset;
/// Base class for <see cref="IVisualLineTransformer"/> that helps
/// colorizing the document. Derived classes can work with document lines
/// and text offsets and this class takes care of the visual lines and visual columns.
/// </summary>
public abstract class DocumentColorizingTransformer : ColorizingTransformer
{
private DocumentLine _currentDocumentLine;
private int _firstLineStart;
private int _currentDocumentLineStartOffset, _currentDocumentLineEndOffset;
/// <summary>
/// Gets the current ITextRunConstructionContext.
/// </summary>
protected ITextRunConstructionContext CurrentContext { get; private set; }
/// <summary>
/// Gets the current ITextRunConstructionContext.
/// </summary>
protected ITextRunConstructionContext CurrentContext { get; private set; }
/// <inheritdoc/>
protected override void Colorize(ITextRunConstructionContext context)
{
CurrentContext = context ?? throw new ArgumentNullException(nameof(context));
/// <inheritdoc/>
protected override void Colorize(ITextRunConstructionContext context)
{
CurrentContext = context ?? throw new ArgumentNullException(nameof(context));
_currentDocumentLine = context.VisualLine.FirstDocumentLine;
_firstLineStart = _currentDocumentLineStartOffset = _currentDocumentLine.Offset;
_currentDocumentLineEndOffset = _currentDocumentLineStartOffset + _currentDocumentLine.Length;
var currentDocumentLineTotalEndOffset = _currentDocumentLineStartOffset + _currentDocumentLine.TotalLength;
_currentDocumentLine = context.VisualLine.FirstDocumentLine;
_firstLineStart = _currentDocumentLineStartOffset = _currentDocumentLine.Offset;
_currentDocumentLineEndOffset = _currentDocumentLineStartOffset + _currentDocumentLine.Length;
var currentDocumentLineTotalEndOffset = _currentDocumentLineStartOffset + _currentDocumentLine.TotalLength;
if (context.VisualLine.FirstDocumentLine == context.VisualLine.LastDocumentLine)
{
ColorizeLine(_currentDocumentLine);
}
else
{
ColorizeLine(_currentDocumentLine);
// ColorizeLine modifies the visual line elements, loop through a copy of the line elements
foreach (var e in context.VisualLine.Elements.ToArray())
{
var elementOffset = _firstLineStart + e.RelativeTextOffset;
if (elementOffset >= currentDocumentLineTotalEndOffset)
{
_currentDocumentLine = context.Document.GetLineByOffset(elementOffset);
_currentDocumentLineStartOffset = _currentDocumentLine.Offset;
_currentDocumentLineEndOffset = _currentDocumentLineStartOffset + _currentDocumentLine.Length;
currentDocumentLineTotalEndOffset = _currentDocumentLineStartOffset + _currentDocumentLine.TotalLength;
ColorizeLine(_currentDocumentLine);
}
}
}
_currentDocumentLine = null;
CurrentContext = null;
}
if (context.VisualLine.FirstDocumentLine == context.VisualLine.LastDocumentLine) {
ColorizeLine(_currentDocumentLine);
} else {
ColorizeLine(_currentDocumentLine);
// ColorizeLine modifies the visual line elements, loop through a copy of the line elements
foreach (var e in context.VisualLine.Elements.ToArray()) {
var elementOffset = _firstLineStart + e.RelativeTextOffset;
if (elementOffset >= currentDocumentLineTotalEndOffset) {
_currentDocumentLine = context.Document.GetLineByOffset(elementOffset);
_currentDocumentLineStartOffset = _currentDocumentLine.Offset;
_currentDocumentLineEndOffset = _currentDocumentLineStartOffset + _currentDocumentLine.Length;
currentDocumentLineTotalEndOffset = _currentDocumentLineStartOffset + _currentDocumentLine.TotalLength;
ColorizeLine(_currentDocumentLine);
}
}
}
_currentDocumentLine = null;
CurrentContext = null;
}
/// <summary>
/// Override this method to colorize an individual document line.
/// </summary>
protected abstract void ColorizeLine(DocumentLine line);
/// <summary>
/// Override this method to colorize an individual document line.
/// </summary>
protected abstract void ColorizeLine(DocumentLine line);
/// <summary>
/// Changes a part of the current document line.
/// </summary>
/// <param name="startOffset">Start offset of the region to change</param>
/// <param name="endOffset">End offset of the region to change</param>
/// <param name="action">Action that changes an individual <see cref="VisualLineElement"/>.</param>
protected void ChangeLinePart(int startOffset, int endOffset, Action<VisualLineElement> action)
{
if (startOffset < _currentDocumentLineStartOffset || startOffset > _currentDocumentLineEndOffset)
throw new ArgumentOutOfRangeException(nameof(startOffset), startOffset, "Value must be between " + _currentDocumentLineStartOffset + " and " + _currentDocumentLineEndOffset);
if (endOffset < _currentDocumentLineStartOffset || endOffset > _currentDocumentLineEndOffset)
throw new ArgumentOutOfRangeException(nameof(endOffset), endOffset, "Value must be between " + _currentDocumentLineStartOffset + " and " + _currentDocumentLineEndOffset);
var vl = CurrentContext.VisualLine;
var visualStart = vl.GetVisualColumn(startOffset - _firstLineStart);
var visualEnd = vl.GetVisualColumn(endOffset - _firstLineStart);
if (visualStart < visualEnd)
{
ChangeVisualElements(visualStart, visualEnd, action);
}
}
}
/// <summary>
/// Changes a part of the current document line.
/// </summary>
/// <param name="startOffset">Start offset of the region to change</param>
/// <param name="endOffset">End offset of the region to change</param>
/// <param name="action">Action that changes an individual <see cref="VisualLineElement"/>.</param>
protected void ChangeLinePart(int startOffset, int endOffset, Action<VisualLineElement> action)
{
if (startOffset < _currentDocumentLineStartOffset || startOffset > _currentDocumentLineEndOffset)
throw new ArgumentOutOfRangeException(nameof(startOffset), startOffset, "Value must be between " + _currentDocumentLineStartOffset + " and " + _currentDocumentLineEndOffset);
if (endOffset < startOffset || endOffset > _currentDocumentLineEndOffset)
throw new ArgumentOutOfRangeException(nameof(endOffset), endOffset, "Value must be between " + startOffset + " and " + _currentDocumentLineEndOffset);
var vl = CurrentContext.VisualLine;
var visualStart = vl.GetVisualColumn(startOffset - _firstLineStart);
var visualEnd = vl.GetVisualColumn(endOffset - _firstLineStart);
if (visualStart < visualEnd) {
ChangeVisualElements(visualStart, visualEnd, action);
}
}
}
}

Просмотреть файл

@ -20,144 +20,136 @@ using System;
using Avalonia;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using AvaloniaEdit.Utils;
using JetBrains.Annotations;
namespace AvaloniaEdit.Rendering
{
/// <summary>
/// Formatted text (not normal document text).
/// This is used as base class for various VisualLineElements that are displayed using a
/// FormattedText, for example newline markers or collapsed folding sections.
/// </summary>
public class FormattedTextElement : VisualLineElement
{
internal FormattedText FormattedText { get; }
internal string Text { get; set; }
internal TextLine TextLine { get; set; }
/// <summary>
/// Formatted text (not normal document text).
/// This is used as base class for various VisualLineElements that are displayed using a
/// FormattedText, for example newline markers or collapsed folding sections.
/// </summary>
public class FormattedTextElement : VisualLineElement
{
internal readonly FormattedText FormattedText;
internal string Text;
internal TextLine TextLine;
/// <summary>
/// Creates a new FormattedTextElement that displays the specified text
/// and occupies the specified length in the document.
/// </summary>
public FormattedTextElement(string text, int documentLength) : base(1, documentLength)
{
Text = text ?? throw new ArgumentNullException(nameof(text));
}
/// <summary>
/// Creates a new FormattedTextElement that displays the specified text
/// and occupies the specified length in the document.
/// </summary>
public FormattedTextElement(string text, int documentLength) : base(1, documentLength)
{
Text = text ?? throw new ArgumentNullException(nameof(text));
}
/// <summary>
/// Creates a new FormattedTextElement that displays the specified text
/// and occupies the specified length in the document.
/// </summary>
internal FormattedTextElement(TextLine text, int documentLength) : base(1, documentLength)
{
TextLine = text ?? throw new ArgumentNullException(nameof(text));
}
/// <summary>
/// Creates a new FormattedTextElement that displays the specified text
/// and occupies the specified length in the document.
/// </summary>
public FormattedTextElement(TextLine text, int documentLength) : base(1, documentLength)
{
TextLine = text ?? throw new ArgumentNullException(nameof(text));
}
/// <summary>
/// Creates a new FormattedTextElement that displays the specified text
/// and occupies the specified length in the document.
/// </summary>
public FormattedTextElement(FormattedText text, int documentLength) : base(1, documentLength)
{
FormattedText = text ?? throw new ArgumentNullException(nameof(text));
}
/// <summary>
/// Creates a new FormattedTextElement that displays the specified text
/// and occupies the specified length in the document.
/// </summary>
public FormattedTextElement(FormattedText text, int documentLength) : base(1, documentLength)
{
FormattedText = text ?? throw new ArgumentNullException(nameof(text));
}
/// <inheritdoc/>
[CanBeNull]
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
if (TextLine == null)
{
var formatter = TextFormatter.Current;
TextLine = PrepareText(formatter, Text, TextRunProperties);
Text = null;
}
return new FormattedTextRun(this, TextRunProperties);
}
/// <inheritdoc/>
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
if (TextLine == null) {
var formatter = TextFormatterFactory.Create(context.TextView);
TextLine = PrepareText(formatter, Text, TextRunProperties);
Text = null;
}
return new FormattedTextRun(this, TextRunProperties);
}
/// <summary>
/// Constructs a TextLine from a simple text.
/// </summary>
internal static TextLine PrepareText(TextFormatter formatter, string text, TextRunProperties properties)
{
if (formatter == null)
throw new ArgumentNullException(nameof(formatter));
if (text == null)
throw new ArgumentNullException(nameof(text));
if (properties == null)
throw new ArgumentNullException(nameof(properties));
return formatter.FormatLine(
new SimpleTextSource(text.AsMemory(), properties),
0,
32000,
/// <summary>
/// Constructs a TextLine from a simple text.
/// </summary>
public static TextLine PrepareText(TextFormatter formatter, string text, TextRunProperties properties)
{
if (formatter == null)
throw new ArgumentNullException(nameof(formatter));
if (text == null)
throw new ArgumentNullException(nameof(text));
if (properties == null)
throw new ArgumentNullException(nameof(properties));
return formatter.FormatLine(
new SimpleTextSource(text, properties),
0,
32000,
new VisualLineTextParagraphProperties {
defaultTextRunProperties = properties,
textWrapping = TextWrapping.NoWrap,
tabSize = 40
},
null);
}
}
//DefaultIncrementalTab = 40
/// <summary>
/// This is the TextRun implementation used by the <see cref="FormattedTextElement"/> class.
/// </summary>
public class FormattedTextRun : DrawableTextRun
{
/// <summary>
/// Creates a new FormattedTextRun.
/// </summary>
public FormattedTextRun(FormattedTextElement element, TextRunProperties properties)
{
if (properties == null)
throw new ArgumentNullException(nameof(properties));
Properties = properties;
Element = element ?? throw new ArgumentNullException(nameof(element));
}
new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left, true, false,
properties, TextWrapping.NoWrap, 0, 0));
}
}
/// <summary>
/// Gets the element for which the FormattedTextRun was created.
/// </summary>
public FormattedTextElement Element { get; }
/// <summary>
/// This is the TextRun implementation used by the <see cref="FormattedTextElement"/> class.
/// </summary>
public class FormattedTextRun : DrawableTextRun
{
/// <summary>
/// Creates a new FormattedTextRun.
/// </summary>
public FormattedTextRun(FormattedTextElement element, TextRunProperties properties)
{
Properties = properties ?? throw new ArgumentNullException(nameof(properties));
Element = element ?? throw new ArgumentNullException(nameof(element));
/// <inheritdoc/>
public override TextRunProperties Properties { get; }
Size = GetSize();
}
public override double Baseline => Element.FormattedText?.Baseline ?? Element.TextLine.Baseline;
/// <summary>
/// Gets the element for which the FormattedTextRun was created.
/// </summary>
public FormattedTextElement Element { get; }
/// <inheritdoc/>
public override Size Size
{
get
{
var formattedText = Element.FormattedText;
if (formattedText != null) {
return new Size(formattedText.WidthIncludingTrailingWhitespace, formattedText.Height);
}
/// <inheritdoc/>
public override int TextSourceLength => Element.VisualLength;
var text = Element.TextLine;
return new Size( text.WidthIncludingTrailingWhitespace, text.Height);
}
}
/// <inheritdoc/>
public override TextRunProperties Properties { get; }
public override Size Size { get; }
public override double Baseline =>
Element.FormattedText?.Baseline ?? Element.TextLine.Baseline;
private Size GetSize()
{
var formattedText = Element.FormattedText;
if (formattedText != null)
{
return new Size(formattedText.WidthIncludingTrailingWhitespace, formattedText.Height);
}
var text = Element.TextLine;
return new Size(text.WidthIncludingTrailingWhitespace,
text.Height);
}
/// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point origin)
{
if (Element.FormattedText != null)
{
//origin = origin.WithY(origin.Y - Element.formattedText.Baseline);
drawingContext.DrawText(Element.FormattedText, origin);
}
else
{
//origin.Y -= element.textLine.Baseline;
Element.TextLine.Draw(drawingContext, origin);
}
}
}
/// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point origin)
{
if (Element.FormattedText != null) {
//var y = origin.Y - Element.FormattedText.Baseline;
drawingContext.DrawText(Element.FormattedText, origin);
} else {
//var y = origin.Y - Element.TextLine.Baseline;
Element.TextLine.Draw(drawingContext, origin);
}
}
}
}

Просмотреть файл

@ -1,6 +0,0 @@
namespace AvaloniaEdit.Rendering
{
using Avalonia.Media;
using System.Collections.Generic;
}

Просмотреть файл

@ -0,0 +1,46 @@
// 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.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
#nullable enable
namespace AvaloniaEdit.Rendering
{
internal sealed class GlobalTextRunProperties : TextRunProperties
{
internal Typeface typeface;
internal double fontRenderingEmSize;
internal IBrush? foregroundBrush;
internal CultureInfo? cultureInfo;
public override Typeface Typeface => typeface;
public override double FontRenderingEmSize => fontRenderingEmSize;
//public override double FontHintingEmSize { get { return fontRenderingEmSize; } }
public override TextDecorationCollection? TextDecorations => null;
public override IBrush? ForegroundBrush => foregroundBrush;
public override IBrush? BackgroundBrush => null;
public override CultureInfo? CultureInfo => cultureInfo;
//public override TextEffectCollection TextEffects { get { return null; } }
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -32,7 +32,9 @@ namespace AvaloniaEdit.Rendering
internal double Height;
internal List<CollapsedLineSection> CollapsedSections;
internal bool IsDirectlyCollapsed => CollapsedSections != null;
internal bool IsDirectlyCollapsed {
get { return CollapsedSections != null; }
}
internal void AddDirectlyCollapsed(CollapsedLineSection section)
{
@ -52,6 +54,10 @@ namespace AvaloniaEdit.Rendering
/// <summary>
/// Returns 0 if the line is directly collapsed, otherwise, returns <see cref="Height"/>.
/// </summary>
internal double TotalHeight => IsDirectlyCollapsed ? 0 : Height;
internal double TotalHeight {
get {
return IsDirectlyCollapsed ? 0 : Height;
}
}
}
}

Просмотреть файл

@ -26,28 +26,26 @@ namespace AvaloniaEdit.Rendering
/// <summary>
/// A node in the text view's height tree.
/// </summary>
sealed class HeightTreeNode
internal sealed class HeightTreeNode
{
internal readonly DocumentLine DocumentLine;
internal HeightTreeLineNode LineNode;
internal HeightTreeNode Left;
internal HeightTreeNode Right;
internal HeightTreeNode Parent;
internal bool Color;
internal HeightTreeNode Left, Right, Parent;
internal bool Color;
internal HeightTreeNode()
{
}
internal HeightTreeNode(DocumentLine documentLine, double height)
{
DocumentLine = documentLine;
TotalCount = 1;
LineNode = new HeightTreeLineNode(height);
TotalHeight = height;
this.DocumentLine = documentLine;
this.TotalCount = 1;
this.LineNode = new HeightTreeLineNode(height);
this.TotalHeight = height;
}
internal HeightTreeNode LeftMost {
get {
HeightTreeNode node = this;
@ -56,7 +54,7 @@ namespace AvaloniaEdit.Rendering
return node;
}
}
internal HeightTreeNode RightMost {
get {
HeightTreeNode node = this;
@ -65,7 +63,7 @@ namespace AvaloniaEdit.Rendering
return node;
}
}
/// <summary>
/// Gets the inorder successor of the node.
/// </summary>
@ -73,25 +71,26 @@ namespace AvaloniaEdit.Rendering
get {
if (Right != null) {
return Right.LeftMost;
} else {
HeightTreeNode node = this;
HeightTreeNode oldNode;
do {
oldNode = node;
node = node.Parent;
// go up until we are coming out of a left subtree
} while (node != null && node.Right == oldNode);
return node;
}
HeightTreeNode node = this;
HeightTreeNode oldNode;
do {
oldNode = node;
node = node.Parent;
// go up until we are coming out of a left subtree
} while (node != null && node.Right == oldNode);
return node;
}
}
/// <summary>
/// The number of lines in this node and its child nodes.
/// Invariant:
/// totalCount = 1 + left.totalCount + right.totalCount
/// </summary>
internal int TotalCount;
/// <summary>
/// The total height of this node and its child nodes, excluding directly collapsed nodes.
/// Invariant:
@ -100,7 +99,7 @@ namespace AvaloniaEdit.Rendering
/// + right.IsDirectlyCollapsed ? 0 : right.totalHeight
/// </summary>
internal double TotalHeight;
/// <summary>
/// List of the sections that hold this node collapsed.
/// Invariant 1:
@ -113,10 +112,14 @@ namespace AvaloniaEdit.Rendering
/// documentLine (middle node).
/// </summary>
internal List<CollapsedLineSection> CollapsedSections;
internal bool IsDirectlyCollapsed => CollapsedSections != null;
internal void AddDirectlyCollapsed(CollapsedLineSection section)
internal bool IsDirectlyCollapsed {
get {
return CollapsedSections != null;
}
}
internal void AddDirectlyCollapsed(CollapsedLineSection section)
{
if (CollapsedSections == null) {
CollapsedSections = new List<CollapsedLineSection>();
@ -125,8 +128,8 @@ namespace AvaloniaEdit.Rendering
Debug.Assert(!CollapsedSections.Contains(section));
CollapsedSections.Add(section);
}
internal void RemoveDirectlyCollapsed(CollapsedLineSection section)
{
Debug.Assert(CollapsedSections.Contains(section));
@ -140,8 +143,8 @@ namespace AvaloniaEdit.Rendering
TotalHeight += Right.TotalHeight;
}
}
#if DEBUG
#if DEBUG
public override string ToString()
{
return "[HeightTreeNode "
@ -151,16 +154,16 @@ namespace AvaloniaEdit.Rendering
+ " TotalHeight=" + TotalHeight
+ "]";
}
static string GetCollapsedSections(List<CollapsedLineSection> list)
{
if (list == null)
return "{}";
return "{" +
string.Join(",",
list.Select(cs=>cs.Id).ToArray())
list.ConvertAll(cs => cs.Id).ToArray())
+ "}";
}
#endif
#endif
}
}

Просмотреть файл

@ -16,8 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using AvaloniaEdit.Document;
using AvaloniaEdit.Utils;
@ -35,22 +33,22 @@ namespace AvaloniaEdit.Rendering
/// Gets the text document.
/// </summary>
TextDocument Document { get; }
/// <summary>
/// Gets the text view for which the construction runs.
/// </summary>
TextView TextView { get; }
/// <summary>
/// Gets the visual line that is currently being constructed.
/// </summary>
VisualLine VisualLine { get; }
/// <summary>
/// Gets the global text run properties.
/// </summary>
CustomTextRunProperties GlobalTextRunProperties { get; }
TextRunProperties GlobalTextRunProperties { get; }
/// <summary>
/// Gets a piece of text from the document.
/// </summary>
@ -60,115 +58,6 @@ namespace AvaloniaEdit.Rendering
/// This method should be the preferred text access method in the text transformation pipeline, as it can avoid repeatedly allocating string instances
/// for text within the same line.
/// </remarks>
string GetText(int offset, int length);
}
public sealed class CustomTextRunProperties : TextRunProperties
{
public const double DefaultFontRenderingEmSize = 12;
private Typeface _typeface;
private double _fontRenderingEmSize;
private TextDecorationCollection? _textDecorations;
private IBrush? _foregroundBrush;
private IBrush? _backgroundBrush;
private CultureInfo? _cultureInfo;
private BaselineAlignment _baselineAlignment;
internal CustomTextRunProperties(Typeface typeface,
double fontRenderingEmSize = 12,
TextDecorationCollection? textDecorations = null,
IBrush? foregroundBrush = null,
IBrush? backgroundBrush = null,
CultureInfo? cultureInfo = null,
BaselineAlignment baselineAlignment = BaselineAlignment.Baseline)
{
_typeface = typeface;
_fontRenderingEmSize = fontRenderingEmSize;
_textDecorations = textDecorations;
_foregroundBrush = foregroundBrush;
_backgroundBrush = backgroundBrush;
_cultureInfo = cultureInfo;
_baselineAlignment = baselineAlignment;
}
public override Typeface Typeface => _typeface;
public override double FontRenderingEmSize => _fontRenderingEmSize;
public override TextDecorationCollection? TextDecorations => _textDecorations;
public override IBrush? ForegroundBrush => _foregroundBrush;
public override IBrush? BackgroundBrush => _backgroundBrush;
public override CultureInfo? CultureInfo => _cultureInfo;
public override BaselineAlignment BaselineAlignment => _baselineAlignment;
public CustomTextRunProperties Clone()
{
return new CustomTextRunProperties(Typeface, FontRenderingEmSize, TextDecorations, ForegroundBrush,
BackgroundBrush, CultureInfo, BaselineAlignment);
}
public void SetForegroundBrush(IBrush foregroundBrush)
{
_foregroundBrush = foregroundBrush;
}
public void SetBackgroundBrush(IBrush backgroundBrush)
{
_backgroundBrush = backgroundBrush;
}
public void SetTypeface(Typeface typeface)
{
_typeface = typeface;
}
public void SetFontSize(int colorFontSize)
{
_fontRenderingEmSize = colorFontSize;
}
public void SetTextDecorations(TextDecorationCollection textDecorations)
{
_textDecorations = textDecorations;
}
}
public sealed class CustomTextParagraphProperties : TextParagraphProperties
{
public const double DefaultIncrementalTabWidth = 4 * CustomTextRunProperties.DefaultFontRenderingEmSize;
private TextWrapping _textWrapping;
private double _lineHeight;
private double _indent;
private double _defaultIncrementalTab;
private readonly bool _firstLineInParagraph;
public CustomTextParagraphProperties(TextRunProperties defaultTextRunProperties,
bool firstLineInParagraph = true,
TextWrapping textWrapping = TextWrapping.NoWrap,
double lineHeight = 0,
double indent = 0,
double defaultIncrementalTab = DefaultIncrementalTabWidth)
{
DefaultTextRunProperties = defaultTextRunProperties;
_firstLineInParagraph = firstLineInParagraph;
_textWrapping = textWrapping;
_lineHeight = lineHeight;
_indent = indent;
_defaultIncrementalTab = defaultIncrementalTab;
}
public override FlowDirection FlowDirection => FlowDirection.LeftToRight;
public override TextAlignment TextAlignment => TextAlignment.Left;
public override double LineHeight => _lineHeight;
public override bool FirstLineInParagraph => _firstLineInParagraph;
public override TextRunProperties DefaultTextRunProperties { get; }
public override TextWrapping TextWrapping => _textWrapping;
public override double Indent => _indent;
StringSegment GetText(int offset, int length);
}
}

Просмотреть файл

@ -22,85 +22,99 @@ using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
#nullable enable
namespace AvaloniaEdit.Rendering
{
/// <summary>
/// A inline UIElement in the document.
/// </summary>
public class InlineObjectElement : VisualLineElement
{
/// <summary>
/// Gets the inline element that is displayed.
/// </summary>
public IControl Element { get; }
/// A inline UIElement in the document.
/// </summary>
public class InlineObjectElement : VisualLineElement
{
/// <summary>
/// Gets the inline element that is displayed.
/// </summary>
public Control Element { get; }
/// <summary>
/// Creates a new InlineObjectElement.
/// </summary>
/// <param name="documentLength">The length of the element in the document. Must be non-negative.</param>
/// <param name="element">The element to display.</param>
public InlineObjectElement(int documentLength, IControl element)
: base(1, documentLength)
{
Element = element ?? throw new ArgumentNullException(nameof(element));
}
/// <summary>
/// Creates a new InlineObjectElement.
/// </summary>
/// <param name="documentLength">The length of the element in the document. Must be non-negative.</param>
/// <param name="element">The element to display.</param>
public InlineObjectElement(int documentLength, Control element)
: base(1, documentLength)
{
Element = element ?? throw new ArgumentNullException(nameof(element));
}
/// <inheritdoc/>
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
/// <inheritdoc/>
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
return new InlineObjectRun(1, TextRunProperties, Element);
}
}
return new InlineObjectRun(1, TextRunProperties, Element);
}
}
/// <summary>
/// A text run with an embedded UIElement.
/// </summary>
public class InlineObjectRun : DrawableTextRun
{
/// <summary>
/// Creates a new InlineObjectRun instance.
/// </summary>
/// <param name="length">The length of the TextRun.</param>
/// <param name="properties">The <see cref="TextRunProperties"/> to use.</param>
/// <param name="element">The <see cref="IControl"/> to display.</param>
public InlineObjectRun(int length, TextRunProperties properties, IControl element)
{
if (length <= 0)
throw new ArgumentOutOfRangeException(nameof(length), length, "Value must be positive");
/// <summary>
/// A text run with an embedded UIElement.
/// </summary>
public class InlineObjectRun : DrawableTextRun
{
internal Size DesiredSize;
TextSourceLength = length;
Properties = properties ?? throw new ArgumentNullException(nameof(properties));
Element = element ?? throw new ArgumentNullException(nameof(element));
}
/// <summary>
/// Creates a new InlineObjectRun instance.
/// </summary>
/// <param name="length">The length of the TextRun.</param>
/// <param name="properties">The <see cref="TextRunProperties"/> to use.</param>
/// <param name="element">The <see cref="Control"/> to display.</param>
public InlineObjectRun(int length, TextRunProperties? properties, Control element)
{
if (length <= 0)
throw new ArgumentOutOfRangeException(nameof(length), length, "Value must be positive");
/// <summary>
/// Gets the element displayed by the InlineObjectRun.
/// </summary>
public IControl Element { get; }
TextSourceLength = length;
Properties = properties ?? throw new ArgumentNullException(nameof(properties));
Element = element ?? throw new ArgumentNullException(nameof(element));
/// <summary>
/// Gets the VisualLine that contains this object. This property is only available after the object
/// was added to the text view.
/// </summary>
public VisualLine VisualLine { get; internal set; }
DesiredSize = element.DesiredSize;
}
/// <inheritdoc/>
public override int TextSourceLength { get; }
/// <summary>
/// Gets the element displayed by the InlineObjectRun.
/// </summary>
public Control Element { get; }
/// <inheritdoc/>
public override TextRunProperties Properties { get; }
/// <summary>
/// Gets the VisualLine that contains this object. This property is only available after the object
/// was added to the text view.
/// </summary>
public VisualLine? VisualLine { get; internal set; }
public override double Baseline => Element.DesiredSize.Height;
public override TextRunProperties? Properties { get; }
public override Size Size => Element.IsMeasureValid ? Element.DesiredSize : Size.Empty;
public Size DesiredSize { get; set; }
public override int TextSourceLength { get; }
public override void Draw(DrawingContext drawingContext, Point origin)
{
//noop
}
}
public override double Baseline
{
get
{
double baseline = TextBlock.GetBaselineOffset(Element);
if (double.IsNaN(baseline))
baseline = DesiredSize.Height;
return baseline;
}
}
/// <inheritdoc/>
public override Size Size => Element.IsArrangeValid ? Element.DesiredSize : Size.Empty;
/// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point origin)
{
// noop
}
}
}

Просмотреть файл

@ -18,11 +18,12 @@
using System;
using System.Text.RegularExpressions;
using AvaloniaEdit.Utils;
namespace AvaloniaEdit.Rendering
{
// This class is public because it can be used as a base class for custom links.
/// <summary>
/// Detects hyperlinks and makes them clickable.
/// </summary>
@ -34,19 +35,19 @@ namespace AvaloniaEdit.Rendering
{
// a link starts with a protocol (or just with www), followed by 0 or more 'link characters', followed by a link end character
// (this allows accepting punctuation inside links but not at the end)
internal static readonly Regex DefaultLinkRegex = new Regex(@"\b(https?://|ftp://|www\.)[\w\d\._/\-~%@()+:?&=#!]*[\w\d/]");
// try to detect email addresses
internal static readonly Regex DefaultMailRegex = new Regex(@"\b[\w\d\.\-]+\@[\w\d\.\-]+\.[a-z]{2,6}\b");
internal readonly static Regex DefaultLinkRegex = new Regex(@"\b(https?://|ftp://|www\.)[\w\d\._/\-~%@()+:?&=#!]*[\w\d/]");
// try to detect email addresses
internal readonly static Regex DefaultMailRegex = new Regex(@"\b[\w\d\.\-]+\@[\w\d\.\-]+\.[a-z]{2,6}\b");
private readonly Regex _linkRegex;
private readonly Regex _linkRegex;
/// <summary>
/// Gets/Sets whether the user needs to press Control to click the link.
/// The default value is true.
/// </summary>
public bool RequireControlModifierForClick { get; set; }
/// <summary>
/// Creates a new LinkElementGenerator.
/// </summary>
@ -55,46 +56,47 @@ namespace AvaloniaEdit.Rendering
_linkRegex = DefaultLinkRegex;
RequireControlModifierForClick = true;
}
/// <summary>
/// Creates a new LinkElementGenerator using the specified regex.
/// </summary>
protected LinkElementGenerator(Regex regex) : this()
{
_linkRegex = regex ?? throw new ArgumentNullException(nameof(regex));
_linkRegex = regex ?? throw new ArgumentNullException(nameof(regex));
}
void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options)
{
RequireControlModifierForClick = options.RequireControlModifierForHyperlinkClick;
}
private Match GetMatch(int startOffset, out int matchOffset)
private Match GetMatch(int startOffset, out int matchOffset)
{
var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
var relevantText = CurrentContext.GetText(startOffset, endOffset - startOffset);
var m = _linkRegex.Match(relevantText);
matchOffset = m.Success ? m.Index + startOffset : -1;
var m = _linkRegex.Match(relevantText.Text, relevantText.Offset, relevantText.Count);
matchOffset = m.Success ? m.Index - relevantText.Offset + startOffset : -1;
return m;
}
/// <inheritdoc/>
public override int GetFirstInterestedOffset(int startOffset)
{
GetMatch(startOffset, out var matchOffset);
return matchOffset;
}
/// <inheritdoc/>
public override VisualLineElement ConstructElement(int offset)
{
var m = GetMatch(offset, out var matchOffset);
if (m.Success && matchOffset == offset) {
return ConstructElementFromMatch(m);
} else {
return null;
}
return null;
}
/// <summary>
/// Constructs a VisualLineElement that replaces the matched text.
/// The default implementation will create a <see cref="VisualLineLinkText"/>
@ -105,14 +107,14 @@ namespace AvaloniaEdit.Rendering
var uri = GetUriFromMatch(m);
if (uri == null)
return null;
var linkText = new VisualLineLinkText(CurrentContext.VisualLine, m.Length)
{
NavigateUri = uri,
RequireControlModifierForClick = RequireControlModifierForClick
};
return linkText;
var linkText = new VisualLineLinkText(CurrentContext.VisualLine, m.Length)
{
NavigateUri = uri,
RequireControlModifierForClick = RequireControlModifierForClick
};
return linkText;
}
/// <summary>
/// Fetches the URI from the regex match. Returns null if the URI format is invalid.
/// </summary>
@ -121,15 +123,12 @@ namespace AvaloniaEdit.Rendering
var targetUrl = match.Value;
if (targetUrl.StartsWith("www.", StringComparison.Ordinal))
targetUrl = "http://" + targetUrl;
if (Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute))
return new Uri(targetUrl);
return null;
return Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute) ? new Uri(targetUrl) : null;
}
}
// This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions.
/// <summary>
/// Detects e-mail addresses and makes them clickable.
/// </summary>
@ -146,14 +145,11 @@ namespace AvaloniaEdit.Rendering
: base(DefaultMailRegex)
{
}
protected override Uri GetUriFromMatch(Match match)
{
var targetUrl = "mailto:" + match.Value;
if (Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute))
return new Uri(targetUrl);
return null;
var targetUrl = "mailto:" + match.Value;
return Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute) ? new Uri(targetUrl) : null;
}
}
}

Просмотреть файл

@ -16,35 +16,31 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace AvaloniaEdit.Rendering
{
internal sealed class SimpleTextSource : ITextSource
internal sealed class SimpleTextSource : ITextSource
{
private readonly ReadOnlySlice<char> _text;
private readonly TextRunProperties _properties;
public SimpleTextSource(ReadOnlySlice<char> text, TextRunProperties properties)
private readonly string _text;
private readonly TextRunProperties _properties;
public SimpleTextSource(string text, TextRunProperties properties)
{
_text = text;
_properties = properties;
}
public TextRun GetTextRun(int textSourceIndex)
public TextRun GetTextRun(int textSourceCharacterIndex)
{
if (textSourceIndex < _text.Length)
{
return new TextCharacters(_text, textSourceIndex, _text.Length - textSourceIndex, _properties);
}
if (textSourceCharacterIndex < _text.Length)
return new TextCharacters(
new ReadOnlySlice<char>(_text.AsMemory(), textSourceCharacterIndex,
_text.Length - textSourceCharacterIndex), _properties);
if (textSourceIndex > _text.Length)
{
return null;
}
return new TextEndOfParagraph(1);
return new TextEndOfParagraph(1);
}
}
}

Просмотреть файл

@ -23,195 +23,199 @@ using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Media.TextFormatting;
using AvaloniaEdit.Document;
using AvaloniaEdit.Utils;
using LogicalDirection = AvaloniaEdit.Document.LogicalDirection;
namespace AvaloniaEdit.Rendering
{
// This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions.
/// <summary>
/// Element generator that displays · for spaces and » for tabs and a box for control characters.
/// </summary>
/// <remarks>
/// This element generator is present in every TextView by default; the enabled features can be configured using the
/// <see cref="TextEditorOptions"/>.
/// </remarks>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace")]
internal sealed class SingleCharacterElementGenerator : VisualLineElementGenerator, IBuiltinElementGenerator
{
/// <summary>
/// Gets/Sets whether to show · for spaces.
/// </summary>
public bool ShowSpaces { get; set; }
/// <summary>
/// Element generator that displays · for spaces and » for tabs and a box for control characters.
/// </summary>
/// <remarks>
/// This element generator is present in every TextView by default; the enabled features can be configured using the
/// <see cref="TextEditorOptions"/>.
/// </remarks>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace")]
internal sealed class SingleCharacterElementGenerator : VisualLineElementGenerator, IBuiltinElementGenerator
{
/// <summary>
/// Gets/Sets whether to show · for spaces.
/// </summary>
public bool ShowSpaces { get; set; }
/// <summary>
/// Gets/Sets whether to show » for tabs.
/// </summary>
public bool ShowTabs { get; set; }
/// <summary>
/// Gets/Sets whether to show » for tabs.
/// </summary>
public bool ShowTabs { get; set; }
/// <summary>
/// Gets/Sets whether to show a box with the hex code for control characters.
/// </summary>
public bool ShowBoxForControlCharacters { get; set; }
/// <summary>
/// Gets/Sets whether to show a box with the hex code for control characters.
/// </summary>
public bool ShowBoxForControlCharacters { get; set; }
/// <summary>
/// Creates a new SingleCharacterElementGenerator instance.
/// </summary>
public SingleCharacterElementGenerator()
{
ShowSpaces = true;
ShowTabs = true;
ShowBoxForControlCharacters = true;
}
/// <summary>
/// Creates a new SingleCharacterElementGenerator instance.
/// </summary>
public SingleCharacterElementGenerator()
{
ShowSpaces = true;
ShowTabs = true;
ShowBoxForControlCharacters = true;
}
void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options)
{
ShowSpaces = options.ShowSpaces;
ShowTabs = options.ShowTabs;
ShowBoxForControlCharacters = options.ShowBoxForControlCharacters;
}
void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options)
{
ShowSpaces = options.ShowSpaces;
ShowTabs = options.ShowTabs;
ShowBoxForControlCharacters = options.ShowBoxForControlCharacters;
}
public override int GetFirstInterestedOffset(int startOffset)
{
var endLine = CurrentContext.VisualLine.LastDocumentLine;
var relevantText = CurrentContext.GetText(startOffset, endLine.EndOffset - startOffset);
public override int GetFirstInterestedOffset(int startOffset)
{
var endLine = CurrentContext.VisualLine.LastDocumentLine;
var relevantText = CurrentContext.GetText(startOffset, endLine.EndOffset - startOffset);
for (var i = 0; i < relevantText.Length; i++)
{
var c = relevantText[i];
switch (c)
{
case ' ':
if (ShowSpaces)
return startOffset + i;
break;
case '\t':
if (ShowTabs)
return startOffset + i;
break;
default:
if (ShowBoxForControlCharacters && char.IsControl(c))
{
return startOffset + i;
}
break;
}
}
return -1;
}
for (var i = 0; i < relevantText.Count; i++) {
var c = relevantText.Text[relevantText.Offset + i];
switch (c) {
case ' ':
if (ShowSpaces)
return startOffset + i;
break;
case '\t':
if (ShowTabs)
return startOffset + i;
break;
default:
if (ShowBoxForControlCharacters && char.IsControl(c)) {
return startOffset + i;
}
break;
}
}
return -1;
}
public override VisualLineElement ConstructElement(int offset)
{
var c = CurrentContext.Document.GetCharAt(offset);
if (ShowSpaces && c == ' ')
{
return new SpaceTextElement(CurrentContext.TextView.CachedElements.GetTextForNonPrintableCharacter("\u00B7", CurrentContext));
}
if (ShowTabs && c == '\t')
{
return new TabTextElement(CurrentContext.TextView.CachedElements.GetTextForNonPrintableCharacter("\u00BB", CurrentContext));
}
if (ShowBoxForControlCharacters && char.IsControl(c))
{
var p = CurrentContext.GlobalTextRunProperties.Clone();
p.SetForegroundBrush(Brushes.White);
public override VisualLineElement ConstructElement(int offset)
{
var c = CurrentContext.Document.GetCharAt(offset);
if (ShowSpaces && c == ' ') {
return new SpaceTextElement(CurrentContext.TextView.CachedElements.GetTextForNonPrintableCharacter("\u00B7", CurrentContext));
} else if (ShowTabs && c == '\t') {
return new TabTextElement(CurrentContext.TextView.CachedElements.GetTextForNonPrintableCharacter("\u00BB", CurrentContext));
} else if (ShowBoxForControlCharacters && char.IsControl(c)) {
var p = new VisualLineElementTextRunProperties(CurrentContext.GlobalTextRunProperties);
p.SetForegroundBrush(Brushes.White);
var textFormatter = TextFormatterFactory.Create(CurrentContext.TextView);
var text = FormattedTextElement.PrepareText(textFormatter,
TextUtilities.GetControlCharacterName(c), p);
return new SpecialCharacterBoxElement(text);
} else {
return null;
}
}
var textFormatter = TextFormatter.Current;
var text = FormattedTextElement.PrepareText(textFormatter,
TextUtilities.GetControlCharacterName(c), p);
return new SpecialCharacterBoxElement(text);
}
return null;
}
private sealed class SpaceTextElement : FormattedTextElement
{
public SpaceTextElement(TextLine textLine) : base(textLine, 1)
{
}
private sealed class SpaceTextElement : FormattedTextElement
{
public SpaceTextElement(TextLine textLine) : base(textLine, 1)
{
}
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
{
if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
return base.GetNextCaretPosition(visualColumn, direction, mode);
else
return -1;
}
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
{
if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
return base.GetNextCaretPosition(visualColumn, direction, mode);
return -1;
}
public override bool IsWhitespace(int visualColumn)
{
return true;
}
}
public override bool IsWhitespace(int visualColumn)
{
return true;
}
}
private sealed class TabTextElement : VisualLineElement
{
internal readonly TextLine Text;
internal sealed class TabTextElement : VisualLineElement
{
internal readonly TextLine Text;
public TabTextElement(TextLine text) : base(2, 1)
{
Text = text;
}
public TabTextElement(TextLine text) : base(2, 1)
{
Text = text;
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
// the TabTextElement consists of two TextRuns:
// first a TabGlyphRun, then TextCharacters '\t' to let WPF handle the tab indentation
if (startVisualColumn == VisualColumn)
return new TabGlyphRun(this, TextRunProperties);
else if (startVisualColumn == VisualColumn + 1)
return new TextCharacters("\t".AsMemory(), 0, 1, TextRunProperties);
else
throw new ArgumentOutOfRangeException(nameof(startVisualColumn));
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
// the TabTextElement consists of two TextRuns:
// first a TabGlyphRun, then TextCharacters '\t' to let the fx handle the tab indentation
if (startVisualColumn == VisualColumn)
return new TabGlyphRun(this, TextRunProperties);
if (startVisualColumn == VisualColumn + 1)
return new TextCharacters("\t".AsMemory(), TextRunProperties);
throw new ArgumentOutOfRangeException(nameof(startVisualColumn));
}
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
{
if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
return base.GetNextCaretPosition(visualColumn, direction, mode);
else
return -1;
}
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
{
if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
return base.GetNextCaretPosition(visualColumn, direction, mode);
return -1;
}
public override bool IsWhitespace(int visualColumn)
{
return true;
}
}
public override bool IsWhitespace(int visualColumn)
{
return true;
}
}
private sealed class TabGlyphRun : DrawableTextRun
{
private readonly TabTextElement _element;
internal sealed class TabGlyphRun : DrawableTextRun
{
private readonly TabTextElement _element;
public TabGlyphRun(TabTextElement element, TextRunProperties properties)
{
if (properties == null)
throw new ArgumentNullException(nameof(properties));
Properties = properties;
_element = element;
}
public TabGlyphRun(TabTextElement element, TextRunProperties properties)
{
Properties = properties ?? throw new ArgumentNullException(nameof(properties));
_element = element;
}
public override TextRunProperties Properties { get; }
public override int TextSourceLength => 1;
public override double Baseline => _element.Text.Baseline;
public override TextRunProperties Properties { get; }
public override Size Size
{
get
{
var width = Math.Min(0, _element.Text.WidthIncludingTrailingWhitespace - 1);
return new Size(width, _element.Text.Height);
}
}
public override double Baseline => _element.Text.Height;
public override void Draw(DrawingContext drawingContext, Point origin)
{
var y = origin.Y - _element.Text.Baseline;
_element.Text.Draw(drawingContext, origin.WithY(y));
}
}
public override Size Size => new(0, _element.Text.Height);
private sealed class SpecialCharacterBoxElement : FormattedTextElement
{
public SpecialCharacterBoxElement(TextLine text) : base(text, 1)
{
}
public override void Draw(DrawingContext drawingContext, Point origin)
{
_element.Text.Draw(drawingContext, origin);
}
}
private sealed class SpecialCharacterBoxElement : FormattedTextElement
{
public SpecialCharacterBoxElement(TextLine text) : base(text, 1)
{
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
return new SpecialCharacterTextRun(this, TextRunProperties);
}
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
return new SpecialCharacterTextRun(this, TextRunProperties);
}
}
internal sealed class SpecialCharacterTextRun : FormattedTextRun
{
@ -241,12 +245,19 @@ namespace AvaloniaEdit.Rendering
public override void Draw(DrawingContext drawingContext, Point origin)
{
var newOrigin = new Point(origin.X + (BoxMargin / 2), origin.Y);
var metrics = Size;
var r = new Rect(origin.X, origin.Y, metrics.Width, metrics.Height);
var (x, y) = origin;
var newOrigin = new Point(x + (BoxMargin / 2), y);
var (width, height) = Size;
var r = new Rect(x, y, width, height);
drawingContext.FillRectangle(DarkGrayBrush, r, 2.5f);
base.Draw(drawingContext, newOrigin);
}
}
}
}

Просмотреть файл

@ -27,11 +27,13 @@ using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Media.TextFormatting;
using Avalonia.Threading;
using Avalonia.VisualTree;
@ -61,8 +63,8 @@ namespace AvaloniaEdit.Rendering
FocusableProperty.OverrideDefaultValue<TextView>(false);
OptionsProperty.Changed.Subscribe(OnOptionsChanged);
DocumentProperty.Changed.Subscribe(OnDocumentChanged);
}
DocumentProperty.Changed.Subscribe(OnDocumentChanged);
}
private readonly ColumnRulerRenderer _columnRulerRenderer;
private readonly CurrentLineHighlightRenderer _currentLineHighlighRenderer;
@ -92,7 +94,7 @@ namespace AvaloniaEdit.Rendering
_hoverLogic.PointerHoverStopped += (sender, e) => RaiseHoverEventPair(e, PreviewPointerHoverStoppedEvent, PointerHoverStoppedEvent);
}
#endregion
#endregion
#region Document Property
/// <summary>
@ -425,12 +427,12 @@ namespace AvaloniaEdit.Rendering
private readonly List<InlineObjectRun> _inlineObjects = new List<InlineObjectRun>();
/// <summary>
/// Adds a new inline object.
/// </summary>
internal void AddInlineObject(InlineObjectRun inlineObject)
{
Debug.Assert(inlineObject.VisualLine != null);
/// <summary>
/// Adds a new inline object.
/// </summary>
internal void AddInlineObject(InlineObjectRun inlineObject)
{
Debug.Assert(inlineObject.VisualLine != null);
// Remove inline object if its already added, can happen e.g. when recreating textrun for word-wrapping
var alreadyAdded = false;
@ -782,43 +784,40 @@ namespace AvaloniaEdit.Rendering
return null;
}
/// <summary>
/// Gets the visual line that contains the document line with the specified number.
/// If that line is outside the visible range, a new VisualLine for that document line is constructed.
/// </summary>
public VisualLine GetOrConstructVisualLine(DocumentLine documentLine)
{
if (documentLine == null)
throw new ArgumentNullException(nameof(documentLine));
if (!Document.Lines.Contains(documentLine))
throw new InvalidOperationException("Line belongs to wrong document");
VerifyAccess();
/// <summary>
/// Gets the visual line that contains the document line with the specified number.
/// If that line is outside the visible range, a new VisualLine for that document line is constructed.
/// </summary>
public VisualLine GetOrConstructVisualLine(DocumentLine documentLine)
{
if (documentLine == null)
throw new ArgumentNullException("documentLine");
if (!this.Document.Lines.Contains(documentLine))
throw new InvalidOperationException("Line belongs to wrong document");
VerifyAccess();
var l = GetVisualLine(documentLine.LineNumber);
if (l == null)
{
var globalTextRunProperties = CreateGlobalTextRunProperties();
var paragraphProperties = CreateParagraphProperties(globalTextRunProperties);
VisualLine l = GetVisualLine(documentLine.LineNumber);
if (l == null) {
TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties();
VisualLineTextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties);
while (_heightTree.GetIsCollapsed(documentLine.LineNumber))
{
documentLine = documentLine.PreviousLine;
}
while (_heightTree.GetIsCollapsed(documentLine.LineNumber)) {
documentLine = documentLine.PreviousLine;
}
l = BuildVisualLine(documentLine,
globalTextRunProperties, paragraphProperties,
_elementGenerators.ToArray(), _lineTransformers.ToArray(),
_lastAvailableSize);
_allVisualLines.Add(l);
// update all visual top values (building the line might have changed visual top of other lines due to word wrapping)
foreach (var line in _allVisualLines)
{
line.VisualTop = _heightTree.GetVisualPosition(line.FirstDocumentLine);
}
}
return l;
}
#endregion
l = BuildVisualLine(documentLine,
globalTextRunProperties, paragraphProperties,
_elementGenerators.ToArray(), _lineTransformers.ToArray(),
_lastAvailableSize);
_allVisualLines.Add(l);
// update all visual top values (building the line might have changed visual top of other lines due to word wrapping)
foreach (var line in _allVisualLines) {
line.VisualTop = _heightTree.GetVisualPosition(line.FirstDocumentLine);
}
}
return l;
}
#endregion
#region Visual Lines (fields and properties)
@ -897,35 +896,33 @@ namespace AvaloniaEdit.Rendering
}
#endregion
#region Measure
/// <summary>
/// Additonal amount that allows horizontal scrolling past the end of the longest line.
/// This is necessary to ensure the caret always is visible, even when it is at the end of the longest line.
/// </summary>
private const double AdditionalHorizontalScrollAmount = 3;
#region Measure
/// <summary>
/// Additonal amount that allows horizontal scrolling past the end of the longest line.
/// This is necessary to ensure the caret always is visible, even when it is at the end of the longest line.
/// </summary>
private const double AdditionalHorizontalScrollAmount = 3;
private Size _lastAvailableSize;
private bool _inMeasure;
private Size _lastAvailableSize;
private bool _inMeasure;
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
// We don't support infinite available width, so we'll limit it to 32000 pixels.
if (availableSize.Width > 32000)
availableSize = new Size(32000, availableSize.Height);
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
// We don't support infinite available width, so we'll limit it to 32000 pixels.
if (availableSize.Width > 32000)
availableSize = availableSize.WithWidth(32000);
if (!_canHorizontallyScroll && !availableSize.Width.IsClose(_lastAvailableSize.Width))
ClearVisualLines();
_lastAvailableSize = availableSize;
if (!_canHorizontallyScroll && !availableSize.Width.IsClose(_lastAvailableSize.Width))
ClearVisualLines();
_lastAvailableSize = availableSize;
foreach (var layer in Layers)
{
layer.Measure(availableSize);
}
MeasureInlineObjects();
foreach (var layer in Layers) {
layer.Measure(availableSize);
}
MeasureInlineObjects();
// TODO: is this needed?
//InvalidateVisual(); // = InvalidateArrange+InvalidateRender
InvalidateVisual(); // = InvalidateArrange+InvalidateRender
double maxWidth;
if (_document == null)
@ -982,14 +979,14 @@ namespace AvaloniaEdit.Rendering
return new Size(Math.Min(availableSize.Width, maxWidth), Math.Min(availableSize.Height, heightTreeHeight));
}
/// <summary>
/// Build all VisualLines in the visible range.
/// </summary>
/// <returns>Width the longest line</returns>
private double CreateAndMeasureVisualLines(Size availableSize)
{
var globalTextRunProperties = CreateGlobalTextRunProperties();
var paragraphProperties = CreateParagraphProperties(globalTextRunProperties);
/// <summary>
/// Build all VisualLines in the visible range.
/// </summary>
/// <returns>Width the longest line</returns>
private double CreateAndMeasureVisualLines(Size availableSize)
{
TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties();
VisualLineTextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties);
//Debug.WriteLine("Measure availableSize=" + availableSize + ", scrollOffset=" + _scrollOffset);
var firstLineInView = _heightTree.GetLineByVisualPosition(_scrollOffset.Y);
@ -1058,123 +1055,106 @@ namespace AvaloniaEdit.Rendering
private TextFormatter _formatter;
internal TextViewCachedElements CachedElements;
private CustomTextRunProperties CreateGlobalTextRunProperties()
{
var properties = new CustomTextRunProperties
(
new Typeface(TextBlock.GetFontFamily(this), TextBlock.GetFontStyle(this),
TextBlock.GetFontWeight(this)),
FontSize,
null,
TextBlock.GetForeground(this),
null,
cultureInfo: CultureInfo.CurrentCulture,
BaselineAlignment.Baseline
);
return properties;
}
private TextRunProperties CreateGlobalTextRunProperties()
{
var p = new GlobalTextRunProperties();
p.typeface = this.CreateTypeface();
p.fontRenderingEmSize = FontSize;
p.foregroundBrush = GetValue(TextBlock.ForegroundProperty);
ExtensionMethods.CheckIsFrozen(p.foregroundBrush);
p.cultureInfo = CultureInfo.CurrentCulture;
return p;
}
private GenericTextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties)
{
return new GenericTextParagraphProperties
(
FlowDirection.LeftToRight,
TextAlignment.Left,
true,
false,
defaultTextRunProperties,
_canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap,
0,
0/*,
DefaultIncrementalTab = Options.IndentationSize * WideSpaceWidth*/
);
}
private VisualLineTextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties)
{
return new VisualLineTextParagraphProperties {
defaultTextRunProperties = defaultTextRunProperties,
textWrapping = _canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap,
tabSize = Options.IndentationSize * WideSpaceWidth
};
}
private VisualLine BuildVisualLine(DocumentLine documentLine,
CustomTextRunProperties globalTextRunProperties,
TextParagraphProperties paragraphProperties,
VisualLineElementGenerator[] elementGeneratorsArray,
IVisualLineTransformer[] lineTransformersArray,
Size availableSize)
{
if (_heightTree.GetIsCollapsed(documentLine.LineNumber))
throw new InvalidOperationException("Trying to build visual line from collapsed line");
private VisualLine BuildVisualLine(DocumentLine documentLine,
TextRunProperties globalTextRunProperties,
VisualLineTextParagraphProperties paragraphProperties,
VisualLineElementGenerator[] elementGeneratorsArray,
IVisualLineTransformer[] lineTransformersArray,
Size availableSize)
{
if (_heightTree.GetIsCollapsed(documentLine.LineNumber))
throw new InvalidOperationException("Trying to build visual line from collapsed line");
var visualLine = new VisualLine(this, documentLine);
var textSource = new VisualLineTextSource(visualLine)
{
Document = _document,
GlobalTextRunProperties = globalTextRunProperties,
TextView = this
};
//Debug.WriteLine("Building line " + documentLine.LineNumber);
visualLine.ConstructVisualElements(textSource, elementGeneratorsArray);
VisualLine visualLine = new VisualLine(this, documentLine);
VisualLineTextSource textSource = new VisualLineTextSource(visualLine) {
Document = _document,
GlobalTextRunProperties = globalTextRunProperties,
TextView = this
};
if (visualLine.FirstDocumentLine != visualLine.LastDocumentLine)
{
// Check whether the lines are collapsed correctly:
var firstLinePos = _heightTree.GetVisualPosition(visualLine.FirstDocumentLine.NextLine);
var lastLinePos = _heightTree.GetVisualPosition(visualLine.LastDocumentLine.NextLine ?? visualLine.LastDocumentLine);
if (!firstLinePos.IsClose(lastLinePos))
{
for (var i = visualLine.FirstDocumentLine.LineNumber + 1; i <= visualLine.LastDocumentLine.LineNumber; i++)
{
if (!_heightTree.GetIsCollapsed(i))
throw new InvalidOperationException("Line " + i + " was skipped by a VisualLineElementGenerator, but it is not collapsed.");
}
throw new InvalidOperationException("All lines collapsed but visual pos different - height tree inconsistency?");
}
}
visualLine.ConstructVisualElements(textSource, elementGeneratorsArray);
if (visualLine.FirstDocumentLine != visualLine.LastDocumentLine) {
// Check whether the lines are collapsed correctly:
double firstLinePos = _heightTree.GetVisualPosition(visualLine.FirstDocumentLine.NextLine);
double lastLinePos = _heightTree.GetVisualPosition(visualLine.LastDocumentLine.NextLine ?? visualLine.LastDocumentLine);
if (!firstLinePos.IsClose(lastLinePos)) {
for (int i = visualLine.FirstDocumentLine.LineNumber + 1; i <= visualLine.LastDocumentLine.LineNumber; i++) {
if (!_heightTree.GetIsCollapsed(i))
throw new InvalidOperationException("Line " + i + " was skipped by a VisualLineElementGenerator, but it is not collapsed.");
}
throw new InvalidOperationException("All lines collapsed but visual pos different - height tree inconsistency?");
}
}
visualLine.RunTransformers(textSource, lineTransformersArray);
// now construct textLines:
TextLineBreak lastLineBreak = null;
var textOffset = 0;
var textLines = new List<TextLine>();
while (textOffset < visualLine.VisualLengthWithEndOfLineMarker)
while (textOffset <= visualLine.VisualLengthWithEndOfLineMarker)
{
var textLine = _formatter.FormatLine(
textSource,
textOffset,
availableSize.Width,
paragraphProperties
paragraphProperties,
lastLineBreak
);
textLines.Add(textLine);
textOffset += textLine.TextRange.Length;
// exit loop so that we don't do the indentation calculation if there's only a single line
if (textOffset >= visualLine.VisualLengthWithEndOfLineMarker)
break;
// exit loop so that we don't do the indentation calculation if there's only a single line
if (textOffset >= visualLine.VisualLengthWithEndOfLineMarker)
break;
if (paragraphProperties.FirstLineInParagraph)
{
//paragraphProperties.FirstLineInParagraph = false;
if (paragraphProperties.firstLineInParagraph) {
paragraphProperties.firstLineInParagraph = false;
var options = Options;
double indentation = 0;
if (options.InheritWordWrapIndentation)
{
// determine indentation for next line:
var indentVisualColumn = GetIndentationVisualColumn(visualLine);
if (indentVisualColumn > 0 && indentVisualColumn < textOffset)
{
indentation = textLine.GetDistanceFromCharacterHit(new CharacterHit(indentVisualColumn));
}
}
indentation += options.WordWrapIndentation;
// apply the calculated indentation unless it's more than half of the text editor size:
if (indentation > 0 && indentation * 2 < availableSize.Width)
{
//paragraphProperties.Indent = indentation;
}
}
TextEditorOptions options = this.Options;
double indentation = 0;
if (options.InheritWordWrapIndentation) {
// determine indentation for next line:
int indentVisualColumn = GetIndentationVisualColumn(visualLine);
if (indentVisualColumn > 0 && indentVisualColumn < textOffset) {
indentation = textLine.GetDistanceFromCharacterHit(new CharacterHit(indentVisualColumn, 0));
}
}
indentation += options.WordWrapIndentation;
// apply the calculated indentation unless it's more than half of the text editor size:
if (indentation > 0 && indentation * 2 < availableSize.Width)
paragraphProperties.indent = indentation;
}
lastLineBreak = textLine.TextLineBreak;
}
visualLine.SetTextLines(textLines);
_heightTree.SetHeight(visualLine.FirstDocumentLine, visualLine.Height);
return visualLine;
}
visualLine.SetTextLines(textLines);
_heightTree.SetHeight(visualLine.FirstDocumentLine, visualLine.Height);
return visualLine;
}
private static int GetIndentationVisualColumn(VisualLine visualLine)
{
@ -1532,11 +1512,11 @@ namespace AvaloniaEdit.Rendering
if (_formatter != null)
{
var textRunProperties = CreateGlobalTextRunProperties();
var line = _formatter.FormatLine(
new SimpleTextSource("x".AsMemory(), textRunProperties),
new SimpleTextSource("x", textRunProperties),
0, 32000,
new GenericTextParagraphProperties(textRunProperties));
new VisualLineTextParagraphProperties {defaultTextRunProperties = textRunProperties},
null);
_wideSpaceWidth = Math.Max(1, line.WidthIncludingTrailingWhitespace);
_defaultBaseline = Math.Max(1, line.Baseline);
@ -1548,6 +1528,7 @@ namespace AvaloniaEdit.Rendering
_defaultBaseline = FontSize;
_defaultLineHeight = FontSize + 3;
}
// Update heightTree.DefaultLineHeight, if a document is loaded.
if (_heightTree != null)
_heightTree.DefaultLineHeight = _defaultLineHeight;
@ -1990,12 +1971,12 @@ namespace AvaloniaEdit.Rendering
/// The pen used to draw the column ruler.
/// <seealso cref="TextEditorOptions.ShowColumnRuler"/>
/// </summary>
public static readonly StyledProperty<Pen> ColumnRulerPenProperty =
AvaloniaProperty.Register<TextView, Pen>("ColumnRulerBrush", CreateFrozenPen(Brushes.LightGray));
public static readonly StyledProperty<IPen> ColumnRulerPenProperty =
AvaloniaProperty.Register<TextView, IPen>("ColumnRulerBrush", CreateFrozenPen(Brushes.LightGray));
private static Pen CreateFrozenPen(IBrush brush)
private static ImmutablePen CreateFrozenPen(IBrush brush)
{
var pen = new Pen(brush);
var pen = new ImmutablePen(brush?.ToImmutable());
return pen;
}
@ -2036,7 +2017,7 @@ namespace AvaloniaEdit.Rendering
/// Gets/Sets the pen used to draw the column ruler.
/// <seealso cref="TextEditorOptions.ShowColumnRuler"/>
/// </summary>
public Pen ColumnRulerPen
public IPen ColumnRulerPen
{
get => GetValue(ColumnRulerPenProperty);
set => SetValue(ColumnRulerPenProperty, value);
@ -2060,13 +2041,13 @@ namespace AvaloniaEdit.Rendering
/// <summary>
/// The <see cref="CurrentLineBorder"/> property.
/// </summary>
public static readonly StyledProperty<Pen> CurrentLineBorderProperty =
AvaloniaProperty.Register<TextView, Pen>("CurrentLineBorder");
public static readonly StyledProperty<IPen> CurrentLineBorderProperty =
AvaloniaProperty.Register<TextView, IPen>("CurrentLineBorder");
/// <summary>
/// Gets/Sets the background brush used for the current line.
/// </summary>
public Pen CurrentLineBorder
public IPen CurrentLineBorder
{
get => GetValue(CurrentLineBorderProperty);
set => SetValue(CurrentLineBorderProperty, value);

Просмотреть файл

@ -21,37 +21,35 @@ using Avalonia.Media.TextFormatting;
namespace AvaloniaEdit.Rendering
{
internal sealed class TextViewCachedElements
internal sealed class TextViewCachedElements /*: IDisposable*/
{
private Dictionary<string, TextLine> _nonPrintableCharacterTexts;
private TextFormatter _formatter;
private Dictionary<string, TextLine> _nonPrintableCharacterTexts;
public TextLine GetTextForNonPrintableCharacter(string text, ITextRunConstructionContext context)
{
if (_nonPrintableCharacterTexts == null)
{
_nonPrintableCharacterTexts = new Dictionary<string, TextLine>();
TextLine textLine;
if (!_nonPrintableCharacterTexts.TryGetValue(text, out textLine)) {
var p = new VisualLineElementTextRunProperties(context.GlobalTextRunProperties);
p.SetForegroundBrush(context.TextView.NonPrintableCharacterBrush);
if (_formatter == null)
_formatter = TextFormatter.Current;//TextFormatterFactory.Create(context.TextView);
textLine = FormattedTextElement.PrepareText(_formatter, text, p);
_nonPrintableCharacterTexts[text] = textLine;
}
if (_nonPrintableCharacterTexts.TryGetValue(text, out var textLine))
{
return textLine;
}
var properties = context.GlobalTextRunProperties.Clone();
properties.SetForegroundBrush(context.TextView.NonPrintableCharacterBrush);
if (_formatter == null)
{
_formatter = TextFormatter.Current;
}
textLine = FormattedTextElement.PrepareText(_formatter, text, properties);
_nonPrintableCharacterTexts[text] = textLine;
return textLine;
}
/*public void Dispose()
{
if (nonPrintableCharacterTexts != null) {
foreach (TextLine line in nonPrintableCharacterTexts.Values)
line.Dispose();
}
if (formatter != null)
formatter.Dispose();
}*/
}
}

Просмотреть файл

@ -145,95 +145,70 @@ namespace AvaloniaEdit.Rendering
g.FinishGeneration();
}
var globalTextRunProperties = context.GlobalTextRunProperties;
foreach (var element in _elements)
{
element.SetTextRunProperties(globalTextRunProperties.Clone());
}
Elements = new ReadOnlyCollection<VisualLineElement>(_elements);
CalculateOffsets();
_phase = LifetimePhase.Transforming;
}
var globalTextRunProperties = context.GlobalTextRunProperties;
foreach (var element in _elements) {
element.SetTextRunProperties(new VisualLineElementTextRunProperties(globalTextRunProperties));
}
this.Elements = new ReadOnlyCollection<VisualLineElement>(_elements);
CalculateOffsets();
_phase = LifetimePhase.Transforming;
}
private void PerformVisualElementConstruction(VisualLineElementGenerator[] generators)
{
var document = Document;
var lineLength = FirstDocumentLine.Length;
var offset = FirstDocumentLine.Offset;
var currentLineEnd = offset + FirstDocumentLine.Length;
LastDocumentLine = FirstDocumentLine;
var askInterestOffset = 0; // 0 or 1
while (offset + askInterestOffset <= currentLineEnd)
{
var textPieceEndOffset = currentLineEnd;
foreach (var g in generators)
{
g.CachedInterest = (lineLength > LENGTH_LIMIT) ? -1: g.GetFirstInterestedOffset(offset + askInterestOffset);
if (g.CachedInterest != -1)
{
if (g.CachedInterest < offset)
throw new ArgumentOutOfRangeException(g.GetType().Name + ".GetFirstInterestedOffset",
g.CachedInterest,
"GetFirstInterestedOffset must not return an offset less than startOffset. Return -1 to signal no interest.");
if (g.CachedInterest < textPieceEndOffset)
textPieceEndOffset = g.CachedInterest;
}
}
Debug.Assert(textPieceEndOffset >= offset);
if (textPieceEndOffset > offset)
{
var textPieceLength = textPieceEndOffset - offset;
int remaining = textPieceLength;
while (true)
{
if (remaining > LENGTH_LIMIT)
{
// split in chunks of LENGTH_LIMIT
_elements.Add(new VisualLineText(this, LENGTH_LIMIT));
remaining -= LENGTH_LIMIT;
}
else
{
_elements.Add(new VisualLineText(this, remaining));
break;
}
}
offset = textPieceEndOffset;
}
// If no elements constructed / only zero-length elements constructed:
// do not asking the generators again for the same location (would cause endless loop)
askInterestOffset = 1;
foreach (var g in generators)
{
if (g.CachedInterest == offset)
{
var element = g.ConstructElement(offset);
if (element != null)
{
_elements.Add(element);
if (element.DocumentLength > 0)
{
// a non-zero-length element was constructed
askInterestOffset = 0;
offset += element.DocumentLength;
if (offset > currentLineEnd)
{
var newEndLine = document.GetLineByOffset(offset);
currentLineEnd = newEndLine.Offset + newEndLine.Length;
LastDocumentLine = newEndLine;
if (currentLineEnd < offset)
{
throw new InvalidOperationException(
$"The VisualLineElementGenerator {g.GetType().Name} produced an element which ends within the line delimiter");
}
}
break;
}
}
}
}
}
}
void PerformVisualElementConstruction(VisualLineElementGenerator[] generators)
{
TextDocument document = this.Document;
int offset = FirstDocumentLine.Offset;
int currentLineEnd = offset + FirstDocumentLine.Length;
LastDocumentLine = FirstDocumentLine;
int askInterestOffset = 0; // 0 or 1
while (offset + askInterestOffset <= currentLineEnd) {
int textPieceEndOffset = currentLineEnd;
foreach (VisualLineElementGenerator g in generators) {
g.CachedInterest = g.GetFirstInterestedOffset(offset + askInterestOffset);
if (g.CachedInterest != -1) {
if (g.CachedInterest < offset)
throw new ArgumentOutOfRangeException(g.GetType().Name + ".GetFirstInterestedOffset",
g.CachedInterest,
"GetFirstInterestedOffset must not return an offset less than startOffset. Return -1 to signal no interest.");
if (g.CachedInterest < textPieceEndOffset)
textPieceEndOffset = g.CachedInterest;
}
}
Debug.Assert(textPieceEndOffset >= offset);
if (textPieceEndOffset > offset) {
int textPieceLength = textPieceEndOffset - offset;
_elements.Add(new VisualLineText(this, textPieceLength));
offset = textPieceEndOffset;
}
// If no elements constructed / only zero-length elements constructed:
// do not asking the generators again for the same location (would cause endless loop)
askInterestOffset = 1;
foreach (VisualLineElementGenerator g in generators) {
if (g.CachedInterest == offset) {
VisualLineElement element = g.ConstructElement(offset);
if (element != null) {
_elements.Add(element);
if (element.DocumentLength > 0) {
// a non-zero-length element was constructed
askInterestOffset = 0;
offset += element.DocumentLength;
if (offset > currentLineEnd) {
DocumentLine newEndLine = document.GetLineByOffset(offset);
currentLineEnd = newEndLine.Offset + newEndLine.Length;
this.LastDocumentLine = newEndLine;
if (currentLineEnd < offset) {
throw new InvalidOperationException(
"The VisualLineElementGenerator " + g.GetType().Name +
" produced an element which ends within the line delimiter");
}
}
break;
}
}
}
}
}
}
private void CalculateOffsets()
{
@ -769,7 +744,7 @@ namespace AvaloniaEdit.Rendering
internal VisualLineDrawingVisual Render()
{
Debug.Assert(_phase == LifetimePhase.Live);
return _visual ?? (_visual = new VisualLineDrawingVisual(this));
return _visual ??= new VisualLineDrawingVisual(this);
}
}

Просмотреть файл

@ -72,19 +72,19 @@ namespace AvaloniaEdit.Rendering
/// <summary>
/// Gets the text run properties.
/// A unique <see cref="TextRunProperties"/> instance is used for each
/// A unique <see cref="VisualLineElementTextRunProperties"/> instance is used for each
/// <see cref="VisualLineElement"/>; colorizing code may assume that modifying the
/// <see cref="TextRunProperties"/> will affect only this
/// <see cref="VisualLineElementTextRunProperties"/> will affect only this
/// <see cref="VisualLineElement"/>.
/// </summary>
public CustomTextRunProperties TextRunProperties { get; private set; }
public VisualLineElementTextRunProperties TextRunProperties { get; private set; }
/// <summary>
/// Gets/sets the brush used for the background of this <see cref="VisualLineElement" />.
/// </summary>
public IBrush BackgroundBrush { get; set; }
internal void SetTextRunProperties(CustomTextRunProperties p)
internal void SetTextRunProperties(VisualLineElementTextRunProperties p)
{
TextRunProperties = p;
}
@ -106,9 +106,9 @@ namespace AvaloniaEdit.Rendering
/// Retrieves the text span immediately before the visual column.
/// </summary>
/// <remarks>This method is used for word-wrapping in bidirectional text.</remarks>
public virtual string GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context)
public virtual ReadOnlySlice<char> GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context)
{
return string.Empty;
return ReadOnlySlice<char>.Empty;
}
/// <summary>
@ -162,13 +162,13 @@ namespace AvaloniaEdit.Rendering
firstPart.DocumentLength = relativeSplitRelativeTextOffset;
secondPart.DocumentLength = oldDocumentLength - relativeSplitRelativeTextOffset;
if (firstPart.TextRunProperties == null)
firstPart.TextRunProperties = TextRunProperties;
firstPart.TextRunProperties = TextRunProperties.Clone();
if (secondPart.TextRunProperties == null)
secondPart.TextRunProperties = TextRunProperties;
secondPart.TextRunProperties = TextRunProperties.Clone();
firstPart.BackgroundBrush = BackgroundBrush;
secondPart.BackgroundBrush = BackgroundBrush;
}
/// <summary>
/// Gets the visual column of a text location inside this element.
/// The text offset is given relative to the visual line start.

Просмотреть файл

@ -0,0 +1,244 @@
// 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.Globalization;
using System.Linq;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using AvaloniaEdit.Utils;
namespace AvaloniaEdit.Rendering
{
/// <summary>
/// <see cref="TextRunProperties"/> implementation that allows changing the properties.
/// A <see cref="VisualLineElementTextRunProperties"/> instance usually is assigned to a single
/// <see cref="VisualLineElement"/>.
/// </summary>
public class VisualLineElementTextRunProperties : TextRunProperties, ICloneable
{
private IBrush _backgroundBrush;
private BaselineAlignment _baselineAlignment;
private CultureInfo _cultureInfo;
//double fontHintingEmSize;
private double _fontRenderingEmSize;
private IBrush _foregroundBrush;
private Typeface _typeface;
private TextDecorationCollection _textDecorations;
//TextEffectCollection textEffects;
//TextRunTypographyProperties typographyProperties;
//NumberSubstitution numberSubstitution;
/// <summary>
/// Creates a new VisualLineElementTextRunProperties instance that copies its values
/// from the specified <paramref name="textRunProperties"/>.
/// For the <see cref="TextDecorations"/> and <see cref="TextEffects"/> collections, deep copies
/// are created if those collections are not frozen.
/// </summary>
public VisualLineElementTextRunProperties(TextRunProperties textRunProperties)
{
if (textRunProperties == null)
throw new ArgumentNullException(nameof(textRunProperties));
_backgroundBrush = textRunProperties.BackgroundBrush;
_baselineAlignment = textRunProperties.BaselineAlignment;
_cultureInfo = textRunProperties.CultureInfo;
//fontHintingEmSize = textRunProperties.FontHintingEmSize;
_fontRenderingEmSize = textRunProperties.FontRenderingEmSize;
_foregroundBrush = textRunProperties.ForegroundBrush;
_typeface = textRunProperties.Typeface;
_textDecorations = textRunProperties.TextDecorations;
/*if (textDecorations != null && !textDecorations.IsFrozen) {
textDecorations = textDecorations.Clone();
}*/
/*textEffects = textRunProperties.TextEffects;
if (textEffects != null && !textEffects.IsFrozen) {
textEffects = textEffects.Clone();
}
typographyProperties = textRunProperties.TypographyProperties;
numberSubstitution = textRunProperties.NumberSubstitution;*/
}
/// <summary>
/// Creates a copy of this instance.
/// </summary>
public virtual VisualLineElementTextRunProperties Clone()
{
return new VisualLineElementTextRunProperties(this);
}
object ICloneable.Clone()
{
return Clone();
}
/// <inheritdoc/>
public override IBrush BackgroundBrush => _backgroundBrush;
/// <summary>
/// Sets the <see cref="BackgroundBrush"/>.
/// </summary>
public void SetBackgroundBrush(IBrush value)
{
_backgroundBrush = value?.ToImmutable();
}
/// <inheritdoc/>
public override BaselineAlignment BaselineAlignment => _baselineAlignment;
/// <summary>
/// Sets the <see cref="BaselineAlignment"/>.
/// </summary>
public void SetBaselineAlignment(BaselineAlignment value)
{
_baselineAlignment = value;
}
/// <inheritdoc/>
public override CultureInfo CultureInfo => _cultureInfo;
/// <summary>
/// Sets the <see cref="CultureInfo"/>.
/// </summary>
public void SetCultureInfo(CultureInfo value)
{
_cultureInfo = value ?? throw new ArgumentNullException(nameof(value));
}
/*public override double FontHintingEmSize {
get { return fontHintingEmSize; }
}
/// <summary>
/// Sets the <see cref="FontHintingEmSize"/>.
/// </summary>
public void SetFontHintingEmSize(double value)
{
fontHintingEmSize = value;
}*/
/// <inheritdoc/>
public override double FontRenderingEmSize => _fontRenderingEmSize;
/// <summary>
/// Sets the <see cref="FontRenderingEmSize"/>.
/// </summary>
public void SetFontRenderingEmSize(double value)
{
_fontRenderingEmSize = value;
}
/// <inheritdoc/>
public override IBrush ForegroundBrush => _foregroundBrush;
/// <summary>
/// Sets the <see cref="ForegroundBrush"/>.
/// </summary>
public void SetForegroundBrush(IBrush value)
{
_foregroundBrush = value?.ToImmutable();
}
/// <inheritdoc/>
public override Typeface Typeface => _typeface;
/// <summary>
/// Sets the <see cref="Typeface"/>.
/// </summary>
public void SetTypeface(Typeface value)
{
_typeface = value;
}
/// <summary>
/// Gets the text decorations. The value may be null, a frozen <see cref="TextDecorationCollection"/>
/// or an unfrozen <see cref="TextDecorationCollection"/>.
/// If the value is an unfrozen <see cref="TextDecorationCollection"/>, you may assume that the
/// collection instance is only used for this <see cref="TextRunProperties"/> instance and it is safe
/// to add <see cref="TextDecoration"/>s.
/// </summary>
public override TextDecorationCollection TextDecorations => _textDecorations;
/// <summary>
/// Sets the <see cref="TextDecorations"/>.
/// </summary>
public void SetTextDecorations(TextDecorationCollection value)
{
ExtensionMethods.CheckIsFrozen(value);
if (_textDecorations == null)
_textDecorations = value;
else
_textDecorations = new TextDecorationCollection(_textDecorations.Union(value));
}
/*
/// <summary>
/// Gets the text effects. The value may be null, a frozen <see cref="TextEffectCollection"/>
/// or an unfrozen <see cref="TextEffectCollection"/>.
/// If the value is an unfrozen <see cref="TextEffectCollection"/>, you may assume that the
/// collection instance is only used for this <see cref="TextRunProperties"/> instance and it is safe
/// to add <see cref="TextEffect"/>s.
/// </summary>
public override TextEffectCollection TextEffects {
get { return textEffects; }
}
/// <summary>
/// Sets the <see cref="TextEffects"/>.
/// </summary>
public void SetTextEffects(TextEffectCollection value)
{
ExtensionMethods.CheckIsFrozen(value);
textEffects = value;
}
/// <summary>
/// Gets the typography properties for the text run.
/// </summary>
public override TextRunTypographyProperties TypographyProperties {
get { return typographyProperties; }
}
/// <summary>
/// Sets the <see cref="TypographyProperties"/>.
/// </summary>
public void SetTypographyProperties(TextRunTypographyProperties value)
{
typographyProperties = value;
}
/// <summary>
/// Gets the number substitution settings for the text run.
/// </summary>
public override NumberSubstitution NumberSubstitution {
get { return numberSubstitution; }
}
/// <summary>
/// Sets the <see cref="NumberSubstitution"/>.
/// </summary>
public void SetNumberSubstitution(NumberSubstitution value)
{
numberSubstitution = value;
}
*/
}
}

Просмотреть файл

@ -21,6 +21,7 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Controls;
using System.Diagnostics;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
namespace AvaloniaEdit.Rendering
@ -66,14 +67,15 @@ namespace AvaloniaEdit.Rendering
RequireControlModifierForClick = true;
}
/// <inheritdoc/>
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
TextRunProperties.SetForegroundBrush(context.TextView.LinkTextForegroundBrush);
TextRunProperties.SetBackgroundBrush(context.TextView.LinkTextBackgroundBrush);
return base.CreateTextRun(startVisualColumn, context);
}
/// <inheritdoc/>
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
this.TextRunProperties.SetForegroundBrush(context.TextView.LinkTextForegroundBrush);
this.TextRunProperties.SetBackgroundBrush(context.TextView.LinkTextBackgroundBrush);
if (context.TextView.LinkTextUnderline)
this.TextRunProperties.SetTextDecorations(TextDecorations.Underline);
return base.CreateTextRun(startVisualColumn, context);
}
/// <summary>
/// Gets whether the link is currently clickable.

Просмотреть файл

@ -21,6 +21,7 @@ using System.Collections.Generic;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
using AvaloniaEdit.Document;
using AvaloniaEdit.Utils;
using LogicalDirection = AvaloniaEdit.Document.LogicalDirection;
namespace AvaloniaEdit.Rendering
@ -61,32 +62,27 @@ namespace AvaloniaEdit.Rendering
throw new ArgumentNullException(nameof(context));
var relativeOffset = startVisualColumn - VisualColumn;
var offset = context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset + relativeOffset;
var text = context.GetText(offset, DocumentLength - relativeOffset);
return new TextCharacters(new ReadOnlySlice<char>(text.AsMemory(), offset, text.Length), TextRunProperties);
StringSegment text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset + relativeOffset, DocumentLength - relativeOffset);
return new TextCharacters(new ReadOnlySlice<char>(text.Text.AsMemory(), text.Offset, text.Count), this.TextRunProperties);
}
/// <inheritdoc/>
public override bool IsWhitespace(int visualColumn)
{
var offset = visualColumn - VisualColumn + ParentVisualLine.FirstDocumentLine.Offset + RelativeTextOffset;
return char.IsWhiteSpace(ParentVisualLine.Document.GetCharAt(offset));
}
/// <inheritdoc/>
public override string GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context)
public override ReadOnlySlice<char> GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var relativeOffset = visualColumnLimit - VisualColumn;
var text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset, relativeOffset);
return text;
int relativeOffset = visualColumnLimit - VisualColumn;
StringSegment text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset, relativeOffset);
return new ReadOnlySlice<char>(text.Text.AsMemory(), text.Offset, text.Count);
}
/// <inheritdoc/>

Просмотреть файл

@ -0,0 +1,46 @@
// 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 Avalonia.Media;
using Avalonia.Media.TextFormatting;
namespace AvaloniaEdit.Rendering
{
sealed class VisualLineTextParagraphProperties : TextParagraphProperties
{
internal TextRunProperties defaultTextRunProperties;
internal TextWrapping textWrapping;
internal double tabSize;
internal double indent;
internal bool firstLineInParagraph;
public override double DefaultIncrementalTab => tabSize;
public override FlowDirection FlowDirection => FlowDirection.LeftToRight;
public override TextAlignment TextAlignment => TextAlignment.Left;
public override double LineHeight => double.NaN;
public override bool FirstLineInParagraph => firstLineInParagraph;
public override TextRunProperties DefaultTextRunProperties => defaultTextRunProperties;
public override TextWrapping TextWrapping => textWrapping;
//public override TextMarkerProperties TextMarkerProperties { get { return null; } }
public override double Indent => indent;
}
}

Просмотреть файл

@ -21,117 +21,112 @@ using System.Diagnostics;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
using AvaloniaEdit.Document;
using AvaloniaEdit.Utils;
using JetBrains.Annotations;
using ITextSource = Avalonia.Media.TextFormatting.ITextSource;
namespace AvaloniaEdit.Rendering
{
/// <summary>
/// TextSource implementation that creates TextRuns for a VisualLine.
/// </summary>
internal sealed class VisualLineTextSource : ITextSource, ITextRunConstructionContext
{
public VisualLineTextSource(VisualLine visualLine)
{
VisualLine = visualLine;
}
/// <summary>
/// WPF TextSource implementation that creates TextRuns for a VisualLine.
/// </summary>
internal sealed class VisualLineTextSource : ITextSource, ITextRunConstructionContext
{
public VisualLineTextSource(VisualLine visualLine)
{
VisualLine = visualLine;
}
public VisualLine VisualLine { get; }
public TextView TextView { get; set; }
public TextDocument Document { get; set; }
public CustomTextRunProperties GlobalTextRunProperties { get; set; }
public VisualLine VisualLine { get; private set; }
public TextView TextView { get; set; }
public TextDocument Document { get; set; }
public TextRunProperties GlobalTextRunProperties { get; set; }
[CanBeNull]
public TextRun GetTextRun(int characterIndex)
{
if (characterIndex > VisualLine.VisualLengthWithEndOfLineMarker)
{
return null;
}
try
{
foreach (var element in VisualLine.Elements)
{
if (characterIndex >= element.VisualColumn
&& characterIndex < element.VisualColumn + element.VisualLength)
{
var relativeOffset = characterIndex - element.VisualColumn;
var run = element.CreateTextRun(characterIndex, this);
if (run == null)
throw new ArgumentNullException(element.GetType().Name + ".CreateTextRun");
if (run.TextSourceLength == 0)
throw new ArgumentException("The returned TextRun must not have length 0.", element.GetType().Name + ".Length");
if (relativeOffset + run.TextSourceLength > element.VisualLength)
throw new ArgumentException("The returned TextRun is too long.", element.GetType().Name + ".CreateTextRun");
if (run is InlineObjectRun inlineRun)
{
inlineRun.VisualLine = VisualLine;
VisualLine.HasInlineObjects = true;
TextView.AddInlineObject(inlineRun);
}
return run;
}
}
if (TextView.Options.ShowEndOfLine && characterIndex == VisualLine.VisualLength)
{
return CreateTextRunForNewLine();
}
public TextRun GetTextRun(int textSourceCharacterIndex)
{
try {
foreach (VisualLineElement element in VisualLine.Elements) {
if (textSourceCharacterIndex >= element.VisualColumn
&& textSourceCharacterIndex < element.VisualColumn + element.VisualLength) {
int relativeOffset = textSourceCharacterIndex - element.VisualColumn;
TextRun run = element.CreateTextRun(textSourceCharacterIndex, this);
if (run == null)
throw new ArgumentNullException(element.GetType().Name + ".CreateTextRun");
if (run.TextSourceLength == 0)
throw new ArgumentException("The returned TextRun must not have length 0.", element.GetType().Name + ".Length");
if (relativeOffset + run.TextSourceLength > element.VisualLength)
throw new ArgumentException("The returned TextRun is too long.", element.GetType().Name + ".CreateTextRun");
if (run is InlineObjectRun inlineRun) {
inlineRun.VisualLine = VisualLine;
VisualLine.HasInlineObjects = true;
TextView.AddInlineObject(inlineRun);
}
return run;
}
}
if (TextView.Options.ShowEndOfLine && textSourceCharacterIndex == VisualLine.VisualLength) {
return CreateTextRunForNewLine();
}
return new TextEndOfParagraph(1);
} catch (Exception ex) {
Debug.WriteLine(ex.ToString());
throw;
}
}
return new TextEndOfLine(2);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
throw;
}
}
private TextRun CreateTextRunForNewLine()
{
string newlineText = "";
DocumentLine lastDocumentLine = VisualLine.LastDocumentLine;
if (lastDocumentLine.DelimiterLength == 2) {
newlineText = "¶";
} else if (lastDocumentLine.DelimiterLength == 1) {
char newlineChar = Document.GetCharAt(lastDocumentLine.Offset + lastDocumentLine.Length);
if (newlineChar == '\r')
newlineText = "\\r";
else if (newlineChar == '\n')
newlineText = "\\n";
else
newlineText = "?";
}
return new FormattedTextRun(new FormattedTextElement(TextView.CachedElements.GetTextForNonPrintableCharacter(newlineText, this), 0), GlobalTextRunProperties);
}
private TextRun CreateTextRunForNewLine()
{
var newlineText = "";
var lastDocumentLine = VisualLine.LastDocumentLine;
if (lastDocumentLine.DelimiterLength == 2)
{
newlineText = "¶";
}
else if (lastDocumentLine.DelimiterLength == 1)
{
var newlineChar = Document.GetCharAt(lastDocumentLine.Offset + lastDocumentLine.Length);
switch (newlineChar)
{
case '\r':
newlineText = "\\r";
break;
case '\n':
newlineText = "\\n";
break;
default:
newlineText = "?";
break;
}
}
return new FormattedTextRun(new FormattedTextElement(TextView.CachedElements.GetTextForNonPrintableCharacter(newlineText, this), 0), GlobalTextRunProperties);
}
public ReadOnlySlice<char> GetPrecedingText(int textSourceCharacterIndexLimit)
{
try {
foreach (VisualLineElement element in VisualLine.Elements) {
if (textSourceCharacterIndexLimit > element.VisualColumn
&& textSourceCharacterIndexLimit <= element.VisualColumn + element.VisualLength) {
var span = element.GetPrecedingText(textSourceCharacterIndexLimit, this);
if (span.IsEmpty)
break;
int relativeOffset = textSourceCharacterIndexLimit - element.VisualColumn;
if (span.Length > relativeOffset)
throw new ArgumentException("The returned TextSpan is too long.", element.GetType().Name + ".GetPrecedingText");
return span;
}
}
return ReadOnlySlice<char>.Empty;
} catch (Exception ex) {
Debug.WriteLine(ex.ToString());
throw;
}
}
private string _cachedString;
private int _cachedStringOffset;
private string _cachedString;
private int _cachedStringOffset;
public string GetText(int offset, int length)
{
if (_cachedString != null)
{
if (offset >= _cachedStringOffset && offset + length <= _cachedStringOffset + _cachedString.Length)
{
return _cachedString.Substring(offset - _cachedStringOffset, length);
}
}
_cachedStringOffset = offset;
_cachedString = Document.GetText(offset, length);
return _cachedString;
}
}
public StringSegment GetText(int offset, int length)
{
if (_cachedString != null) {
if (offset >= _cachedStringOffset && offset + length <= _cachedStringOffset + _cachedString.Length) {
return new StringSegment(_cachedString, offset - _cachedStringOffset, length);
}
}
_cachedStringOffset = offset;
return new StringSegment(_cachedString = Document.GetText(offset, length));
}
}
}

Просмотреть файл

@ -21,7 +21,9 @@ using System.Collections.Immutable;
using System.Diagnostics;
using System.Xml;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace AvaloniaEdit.Utils
@ -88,6 +90,19 @@ namespace AvaloniaEdit.Utils
return Math.Max(Math.Min(value, maximum), minimum);
}
#endregion
#region CreateTypeface
/// <summary>
/// Creates typeface from the framework element.
/// </summary>
public static Typeface CreateTypeface(this Control fe)
{
return new Typeface(fe.GetValue(TextBlock.FontFamilyProperty),
fe.GetValue(TextBlock.FontStyleProperty),
fe.GetValue(TextBlock.FontWeightProperty),
fe.GetValue(TextBlock.FontStretchProperty));
}
#endregion
#region AddRange / Sequence
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> elements)

Просмотреть файл

@ -16,21 +16,27 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Globalization;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
using AvaloniaEdit.Rendering;
using TextLine = Avalonia.Media.TextFormatting.TextLine;
using TextRun = Avalonia.Media.TextFormatting.TextRun;
using TextRunProperties = Avalonia.Media.TextFormatting.TextRunProperties;
namespace AvaloniaEdit.Utils
{
/// <summary>
/// Creates TextFormatter instances that with the correct TextFormattingMode, if running on .NET 4.0.
/// </summary>
public static class TextFormatterFactory
/// Creates TextFormatter instances that with the correct TextFormattingMode, if running on .NET 4.0.
/// </summary>
static class TextFormatterFactory
{
/// <summary>
/// Creates a <see cref="TextFormatter"/> using the formatting mode used by the specified owner object.
/// </summary>
public static TextFormatter Create(Control owner)
{
return TextFormatter.Current;
}
/// <summary>
/// Creates formatted text.
/// </summary>
@ -40,41 +46,26 @@ namespace AvaloniaEdit.Utils
/// <param name="emSize">The font size. If this parameter is null, the font size of the <paramref name="element"/> will be used.</param>
/// <param name="foreground">The foreground color. If this parameter is null, the foreground of the <paramref name="element"/> will be used.</param>
/// <returns>A FormattedText object using the specified settings.</returns>
public static TextLine FormatLine(ReadOnlySlice<char> text, Typeface typeface, double emSize, IBrush foreground)
{
var defaultProperties = new CustomTextRunProperties(typeface, emSize, null, foreground);
var paragraphProperties = new CustomTextParagraphProperties(defaultProperties);
var textSource = new SimpleTextSource(text, defaultProperties);
return TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
}
private readonly struct SimpleTextSource : ITextSource
public static FormattedText CreateFormattedText(Control element, string text, Typeface typeface, double? emSize, IBrush foreground)
{
private readonly ReadOnlySlice<char> _text;
private readonly TextRunProperties _defaultProperties;
public SimpleTextSource(ReadOnlySlice<char> text, TextRunProperties defaultProperties)
{
_text = text;
_defaultProperties = defaultProperties;
}
if (element == null)
throw new ArgumentNullException(nameof(element));
if (text == null)
throw new ArgumentNullException(nameof(text));
if (typeface == default)
typeface = element.CreateTypeface();
if (emSize == null)
emSize = TextBlock.GetFontSize(element);
if (foreground == null)
foreground = TextBlock.GetForeground(element);
public TextRun GetTextRun(int textSourceIndex)
{
if (textSourceIndex < _text.Length)
{
return new TextCharacters(_text, textSourceIndex, _text.Length - textSourceIndex, _defaultProperties);
}
if (textSourceIndex > _text.Length)
{
return null;
}
return new TextEndOfParagraph(1);
}
return new FormattedText(
text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
typeface,
emSize.Value,
foreground);
}
}
}

Просмотреть файл

@ -1,17 +1,208 @@
using Avalonia;
using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace AvaloniaEdit.Utils;
#nullable enable
public static class TextLineExtensions
{
public static Rect GetTextBounds(this TextLine textLine, int start, int length)
public static IReadOnlyList<TextBounds> GetTextBounds(this TextLine textLine, int start, int length)
{
var startX = textLine.GetDistanceFromCharacterHit(new CharacterHit(start));
if (start + length <= 0)
{
return Array.Empty<TextBounds>();
}
var endX = textLine.GetDistanceFromCharacterHit(new CharacterHit(start + length));
var result = new List<TextBounds>(textLine.TextRuns.Count);
var currentPosition = 0;
var currentRect = Rect.Empty;
//Current line isn't covered.
if (textLine.TextRange.Length <= start)
{
return result;
}
return new Rect(startX, 0, endX - startX, textLine.Height);
//The whole line is covered.
if (currentPosition >= start && start + length > textLine.TextRange.Length)
{
currentRect = new Rect(textLine.Start, 0, textLine.WidthIncludingTrailingWhitespace,
textLine.Height);
result.Add(new TextBounds{ Rectangle = currentRect});
return result;
}
var startX = textLine.Start;
//A portion of the line is covered.
for (var index = 0; index < textLine.TextRuns.Count; index++)
{
var currentRun = textLine.TextRuns[index] as DrawableTextRun;
var currentShaped = currentRun as ShapedTextCharacters;
if (currentRun is null)
{
continue;
}
TextRun? nextRun = null;
if (index + 1 < textLine.TextRuns.Count)
{
nextRun = textLine.TextRuns[index + 1];
}
if (nextRun != null)
{
if (nextRun.Text.Start < currentRun.Text.Start && start + length < currentRun.Text.End)
{
goto skip;
}
if (currentRun.Text.Start >= start + length)
{
goto skip;
}
if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < start)
{
goto skip;
}
if (currentRun.Text.End < start)
{
goto skip;
}
goto noop;
skip:
{
startX += currentRun.Size.Width;
}
continue;
noop:
{
}
}
var endX = startX;
var endOffset = 0d;
if (currentShaped != null)
{
endOffset = currentShaped.GlyphRun.GetDistanceFromCharacterHit(
currentShaped.ShapedBuffer.IsLeftToRight ? new CharacterHit(start + length) : new CharacterHit(start));
endX += endOffset;
var startOffset = currentShaped.GlyphRun.GetDistanceFromCharacterHit(
currentShaped.ShapedBuffer.IsLeftToRight ? new CharacterHit(start) : new CharacterHit(start + length));
startX += startOffset;
var characterHit = currentShaped.GlyphRun.IsLeftToRight
? currentShaped.GlyphRun.GetCharacterHitFromDistance(endOffset, out _)
: currentShaped.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (nextRun is ShapedTextCharacters nextShaped)
{
if (currentShaped.ShapedBuffer.IsLeftToRight == nextShaped.ShapedBuffer.IsLeftToRight)
{
endOffset = nextShaped.GlyphRun.GetDistanceFromCharacterHit(
nextShaped.ShapedBuffer.IsLeftToRight
? new CharacterHit(start + length)
: new CharacterHit(start));
index++;
endX += endOffset;
currentRun = currentShaped = nextShaped;
if (nextShaped.ShapedBuffer.IsLeftToRight)
{
characterHit = nextShaped.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
currentPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
}
}
}
}
if (endX < startX)
{
(endX, startX) = (startX, endX);
}
var width = endX - startX;
if (result.Count > 0 && MathUtilities.AreClose(currentRect.Top, 0) &&
MathUtilities.AreClose(currentRect.Right, startX))
{
var textBounds = new TextBounds {Rectangle = currentRect.WithWidth(currentRect.Width + width)};
result[result.Count - 1] = textBounds;
}
else
{
currentRect = new Rect(startX, 0, width, textLine.Height);
result.Add(new TextBounds{ Rectangle = currentRect});
}
if (currentShaped != null && currentShaped.ShapedBuffer.IsLeftToRight)
{
if (nextRun != null)
{
if (nextRun.Text.Start > currentRun.Text.Start && nextRun.Text.Start >= start + length)
{
break;
}
currentPosition = nextRun.Text.End;
}
else
{
if (currentPosition >= start + length)
{
break;
}
}
}
else
{
if (currentPosition <= start)
{
break;
}
}
if (currentShaped != null && !currentShaped.ShapedBuffer.IsLeftToRight && currentPosition != currentRun.Text.Start)
{
endX += currentShaped.GlyphRun.Size.Width - endOffset;
}
startX = endX;
}
return result;
}
}
public struct TextBounds
{
public Rect Rectangle { get; set; }
}