|
|
@@ -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<RichTextBlock, InlineCollection>(
|
|
|
nameof(Inlines));
|
|
|
|
|
|
+ public static readonly DirectProperty<TextBox, bool> CanCopyProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<TextBox, bool>(
|
|
|
+ nameof(CanCopy),
|
|
|
+ o => o.CanCopy);
|
|
|
+
|
|
|
+ public static readonly RoutedEvent<RoutedEventArgs> CopyingToClipboardEvent =
|
|
|
+ RoutedEvent.Register<RichTextBlock, RoutedEventArgs>(
|
|
|
+ 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
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Creates the <see cref="TextLayout"/> used to render the text.
|
|
|
+ /// Property for determining if the Copy command can be executed.
|
|
|
/// </summary>
|
|
|
- /// <param name="constraint">The constraint of the text.</param>
|
|
|
- /// <param name="text">The text to format.</param>
|
|
|
- /// <returns>A <see cref="TextLayout"/> object.</returns>
|
|
|
- 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<RoutedEventArgs>? 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<TextRun>();
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Creates the <see cref="TextLayout"/> used to render the text.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>A <see cref="TextLayout"/> object.</returns>
|
|
|
+ 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<TextRun>();
|
|
|
+
|
|
|
+ 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<PlatformHotkeyConfiguration>();
|
|
|
+
|
|
|
+ bool Match(List<KeyGesture> 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))
|