diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 239cd3b08a..0ae27a520f 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Reactive.Linq; using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.Metadata; @@ -26,14 +25,14 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty PasswordCharProperty = AvaloniaProperty.Register(nameof(PasswordChar)); - public static readonly StyledProperty SelectionBrushProperty = - AvaloniaProperty.Register(nameof(SelectionBrushProperty)); + public static readonly StyledProperty SelectionBrushProperty = + AvaloniaProperty.Register(nameof(SelectionBrushProperty)); - public static readonly StyledProperty SelectionForegroundBrushProperty = - AvaloniaProperty.Register(nameof(SelectionForegroundBrushProperty)); + public static readonly StyledProperty SelectionForegroundBrushProperty = + AvaloniaProperty.Register(nameof(SelectionForegroundBrushProperty)); - public static readonly StyledProperty CaretBrushProperty = - AvaloniaProperty.Register(nameof(CaretBrushProperty)); + public static readonly StyledProperty CaretBrushProperty = + AvaloniaProperty.Register(nameof(CaretBrushProperty)); public static readonly DirectProperty SelectionStartProperty = TextBox.SelectionStartProperty.AddOwner( @@ -48,8 +47,8 @@ namespace Avalonia.Controls.Presenters /// /// Defines the property. /// - public static readonly DirectProperty TextProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty TextProperty = + AvaloniaProperty.RegisterDirect( nameof(Text), o => o.Text, (o, v) => o.Text = v); @@ -77,16 +76,14 @@ namespace Avalonia.Controls.Presenters private int _selectionStart; private int _selectionEnd; private bool _caretBlink; - private string _text; + private string? _text; private TextLayout? _textLayout; - private Size _constraint = Size.Infinity; + private Size _constraint; private CharacterHit _lastCharacterHit; private Rect _caretBounds; private Point _navigationPosition; - private ScrollViewer? _scrollViewer; - static TextPresenter() { AffectsRender(CaretBrushProperty, SelectionBrushProperty); @@ -114,7 +111,7 @@ namespace Avalonia.Controls.Presenters /// Gets or sets the text. /// [Content] - public string Text + public string? Text { get => _text; set => SetAndRaise(TextProperty, ref _text, value); @@ -186,7 +183,7 @@ namespace Avalonia.Controls.Presenters /// /// Gets the used to render the text. /// - public TextLayout? TextLayout + public TextLayout TextLayout { get { @@ -230,19 +227,19 @@ namespace Avalonia.Controls.Presenters set => SetValue(RevealPasswordProperty, value); } - public IBrush SelectionBrush + public IBrush? SelectionBrush { get => GetValue(SelectionBrushProperty); set => SetValue(SelectionBrushProperty, value); } - public IBrush SelectionForegroundBrush + public IBrush? SelectionForegroundBrush { get => GetValue(SelectionForegroundBrushProperty); set => SetValue(SelectionForegroundBrushProperty, value); } - public IBrush CaretBrush + public IBrush? CaretBrush { get => GetValue(CaretBrushProperty); set => SetValue(CaretBrushProperty, value); @@ -284,20 +281,14 @@ namespace Avalonia.Controls.Presenters /// /// /// A object. - private TextLayout? CreateTextLayoutInternal(Size constraint, string text, Typeface typeface, + private TextLayout CreateTextLayoutInternal(Size constraint, string? text, Typeface typeface, IReadOnlyList>? textStyleOverrides) { var foreground = Foreground; - - if (foreground == null) - { - return null; - } - var maxWidth = MathUtilities.IsZero(constraint.Width) ? double.PositiveInfinity : constraint.Width; var maxHeight = MathUtilities.IsZero(constraint.Height) ? double.PositiveInfinity : constraint.Height; - var textLayout = new TextLayout(text ?? string.Empty, typeface, FontSize, foreground, TextAlignment, + var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment, TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, flowDirection: FlowDirection); @@ -317,11 +308,6 @@ namespace Avalonia.Controls.Presenters context.FillRectangle(background, new Rect(Bounds.Size)); } - if (TextLayout == null) - { - return; - } - var top = 0d; var left = 0.0; @@ -346,17 +332,11 @@ namespace Avalonia.Controls.Presenters public override void Render(DrawingContext context) { - if (double.IsPositiveInfinity (_constraint.Width)) - { - _constraint = _scrollViewer?.Viewport ?? Size.Infinity; - - InvalidateTextLayout(); - } - var selectionStart = SelectionStart; var selectionEnd = SelectionEnd; + var selectionBrush = SelectionBrush; - if (selectionStart != selectionEnd && TextLayout != null) + if (selectionStart != selectionEnd && selectionBrush != null) { var start = Math.Min(selectionStart, selectionEnd); var length = Math.Max(selectionStart, selectionEnd) - start; @@ -365,7 +345,7 @@ namespace Avalonia.Controls.Presenters foreach (var rect in rects) { - context.FillRectangle(SelectionBrush, rect); + context.FillRectangle(selectionBrush, rect); } } @@ -470,9 +450,9 @@ namespace Avalonia.Controls.Presenters /// Creates the used to render the text. /// /// A object. - protected virtual TextLayout? CreateTextLayout() + protected virtual TextLayout CreateTextLayout() { - TextLayout? result; + TextLayout result; var text = Text; @@ -517,14 +497,25 @@ namespace Avalonia.Controls.Presenters protected override Size MeasureOverride(Size availableSize) { - if (!double.IsInfinity(availableSize.Width) && availableSize != _constraint) + _textLayout = null; + + _constraint = availableSize; + + return TextLayout.Size; + } + + protected override Size ArrangeOverride(Size finalSize) + { + if (!double.IsInfinity(_constraint.Width)) { - _constraint = availableSize; - - InvalidateTextLayout(); + return base.ArrangeOverride(finalSize); } + + _constraint = finalSize; + + _textLayout = null; - return TextLayout?.Size ?? default; + return base.ArrangeOverride(finalSize); } private int CoerceCaretIndex(int value) @@ -542,11 +533,6 @@ namespace Avalonia.Controls.Presenters public void MoveCaretToTextPosition(int textPosition, bool trailingEdge = false) { - if (TextLayout == null) - { - return; - } - var lineIndex = TextLayout.GetLineIndexFromCharacterIndex(textPosition, trailingEdge); var textLine = TextLayout.TextLines[lineIndex]; @@ -573,11 +559,6 @@ namespace Avalonia.Controls.Presenters public void MoveCaretToPoint(Point point) { - if (TextLayout == null) - { - return; - } - var hit = TextLayout.HitTestPoint(point); UpdateCaret(hit.CharacterHit); @@ -587,11 +568,6 @@ namespace Avalonia.Controls.Presenters public void MoveCaretVertical(LogicalDirection direction = LogicalDirection.Forward) { - if (TextLayout == null) - { - return; - } - var lineIndex = TextLayout.GetLineIndexFromCharacterIndex(CaretIndex, _lastCharacterHit.TrailingLength > 0); if (lineIndex < 0) @@ -633,11 +609,11 @@ namespace Avalonia.Controls.Presenters public void MoveCaretHorizontal(LogicalDirection direction = LogicalDirection.Forward) { - if (TextLayout == null) + if (Text is null) { return; } - + if (FlowDirection == FlowDirection.RightToLeft) { direction = direction == LogicalDirection.Forward ? @@ -720,11 +696,6 @@ namespace Avalonia.Controls.Presenters private void UpdateCaret(CharacterHit characterHit) { - if (TextLayout == null) - { - return; - } - _lastCharacterHit = characterHit; var caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; @@ -761,19 +732,10 @@ namespace Avalonia.Controls.Presenters return _caretBounds; } - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - _scrollViewer = this.FindAncestorOfType(); - } - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); - _scrollViewer = null; - _caretTimer.Stop(); _caretTimer.Tick -= CaretTimerTick; diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index c0f5980321..ed012bd8b1 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -492,13 +492,6 @@ namespace Avalonia.Controls return measuredSize.Inflate(padding); } - protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) - { - base.OnAttachedToLogicalTree(e); - - InvalidateTextLayout(); - } - private static bool IsValidMaxLines(int maxLines) => maxLines >= 0; private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0; diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 8cebcaf093..b8b46dac6e 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -540,6 +540,8 @@ namespace Avalonia.Controls protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); + + _imClient.SetPresenter(_presenter, this); if (IsFocused) { @@ -551,8 +553,6 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTree(e); - _imClient.SetPresenter(_presenter, this); - if (IsFocused) { _presenter?.ShowCaret(); @@ -612,6 +612,8 @@ namespace Avalonia.Controls } UpdateCommandStates(); + + _imClient.SetPresenter(_presenter, this); _presenter?.ShowCaret(); } @@ -630,6 +632,8 @@ namespace Avalonia.Controls UpdateCommandStates(); _presenter?.HideCaret(); + + _imClient.SetPresenter(null, null); } protected override void OnTextInput(TextInputEventArgs e) @@ -1278,7 +1282,7 @@ namespace Avalonia.Controls { caretIndex = 0; } - else if (_presenter.TextLayout is not null) + else { var lines = _presenter.TextLayout.TextLines; var pos = 0; @@ -1313,7 +1317,7 @@ namespace Avalonia.Controls { caretIndex = text.Length; } - else if (_presenter.TextLayout is not null) + else { var lines = _presenter.TextLayout.TextLines; var pos = 0; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index 26abf4fc98..64f0eaab53 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -194,6 +194,7 @@ namespace Avalonia.Media.TextFormatting var currentY = 0d; var currentPosition = 0; + var currentRect = Rect.Empty; foreach (var textLine in TextLines) { @@ -209,7 +210,10 @@ namespace Avalonia.Media.TextFormatting //The whole line is covered. if (currentPosition >= start && start + length > currentPosition + textLine.TextRange.Length) { - result.Add(new Rect(textLine.Start, currentY, textLine.WidthIncludingTrailingWhitespace, textLine.Height)); + currentRect = new Rect(textLine.Start, currentY, textLine.WidthIncludingTrailingWhitespace, + textLine.Height); + + result.Add(currentRect); currentY += textLine.Height; currentPosition += textLine.TextRange.Length; @@ -317,15 +321,16 @@ namespace Avalonia.Media.TextFormatting var width = endX - startX; - if (result.Count > 0 && MathUtilities.AreClose(result[result.Count - 1].Right, startX)) + if (result.Count > 0 && MathUtilities.AreClose(currentRect.Top, currentY) && + MathUtilities.AreClose(currentRect.Right, startX)) { - var rect = result[result.Count - 1]; - - result[result.Count - 1] = rect.WithWidth(rect.Width + width); + result[result.Count - 1] = currentRect.WithWidth(currentRect.Width + width); } else { - result.Add(new Rect(startX, currentY, width, textLine.Height)); + currentRect = new Rect(startX, currentY, width, textLine.Height); + + result.Add(currentRect); } if (currentRun.ShapedBuffer.IsLeftToRight) @@ -433,7 +438,7 @@ namespace Avalonia.Media.TextFormatting continue; } - if (charIndex >= textLine.Start && charIndex <= textLine.TextRange.End + (trailingEdge ? 1 : 0)) + if (charIndex >= textLine.TextRange.Start && charIndex <= textLine.TextRange.End + (trailingEdge ? 1 : 0)) { return index; } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs index 9bbc4a8a9d..130d0e9c39 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Avalonia.Media.TextFormatting { @@ -210,10 +211,10 @@ namespace Avalonia.Media.TextFormatting switch (textAlignment) { case TextAlignment.Center: - return (paragraphWidth - width) / 2; + return Math.Max(0, (paragraphWidth - width) / 2); case TextAlignment.Right: - return paragraphWidth - widthIncludingTrailingWhitespace; + return Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace); default: return 0; @@ -223,13 +224,13 @@ namespace Avalonia.Media.TextFormatting switch (textAlignment) { case TextAlignment.Center: - return (paragraphWidth - width) / 2; + return Math.Max(0, (paragraphWidth - width) / 2); case TextAlignment.Right: return 0; default: - return paragraphWidth - widthIncludingTrailingWhitespace; + return Math.Max(0, paragraphWidth - widthIncludingTrailingWhitespace); } } }