Redo Avalonia port of AvalonEdit.Rendering
This commit is contained in:
Родитель
61526254a0
Коммит
19dc0f3bcb
|
@ -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(text.Width, 0);
|
||||||
|
}
|
||||||
|
|
||||||
return new Size(textLine.WidthIncludingTrailingWhitespace, textLine.Height);
|
public override void Render(DrawingContext drawingContext)
|
||||||
}
|
{
|
||||||
|
var textView = TextView;
|
||||||
|
var renderSize = Bounds.Size;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
if (textView is {VisualLinesValid: true}) {
|
||||||
public override void Render(DrawingContext drawingContext)
|
var foreground = GetValue(TextBlock.ForegroundProperty);
|
||||||
{
|
foreach (var line in textView.VisualLines) {
|
||||||
var textView = TextView;
|
var lineNumber = line.FirstDocumentLine.LineNumber;
|
||||||
var renderSize = Bounds.Size;
|
var text = TextFormatterFactory.CreateFormattedText(
|
||||||
|
this,
|
||||||
if (textView != null && textView.VisualLinesValid)
|
lineNumber.ToString(CultureInfo.CurrentCulture),
|
||||||
{
|
Typeface, EmSize, foreground
|
||||||
var foreground = GetValue(TemplatedControl.ForegroundProperty);
|
);
|
||||||
|
var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop);
|
||||||
foreach (var line in textView.VisualLines)
|
drawingContext.DrawText(text, new Point(renderSize.Width - text.Width, y - textView.VerticalOffset));
|
||||||
{
|
}
|
||||||
var lineNumber = line.FirstDocumentLine.LineNumber;
|
}
|
||||||
var text = lineNumber.ToString(CultureInfo.CurrentCulture);
|
}
|
||||||
var textLine = TextFormatterFactory.FormatLine(text.AsMemory(),
|
|
||||||
Typeface,
|
|
||||||
EmSize,
|
|
||||||
foreground
|
|
||||||
);
|
|
||||||
|
|
||||||
var y = line.TextLines.Count > 0
|
|
||||||
? line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.TextTop)
|
|
||||||
: line.VisualTop;
|
|
||||||
|
|
||||||
textLine.Draw(drawingContext,
|
|
||||||
new Point(renderSize.Width - textLine.WidthIncludingTrailingWhitespace,
|
|
||||||
y - textView.VerticalOffset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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();
|
/// <summary>
|
||||||
var scrollOffset = textView.ScrollOffset;
|
/// Calculates the rectangles for the visual column segment.
|
||||||
|
/// This returns one rectangle for each line inside the segment.
|
||||||
|
/// </summary>
|
||||||
|
public static IEnumerable<Rect> GetRectsFromVisualSegment(TextView textView, VisualLine line, int startVc, int endVc)
|
||||||
|
{
|
||||||
|
if (textView == null)
|
||||||
|
throw new ArgumentNullException("textView");
|
||||||
|
if (line == null)
|
||||||
|
throw new ArgumentNullException("line");
|
||||||
|
return ProcessTextLines(textView, line, startVc, endVc);
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < visualLine.TextLines.Count; i++)
|
private static IEnumerable<Rect> ProcessTextLines(TextView textView, VisualLine visualLine, int segmentStartVc, int segmentEndVc)
|
||||||
{
|
{
|
||||||
var line = visualLine.TextLines[i];
|
TextLine lastTextLine = visualLine.TextLines.Last();
|
||||||
var y = visualLine.GetTextLineVisualYPosition(line, VisualYPosition.LineTop);
|
Vector scrollOffset = textView.ScrollOffset;
|
||||||
var visualStartCol = visualLine.GetTextLineVisualStartColumn(line);
|
|
||||||
var visualEndCol = visualStartCol + line.TextRange.Length;
|
|
||||||
if (line == lastTextLine)
|
|
||||||
visualEndCol -= 1; // 1 position for the TextEndOfParagraph
|
|
||||||
// TODO: ?
|
|
||||||
//else
|
|
||||||
// visualEndCol -= line.TrailingWhitespaceLength;
|
|
||||||
|
|
||||||
if (segmentEndVc < visualStartCol)
|
for (int i = 0; i < visualLine.TextLines.Count; i++) {
|
||||||
break;
|
TextLine line = visualLine.TextLines[i];
|
||||||
if (lastTextLine != line && segmentStartVc > visualEndCol)
|
double y = visualLine.GetTextLineVisualYPosition(line, VisualYPosition.LineTop);
|
||||||
continue;
|
int visualStartCol = visualLine.GetTextLineVisualStartColumn(line);
|
||||||
var segmentStartVcInLine = Math.Max(segmentStartVc, visualStartCol);
|
int visualEndCol = visualStartCol + line.TextRange.Length;
|
||||||
var segmentEndVcInLine = Math.Min(segmentEndVc, visualEndCol);
|
if (line == lastTextLine)
|
||||||
y -= scrollOffset.Y;
|
visualEndCol -= 1; // 1 position for the TextEndOfParagraph
|
||||||
var lastRect = Rect.Empty;
|
else
|
||||||
if (segmentStartVcInLine == segmentEndVcInLine)
|
visualEndCol -= line.TrailingWhitespaceLength;
|
||||||
{
|
|
||||||
// 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);
|
if (segmentEndVc < visualStartCol)
|
||||||
//}
|
break;
|
||||||
var extendSelection = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height);
|
if (lastTextLine != line && segmentStartVc > visualEndCol)
|
||||||
if (!lastRect.IsEmpty)
|
continue;
|
||||||
{
|
int segmentStartVcInLine = Math.Max(segmentStartVc, visualStartCol);
|
||||||
if (extendSelection.Intersects(lastRect))
|
int segmentEndVcInLine = Math.Min(segmentEndVc, visualEndCol);
|
||||||
{
|
y -= scrollOffset.Y;
|
||||||
lastRect.Union(extendSelection);
|
Rect lastRect = Rect.Empty;
|
||||||
yield return lastRect;
|
if (segmentStartVcInLine == segmentEndVcInLine) {
|
||||||
}
|
// GetTextBounds crashes for length=0, so we'll handle this case with GetDistanceFromCharacterHit
|
||||||
else
|
// We need to return a rectangle to ensure empty lines are still visible
|
||||||
{
|
double pos = visualLine.GetTextLineVisualXPosition(line, segmentStartVcInLine);
|
||||||
// If the end of the line is in an RTL segment, keep lastRect and extendSelection separate.
|
pos -= scrollOffset.X;
|
||||||
yield return lastRect;
|
// The following special cases are necessary to get rid of empty rectangles at the end of a TextLine if "Show Spaces" is active.
|
||||||
yield return extendSelection;
|
// 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)
|
||||||
else
|
continue;
|
||||||
yield return extendSelection;
|
if (segmentStartVcInLine == visualStartCol && i > 0 && segmentStartVc < segmentStartVcInLine && visualLine.TextLines[i - 1].TrailingWhitespaceLength == 0)
|
||||||
}
|
continue;
|
||||||
else
|
lastRect = new Rect(pos, y, textView.EmptyLineSelectionWidth, line.Height);
|
||||||
yield return lastRect;
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly PathFigures _figures = new PathFigures();
|
private readonly PathFigures _figures = new PathFigures();
|
||||||
private PathFigure _figure;
|
private PathFigure _figure;
|
||||||
private int _insertionIndex;
|
private int _insertionIndex;
|
||||||
private double _lastTop, _lastBottom;
|
private double _lastTop, _lastBottom;
|
||||||
private double _lastLeft, _lastRight;
|
private double _lastLeft, _lastRight;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a rectangle to the geometry.
|
/// Adds a rectangle to the geometry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This overload assumes that the coordinates are aligned properly
|
/// This overload assumes that the coordinates are aligned properly
|
||||||
/// (see <see cref="AlignToWholePixels"/>).
|
/// (see <see cref="AlignToWholePixels"/>).
|
||||||
/// Use the <see cref="AddRectangle(TextView,Rect)"/>-overload instead if the coordinates are not yet aligned.
|
/// Use the <see cref="AddRectangle(TextView,Rect)"/>-overload instead if the coordinates are not yet aligned.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void AddRectangle(double left, double top, double right, double bottom)
|
public void AddRectangle(double left, double top, double right, double bottom)
|
||||||
{
|
{
|
||||||
if (!top.IsClose(_lastBottom))
|
if (!top.IsClose(_lastBottom)) {
|
||||||
{
|
CloseFigure();
|
||||||
CloseFigure();
|
}
|
||||||
}
|
if (_figure == null) {
|
||||||
if (_figure == null)
|
_figure = new PathFigure();
|
||||||
{
|
_figure.StartPoint = new Point(left, top + _cornerRadius);
|
||||||
_figure = new PathFigure { StartPoint = new Point(left, top + CornerRadius) };
|
if (Math.Abs(left - right) > _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(left + CornerRadius, top, SweepDirection.Clockwise));
|
_figure.Segments.Add(MakeArc(right, top + _cornerRadius, 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(MakeLineSegment(right, bottom - CornerRadius));
|
//figure.Segments.Add(MakeArc(left, bottom - cornerRadius, SweepDirection.Clockwise));
|
||||||
_insertionIndex = _figure.Segments.Count;
|
} else {
|
||||||
//figure.Segments.Add(MakeArc(left, bottom - cornerRadius, SweepDirection.Clockwise));
|
if (!_lastRight.IsClose(right)) {
|
||||||
}
|
double cr = right < _lastRight ? -_cornerRadius : _cornerRadius;
|
||||||
else
|
SweepDirection dir1 = right < _lastRight ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
|
||||||
{
|
SweepDirection dir2 = right < _lastRight ? SweepDirection.CounterClockwise : SweepDirection.Clockwise;
|
||||||
if (!_lastRight.IsClose(right))
|
_figure.Segments.Insert(_insertionIndex++, MakeArc(_lastRight + cr, _lastBottom, dir1));
|
||||||
{
|
_figure.Segments.Insert(_insertionIndex++, MakeLineSegment(right - cr, top));
|
||||||
var cr = right < _lastRight ? -CornerRadius : CornerRadius;
|
_figure.Segments.Insert(_insertionIndex++, MakeArc(right, top + _cornerRadius, dir2));
|
||||||
var dir1 = right < _lastRight ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
|
}
|
||||||
var dir2 = right < _lastRight ? SweepDirection.CounterClockwise : SweepDirection.Clockwise;
|
_figure.Segments.Insert(_insertionIndex++, MakeLineSegment(right, bottom - _cornerRadius));
|
||||||
_figure.Segments.Insert(_insertionIndex++, MakeArc(_lastRight + cr, _lastBottom, dir1));
|
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft, _lastTop + _cornerRadius));
|
||||||
_figure.Segments.Insert(_insertionIndex++, MakeLineSegment(right - cr, top));
|
if (!_lastLeft.IsClose(left)) {
|
||||||
_figure.Segments.Insert(_insertionIndex++, MakeArc(right, top + CornerRadius, dir2));
|
double cr = left < _lastLeft ? _cornerRadius : -_cornerRadius;
|
||||||
}
|
SweepDirection dir1 = left < _lastLeft ? SweepDirection.CounterClockwise : SweepDirection.Clockwise;
|
||||||
_figure.Segments.Insert(_insertionIndex++, MakeLineSegment(right, bottom - CornerRadius));
|
SweepDirection dir2 = left < _lastLeft ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
|
||||||
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft, _lastTop + CornerRadius));
|
_figure.Segments.Insert(_insertionIndex, MakeArc(_lastLeft, _lastBottom - _cornerRadius, dir1));
|
||||||
if (!_lastLeft.IsClose(left))
|
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft - cr, _lastBottom));
|
||||||
{
|
_figure.Segments.Insert(_insertionIndex, MakeArc(left + cr, _lastBottom, dir2));
|
||||||
var cr = left < _lastLeft ? CornerRadius : -CornerRadius;
|
}
|
||||||
var dir1 = left < _lastLeft ? SweepDirection.CounterClockwise : SweepDirection.Clockwise;
|
}
|
||||||
var dir2 = left < _lastLeft ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
|
this._lastTop = top;
|
||||||
_figure.Segments.Insert(_insertionIndex, MakeArc(_lastLeft, _lastBottom - CornerRadius, dir1));
|
this._lastBottom = bottom;
|
||||||
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft - cr, _lastBottom));
|
this._lastLeft = left;
|
||||||
_figure.Segments.Insert(_insertionIndex, MakeArc(left + cr, _lastBottom, dir2));
|
this._lastRight = right;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_lastTop = top;
|
|
||||||
_lastBottom = bottom;
|
|
||||||
_lastLeft = left;
|
|
||||||
_lastRight = right;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArcSegment MakeArc(double x, double y, SweepDirection dir)
|
private ArcSegment MakeArc(double x, double y, SweepDirection dir)
|
||||||
{
|
{
|
||||||
var arc = new ArcSegment
|
var arc = new ArcSegment
|
||||||
{
|
{
|
||||||
Point = new Point(x, y),
|
Point = new Point(x, y),
|
||||||
Size = new Size(CornerRadius, CornerRadius),
|
Size = new Size(CornerRadius, CornerRadius),
|
||||||
SweepDirection = dir
|
SweepDirection = dir
|
||||||
};
|
};
|
||||||
return arc;
|
return arc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LineSegment MakeLineSegment(double x, double y)
|
private static LineSegment MakeLineSegment(double x, double y)
|
||||||
{
|
{
|
||||||
return new LineSegment { Point = new Point(x, y) };
|
return new LineSegment { Point = new Point(x, y) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Closes the current figure.
|
/// Closes the current figure.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CloseFigure()
|
public void CloseFigure()
|
||||||
{
|
{
|
||||||
if (_figure != null)
|
if (_figure != null) {
|
||||||
{
|
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft, _lastTop + _cornerRadius));
|
||||||
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft, _lastTop + CornerRadius));
|
if (Math.Abs(_lastLeft - _lastRight) > _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(_lastLeft, _lastBottom - CornerRadius, SweepDirection.Clockwise));
|
_figure.Segments.Insert(_insertionIndex, MakeArc(_lastRight - _cornerRadius, _lastBottom, SweepDirection.Clockwise));
|
||||||
_figure.Segments.Insert(_insertionIndex, MakeLineSegment(_lastLeft + CornerRadius, _lastBottom));
|
}
|
||||||
_figure.Segments.Insert(_insertionIndex, MakeArc(_lastRight - CornerRadius, _lastBottom, SweepDirection.Clockwise));
|
|
||||||
}
|
|
||||||
|
|
||||||
_figure.IsClosed = true;
|
_figure.IsClosed = true;
|
||||||
_figures.Add(_figure);
|
_figures.Add(_figure);
|
||||||
_figure = null;
|
_figure = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the geometry.
|
/// Creates the geometry.
|
||||||
/// Returns null when the geometry is empty!
|
/// Returns null when the geometry is empty!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Geometry CreateGeometry()
|
public Geometry CreateGeometry()
|
||||||
{
|
{
|
||||||
CloseFigure();
|
CloseFigure();
|
||||||
return _figures.Count != 0 ? new PathGeometry { Figures = _figures } : null;
|
return _figures.Count != 0 ? new PathGeometry { Figures = _figures } : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,54 +27,46 @@ 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.
|
||||||
|
@ -83,16 +75,18 @@ 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;
|
Start = null;
|
||||||
_end = null;
|
End = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -101,8 +95,8 @@ namespace AvaloniaEdit.Rendering
|
||||||
[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;
|
||||||
|
@ -59,12 +59,12 @@ namespace AvaloniaEdit.Rendering
|
||||||
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;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
if (formattedText != null) {
|
||||||
public override int TextSourceLength => Element.VisualLength;
|
return new Size(formattedText.WidthIncludingTrailingWhitespace, formattedText.Height);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
var text = Element.TextLine;
|
||||||
public override TextRunProperties Properties { get; }
|
return new Size( text.WidthIncludingTrailingWhitespace, text.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override Size Size { get; }
|
/// <inheritdoc/>
|
||||||
|
public override void Draw(DrawingContext drawingContext, Point origin)
|
||||||
public override double Baseline =>
|
{
|
||||||
Element.FormattedText?.Baseline ?? Element.TextLine.Baseline;
|
if (Element.FormattedText != null) {
|
||||||
|
//var y = origin.Y - Element.FormattedText.Baseline;
|
||||||
private Size GetSize()
|
drawingContext.DrawText(Element.FormattedText, origin);
|
||||||
{
|
} else {
|
||||||
var formattedText = Element.FormattedText;
|
//var y = origin.Y - Element.TextLine.Baseline;
|
||||||
|
Element.TextLine.Draw(drawingContext, origin);
|
||||||
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,15 +26,13 @@ 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()
|
||||||
{
|
{
|
||||||
|
@ -42,10 +40,10 @@ namespace AvaloniaEdit.Rendering
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -73,15 +71,16 @@ 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,9 +113,13 @@ namespace AvaloniaEdit.Rendering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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)
|
||||||
{
|
{
|
||||||
if (CollapsedSections == null) {
|
if (CollapsedSections == null) {
|
||||||
CollapsedSections = new List<CollapsedLineSection>();
|
CollapsedSections = new List<CollapsedLineSection>();
|
||||||
|
@ -141,7 +144,7 @@ namespace AvaloniaEdit.Rendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return "[HeightTreeNode "
|
return "[HeightTreeNode "
|
||||||
|
@ -158,9 +161,9 @@ namespace AvaloniaEdit.Rendering
|
||||||
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;
|
||||||
|
@ -49,7 +47,7 @@ namespace AvaloniaEdit.Rendering
|
||||||
/// <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.
|
||||||
|
@ -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,6 +18,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using AvaloniaEdit.Utils;
|
||||||
|
|
||||||
namespace AvaloniaEdit.Rendering
|
namespace AvaloniaEdit.Rendering
|
||||||
{
|
{
|
||||||
|
@ -34,12 +35,12 @@ 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.
|
||||||
|
@ -61,7 +62,7 @@ namespace AvaloniaEdit.Rendering
|
||||||
/// </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)
|
||||||
|
@ -69,12 +70,12 @@ namespace AvaloniaEdit.Rendering
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,8 +92,9 @@ namespace AvaloniaEdit.Rendering
|
||||||
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>
|
||||||
|
@ -105,12 +107,12 @@ 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>
|
||||||
|
@ -121,10 +123,7 @@ 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,11 +148,8 @@ namespace AvaloniaEdit.Rendering
|
||||||
|
|
||||||
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 {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p.SetForegroundBrush(Brushes.White);
|
private sealed class SpaceTextElement : FormattedTextElement
|
||||||
|
{
|
||||||
|
public SpaceTextElement(TextLine textLine) : base(textLine, 1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
var textFormatter = TextFormatter.Current;
|
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
|
||||||
var text = FormattedTextElement.PrepareText(textFormatter,
|
{
|
||||||
TextUtilities.GetControlCharacterName(c), p);
|
if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
|
||||||
return new SpecialCharacterBoxElement(text);
|
return base.GetNextCaretPosition(visualColumn, direction, mode);
|
||||||
}
|
else
|
||||||
return null;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class SpaceTextElement : FormattedTextElement
|
public override bool IsWhitespace(int visualColumn)
|
||||||
{
|
{
|
||||||
public SpaceTextElement(TextLine textLine) : base(textLine, 1)
|
return true;
|
||||||
{
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
|
private sealed class TabTextElement : VisualLineElement
|
||||||
{
|
{
|
||||||
if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
|
internal readonly TextLine Text;
|
||||||
return base.GetNextCaretPosition(visualColumn, direction, mode);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsWhitespace(int visualColumn)
|
public TabTextElement(TextLine text) : base(2, 1)
|
||||||
{
|
{
|
||||||
return true;
|
Text = text;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class TabTextElement : VisualLineElement
|
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
|
||||||
{
|
{
|
||||||
internal readonly TextLine 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 TabTextElement(TextLine text) : base(2, 1)
|
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
|
||||||
{
|
{
|
||||||
Text = text;
|
if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
|
||||||
}
|
return base.GetNextCaretPosition(visualColumn, direction, mode);
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
|
public override bool IsWhitespace(int visualColumn)
|
||||||
{
|
{
|
||||||
// the TabTextElement consists of two TextRuns:
|
return true;
|
||||||
// first a TabGlyphRun, then TextCharacters '\t' to let the fx handle the tab indentation
|
}
|
||||||
if (startVisualColumn == VisualColumn)
|
}
|
||||||
return new TabGlyphRun(this, TextRunProperties);
|
|
||||||
if (startVisualColumn == VisualColumn + 1)
|
|
||||||
return new TextCharacters("\t".AsMemory(), TextRunProperties);
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(startVisualColumn));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
|
private sealed class TabGlyphRun : DrawableTextRun
|
||||||
{
|
{
|
||||||
if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
|
private readonly TabTextElement _element;
|
||||||
return base.GetNextCaretPosition(visualColumn, direction, mode);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsWhitespace(int visualColumn)
|
public TabGlyphRun(TabTextElement element, TextRunProperties properties)
|
||||||
{
|
{
|
||||||
return true;
|
if (properties == null)
|
||||||
}
|
throw new ArgumentNullException(nameof(properties));
|
||||||
}
|
Properties = properties;
|
||||||
|
_element = element;
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class TabGlyphRun : DrawableTextRun
|
public override TextRunProperties Properties { get; }
|
||||||
{
|
|
||||||
private readonly TabTextElement _element;
|
|
||||||
|
|
||||||
public TabGlyphRun(TabTextElement element, TextRunProperties properties)
|
public override double Baseline => _element.Text.Baseline;
|
||||||
{
|
|
||||||
Properties = properties ?? throw new ArgumentNullException(nameof(properties));
|
|
||||||
_element = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int TextSourceLength => 1;
|
public override Size Size
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var width = Math.Min(0, _element.Text.WidthIncludingTrailingWhitespace - 1);
|
||||||
|
|
||||||
public override TextRunProperties Properties { get; }
|
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 VisualLineTextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties)
|
||||||
}
|
{
|
||||||
|
return new VisualLineTextParagraphProperties {
|
||||||
|
defaultTextRunProperties = defaultTextRunProperties,
|
||||||
|
textWrapping = _canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap,
|
||||||
|
tabSize = Options.IndentationSize * WideSpaceWidth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private GenericTextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties)
|
private VisualLine BuildVisualLine(DocumentLine documentLine,
|
||||||
{
|
TextRunProperties globalTextRunProperties,
|
||||||
return new GenericTextParagraphProperties
|
VisualLineTextParagraphProperties paragraphProperties,
|
||||||
(
|
VisualLineElementGenerator[] elementGeneratorsArray,
|
||||||
FlowDirection.LeftToRight,
|
IVisualLineTransformer[] lineTransformersArray,
|
||||||
TextAlignment.Left,
|
Size availableSize)
|
||||||
true,
|
{
|
||||||
false,
|
if (_heightTree.GetIsCollapsed(documentLine.LineNumber))
|
||||||
defaultTextRunProperties,
|
throw new InvalidOperationException("Trying to build visual line from collapsed line");
|
||||||
_canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap,
|
|
||||||
0,
|
|
||||||
0/*,
|
|
||||||
DefaultIncrementalTab = Options.IndentationSize * WideSpaceWidth*/
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private VisualLine BuildVisualLine(DocumentLine documentLine,
|
//Debug.WriteLine("Building line " + documentLine.LineNumber);
|
||||||
CustomTextRunProperties globalTextRunProperties,
|
|
||||||
TextParagraphProperties paragraphProperties,
|
|
||||||
VisualLineElementGenerator[] elementGeneratorsArray,
|
|
||||||
IVisualLineTransformer[] lineTransformersArray,
|
|
||||||
Size availableSize)
|
|
||||||
{
|
|
||||||
if (_heightTree.GetIsCollapsed(documentLine.LineNumber))
|
|
||||||
throw new InvalidOperationException("Trying to build visual line from collapsed line");
|
|
||||||
|
|
||||||
var visualLine = new VisualLine(this, documentLine);
|
VisualLine visualLine = new VisualLine(this, documentLine);
|
||||||
|
VisualLineTextSource textSource = new VisualLineTextSource(visualLine) {
|
||||||
|
Document = _document,
|
||||||
|
GlobalTextRunProperties = globalTextRunProperties,
|
||||||
|
TextView = this
|
||||||
|
};
|
||||||
|
|
||||||
var textSource = new VisualLineTextSource(visualLine)
|
visualLine.ConstructVisualElements(textSource, elementGeneratorsArray);
|
||||||
{
|
|
||||||
Document = _document,
|
|
||||||
GlobalTextRunProperties = globalTextRunProperties,
|
|
||||||
TextView = this
|
|
||||||
};
|
|
||||||
|
|
||||||
visualLine.ConstructVisualElements(textSource, elementGeneratorsArray);
|
if (visualLine.FirstDocumentLine != visualLine.LastDocumentLine) {
|
||||||
|
// Check whether the lines are collapsed correctly:
|
||||||
if (visualLine.FirstDocumentLine != visualLine.LastDocumentLine)
|
double firstLinePos = _heightTree.GetVisualPosition(visualLine.FirstDocumentLine.NextLine);
|
||||||
{
|
double lastLinePos = _heightTree.GetVisualPosition(visualLine.LastDocumentLine.NextLine ?? visualLine.LastDocumentLine);
|
||||||
// Check whether the lines are collapsed correctly:
|
if (!firstLinePos.IsClose(lastLinePos)) {
|
||||||
var firstLinePos = _heightTree.GetVisualPosition(visualLine.FirstDocumentLine.NextLine);
|
for (int i = visualLine.FirstDocumentLine.LineNumber + 1; i <= visualLine.LastDocumentLine.LineNumber; i++) {
|
||||||
var lastLinePos = _heightTree.GetVisualPosition(visualLine.LastDocumentLine.NextLine ?? visualLine.LastDocumentLine);
|
if (!_heightTree.GetIsCollapsed(i))
|
||||||
if (!firstLinePos.IsClose(lastLinePos))
|
throw new InvalidOperationException("Line " + i + " was skipped by a VisualLineElementGenerator, but it is not collapsed.");
|
||||||
{
|
}
|
||||||
for (var i = visualLine.FirstDocumentLine.LineNumber + 1; i <= visualLine.LastDocumentLine.LineNumber; i++)
|
throw new InvalidOperationException("All lines collapsed but visual pos different - height tree inconsistency?");
|
||||||
{
|
}
|
||||||
if (!_heightTree.GetIsCollapsed(i))
|
}
|
||||||
throw new InvalidOperationException("Line " + i + " was skipped by a VisualLineElementGenerator, but it is not collapsed.");
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException("All lines collapsed but visual pos different - height tree inconsistency?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
visualLine.RunTransformers(textSource, lineTransformersArray);
|
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,9 +162,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -62,11 +63,8 @@ namespace AvaloniaEdit.Rendering
|
||||||
|
|
||||||
var relativeOffset = startVisualColumn - VisualColumn;
|
var relativeOffset = startVisualColumn - VisualColumn;
|
||||||
|
|
||||||
var offset = context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset + 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);
|
||||||
var text = context.GetText(offset, DocumentLength - relativeOffset);
|
|
||||||
|
|
||||||
return new TextCharacters(new ReadOnlySlice<char>(text.AsMemory(), offset, text.Length), TextRunProperties);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -77,16 +75,14 @@ namespace AvaloniaEdit.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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;
|
int relativeOffset = visualColumnLimit - VisualColumn;
|
||||||
|
StringSegment text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset, relativeOffset);
|
||||||
var text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset, relativeOffset);
|
return new ReadOnlySlice<char>(text.Text.AsMemory(), text.Offset, text.Count);
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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);
|
||||||
|
if (run == null)
|
||||||
|
throw new ArgumentNullException(element.GetType().Name + ".CreateTextRun");
|
||||||
|
if (run.TextSourceLength == 0)
|
||||||
|
throw new ArgumentException("The returned TextRun must not have length 0.", element.GetType().Name + ".Length");
|
||||||
|
if (relativeOffset + run.TextSourceLength > element.VisualLength)
|
||||||
|
throw new ArgumentException("The returned TextRun is too long.", element.GetType().Name + ".CreateTextRun");
|
||||||
|
if (run is InlineObjectRun inlineRun) {
|
||||||
|
inlineRun.VisualLine = VisualLine;
|
||||||
|
VisualLine.HasInlineObjects = true;
|
||||||
|
TextView.AddInlineObject(inlineRun);
|
||||||
|
}
|
||||||
|
return run;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (TextView.Options.ShowEndOfLine && textSourceCharacterIndex == VisualLine.VisualLength) {
|
||||||
|
return CreateTextRunForNewLine();
|
||||||
|
}
|
||||||
|
return new TextEndOfParagraph(1);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Debug.WriteLine(ex.ToString());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
private TextRun CreateTextRunForNewLine()
|
||||||
{
|
{
|
||||||
foreach (var element in VisualLine.Elements)
|
string newlineText = "";
|
||||||
{
|
DocumentLine lastDocumentLine = VisualLine.LastDocumentLine;
|
||||||
if (characterIndex >= element.VisualColumn
|
if (lastDocumentLine.DelimiterLength == 2) {
|
||||||
&& characterIndex < element.VisualColumn + element.VisualLength)
|
newlineText = "¶";
|
||||||
{
|
} else if (lastDocumentLine.DelimiterLength == 1) {
|
||||||
var relativeOffset = characterIndex - element.VisualColumn;
|
char newlineChar = Document.GetCharAt(lastDocumentLine.Offset + lastDocumentLine.Length);
|
||||||
var run = element.CreateTextRun(characterIndex, this);
|
if (newlineChar == '\r')
|
||||||
if (run == null)
|
newlineText = "\\r";
|
||||||
throw new ArgumentNullException(element.GetType().Name + ".CreateTextRun");
|
else if (newlineChar == '\n')
|
||||||
if (run.TextSourceLength == 0)
|
newlineText = "\\n";
|
||||||
throw new ArgumentException("The returned TextRun must not have length 0.", element.GetType().Name + ".Length");
|
else
|
||||||
if (relativeOffset + run.TextSourceLength > element.VisualLength)
|
newlineText = "?";
|
||||||
throw new ArgumentException("The returned TextRun is too long.", element.GetType().Name + ".CreateTextRun");
|
}
|
||||||
if (run is InlineObjectRun inlineRun)
|
return new FormattedTextRun(new FormattedTextElement(TextView.CachedElements.GetTextForNonPrintableCharacter(newlineText, this), 0), GlobalTextRunProperties);
|
||||||
{
|
}
|
||||||
inlineRun.VisualLine = VisualLine;
|
|
||||||
VisualLine.HasInlineObjects = true;
|
|
||||||
TextView.AddInlineObject(inlineRun);
|
|
||||||
}
|
|
||||||
return run;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TextView.Options.ShowEndOfLine && characterIndex == VisualLine.VisualLength)
|
public ReadOnlySlice<char> GetPrecedingText(int textSourceCharacterIndexLimit)
|
||||||
{
|
{
|
||||||
return CreateTextRunForNewLine();
|
try {
|
||||||
}
|
foreach (VisualLineElement element in VisualLine.Elements) {
|
||||||
|
if (textSourceCharacterIndexLimit > element.VisualColumn
|
||||||
|
&& textSourceCharacterIndexLimit <= element.VisualColumn + element.VisualLength) {
|
||||||
|
var span = element.GetPrecedingText(textSourceCharacterIndexLimit, this);
|
||||||
|
if (span.IsEmpty)
|
||||||
|
break;
|
||||||
|
int relativeOffset = textSourceCharacterIndexLimit - element.VisualColumn;
|
||||||
|
if (span.Length > relativeOffset)
|
||||||
|
throw new ArgumentException("The returned TextSpan is too long.", element.GetType().Name + ".GetPrecedingText");
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new TextEndOfLine(2);
|
return ReadOnlySlice<char>.Empty;
|
||||||
}
|
} catch (Exception ex) {
|
||||||
catch (Exception ex)
|
Debug.WriteLine(ex.ToString());
|
||||||
{
|
throw;
|
||||||
Debug.WriteLine(ex.ToString());
|
}
|
||||||
throw;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TextRun CreateTextRunForNewLine()
|
private string _cachedString;
|
||||||
{
|
private int _cachedStringOffset;
|
||||||
var newlineText = "";
|
|
||||||
var lastDocumentLine = VisualLine.LastDocumentLine;
|
|
||||||
if (lastDocumentLine.DelimiterLength == 2)
|
|
||||||
{
|
|
||||||
newlineText = "¶";
|
|
||||||
}
|
|
||||||
else if (lastDocumentLine.DelimiterLength == 1)
|
|
||||||
{
|
|
||||||
var newlineChar = Document.GetCharAt(lastDocumentLine.Offset + lastDocumentLine.Length);
|
|
||||||
switch (newlineChar)
|
|
||||||
{
|
|
||||||
case '\r':
|
|
||||||
newlineText = "\\r";
|
|
||||||
break;
|
|
||||||
case '\n':
|
|
||||||
newlineText = "\\n";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
newlineText = "?";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new FormattedTextRun(new FormattedTextElement(TextView.CachedElements.GetTextForNonPrintableCharacter(newlineText, this), 0), GlobalTextRunProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _cachedString;
|
public StringSegment GetText(int offset, int length)
|
||||||
private int _cachedStringOffset;
|
{
|
||||||
|
if (_cachedString != null) {
|
||||||
public string GetText(int offset, int length)
|
if (offset >= _cachedStringOffset && offset + length <= _cachedStringOffset + _cachedString.Length) {
|
||||||
{
|
return new StringSegment(_cachedString, offset - _cachedStringOffset, length);
|
||||||
if (_cachedString != null)
|
}
|
||||||
{
|
}
|
||||||
if (offset >= _cachedStringOffset && offset + length <= _cachedStringOffset + _cachedString.Length)
|
_cachedStringOffset = offset;
|
||||||
{
|
return new StringSegment(_cachedString = Document.GetText(offset, length));
|
||||||
return _cachedString.Substring(offset - _cachedStringOffset, 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
|
||||||
|
@ -89,6 +91,19 @@ namespace AvaloniaEdit.Utils
|
||||||
}
|
}
|
||||||
#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)
|
||||||
|
throw new ArgumentNullException(nameof(text));
|
||||||
|
if (typeface == default)
|
||||||
|
typeface = element.CreateTypeface();
|
||||||
|
if (emSize == null)
|
||||||
|
emSize = TextBlock.GetFontSize(element);
|
||||||
|
if (foreground == null)
|
||||||
|
foreground = TextBlock.GetForeground(element);
|
||||||
|
|
||||||
public SimpleTextSource(ReadOnlySlice<char> text, TextRunProperties defaultProperties)
|
return new FormattedText(
|
||||||
{
|
text,
|
||||||
_text = text;
|
CultureInfo.CurrentCulture,
|
||||||
_defaultProperties = defaultProperties;
|
FlowDirection.LeftToRight,
|
||||||
}
|
typeface,
|
||||||
|
emSize.Value,
|
||||||
public TextRun GetTextRun(int textSourceIndex)
|
foreground);
|
||||||
{
|
|
||||||
if (textSourceIndex < _text.Length)
|
|
||||||
{
|
|
||||||
return new TextCharacters(_text, textSourceIndex, _text.Length - textSourceIndex, _defaultProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
return new Rect(startX, 0, endX - startX, textLine.Height);
|
var currentPosition = 0;
|
||||||
|
var currentRect = Rect.Empty;
|
||||||
|
|
||||||
|
//Current line isn't covered.
|
||||||
|
if (textLine.TextRange.Length <= start)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//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; }
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче