diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml
index 806f6d37da..a834e3fef3 100644
--- a/samples/Sandbox/MainWindow.axaml
+++ b/samples/Sandbox/MainWindow.axaml
@@ -13,6 +13,7 @@
.
+
diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs
index 2f27ca72d0..dc688fc359 100644
--- a/src/Avalonia.Controls/Documents/InlineCollection.cs
+++ b/src/Avalonia.Controls/Documents/InlineCollection.cs
@@ -136,7 +136,7 @@ namespace Avalonia.Controls.Documents
base.Add(new Run(_text));
}
- _text = string.Empty;
+ _text = null;
}
base.Add(item);
@@ -160,8 +160,6 @@ namespace Avalonia.Controls.Documents
Invalidated?.Invoke(this, EventArgs.Empty);
}
- private void Invalidate(object? sender, EventArgs e) => Invalidate();
-
private void OnParentChanged(ILogical? parent)
{
foreach(var child in this)
diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs
index 98851726da..c7289dbc3f 100644
--- a/src/Avalonia.Controls/Documents/Span.cs
+++ b/src/Avalonia.Controls/Documents/Span.cs
@@ -67,10 +67,12 @@ namespace Avalonia.Controls.Documents
inline.AppendText(stringBuilder);
}
}
-
- if (Inlines.Text is string text)
+ else
{
- stringBuilder.Append(text);
+ if (Inlines.Text is string text)
+ {
+ stringBuilder.Append(text);
+ }
}
}
@@ -87,9 +89,9 @@ namespace Avalonia.Controls.Documents
}
}
- internal override void OnInlinesHostChanged(IInlineHost? oldValue, IInlineHost? newValue)
+ internal override void OnInlineHostChanged(IInlineHost? oldValue, IInlineHost? newValue)
{
- base.OnInlinesHostChanged(oldValue, newValue);
+ base.OnInlineHostChanged(oldValue, newValue);
if(Inlines is not null)
{
diff --git a/src/Avalonia.Controls/Documents/TextElement.cs b/src/Avalonia.Controls/Documents/TextElement.cs
index e75fd87615..5bac3642ed 100644
--- a/src/Avalonia.Controls/Documents/TextElement.cs
+++ b/src/Avalonia.Controls/Documents/TextElement.cs
@@ -259,11 +259,11 @@ namespace Avalonia.Controls.Documents
{
var oldValue = _inlineHost;
_inlineHost = value;
- OnInlinesHostChanged(oldValue, value);
+ OnInlineHostChanged(oldValue, value);
}
}
- internal virtual void OnInlinesHostChanged(IInlineHost? oldValue, IInlineHost? newValue)
+ internal virtual void OnInlineHostChanged(IInlineHost? oldValue, IInlineHost? newValue)
{
}
diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs
index 87cf660cad..7e5b34acd9 100644
--- a/src/Avalonia.Controls/Primitives/AccessText.cs
+++ b/src/Avalonia.Controls/Primitives/AccessText.cs
@@ -79,9 +79,9 @@ namespace Avalonia.Controls.Primitives
}
///
- protected override TextLayout CreateTextLayout(Size constraint, string? text)
+ protected override TextLayout CreateTextLayout(string? text)
{
- return base.CreateTextLayout(constraint, RemoveAccessKeyMarker(text));
+ return base.CreateTextLayout(RemoveAccessKeyMarker(text));
}
///
diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs
index 859503e693..1411d715ec 100644
--- a/src/Avalonia.Controls/RichTextBlock.cs
+++ b/src/Avalonia.Controls/RichTextBlock.cs
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
+using System.Linq;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Utils;
using Avalonia.Input;
+using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
@@ -56,6 +57,16 @@ namespace Avalonia.Controls
AvaloniaProperty.Register(
nameof(Inlines));
+ public static readonly DirectProperty CanCopyProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(CanCopy),
+ o => o.CanCopy);
+
+ public static readonly RoutedEvent CopyingToClipboardEvent =
+ RoutedEvent.Register(
+ nameof(CopyingToClipboard), RoutingStrategies.Bubble);
+
+ private bool _canCopy;
private int _caretIndex;
private int _selectionStart;
private int _selectionEnd;
@@ -75,7 +86,7 @@ namespace Avalonia.Controls
InlineHost = this
};
}
-
+
public IBrush? SelectionBrush
{
get => GetValue(SelectionBrushProperty);
@@ -156,50 +167,43 @@ namespace Avalonia.Controls
}
///
- /// Creates the used to render the text.
+ /// Property for determining if the Copy command can be executed.
///
- /// The constraint of the text.
- /// The text to format.
- /// A object.
- protected override TextLayout CreateTextLayout(Size constraint, string? text)
+ public bool CanCopy
{
- var defaultProperties = new GenericTextRunProperties(
- new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
- FontSize,
- TextDecorations,
- Foreground);
+ get => _canCopy;
+ private set => SetAndRaise(CanCopyProperty, ref _canCopy, value);
+ }
- var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false,
- defaultProperties, TextWrapping, LineHeight, 0);
+ public event EventHandler? CopyingToClipboard
+ {
+ add => AddHandler(CopyingToClipboardEvent, value);
+ remove => RemoveHandler(CopyingToClipboardEvent, value);
+ }
- ITextSource textSource;
+ public async void Copy()
+ {
+ if (_canCopy || !IsTextSelectionEnabled)
+ {
+ return;
+ }
- var inlines = Inlines;
+ var text = GetSelection();
- if (inlines is not null && inlines.HasComplexContent)
+ if (string.IsNullOrEmpty(text))
{
- var textRuns = new List();
+ return;
+ }
- foreach (var inline in inlines)
- {
- inline.BuildTextRun(textRuns);
- }
+ var eventArgs = new RoutedEventArgs(CopyingToClipboardEvent);
- textSource = new InlinesTextSource(textRuns);
- }
- else
+ RaiseEvent(eventArgs);
+
+ if (!eventArgs.Handled)
{
- textSource = new SimpleTextSource((text ?? "").AsMemory(), defaultProperties);
+ await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard)))
+ .SetTextAsync(text);
}
-
- return new TextLayout(
- textSource,
- paragraphProperties,
- TextTrimming,
- constraint.Width,
- constraint.Height,
- maxLines: MaxLines,
- lineHeight: LineHeight);
}
public override void Render(DrawingContext context)
@@ -236,7 +240,7 @@ namespace Avalonia.Controls
return;
}
- var text = Inlines.Text ?? Text;
+ var text = Text;
SelectionStart = 0;
SelectionEnd = text?.Length ?? 0;
@@ -255,6 +259,75 @@ namespace Avalonia.Controls
SelectionEnd = SelectionStart;
}
+
+ protected override string? GetText()
+ {
+ return _text ?? Inlines.Text;
+ }
+
+ protected override void SetText(string? text)
+ {
+ var oldValue = _text ?? Inlines?.Text;
+
+ if (Inlines is not null && Inlines.HasComplexContent)
+ {
+ Inlines.Text = text;
+
+ _text = null;
+ }
+ else
+ {
+ _text = text;
+ }
+
+ RaisePropertyChanged(TextProperty, oldValue, text);
+ }
+
+ ///
+ /// Creates the used to render the text.
+ ///
+ /// A object.
+ protected override TextLayout CreateTextLayout(string? text)
+ {
+ var defaultProperties = new GenericTextRunProperties(
+ new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
+ FontSize,
+ TextDecorations,
+ Foreground);
+
+ var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false,
+ defaultProperties, TextWrapping, LineHeight, 0);
+
+ ITextSource textSource;
+
+ var inlines = Inlines;
+
+ if (inlines is not null && inlines.HasComplexContent)
+ {
+ var textRuns = new List();
+
+ foreach (var inline in inlines)
+ {
+ inline.BuildTextRun(textRuns);
+ }
+
+ textSource = new InlinesTextSource(textRuns);
+ }
+ else
+ {
+ textSource = new SimpleTextSource((text ?? "").AsMemory(), defaultProperties);
+ }
+
+ return new TextLayout(
+ textSource,
+ paragraphProperties,
+ TextTrimming,
+ _constraint.Width,
+ _constraint.Height,
+ maxLines: MaxLines,
+ lineHeight: LineHeight);
+ }
+
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
@@ -262,6 +335,24 @@ namespace Avalonia.Controls
ClearSelection();
}
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ var handled = false;
+ var modifiers = e.KeyModifiers;
+ var keymap = AvaloniaLocator.Current.GetRequiredService();
+
+ bool Match(List gestures) => gestures.Any(g => g.Matches(e));
+
+ if (Match(keymap.Copy))
+ {
+ Copy();
+
+ handled = true;
+ }
+
+ e.Handled = handled;
+ }
+
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (!IsTextSelectionEnabled)
@@ -269,20 +360,21 @@ namespace Avalonia.Controls
return;
}
- var text = Inlines.Text;
+ var text = Text;
var clickInfo = e.GetCurrentPoint(this);
if (text != null && clickInfo.Properties.IsLeftButtonPressed)
{
var point = e.GetPosition(this);
- var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
-
- var hit = TextLayout.HitTestPoint(point);
+ var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
var oldIndex = CaretIndex;
+
+ var hit = TextLayout.HitTestPoint(point);
var index = hit.TextPosition;
- CaretIndex = index;
+
+ SetAndRaise(CaretIndexProperty, ref _caretIndex, index);
#pragma warning disable CS0618 // Type or member is obsolete
switch (e.ClickCount)
@@ -368,7 +460,7 @@ namespace Avalonia.Controls
caretIndex >= firstSelection && caretIndex <= lastSelection;
if (!didClickInSelection)
{
- _caretIndex = SelectionEnd = SelectionStart = caretIndex;
+ CaretIndex = SelectionEnd = SelectionStart = caretIndex;
}
}
@@ -389,29 +481,19 @@ namespace Avalonia.Controls
}
case nameof(TextProperty):
{
- OnTextChanged(change.OldValue as string, change.NewValue as string);
+ InvalidateTextLayout();
break;
}
}
}
- private void OnTextChanged(string? oldValue, string? newValue)
+ private string GetSelection()
{
- if (oldValue == newValue)
- {
- return;
- }
-
- if (Inlines is null)
+ if (!IsTextSelectionEnabled)
{
- return;
+ return "";
}
- Inlines.Text = newValue;
- }
-
- private string GetSelection()
- {
var text = Inlines.Text ?? Text;
if (string.IsNullOrEmpty(text))
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index 1f891b092f..2f83ee1002 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -130,7 +130,7 @@ namespace Avalonia.Controls
protected string? _text;
protected TextLayout? _textLayout;
- private Size _constraint;
+ protected Size _constraint;
///
/// Initializes static members of the class.
@@ -149,7 +149,7 @@ namespace Avalonia.Controls
{
get
{
- return _textLayout ??= CreateTextLayout(_constraint, Text);
+ return _textLayout ??= CreateTextLayout(_text);
}
}
@@ -176,11 +176,8 @@ namespace Avalonia.Controls
///
public string? Text
{
- get => _text;
- set
- {
- SetAndRaise(TextProperty, ref _text, value);
- }
+ get => GetText();
+ set => SetText(value);
}
///
@@ -302,11 +299,6 @@ namespace Avalonia.Controls
set { SetValue(BaselineOffsetProperty, value); }
}
- public void Add(string text)
- {
- Text = text;
- }
-
///
/// Reads the attached property from the given element
///
@@ -481,6 +473,10 @@ namespace Avalonia.Controls
control.SetValue(MaxLinesProperty, maxLines);
}
+ public void Add(string text)
+ {
+ _text = text;
+ }
///
/// Renders the to a drawing context.
@@ -516,13 +512,21 @@ namespace Avalonia.Controls
TextLayout.Draw(context, new Point(padding.Left, top));
}
+ protected virtual string? GetText()
+ {
+ return _text;
+ }
+
+ protected virtual void SetText(string? text)
+ {
+ SetAndRaise(TextProperty, ref _text, text);
+ }
+
///
/// Creates the used to render the text.
///
- /// The constraint of the text.
- /// The text to format.
/// A object.
- protected virtual TextLayout CreateTextLayout(Size constraint, string? text)
+ protected virtual TextLayout CreateTextLayout(string? text)
{
var defaultProperties = new GenericTextRunProperties(
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
@@ -537,8 +541,8 @@ namespace Avalonia.Controls
new SimpleTextSource((text ?? "").AsMemory(), defaultProperties),
paragraphProperties,
TextTrimming,
- constraint.Width,
- constraint.Height,
+ _constraint.Width,
+ _constraint.Height,
maxLines: MaxLines,
lineHeight: LineHeight);
}
diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/BiDiClassTests.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/BiDiClassTests.cs
index 1ed33e6132..f29420ff87 100644
--- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/BiDiClassTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/BiDiClassTests.cs
@@ -30,7 +30,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
private bool Run(BiDiClassData t)
{
- var bidi = BidiAlgorithm.Instance.Value;
+ var bidi = new BidiAlgorithm();
var bidiData = new BidiData(t.ParagraphLevel);
var text = Encoding.UTF32.GetString(MemoryMarshal.Cast(t.CodePoints).ToArray());
diff --git a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
index ed4c78aa3e..99dfc93a68 100644
--- a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
@@ -49,7 +49,7 @@ namespace Avalonia.Base.UnitTests.Styling
setter.Instance(control).Start(false);
- Assert.Equal("", control.Text);
+ Assert.Equal(null, control.Text);
}
[Fact]