Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] New unified TextLayout #1950

Open
wants to merge 15 commits into
base: master
from
@@ -49,23 +49,24 @@ public override void Render(DrawingContext context)

FormattedText text = new FormattedText()
{
Typeface = Typeface.Default
Typeface = Typeface.Default,
Foreground = Brushes.Black
};

text.Text = $"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height), text);
context.DrawText(text, boundsRect.Position.WithY(boundsRect.Size.Height));

text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
context.DrawText(text, boundsRect.Position.WithY(boundsRect.Size.Height + 20));

text.Text = $"Scaling: {screen.PixelDensity * 100}%";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
context.DrawText(text, boundsRect.Position.WithY(boundsRect.Size.Height + 40));

text.Text = $"Primary: {screen.Primary}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
context.DrawText(text, boundsRect.Position.WithY(boundsRect.Size.Height + 60));

text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text);
context.DrawText(text, boundsRect.Position.WithY(boundsRect.Size.Height + 80));
}

context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));
@@ -41,7 +41,7 @@ public void Render(IDrawingContextImpl context)
{
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
if (canvas == null)
context.DrawText(Brushes.Black, new Point(), _noSkia.PlatformImpl);
_noSkia.TextLayout.Draw (context, Brushes.Black, new Point());
else
{
canvas.Save();
@@ -135,7 +135,7 @@ public int SelectionEnd
public int GetCaretIndex(Point point)
{
var hit = FormattedText.HitTestPoint(point);
return hit.TextPosition + (hit.IsTrailing ? 1 : 0);
return hit.CharacterHit.FirstCharacterIndex + hit.CharacterHit.TrailingLength;
}

public override void Render(DrawingContext context)
@@ -145,6 +145,8 @@ public override void Render(DrawingContext context)

if (selectionStart != selectionEnd)
{
var selectionBrush = SelectionBrush;

var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;

@@ -156,7 +158,7 @@ public override void Render(DrawingContext context)

foreach (var rect in rects)
{
context.FillRectangle(SelectionBrush, rect);
context.FillRectangle(selectionBrush, rect);
}
}

@@ -277,7 +279,7 @@ protected override FormattedText CreateFormattedText(Size constraint, string tex
{
result.Spans = new[]
{
new FormattedTextStyleSpan(start, length, SelectionForegroundBrush),
new FormattedTextStyleSpan(start, length, foreground: SelectionForegroundBrush),
};
}

@@ -87,6 +87,12 @@ public class TextBlock : Control
public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
AvaloniaProperty.Register<TextBlock, TextWrapping>(nameof(TextWrapping));

/// <summary>
/// Defines the <see cref="TextTrimming"/> property.
/// </summary>
public static readonly StyledProperty<TextTrimming> TextTrimmingProperty =
AvaloniaProperty.Register<TextBlock, TextTrimming>(nameof(TextTrimming));

private string _text;
private FormattedText _formattedText;
private Size _constraint;
@@ -109,7 +115,8 @@ static TextBlock()
TextAlignmentProperty.Changed,
FontSizeProperty.Changed,
FontStyleProperty.Changed,
FontWeightProperty.Changed
FontWeightProperty.Changed,
TextTrimmingProperty.Changed
).AddClassHandler<TextBlock>((x,_) => x.OnTextPropertiesChanged());
}

@@ -219,6 +226,15 @@ public TextAlignment TextAlignment
set { SetValue(TextAlignmentProperty, value); }
}

/// <summary>
/// Gets or sets the text trimming.
/// </summary>
public TextTrimming TextTrimming
{
get { return GetValue(TextTrimmingProperty); }
set { SetValue(TextTrimmingProperty, value); }
}

/// <summary>
/// Gets the value of the attached <see cref="FontFamilyProperty"/> on a control.
/// </summary>
@@ -338,7 +354,8 @@ public override void Render(DrawingContext context)
}

FormattedText.Constraint = Bounds.Size;
context.DrawText(Foreground, new Point(), FormattedText);

context.DrawText(FormattedText, new Point());
}

/// <summary>
@@ -354,9 +371,11 @@ protected virtual FormattedText CreateFormattedText(Size constraint, string text
Constraint = constraint,
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
FontSize = FontSize,
Foreground = Foreground,
Text = text ?? string.Empty,
TextAlignment = TextAlignment,
TextWrapping = TextWrapping,
TextTrimming = TextTrimming
};
}

@@ -1,20 +1,22 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.

using Avalonia.Input.Platform;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Data;
using Avalonia.Utilities;
using Avalonia.Media.Text;

namespace Avalonia.Controls
{
@@ -110,6 +112,8 @@ public UndoRedoState(string text, int caretPosition)
private string _newLine = Environment.NewLine;
private static readonly string[] invalidCharacters = new String[1] { "\u007f" };

private CharacterHit _characterHit;

static TextBox()
{
FocusableProperty.OverrideDefaultValue(typeof(TextBox), true);
@@ -250,9 +254,9 @@ public string Text
if (!_ignoreTextChanges)
{
var caretIndex = CaretIndex;
SelectionStart = CoerceCaretIndex(SelectionStart, value);
SelectionEnd = CoerceCaretIndex(SelectionEnd, value);
CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(SelectionStart, value?.Length ?? 0);
SelectionEnd = CoerceCaretIndex(SelectionEnd, value?.Length ?? 0);
CaretIndex = CoerceCaretIndex(caretIndex, value?.Length ?? 0);

if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing)
{
@@ -566,9 +570,12 @@ protected override void OnKeyDown(KeyEventArgs e)
// handle deleting /r/n
// you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
// a /r should also be deleted.
if (CaretIndex > 1 &&
text[CaretIndex - 1] == '\n' &&
text[CaretIndex - 2] == '\r')
if (CaretIndex > 1 && text[CaretIndex - 1] == '\n' && text[CaretIndex - 2] == '\r')
{
removedCharacters = 2;
}

if (caretIndex >= 2 && char.IsSurrogatePair(text[caretIndex - 2], text[caretIndex - 1]))
{
removedCharacters = 2;
}
@@ -590,16 +597,7 @@ protected override void OnKeyDown(KeyEventArgs e)

if (!DeleteSelection() && caretIndex < text.Length)
{
var removedCharacters = 1;
// handle deleting /r/n
// you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
// a /r should also be deleted.
if (CaretIndex < text.Length - 1 &&
text[caretIndex + 1] == '\n' &&
text[caretIndex] == '\r')
{
removedCharacters = 2;
}
var removedCharacters = _characterHit.TrailingLength;

SetTextInternal(text.Substring(0, caretIndex) +
text.Substring(caretIndex + removedCharacters));
@@ -654,7 +652,13 @@ protected override void OnKeyDown(KeyEventArgs e)
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
var point = e.GetPosition(_presenter);
var index = CaretIndex = _presenter.GetCaretIndex(point);

var hitTestResult = _presenter.FormattedText.HitTestPoint(point);

_characterHit = hitTestResult.CharacterHit;

var index = CaretIndex = _characterHit.FirstCharacterIndex + _characterHit.TrailingLength;

var text = Text;

if (text != null && e.MouseButton == MouseButton.Left)
@@ -710,55 +714,21 @@ protected override void UpdateDataValidation(AvaloniaProperty property, BindingN
}
}

private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text);
private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text?.Length ?? 0);

private int CoerceCaretIndex(int value, string text)
private int CoerceCaretIndex(int value, int length)
{
if (text == null)
{
return 0;
}
var length = text.Length;

if (value < 0)
{
return 0;
}
else if (value > length)
{
return length;
}
else if (value > 0 && text[value - 1] == '\r' && value < length && text[value] == '\n')
{
return value + 1;
}
else
{
return value;
}
}

private int DeleteCharacter(int index)
{
var start = index + 1;
var text = Text;
var c = text[index];
var result = 1;

if (c == '\n' && index > 0 && text[index - 1] == '\r')
{
--index;
++result;
}
else if (c == '\r' && index < text.Length - 1 && text[index + 1] == '\n')
if (value > length)
{
++start;
++result;
return length;
}

Text = text.Substring(0, index) + text.Substring(start);

return result;
return value;
}

private void MoveHorizontal(int direction, bool wholeWord)
@@ -780,16 +750,11 @@ private void MoveHorizontal(int direction, bool wholeWord)
return;
}

var c = text[index];
var rect = _presenter.FormattedText.HitTestTextPosition(index);

if (direction > 0)
{
CaretIndex += (c == '\r' && index < text.Length - 1 && text[index + 1] == '\n') ? 2 : 1;
}
else
{
CaretIndex -= (c == '\n' && index > 0 && text[index - 1] == '\r') ? 2 : 1;
}
var hitTestResult = _presenter.FormattedText.HitTestPoint(new Point(rect.X, rect.Y));

CaretIndex = hitTestResult.CharacterHit.FirstCharacterIndex;
}
else
{
@@ -807,7 +772,7 @@ private void MoveHorizontal(int direction, bool wholeWord)
private bool MoveVertical(int count)
{
var formattedText = _presenter.FormattedText;
var lines = formattedText.GetLines().ToList();
var lines = formattedText.TextLayout.TextLines;
var caretIndex = CaretIndex;
var lineIndex = GetLine(caretIndex, lines) + count;

@@ -816,15 +781,12 @@ private bool MoveVertical(int count)
var line = lines[lineIndex];
var rect = formattedText.HitTestTextPosition(caretIndex);
var y = count < 0 ? rect.Y : rect.Bottom;
var point = new Point(rect.X, y + (count * (line.Height / 2)));
var point = new Point(rect.X, y + (count * (line.LineMetrics.Size.Height / 2)));
var hit = formattedText.HitTestPoint(point);
CaretIndex = hit.TextPosition + (hit.IsTrailing ? 1 : 0);
CaretIndex = hit.CharacterHit.FirstCharacterIndex + hit.CharacterHit.TrailingLength;
return true;
}
else
{
return false;
}
return false;
}

private void MoveHome(bool document)
@@ -838,17 +800,17 @@ private void MoveHome(bool document)
}
else
{
var lines = _presenter.FormattedText.GetLines();
var lines = _presenter.FormattedText.TextLayout.TextLines;
var pos = 0;

foreach (var line in lines)
{
if (pos + line.Length > caretIndex || pos + line.Length == text.Length)
if (pos + line.Text.Length > caretIndex || pos + line.Text.Length == text.Length)
{
break;
}

pos += line.Length;
pos += line.Text.Length;
}

caretIndex = pos;
@@ -868,12 +830,12 @@ private void MoveEnd(bool document)
}
else
{
var lines = _presenter.FormattedText.GetLines();
var lines = _presenter.FormattedText.TextLayout.TextLines;
var pos = 0;

foreach (var line in lines)
{
pos += line.Length;
pos += line.Text.Length;

if (pos > caretIndex)
{
@@ -945,15 +907,15 @@ private string GetSelection()
return text.Substring(start, end - start);
}

private int GetLine(int caretIndex, IList<FormattedTextLine> lines)
private int GetLine(int caretIndex, IReadOnlyList<TextLine> lines)
{
int pos = 0;
int i;

for (i = 0; i < lines.Count - 1; ++i)
{
var line = lines[i];
pos += line.Length;
pos += line.Text.Length;

if (pos > caretIndex)
{
Binary file not shown.
Binary file not shown.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.