diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs index 2f8c4ad263..a4fc1edd14 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs +++ b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs @@ -48,7 +48,7 @@ namespace Avalonia.Media.TextFormatting /// /// The created text style run. /// - private static ValueSpan CreateTextStyleRun(ReadOnlySpan text, int firstTextSourceIndex, + internal static ValueSpan CreateTextStyleRun(ReadOnlySpan text, int firstTextSourceIndex, TextRunProperties defaultProperties, IReadOnlyList>? textModifier) { if (textModifier == null || textModifier.Count == 0) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index fed0f2a56c..9a89ccaf28 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -105,7 +105,7 @@ namespace Avalonia.Controls.Presenters static TextPresenter() { - AffectsRender(CaretBrushProperty, SelectionBrushProperty, TextElement.ForegroundProperty); + AffectsRender(CaretBrushProperty, SelectionBrushProperty, SelectionForegroundBrushProperty, TextElement.ForegroundProperty); } public TextPresenter() { } diff --git a/src/Avalonia.Controls/SelectableTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs index 4fa8ea989f..143ac29c02 100644 --- a/src/Avalonia.Controls/SelectableTextBlock.cs +++ b/src/Avalonia.Controls/SelectableTextBlock.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; using Avalonia.Controls.Documents; using Avalonia.Controls.Utils; @@ -32,6 +33,9 @@ namespace Avalonia.Controls public static readonly StyledProperty SelectionBrushProperty = TextBox.SelectionBrushProperty.AddOwner(); + public static readonly StyledProperty SelectionForegroundBrushProperty = + TextBox.SelectionForegroundBrushProperty.AddOwner(); + public static readonly DirectProperty CanCopyProperty = TextBox.CanCopyProperty.AddOwner(o => o.CanCopy); @@ -63,6 +67,15 @@ namespace Avalonia.Controls set => SetValue(SelectionBrushProperty, value); } + /// + /// Gets or sets a brush that is used for the foreground of selected text + /// + public IBrush? SelectionForegroundBrush + { + get => GetValue(SelectionForegroundBrushProperty); + set => SetValue(SelectionForegroundBrushProperty, value); + } + /// /// Gets or sets a character index for the beginning of the current selection. /// @@ -167,6 +180,58 @@ namespace Avalonia.Controls UpdateCommandStates(); } + protected override TextLayout CreateTextLayout(string? text) + { + var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); + + var defaultProperties = new GenericTextRunProperties( + typeface, + FontSize, + TextDecorations, + Foreground); + + var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false, + defaultProperties, TextWrapping, LineHeight, 0, LetterSpacing) + { + LineSpacing = LineSpacing + }; + + IReadOnlyList>? textStyleOverrides = null; + var selectionStart = SelectionStart; + var selectionEnd = SelectionEnd; + var start = Math.Min(selectionStart, selectionEnd); + var length = Math.Max(selectionStart, selectionEnd) - start; + + if (length > 0 && SelectionForegroundBrush != null) + { + textStyleOverrides = new[] + { + new ValueSpan(start, length, + new GenericTextRunProperties(typeface, FontSize, + foregroundBrush: SelectionForegroundBrush)) + }; + } + + ITextSource textSource; + + if (_textRuns != null) + { + textSource = new InlinesTextSource(_textRuns, textStyleOverrides); + } + else + { + textSource = new FormattedTextSource(text ?? "", defaultProperties, textStyleOverrides); + } + + return new TextLayout( + textSource, + paragraphProperties, + TextTrimming, + _constraint.Width, + _constraint.Height, + MaxLines); + } + protected override void RenderTextLayout(DrawingContext context, Point origin) { var selectionStart = SelectionStart; @@ -220,10 +285,17 @@ namespace Avalonia.Controls { base.OnPropertyChanged(change); - if (change.Property == SelectionStartProperty || change.Property == SelectionEndProperty) + if (change.Property == SelectionStartProperty || + change.Property == SelectionEndProperty) { RaisePropertyChanged(SelectedTextProperty, "", ""); UpdateCommandStates(); + InvalidateTextLayout(); + } + + if(change.Property == SelectionForegroundBrushProperty) + { + InvalidateTextLayout(); } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 1065bedc4b..cbda1dfbe6 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -7,6 +7,7 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.Metadata; +using Avalonia.Utilities; namespace Avalonia.Controls { @@ -155,8 +156,8 @@ namespace Avalonia.Controls nameof(Inlines), t => t.Inlines, (t, v) => t.Inlines = v); private TextLayout? _textLayout; - private Size _constraint; - private IReadOnlyList? _textRuns; + protected Size _constraint; + protected IReadOnlyList? _textRuns; private InlineCollection? _inlines; /// @@ -846,7 +847,7 @@ namespace Avalonia.Controls InvalidateTextLayout(); } - private readonly record struct SimpleTextSource : ITextSource + protected readonly record struct SimpleTextSource : ITextSource { private readonly string _text; private readonly TextRunProperties _defaultProperties; @@ -875,13 +876,17 @@ namespace Avalonia.Controls } } - private readonly struct InlinesTextSource : ITextSource +#pragma warning disable CA1815 // Equals und Gleichheitsoperator für Werttypen außer Kraft setzen + protected readonly struct InlinesTextSource : ITextSource +#pragma warning restore CA1815 // Equals und Gleichheitsoperator für Werttypen außer Kraft setzen { private readonly IReadOnlyList _textRuns; + private readonly IReadOnlyList>? _textModifier; - public InlinesTextSource(IReadOnlyList textRuns) + public InlinesTextSource(IReadOnlyList textRuns, IReadOnlyList>? textModifier = null) { _textRuns = textRuns; + _textModifier = textModifier; } public IReadOnlyList TextRuns => _textRuns; @@ -904,11 +909,13 @@ namespace Avalonia.Controls continue; } - if (textRun is TextCharacters) + if (textRun is TextCharacters textCharacters) { var skip = Math.Max(0, textSourceIndex - currentPosition); - return new TextCharacters(textRun.Text.Slice(skip), textRun.Properties!); + var textStyleRun = FormattedTextSource.CreateTextStyleRun(textRun.Text.Slice(skip).Span, textSourceIndex, textCharacters.Properties, _textModifier); + + return new TextCharacters(textRun.Text.Slice(skip, textStyleRun.Length), textStyleRun.Value); } return textRun; @@ -916,6 +923,6 @@ namespace Avalonia.Controls return new TextEndOfParagraph(); } - } + } } }