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> <PropertyGroup>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AvaloniaVersion>0.10.999-cibuild0019136-beta</AvaloniaVersion> <AvaloniaVersion>0.10.999-cibuild0019161-beta</AvaloniaVersion>
<TextMateSharpVersion>1.0.31</TextMateSharpVersion> <TextMateSharpVersion>1.0.31</TextMateSharpVersion>
<NewtonsoftJsonVersion>13.0.1</NewtonsoftJsonVersion> <NewtonsoftJsonVersion>13.0.1</NewtonsoftJsonVersion>
<Version>0.10.12.2</Version> <Version>0.10.12.2</Version>

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

@ -156,7 +156,7 @@ namespace AvaloniaEdit.Demo
private void AddControlButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) 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(); _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> /// <summary>
/// Gets the first interested offset using binary search /// Gets the first interested offset using binary search
@ -301,7 +301,7 @@ namespace AvaloniaEdit.Demo
/// <param name="startOffset">Start offset.</param> /// <param name="startOffset">Start offset.</param>
public override int GetFirstInterestedOffset(int startOffset) 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) if (pos < 0)
pos = ~pos; pos = ~pos;
if (pos < controls.Count) if (pos < controls.Count)
@ -312,14 +312,14 @@ namespace AvaloniaEdit.Demo
public override VisualLineElement ConstructElement(int offset) 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) if (pos >= 0)
return new InlineObjectElement(0, controls[pos].Value); return new InlineObjectElement(0, controls[pos].Value);
else else
return null; 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); return x.Key.CompareTo(y.Key);
} }

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

@ -45,11 +45,11 @@ namespace AvaloniaEdit.Editing
_textView = textArea.TextView; _textView = textArea.TextView;
_position = new TextViewPosition(1, 1, 0); _position = new TextViewPosition(1, 1, 0);
_caretAdorner = new CaretLayer(textArea); _caretAdorner = new CaretLayer(textArea);
_textView.InsertLayer(_caretAdorner, KnownLayer.Caret, LayerInsertionPosition.Replace); _textView.InsertLayer(_caretAdorner, KnownLayer.Caret, LayerInsertionPosition.Replace);
_textView.VisualLinesChanged += TextView_VisualLinesChanged; _textView.VisualLinesChanged += TextView_VisualLinesChanged;
_textView.ScrollOffsetChanged += TextView_ScrollOffsetChanged; _textView.ScrollOffsetChanged += TextView_ScrollOffsetChanged;
} }
internal void UpdateIfVisible() internal void UpdateIfVisible()
{ {
@ -402,41 +402,39 @@ namespace AvaloniaEdit.Editing
lineBottom - lineTop); lineBottom - lineTop);
} }
private Rect CalcCaretOverstrikeRectangle(VisualLine visualLine) Rect CalcCaretOverstrikeRectangle(VisualLine visualLine)
{ {
if (!_visualColumnValid) if (!_visualColumnValid) {
{ RevalidateVisualColumn(visualLine);
RevalidateVisualColumn(visualLine); }
}
var currentPos = _position.VisualColumn; int currentPos = _position.VisualColumn;
// The text being overwritten in overstrike mode is everything up to the next normal caret stop // 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); int nextPos = visualLine.GetNextCaretPosition(currentPos, LogicalDirection.Forward, CaretPositioningMode.Normal, true);
var textLine = visualLine.GetTextLine(currentPos); var textLine = visualLine.GetTextLine(currentPos);
Rect r; Rect r;
if (currentPos < visualLine.VisualLength) if (currentPos < visualLine.VisualLength) {
{ // If the caret is within the text, use GetTextBounds() for the text being overwritten.
// 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.
// This is necessary to ensure the rectangle is calculated correctly in bidirectional text. var textBounds = textLine.GetTextBounds(currentPos, nextPos - currentPos)[0];
r = textLine.GetTextBounds(currentPos, nextPos - currentPos); r = textBounds.Rectangle;
r = r.WithY(r.Y + visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineTop)); var y = r.Y + visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineTop);
} r = r.WithY(y);
else } else {
{ // If the caret is at the end of the line (or in virtual space),
// 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)
// 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);
var xPos = visualLine.GetTextLineVisualXPosition(textLine, currentPos); double xPos2 = visualLine.GetTextLineVisualXPosition(textLine, nextPos);
var xPos2 = visualLine.GetTextLineVisualXPosition(textLine, nextPos); double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop);
var lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop); double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom);
var lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom); r = new Rect(xPos, lineTop, xPos2 - xPos, lineBottom - lineTop);
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 the caret is too small (e.g. in front of zero-width character), ensure it's still visible if (r.Width < CaretWidth)
if (r.Width < CaretWidth) r = r.WithWidth(CaretWidth);
r = r.WithWidth(CaretWidth); return r;
return r; }
}
/// <summary> /// <summary>
/// Returns the caret rectangle. The coordinate system is in device-independent pixels from the top of the document. /// 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> /// </summary>
protected double EmSize { get; set; } protected double EmSize { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
Typeface = new Typeface(GetValue(TextBlock.FontFamilyProperty)); Typeface = this.CreateTypeface();
EmSize = GetValue(TextBlock.FontSizeProperty); EmSize = GetValue(TextBlock.FontSizeProperty);
var textLine = TextFormatterFactory.FormatLine(Enumerable.Repeat('9', MaxLineNumberLength).ToArray(), var text = TextFormatterFactory.CreateFormattedText(
Typeface, this,
EmSize, new string('9', MaxLineNumberLength),
GetValue(TemplatedControl.ForegroundProperty) Typeface,
); EmSize,
GetValue(TextBlock.ForegroundProperty)
return new Size(textLine.WidthIncludingTrailingWhitespace, textLine.Height); );
} return new Size(text.Width, 0);
}
/// <inheritdoc/>
public override void Render(DrawingContext drawingContext) public override void Render(DrawingContext drawingContext)
{ {
var textView = TextView; var textView = TextView;
var renderSize = Bounds.Size; var renderSize = Bounds.Size;
if (textView != null && textView.VisualLinesValid) if (textView is {VisualLinesValid: true}) {
{ var foreground = GetValue(TextBlock.ForegroundProperty);
var foreground = GetValue(TemplatedControl.ForegroundProperty); foreach (var line in textView.VisualLines) {
var lineNumber = line.FirstDocumentLine.LineNumber;
foreach (var line in textView.VisualLines) var text = TextFormatterFactory.CreateFormattedText(
{ this,
var lineNumber = line.FirstDocumentLine.LineNumber; lineNumber.ToString(CultureInfo.CurrentCulture),
var text = lineNumber.ToString(CultureInfo.CurrentCulture); Typeface, EmSize, foreground
var textLine = TextFormatterFactory.FormatLine(text.AsMemory(), );
Typeface, var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop);
EmSize, drawingContext.DrawText(text, new Point(renderSize.Width - text.Width, y - textView.VerticalOffset));
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));
}
}
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView) protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView)

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

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

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

@ -255,62 +255,34 @@ namespace AvaloniaEdit.Highlighting
ApplyColorToElement(element, color, CurrentContext); ApplyColorToElement(element, color, CurrentContext);
} }
internal static void ApplyColorToElement(VisualLineElement element, HighlightingColor color, ITextRunConstructionContext context) internal static void ApplyColorToElement(VisualLineElement element, HighlightingColor color, ITextRunConstructionContext context)
{ {
if (color.Foreground != null) if (color.Foreground != null) {
{ var b = color.Foreground.GetBrush(context);
var b = color.Foreground.GetBrush(context); if (b != null)
if (b != null) element.TextRunProperties.SetForegroundBrush(b);
element.TextRunProperties.SetForegroundBrush(b); }
} if (color.Background != null) {
if (color.Background != null) var b = color.Background.GetBrush(context);
{ if (b != null)
var b = color.Background.GetBrush(context); element.BackgroundBrush = b;
if (b != null) }
element.BackgroundBrush = b; if (color.FontStyle != null || color.FontWeight != null || color.FontFamily != null) {
} var tf = element.TextRunProperties.Typeface;
if (color.FontStyle != null || color.FontWeight != null || color.FontFamily != null) element.TextRunProperties.SetTypeface(new Typeface(
{ color.FontFamily ?? tf.FontFamily,
var tf = element.TextRunProperties.Typeface; color.FontStyle ?? tf.Style,
element.TextRunProperties.SetTypeface(new Avalonia.Media.Typeface( color.FontWeight ?? tf.Weight,
color.FontFamily ?? tf.FontFamily, tf.Stretch
color.FontStyle ?? tf.Style, ));
color.FontWeight ?? tf.Weight) }
); if (color.Underline ?? false)
} element.TextRunProperties.SetTextDecorations(TextDecorations.Underline);
if (color.FontSize.HasValue) if (color.Strikethrough ?? false)
element.TextRunProperties.SetFontSize(color.FontSize.Value); element.TextRunProperties.SetTextDecorations(TextDecorations.Strikethrough);
if (color.FontSize.HasValue)
if (color.Underline ?? false) element.TextRunProperties.SetFontRenderingEmSize(color.FontSize.Value);
{ }
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
}
});
}
}
}
/// <summary> /// <summary>
/// This method is responsible for telling the TextView to redraw lines when the highlighting state has changed. /// 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.Diagnostics;
using System.Linq; using System.Linq;
using Avalonia; using Avalonia;
using Avalonia.Controls.Primitives;
using AvaloniaEdit.Document; using AvaloniaEdit.Document;
using AvaloniaEdit.Editing; using AvaloniaEdit.Editing;
using AvaloniaEdit.Utils; using AvaloniaEdit.Utils;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.TextFormatting;
namespace AvaloniaEdit.Rendering namespace AvaloniaEdit.Rendering
{ {
/// <summary> /// <summary>
/// Helper for creating a PathGeometry. /// Helper for creating a PathGeometry.
/// </summary> /// </summary>
public sealed class BackgroundGeometryBuilder public sealed class BackgroundGeometryBuilder
{ {
/// <summary> private double _cornerRadius;
/// Gets/sets the radius of the rounded corners.
/// </summary>
public double CornerRadius { get; set; }
/// <summary> /// <summary>
/// Gets/Sets whether to align to whole pixels. /// Gets/sets the radius of the rounded corners.
/// /// </summary>
/// If BorderThickness is set to 0, the geometry is aligned to whole pixels. public double CornerRadius {
/// If BorderThickness is set to a non-zero value, the outer edge of the border is aligned get { return _cornerRadius; }
/// to whole pixels. set { _cornerRadius = value; }
/// }
/// The default value is <c>false</c>.
/// </summary>
public bool AlignToWholePixels { get; set; }
/// <summary> /// <summary>
/// Gets/sets the border thickness. /// Gets/Sets whether to align to whole pixels.
/// ///
/// This property only has an effect if <c>AlignToWholePixels</c> is enabled. /// If BorderThickness is set to 0, the geometry is aligned to whole pixels.
/// When using the resulting geometry to paint a border, set this property to the border thickness. /// If BorderThickness is set to a non-zero value, the outer edge of the border is aligned
/// Otherwise, leave the property set to the default value <c>0</c>. /// to whole pixels.
/// </summary> ///
public double BorderThickness { get; set; } /// The default value is <c>false</c>.
/// </summary>
public bool AlignToWholePixels { get; set; }
/// <summary> /// <summary>
/// Gets/Sets whether to extend the rectangles to full width at line end. /// Gets/sets the border thickness.
/// </summary> ///
public bool ExtendToFullWidthAtLineEnd { get; set; } /// 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> /// <summary>
/// Adds the specified segment to the geometry. /// Gets/Sets whether to extend the rectangles to full width at line end.
/// </summary> /// </summary>
public void AddSegment(TextView textView, ISegment segment) public bool ExtendToFullWidthAtLineEnd { get; set; }
{
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> /// <summary>
/// Adds a rectangle to the geometry. /// Creates a new BackgroundGeometryBuilder instance.
/// </summary> /// </summary>
/// <remarks> public BackgroundGeometryBuilder()
/// 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 void AddRectangle(Size pixelSize, Rect r) /// <summary>
{ /// Adds the specified segment to the geometry.
if (AlignToWholePixels) /// </summary>
{ public void AddSegment(TextView textView, ISegment segment)
var halfBorder = 0.5 * BorderThickness; {
AddRectangle(PixelSnapHelpers.Round(r.X - halfBorder, pixelSize.Width) + halfBorder, if (textView == null)
PixelSnapHelpers.Round(r.Y - halfBorder, pixelSize.Height) + halfBorder, throw new ArgumentNullException("textView");
PixelSnapHelpers.Round(r.Right + halfBorder, pixelSize.Width) - halfBorder, Size pixelSize = PixelSnapHelpers.GetPixelSize(textView);
PixelSnapHelpers.Round(r.Bottom + halfBorder, pixelSize.Height) - halfBorder); foreach (Rect r in GetRectsForSegment(textView, segment, ExtendToFullWidthAtLineEnd)) {
} AddRectangle(pixelSize, r);
else }
{ }
AddRectangle(r.X, r.Y, r.Right, r.Bottom);
}
}
/// <summary> /// <summary>
/// Calculates the list of rectangle where the segment in shown. /// Adds a rectangle to the geometry.
/// This method usually returns one rectangle for each line inside the segment /// </summary>
/// (but potentially more, e.g. when bidirectional text is involved). /// <remarks>
/// </summary> /// This overload will align the coordinates according to
public static IEnumerable<Rect> GetRectsForSegment(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd = false) /// <see cref="AlignToWholePixels"/>.
{ /// Use the <see cref="AddRectangle(double,double,double,double)"/>-overload instead if the coordinates should not be aligned.
if (textView == null) /// </remarks>
throw new ArgumentNullException(nameof(textView)); public void AddRectangle(TextView textView, Rect rectangle)
if (segment == null) {
throw new ArgumentNullException(nameof(segment)); AddRectangle(PixelSnapHelpers.GetPixelSize(textView), rectangle);
return GetRectsForSegmentImpl(textView, segment, extendToFullWidthAtLineEnd); }
}
private static IEnumerable<Rect> GetRectsForSegmentImpl(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd) private void AddRectangle(Size pixelSize, Rect r)
{ {
var segmentStart = segment.Offset; if (AlignToWholePixels) {
var segmentEnd = segment.Offset + segment.Length; 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); /// <summary>
segmentEnd = segmentEnd.CoerceValue(0, textView.Document.TextLength); /// 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; private static IEnumerable<Rect> GetRectsForSegmentImpl(TextView textView, ISegment segment, bool extendToFullWidthAtLineEnd)
TextViewPosition end; {
int segmentStart = segment.Offset;
int segmentEnd = segment.Offset + segment.Length;
if (segment is SelectionSegment sel) segmentStart = segmentStart.CoerceValue(0, textView.Document.TextLength);
{ segmentEnd = segmentEnd.CoerceValue(0, textView.Document.TextLength);
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));
}
foreach (var vl in textView.VisualLines) TextViewPosition start;
{ TextViewPosition end;
var vlStartOffset = vl.FirstDocumentLine.Offset;
if (vlStartOffset > segmentEnd)
break;
var vlEndOffset = vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length;
if (vlEndOffset < segmentStart)
continue;
int segmentStartVc; if (segment is SelectionSegment) {
segmentStartVc = segmentStart < vlStartOffset ? 0 : vl.ValidateVisualColumn(start, extendToFullWidthAtLineEnd); 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; foreach (VisualLine vl in textView.VisualLines) {
if (segmentEnd > vlEndOffset) int vlStartOffset = vl.FirstDocumentLine.Offset;
segmentEndVc = extendToFullWidthAtLineEnd ? int.MaxValue : vl.VisualLengthWithEndOfLineMarker; if (vlStartOffset > segmentEnd)
else break;
segmentEndVc = vl.ValidateVisualColumn(end, extendToFullWidthAtLineEnd); int vlEndOffset = vl.LastDocumentLine.Offset + vl.LastDocumentLine.Length;
if (vlEndOffset < segmentStart)
continue;
foreach (var rect in ProcessTextLines(textView, vl, segmentStartVc, segmentEndVc)) int segmentStartVc;
yield return rect; if (segmentStart < vlStartOffset)
} segmentStartVc = 0;
} else
segmentStartVc = vl.ValidateVisualColumn(start, extendToFullWidthAtLineEnd);
/// <summary> int segmentEndVc;
/// Calculates the rectangles for the visual column segment. if (segmentEnd > vlEndOffset)
/// This returns one rectangle for each line inside the segment. segmentEndVc = extendToFullWidthAtLineEnd ? int.MaxValue : vl.VisualLengthWithEndOfLineMarker;
/// </summary> else
public static IEnumerable<Rect> GetRectsFromVisualSegment(TextView textView, VisualLine line, int startVc, int endVc) segmentEndVc = vl.ValidateVisualColumn(end, extendToFullWidthAtLineEnd);
{
if (textView == null)
throw new ArgumentNullException(nameof(textView));
if (line == null)
throw new ArgumentNullException(nameof(line));
return ProcessTextLines(textView, line, startVc, endVc);
}
private static IEnumerable<Rect> ProcessTextLines(TextView textView, VisualLine visualLine, int segmentStartVc, int segmentEndVc) foreach (var rect in ProcessTextLines(textView, vl, segmentStartVc, segmentEndVc))
{ yield return rect;
if (visualLine.TextLines.Count == 0) }
{ }
yield break;
}
var lastTextLine = visualLine.TextLines.Last();
var scrollOffset = textView.ScrollOffset;
for (var i = 0; i < visualLine.TextLines.Count; i++) /// <summary>
{ /// Calculates the rectangles for the visual column segment.
var line = visualLine.TextLines[i]; /// This returns one rectangle for each line inside the segment.
var y = visualLine.GetTextLineVisualYPosition(line, VisualYPosition.LineTop); /// </summary>
var visualStartCol = visualLine.GetTextLineVisualStartColumn(line); public static IEnumerable<Rect> GetRectsFromVisualSegment(TextView textView, VisualLine line, int startVc, int endVc)
var visualEndCol = visualStartCol + line.TextRange.Length; {
if (line == lastTextLine) if (textView == null)
visualEndCol -= 1; // 1 position for the TextEndOfParagraph throw new ArgumentNullException("textView");
// TODO: ? if (line == null)
//else throw new ArgumentNullException("line");
// visualEndCol -= line.TrailingWhitespaceLength; return ProcessTextLines(textView, line, startVc, endVc);
}
if (segmentEndVc < visualStartCol) private static IEnumerable<Rect> ProcessTextLines(TextView textView, VisualLine visualLine, int segmentStartVc, int segmentEndVc)
break; {
if (lastTextLine != line && segmentStartVc > visualEndCol) TextLine lastTextLine = visualLine.TextLines.Last();
continue; Vector scrollOffset = textView.ScrollOffset;
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 {
right = visualLine.GetTextLineVisualXPosition(lastTextLine, segmentEndVc); for (int i = 0; i < visualLine.TextLines.Count; i++) {
//} TextLine line = visualLine.TextLines[i];
var extendSelection = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height); double y = visualLine.GetTextLineVisualYPosition(line, VisualYPosition.LineTop);
if (!lastRect.IsEmpty) int visualStartCol = visualLine.GetTextLineVisualStartColumn(line);
{ int visualEndCol = visualStartCol + line.TextRange.Length;
if (extendSelection.Intersects(lastRect)) if (line == lastTextLine)
{ visualEndCol -= 1; // 1 position for the TextEndOfParagraph
lastRect.Union(extendSelection); else
yield return lastRect; visualEndCol -= line.TrailingWhitespaceLength;
}
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;
}
}
private readonly PathFigures _figures = new PathFigures(); if (segmentEndVc < visualStartCol)
private PathFigure _figure; break;
private int _insertionIndex; if (lastTextLine != line && segmentStartVc > visualEndCol)
private double _lastTop, _lastBottom; continue;
private double _lastLeft, _lastRight; 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> private readonly PathFigures _figures = new PathFigures();
/// Adds a rectangle to the geometry. private PathFigure _figure;
/// </summary> private int _insertionIndex;
/// <remarks> private double _lastTop, _lastBottom;
/// This overload assumes that the coordinates are aligned properly private double _lastLeft, _lastRight;
/// (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 ArcSegment MakeArc(double x, double y, SweepDirection dir) /// <summary>
{ /// Adds a rectangle to the geometry.
var arc = new ArcSegment /// </summary>
{ /// <remarks>
Point = new Point(x, y), /// This overload assumes that the coordinates are aligned properly
Size = new Size(CornerRadius, CornerRadius), /// (see <see cref="AlignToWholePixels"/>).
SweepDirection = dir /// Use the <see cref="AddRectangle(TextView,Rect)"/>-overload instead if the coordinates are not yet aligned.
}; /// </remarks>
return arc; 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) private ArcSegment MakeArc(double x, double y, SweepDirection dir)
{ {
return new LineSegment { Point = new Point(x, y) }; var arc = new ArcSegment
} {
Point = new Point(x, y),
Size = new Size(CornerRadius, CornerRadius),
SweepDirection = dir
};
return arc;
}
/// <summary> private static LineSegment MakeLineSegment(double x, double y)
/// Closes the current figure. {
/// </summary> return new LineSegment { Point = new Point(x, y) };
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));
}
_figure.IsClosed = true; /// <summary>
_figures.Add(_figure); /// Closes the current figure.
_figure = null; /// </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> _figure.IsClosed = true;
/// Creates the geometry. _figures.Add(_figure);
/// Returns null when the geometry is empty! _figure = null;
/// </summary> }
public Geometry CreateGeometry() }
{
CloseFigure(); /// <summary>
return _figures.Count != 0 ? new PathGeometry { Figures = _figures } : null; /// 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> /// </summary>
public sealed class CollapsedLineSection public sealed class CollapsedLineSection
{ {
private DocumentLine _start; private readonly HeightTree _heightTree;
private DocumentLine _end;
private readonly HeightTree _heightTree; #if DEBUG
#if DEBUG
internal string Id; internal string Id;
private static int _nextId; private static int _nextId;
#else #else
internal const string Id = ""; const string ID = "";
#endif #endif
internal CollapsedLineSection(HeightTree heightTree, DocumentLine start, DocumentLine end) internal CollapsedLineSection(HeightTree heightTree, DocumentLine start, DocumentLine end)
{ {
_heightTree = heightTree; _heightTree = heightTree;
_start = start; Start = start;
_end = end; End = end;
#if DEBUG #if DEBUG
unchecked { unchecked {
Id = " #" + (_nextId++); Id = " #" + (_nextId++);
} }
#endif #endif
} }
/// <summary> /// <summary>
/// Gets if the document line is collapsed. /// Gets if the document line is collapsed.
/// This property initially is true and turns to false when uncollapsing the section. /// This property initially is true and turns to false when uncollapsing the section.
/// </summary> /// </summary>
public bool IsCollapsed => _start != null; public bool IsCollapsed => Start != null;
/// <summary> /// <summary>
/// Gets the start line of the section. /// Gets the start line of the section.
/// When the section is uncollapsed or the text containing it is deleted, /// When the section is uncollapsed or the text containing it is deleted,
/// this property returns null. /// this property returns null.
/// </summary> /// </summary>
public DocumentLine Start { public DocumentLine Start { get; internal set; }
get => _start;
internal set => _start = value;
}
/// <summary> /// <summary>
/// Gets the end line of the section. /// Gets the end line of the section.
/// When the section is uncollapsed or the text containing it is deleted, /// When the section is uncollapsed or the text containing it is deleted,
/// this property returns null. /// this property returns null.
/// </summary> /// </summary>
public DocumentLine End { public DocumentLine End { get; internal set; }
get => _end;
internal set => _end = value;
}
/// <summary> /// <summary>
/// Uncollapses the section. /// Uncollapses the section.
/// This causes the Start and End properties to be set to null! /// This causes the Start and End properties to be set to null!
@ -83,26 +75,28 @@ namespace AvaloniaEdit.Rendering
/// </summary> /// </summary>
public void Uncollapse() public void Uncollapse()
{ {
if (_start == null) if (Start == null)
return; return;
_heightTree.Uncollapse(this); if (!_heightTree.IsDisposed) {
#if DEBUG _heightTree.Uncollapse(this);
//heightTree.CheckProperties(); #if DEBUG
#endif _heightTree.CheckProperties();
#endif
_start = null; }
_end = null;
Start = null;
End = null;
} }
/// <summary> /// <summary>
/// Gets a string representation of the collapsed section. /// Gets a string representation of the collapsed section.
/// </summary> /// </summary>
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")] [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")]
public override string ToString() public override string ToString()
{ {
return "[CollapsedSection" + Id + " Start=" + (_start != null ? _start.LineNumber.ToString() : "null") return "[CollapsedSection" + Id + " Start=" + (Start != null ? Start.LineNumber.ToString() : "null")
+ " End=" + (_end != null ? _end.LineNumber.ToString() : "null") + "]"; + " End=" + (End != null ? End.LineNumber.ToString() : "null") + "]";
} }
} }
} }

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

@ -21,111 +21,100 @@ using System.Collections.Generic;
namespace AvaloniaEdit.Rendering namespace AvaloniaEdit.Rendering
{ {
/// <summary> /// <summary>
/// Base class for <see cref="IVisualLineTransformer"/> that helps /// Base class for <see cref="IVisualLineTransformer"/> that helps
/// splitting visual elements so that colors (and other text properties) can be easily assigned /// splitting visual elements so that colors (and other text properties) can be easily assigned
/// to individual words/characters. /// to individual words/characters.
/// </summary> /// </summary>
public abstract class ColorizingTransformer : IVisualLineTransformer, ITextViewConnect public abstract class ColorizingTransformer : IVisualLineTransformer, ITextViewConnect
{ {
/// <summary> /// <summary>
/// Gets the list of elements currently being transformed. /// Gets the list of elements currently being transformed.
/// </summary> /// </summary>
protected IList<VisualLineElement> CurrentElements { get; private set; } protected IList<VisualLineElement> CurrentElements { get; private set; }
/// <summary> /// <summary>
/// <see cref="IVisualLineTransformer.Transform"/> implementation. /// <see cref="IVisualLineTransformer.Transform"/> implementation.
/// Sets <see cref="CurrentElements"/> and calls <see cref="Colorize"/>. /// Sets <see cref="CurrentElements"/> and calls <see cref="Colorize"/>.
/// </summary> /// </summary>
public void Transform(ITextRunConstructionContext context, IList<VisualLineElement> elements) public void Transform(ITextRunConstructionContext context, IList<VisualLineElement> elements)
{ {
if (CurrentElements != null) if (CurrentElements != null)
throw new InvalidOperationException("Recursive Transform() call"); throw new InvalidOperationException("Recursive Transform() call");
CurrentElements = elements ?? throw new ArgumentNullException(nameof(elements)); CurrentElements = elements ?? throw new ArgumentNullException(nameof(elements));
try {
Colorize(context);
} finally {
CurrentElements = null;
}
}
try /// <summary>
{ /// Performs the colorization.
Colorize(context); /// </summary>
} protected abstract void Colorize(ITextRunConstructionContext context);
finally
{
CurrentElements = null;
}
}
/// <summary> /// <summary>
/// Performs the colorization. /// Changes visual element properties.
/// </summary> /// This method accesses <see cref="CurrentElements"/>, so it must be called only during
protected abstract void Colorize(ITextRunConstructionContext context); /// 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> /// <summary>
/// Changes visual element properties. /// Called when added to a text view.
/// This method accesses <see cref="CurrentElements"/>, so it must be called only during /// </summary>
/// a <see cref="Transform"/> call. protected virtual void OnAddToTextView(TextView textView)
/// 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> /// <summary>
/// Called when added to a text view. /// Called when removed from a text view.
/// </summary> /// </summary>
protected virtual void OnAddToTextView(TextView textView) protected virtual void OnRemoveFromTextView(TextView textView)
{ {
} }
/// <summary> void ITextViewConnect.AddToTextView(TextView textView)
/// Called when removed from a text view. {
/// </summary> OnAddToTextView(textView);
protected virtual void OnRemoveFromTextView(TextView textView) }
{
}
void ITextViewConnect.AddToTextView(TextView textView) void ITextViewConnect.RemoveFromTextView(TextView textView)
{ {
OnAddToTextView(textView); OnRemoveFromTextView(textView);
} }
}
void ITextViewConnect.RemoveFromTextView(TextView textView)
{
OnRemoveFromTextView(textView);
}
}
} }

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

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

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

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

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

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

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

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

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

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

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

@ -26,28 +26,26 @@ namespace AvaloniaEdit.Rendering
/// <summary> /// <summary>
/// A node in the text view's height tree. /// A node in the text view's height tree.
/// </summary> /// </summary>
sealed class HeightTreeNode internal sealed class HeightTreeNode
{ {
internal readonly DocumentLine DocumentLine; internal readonly DocumentLine DocumentLine;
internal HeightTreeLineNode LineNode; internal HeightTreeLineNode LineNode;
internal HeightTreeNode Left; internal HeightTreeNode Left, Right, Parent;
internal HeightTreeNode Right; internal bool Color;
internal HeightTreeNode Parent;
internal bool Color;
internal HeightTreeNode() internal HeightTreeNode()
{ {
} }
internal HeightTreeNode(DocumentLine documentLine, double height) internal HeightTreeNode(DocumentLine documentLine, double height)
{ {
DocumentLine = documentLine; this.DocumentLine = documentLine;
TotalCount = 1; this.TotalCount = 1;
LineNode = new HeightTreeLineNode(height); this.LineNode = new HeightTreeLineNode(height);
TotalHeight = height; this.TotalHeight = height;
} }
internal HeightTreeNode LeftMost { internal HeightTreeNode LeftMost {
get { get {
HeightTreeNode node = this; HeightTreeNode node = this;
@ -56,7 +54,7 @@ namespace AvaloniaEdit.Rendering
return node; return node;
} }
} }
internal HeightTreeNode RightMost { internal HeightTreeNode RightMost {
get { get {
HeightTreeNode node = this; HeightTreeNode node = this;
@ -65,7 +63,7 @@ namespace AvaloniaEdit.Rendering
return node; return node;
} }
} }
/// <summary> /// <summary>
/// Gets the inorder successor of the node. /// Gets the inorder successor of the node.
/// </summary> /// </summary>
@ -73,25 +71,26 @@ namespace AvaloniaEdit.Rendering
get { get {
if (Right != null) { if (Right != null) {
return Right.LeftMost; 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> /// <summary>
/// The number of lines in this node and its child nodes. /// The number of lines in this node and its child nodes.
/// Invariant: /// Invariant:
/// totalCount = 1 + left.totalCount + right.totalCount /// totalCount = 1 + left.totalCount + right.totalCount
/// </summary> /// </summary>
internal int TotalCount; internal int TotalCount;
/// <summary> /// <summary>
/// The total height of this node and its child nodes, excluding directly collapsed nodes. /// The total height of this node and its child nodes, excluding directly collapsed nodes.
/// Invariant: /// Invariant:
@ -100,7 +99,7 @@ namespace AvaloniaEdit.Rendering
/// + right.IsDirectlyCollapsed ? 0 : right.totalHeight /// + right.IsDirectlyCollapsed ? 0 : right.totalHeight
/// </summary> /// </summary>
internal double TotalHeight; internal double TotalHeight;
/// <summary> /// <summary>
/// List of the sections that hold this node collapsed. /// List of the sections that hold this node collapsed.
/// Invariant 1: /// Invariant 1:
@ -113,10 +112,14 @@ namespace AvaloniaEdit.Rendering
/// documentLine (middle node). /// documentLine (middle node).
/// </summary> /// </summary>
internal List<CollapsedLineSection> CollapsedSections; 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) { if (CollapsedSections == null) {
CollapsedSections = new List<CollapsedLineSection>(); CollapsedSections = new List<CollapsedLineSection>();
@ -125,8 +128,8 @@ namespace AvaloniaEdit.Rendering
Debug.Assert(!CollapsedSections.Contains(section)); Debug.Assert(!CollapsedSections.Contains(section));
CollapsedSections.Add(section); CollapsedSections.Add(section);
} }
internal void RemoveDirectlyCollapsed(CollapsedLineSection section) internal void RemoveDirectlyCollapsed(CollapsedLineSection section)
{ {
Debug.Assert(CollapsedSections.Contains(section)); Debug.Assert(CollapsedSections.Contains(section));
@ -140,8 +143,8 @@ namespace AvaloniaEdit.Rendering
TotalHeight += Right.TotalHeight; TotalHeight += Right.TotalHeight;
} }
} }
#if DEBUG #if DEBUG
public override string ToString() public override string ToString()
{ {
return "[HeightTreeNode " return "[HeightTreeNode "
@ -151,16 +154,16 @@ namespace AvaloniaEdit.Rendering
+ " TotalHeight=" + TotalHeight + " TotalHeight=" + TotalHeight
+ "]"; + "]";
} }
static string GetCollapsedSections(List<CollapsedLineSection> list) static string GetCollapsedSections(List<CollapsedLineSection> list)
{ {
if (list == null) if (list == null)
return "{}"; return "{}";
return "{" + return "{" +
string.Join(",", 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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System.Globalization;
using Avalonia.Media;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using AvaloniaEdit.Document; using AvaloniaEdit.Document;
using AvaloniaEdit.Utils; using AvaloniaEdit.Utils;
@ -35,22 +33,22 @@ namespace AvaloniaEdit.Rendering
/// Gets the text document. /// Gets the text document.
/// </summary> /// </summary>
TextDocument Document { get; } TextDocument Document { get; }
/// <summary> /// <summary>
/// Gets the text view for which the construction runs. /// Gets the text view for which the construction runs.
/// </summary> /// </summary>
TextView TextView { get; } TextView TextView { get; }
/// <summary> /// <summary>
/// Gets the visual line that is currently being constructed. /// Gets the visual line that is currently being constructed.
/// </summary> /// </summary>
VisualLine VisualLine { get; } VisualLine VisualLine { get; }
/// <summary> /// <summary>
/// Gets the global text run properties. /// Gets the global text run properties.
/// </summary> /// </summary>
CustomTextRunProperties GlobalTextRunProperties { get; } TextRunProperties GlobalTextRunProperties { get; }
/// <summary> /// <summary>
/// Gets a piece of text from the document. /// Gets a piece of text from the document.
/// </summary> /// </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 /// 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. /// for text within the same line.
/// </remarks> /// </remarks>
string GetText(int offset, int length); StringSegment 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;
} }
} }

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

@ -22,85 +22,99 @@ using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
#nullable enable
namespace AvaloniaEdit.Rendering namespace AvaloniaEdit.Rendering
{ {
/// <summary> /// <summary>
/// A inline UIElement in the document. /// A inline UIElement in the document.
/// </summary> /// </summary>
public class InlineObjectElement : VisualLineElement public class InlineObjectElement : VisualLineElement
{ {
/// <summary> /// <summary>
/// Gets the inline element that is displayed. /// Gets the inline element that is displayed.
/// </summary> /// </summary>
public IControl Element { get; } public Control Element { get; }
/// <summary> /// <summary>
/// Creates a new InlineObjectElement. /// Creates a new InlineObjectElement.
/// </summary> /// </summary>
/// <param name="documentLength">The length of the element in the document. Must be non-negative.</param> /// <param name="documentLength">The length of the element in the document. Must be non-negative.</param>
/// <param name="element">The element to display.</param> /// <param name="element">The element to display.</param>
public InlineObjectElement(int documentLength, IControl element) public InlineObjectElement(int documentLength, Control element)
: base(1, documentLength) : base(1, documentLength)
{ {
Element = element ?? throw new ArgumentNullException(nameof(element)); Element = element ?? throw new ArgumentNullException(nameof(element));
} }
/// <inheritdoc/> /// <inheritdoc/>
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{ {
if (context == null) if (context == null)
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
return new InlineObjectRun(1, TextRunProperties, Element); return new InlineObjectRun(1, TextRunProperties, Element);
} }
} }
/// <summary> /// <summary>
/// A text run with an embedded UIElement. /// A text run with an embedded UIElement.
/// </summary> /// </summary>
public class InlineObjectRun : DrawableTextRun public class InlineObjectRun : DrawableTextRun
{ {
/// <summary> internal Size DesiredSize;
/// 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");
TextSourceLength = length; /// <summary>
Properties = properties ?? throw new ArgumentNullException(nameof(properties)); /// Creates a new InlineObjectRun instance.
Element = element ?? throw new ArgumentNullException(nameof(element)); /// </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> TextSourceLength = length;
/// Gets the element displayed by the InlineObjectRun. Properties = properties ?? throw new ArgumentNullException(nameof(properties));
/// </summary> Element = element ?? throw new ArgumentNullException(nameof(element));
public IControl Element { get; }
/// <summary> DesiredSize = element.DesiredSize;
/// 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; }
/// <inheritdoc/> /// <summary>
public override int TextSourceLength { get; } /// Gets the element displayed by the InlineObjectRun.
/// </summary>
public Control Element { get; }
/// <inheritdoc/> /// <summary>
public override TextRunProperties Properties { get; } /// 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 override int TextSourceLength { get; }
public Size DesiredSize { get; set; }
public override void Draw(DrawingContext drawingContext, Point origin) public override double Baseline
{ {
//noop 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;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using AvaloniaEdit.Utils;
namespace AvaloniaEdit.Rendering namespace AvaloniaEdit.Rendering
{ {
// This class is public because it can be used as a base class for custom links. // This class is public because it can be used as a base class for custom links.
/// <summary> /// <summary>
/// Detects hyperlinks and makes them clickable. /// Detects hyperlinks and makes them clickable.
/// </summary> /// </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 // 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) // (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/]"); internal readonly static Regex DefaultLinkRegex = new Regex(@"\b(https?://|ftp://|www\.)[\w\d\._/\-~%@()+:?&=#!]*[\w\d/]");
// try to detect email addresses // 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 DefaultMailRegex = new Regex(@"\b[\w\d\.\-]+\@[\w\d\.\-]+\.[a-z]{2,6}\b");
private readonly Regex _linkRegex;
private readonly Regex _linkRegex;
/// <summary> /// <summary>
/// Gets/Sets whether the user needs to press Control to click the link. /// Gets/Sets whether the user needs to press Control to click the link.
/// The default value is true. /// The default value is true.
/// </summary> /// </summary>
public bool RequireControlModifierForClick { get; set; } public bool RequireControlModifierForClick { get; set; }
/// <summary> /// <summary>
/// Creates a new LinkElementGenerator. /// Creates a new LinkElementGenerator.
/// </summary> /// </summary>
@ -55,46 +56,47 @@ namespace AvaloniaEdit.Rendering
_linkRegex = DefaultLinkRegex; _linkRegex = DefaultLinkRegex;
RequireControlModifierForClick = true; RequireControlModifierForClick = true;
} }
/// <summary> /// <summary>
/// Creates a new LinkElementGenerator using the specified regex. /// Creates a new LinkElementGenerator using the specified regex.
/// </summary> /// </summary>
protected LinkElementGenerator(Regex regex) : this() protected LinkElementGenerator(Regex regex) : this()
{ {
_linkRegex = regex ?? throw new ArgumentNullException(nameof(regex)); _linkRegex = regex ?? throw new ArgumentNullException(nameof(regex));
} }
void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options) void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options)
{ {
RequireControlModifierForClick = options.RequireControlModifierForHyperlinkClick; 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 endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset;
var relevantText = CurrentContext.GetText(startOffset, endOffset - startOffset); var relevantText = CurrentContext.GetText(startOffset, endOffset - startOffset);
var m = _linkRegex.Match(relevantText); var m = _linkRegex.Match(relevantText.Text, relevantText.Offset, relevantText.Count);
matchOffset = m.Success ? m.Index + startOffset : -1; matchOffset = m.Success ? m.Index - relevantText.Offset + startOffset : -1;
return m; return m;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override int GetFirstInterestedOffset(int startOffset) public override int GetFirstInterestedOffset(int startOffset)
{ {
GetMatch(startOffset, out var matchOffset); GetMatch(startOffset, out var matchOffset);
return matchOffset; return matchOffset;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override VisualLineElement ConstructElement(int offset) public override VisualLineElement ConstructElement(int offset)
{ {
var m = GetMatch(offset, out var matchOffset); var m = GetMatch(offset, out var matchOffset);
if (m.Success && matchOffset == offset) { if (m.Success && matchOffset == offset) {
return ConstructElementFromMatch(m); return ConstructElementFromMatch(m);
} else {
return null;
} }
return null;
} }
/// <summary> /// <summary>
/// Constructs a VisualLineElement that replaces the matched text. /// Constructs a VisualLineElement that replaces the matched text.
/// The default implementation will create a <see cref="VisualLineLinkText"/> /// The default implementation will create a <see cref="VisualLineLinkText"/>
@ -105,14 +107,14 @@ namespace AvaloniaEdit.Rendering
var uri = GetUriFromMatch(m); var uri = GetUriFromMatch(m);
if (uri == null) if (uri == null)
return null; return null;
var linkText = new VisualLineLinkText(CurrentContext.VisualLine, m.Length) var linkText = new VisualLineLinkText(CurrentContext.VisualLine, m.Length)
{ {
NavigateUri = uri, NavigateUri = uri,
RequireControlModifierForClick = RequireControlModifierForClick RequireControlModifierForClick = RequireControlModifierForClick
}; };
return linkText; return linkText;
} }
/// <summary> /// <summary>
/// Fetches the URI from the regex match. Returns null if the URI format is invalid. /// Fetches the URI from the regex match. Returns null if the URI format is invalid.
/// </summary> /// </summary>
@ -121,15 +123,12 @@ namespace AvaloniaEdit.Rendering
var targetUrl = match.Value; var targetUrl = match.Value;
if (targetUrl.StartsWith("www.", StringComparison.Ordinal)) if (targetUrl.StartsWith("www.", StringComparison.Ordinal))
targetUrl = "http://" + targetUrl; targetUrl = "http://" + targetUrl;
if (Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute)) return Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute) ? new Uri(targetUrl) : null;
return new Uri(targetUrl);
return null;
} }
} }
// This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions. // This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions.
/// <summary> /// <summary>
/// Detects e-mail addresses and makes them clickable. /// Detects e-mail addresses and makes them clickable.
/// </summary> /// </summary>
@ -146,14 +145,11 @@ namespace AvaloniaEdit.Rendering
: base(DefaultMailRegex) : base(DefaultMailRegex)
{ {
} }
protected override Uri GetUriFromMatch(Match match) protected override Uri GetUriFromMatch(Match match)
{ {
var targetUrl = "mailto:" + match.Value; var targetUrl = "mailto:" + match.Value;
if (Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute)) return Uri.IsWellFormedUriString(targetUrl, UriKind.Absolute) ? new Uri(targetUrl) : null;
return new Uri(targetUrl);
return null;
} }
} }
} }

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

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

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

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

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

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

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

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

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

@ -72,19 +72,19 @@ namespace AvaloniaEdit.Rendering
/// <summary> /// <summary>
/// Gets the text run properties. /// 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="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"/>. /// <see cref="VisualLineElement"/>.
/// </summary> /// </summary>
public CustomTextRunProperties TextRunProperties { get; private set; } public VisualLineElementTextRunProperties TextRunProperties { get; private set; }
/// <summary> /// <summary>
/// Gets/sets the brush used for the background of this <see cref="VisualLineElement" />. /// Gets/sets the brush used for the background of this <see cref="VisualLineElement" />.
/// </summary> /// </summary>
public IBrush BackgroundBrush { get; set; } public IBrush BackgroundBrush { get; set; }
internal void SetTextRunProperties(CustomTextRunProperties p) internal void SetTextRunProperties(VisualLineElementTextRunProperties p)
{ {
TextRunProperties = p; TextRunProperties = p;
} }
@ -106,9 +106,9 @@ namespace AvaloniaEdit.Rendering
/// Retrieves the text span immediately before the visual column. /// Retrieves the text span immediately before the visual column.
/// </summary> /// </summary>
/// <remarks>This method is used for word-wrapping in bidirectional text.</remarks> /// <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> /// <summary>
@ -162,13 +162,13 @@ namespace AvaloniaEdit.Rendering
firstPart.DocumentLength = relativeSplitRelativeTextOffset; firstPart.DocumentLength = relativeSplitRelativeTextOffset;
secondPart.DocumentLength = oldDocumentLength - relativeSplitRelativeTextOffset; secondPart.DocumentLength = oldDocumentLength - relativeSplitRelativeTextOffset;
if (firstPart.TextRunProperties == null) if (firstPart.TextRunProperties == null)
firstPart.TextRunProperties = TextRunProperties; firstPart.TextRunProperties = TextRunProperties.Clone();
if (secondPart.TextRunProperties == null) if (secondPart.TextRunProperties == null)
secondPart.TextRunProperties = TextRunProperties; secondPart.TextRunProperties = TextRunProperties.Clone();
firstPart.BackgroundBrush = BackgroundBrush; firstPart.BackgroundBrush = BackgroundBrush;
secondPart.BackgroundBrush = BackgroundBrush; secondPart.BackgroundBrush = BackgroundBrush;
} }
/// <summary> /// <summary>
/// Gets the visual column of a text location inside this element. /// Gets the visual column of a text location inside this element.
/// The text offset is given relative to the visual line start. /// 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.Interactivity;
using Avalonia.Controls; using Avalonia.Controls;
using System.Diagnostics; using System.Diagnostics;
using Avalonia.Media;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
namespace AvaloniaEdit.Rendering namespace AvaloniaEdit.Rendering
@ -66,14 +67,15 @@ namespace AvaloniaEdit.Rendering
RequireControlModifierForClick = true; RequireControlModifierForClick = true;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{ {
TextRunProperties.SetForegroundBrush(context.TextView.LinkTextForegroundBrush); this.TextRunProperties.SetForegroundBrush(context.TextView.LinkTextForegroundBrush);
TextRunProperties.SetBackgroundBrush(context.TextView.LinkTextBackgroundBrush); this.TextRunProperties.SetBackgroundBrush(context.TextView.LinkTextBackgroundBrush);
if (context.TextView.LinkTextUnderline)
return base.CreateTextRun(startVisualColumn, context); this.TextRunProperties.SetTextDecorations(TextDecorations.Underline);
} return base.CreateTextRun(startVisualColumn, context);
}
/// <summary> /// <summary>
/// Gets whether the link is currently clickable. /// Gets whether the link is currently clickable.

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

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

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

@ -21,7 +21,9 @@ using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Xml; using System.Xml;
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Media;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace AvaloniaEdit.Utils namespace AvaloniaEdit.Utils
@ -88,6 +90,19 @@ namespace AvaloniaEdit.Utils
return Math.Max(Math.Min(value, maximum), minimum); return Math.Max(Math.Min(value, maximum), minimum);
} }
#endregion #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 #region AddRange / Sequence
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> elements) 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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Globalization;
using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.TextFormatting; 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 namespace AvaloniaEdit.Utils
{ {
/// <summary> /// <summary>
/// Creates TextFormatter instances that with the correct TextFormattingMode, if running on .NET 4.0. /// Creates TextFormatter instances that with the correct TextFormattingMode, if running on .NET 4.0.
/// </summary> /// </summary>
public static class TextFormatterFactory 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> /// <summary>
/// Creates formatted text. /// Creates formatted text.
/// </summary> /// </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="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> /// <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> /// <returns>A FormattedText object using the specified settings.</returns>
public static TextLine FormatLine(ReadOnlySlice<char> text, Typeface typeface, double emSize, IBrush foreground) public static FormattedText CreateFormattedText(Control element, string 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
{ {
private readonly ReadOnlySlice<char> _text; if (element == null)
private readonly TextRunProperties _defaultProperties; throw new ArgumentNullException(nameof(element));
if (text == null)
public SimpleTextSource(ReadOnlySlice<char> text, TextRunProperties defaultProperties) throw new ArgumentNullException(nameof(text));
{ if (typeface == default)
_text = text; typeface = element.CreateTypeface();
_defaultProperties = defaultProperties; if (emSize == null)
} emSize = TextBlock.GetFontSize(element);
if (foreground == null)
foreground = TextBlock.GetForeground(element);
public TextRun GetTextRun(int textSourceIndex) return new FormattedText(
{ text,
if (textSourceIndex < _text.Length) CultureInfo.CurrentCulture,
{ FlowDirection.LeftToRight,
return new TextCharacters(_text, textSourceIndex, _text.Length - textSourceIndex, _defaultProperties); typeface,
} emSize.Value,
foreground);
if (textSourceIndex > _text.Length)
{
return null;
}
return new TextEndOfParagraph(1);
}
} }
} }
} }

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

@ -1,17 +1,208 @@
using Avalonia; using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace AvaloniaEdit.Utils; namespace AvaloniaEdit.Utils;
#nullable enable
public static class TextLineExtensions 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; }
} }