Browse Source

Merge branch 'master' into features/NetAnalyzers/CA1802

pull/9162/head
Max Katz 4 years ago
committed by GitHub
parent
commit
5b8991b2ab
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      NOTICE.md
  2. 2
      src/Android/Avalonia.Android/AvaloniaView.cs
  3. 91
      src/Avalonia.Controls/TextBox.cs
  4. 2
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  5. 20
      src/Avalonia.Controls/TextChangedEventArgs.cs
  6. 20
      src/Avalonia.Controls/TextChangingEventArgs.cs
  7. 2
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  8. 2
      tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs

4
NOTICE.md

@ -111,7 +111,7 @@ DEALINGS IN THE SOFTWARE.
# Metsys.Bson # Metsys.Bson
Copyright (c) 2010, Karl Seguin - http://www.openmymind.net/ Copyright (c) 2010, Karl Seguin - https://www.openmymind.net/
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@ -302,4 +302,4 @@ https://github.com/chromium/chromium
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

2
src/Android/Avalonia.Android/AvaloniaView.cs

@ -74,7 +74,7 @@ namespace Avalonia.Android
class ViewImpl : TopLevelImpl class ViewImpl : TopLevelImpl
{ {
public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView, true) public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView)
{ {
View.Focusable = true; View.Focusable = true;
View.FocusChange += ViewImpl_FocusChange; View.FocusChange += ViewImpl_FocusChange;

91
src/Avalonia.Controls/TextBox.cs

@ -18,6 +18,7 @@ using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using System.Diagnostics; using System.Diagnostics;
using Avalonia.Threading;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -159,18 +160,41 @@ namespace Avalonia.Controls
(o, v) => o.UndoLimit = v, (o, v) => o.UndoLimit = v,
unsetValue: -1); unsetValue: -1);
/// <summary>
/// Defines the <see cref="CopyingToClipboard"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CopyingToClipboardEvent = public static readonly RoutedEvent<RoutedEventArgs> CopyingToClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>( RoutedEvent.Register<TextBox, RoutedEventArgs>(
nameof(CopyingToClipboard), RoutingStrategies.Bubble); nameof(CopyingToClipboard), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="CuttingToClipboard"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CuttingToClipboardEvent = public static readonly RoutedEvent<RoutedEventArgs> CuttingToClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>( RoutedEvent.Register<TextBox, RoutedEventArgs>(
nameof(CuttingToClipboard), RoutingStrategies.Bubble); nameof(CuttingToClipboard), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="PastingFromClipboard"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> PastingFromClipboardEvent = public static readonly RoutedEvent<RoutedEventArgs> PastingFromClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>( RoutedEvent.Register<TextBox, RoutedEventArgs>(
nameof(PastingFromClipboard), RoutingStrategies.Bubble); nameof(PastingFromClipboard), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TextChanged"/> event.
/// </summary>
public static readonly RoutedEvent<TextChangedEventArgs> TextChangedEvent =
RoutedEvent.Register<TextBox, TextChangedEventArgs>(
nameof(TextChanged), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TextChanging"/> event.
/// </summary>
public static readonly RoutedEvent<TextChangingEventArgs> TextChangingEvent =
RoutedEvent.Register<TextBox, TextChangingEventArgs>(
nameof(TextChanging), RoutingStrategies.Bubble);
readonly struct UndoRedoState : IEquatable<UndoRedoState> readonly struct UndoRedoState : IEquatable<UndoRedoState>
{ {
public string? Text { get; } public string? Text { get; }
@ -359,8 +383,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public double LineHeight public double LineHeight
{ {
get { return GetValue(LineHeightProperty); } get => GetValue(LineHeightProperty);
set { SetValue(LineHeightProperty, value); } set => SetValue(LineHeightProperty, value);
} }
[Content] [Content]
@ -376,11 +400,19 @@ namespace Avalonia.Controls
CaretIndex = CoerceCaretIndex(caretIndex, value); CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(selectionStart, value); SelectionStart = CoerceCaretIndex(selectionStart, value);
SelectionEnd = CoerceCaretIndex(selectionEnd, value); SelectionEnd = CoerceCaretIndex(selectionEnd, value);
if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing)
var textChanged = SetAndRaise(TextProperty, ref _text, value);
if (textChanged && IsUndoEnabled && !_isUndoingRedoing)
{ {
_undoRedoHelper.Clear(); _undoRedoHelper.Clear();
SnapshotUndoRedo(); // so we always have an initial state SnapshotUndoRedo(); // so we always have an initial state
} }
if (textChanged)
{
RaiseTextChangeEvents();
}
} }
} }
@ -564,6 +596,27 @@ namespace Avalonia.Controls
remove => RemoveHandler(PastingFromClipboardEvent, value); remove => RemoveHandler(PastingFromClipboardEvent, value);
} }
/// <summary>
/// Occurs asynchronously after text changes and the new text is rendered.
/// </summary>
public event EventHandler<TextChangedEventArgs>? TextChanged
{
add => AddHandler(TextChangedEvent, value);
remove => RemoveHandler(TextChangedEvent, value);
}
/// <summary>
/// Occurs synchronously when text starts to change but before it is rendered.
/// </summary>
/// <remarks>
/// This event occurs just after the <see cref="Text"/> property value has been updated.
/// </remarks>
public event EventHandler<TextChangingEventArgs>? TextChanging
{
add => AddHandler(TextChangingEvent, value);
remove => RemoveHandler(TextChangingEvent, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter"); _presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
@ -1252,7 +1305,7 @@ namespace Avalonia.Controls
if (text != null && _wordSelectionStart >= 0) if (text != null && _wordSelectionStart >= 0)
{ {
var distance = caretIndex - _wordSelectionStart; var distance = caretIndex - _wordSelectionStart;
if (distance <= 0) if (distance <= 0)
{ {
@ -1539,11 +1592,39 @@ namespace Avalonia.Controls
return text.Substring(start, end - start); return text.Substring(start, end - start);
} }
/// <summary>
/// Raises both the <see cref="TextChanging"/> and <see cref="TextChanged"/> events.
/// </summary>
/// <remarks>
/// This must be called after the <see cref="Text"/> property is set.
/// </remarks>
private void RaiseTextChangeEvents()
{
// Note the following sequence of these events (following WinUI)
// 1. TextChanging occurs synchronously when text starts to change but before it is rendered.
// This occurs after the Text property is set.
// 2. TextChanged occurs asynchronously after text changes and the new text is rendered.
var textChangingEventArgs = new TextChangingEventArgs(TextChangingEvent);
RaiseEvent(textChangingEventArgs);
Dispatcher.UIThread.Post(() =>
{
var textChangedEventArgs = new TextChangedEventArgs(TextChangedEvent);
RaiseEvent(textChangedEventArgs);
}, DispatcherPriority.Normal);
}
private void SetTextInternal(string value, bool raiseTextChanged = true) private void SetTextInternal(string value, bool raiseTextChanged = true)
{ {
if (raiseTextChanged) if (raiseTextChanged)
{ {
SetAndRaise(TextProperty, ref _text, value); bool textChanged = SetAndRaise(TextProperty, ref _text, value);
if (textChanged)
{
RaiseTextChangeEvents();
}
} }
else else
{ {

2
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -64,7 +64,7 @@ namespace Avalonia.Controls
return new TextInputMethodSurroundingText return new TextInputMethodSurroundingText
{ {
Text = lineText ?? "", Text = lineText ?? "",
AnchorOffset = anchorOffset, AnchorOffset = anchorOffset,
CursorOffset = cursorOffset CursorOffset = cursorOffset
}; };

20
src/Avalonia.Controls/TextChangedEventArgs.cs

@ -0,0 +1,20 @@
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data specific to a TextChanged event.
/// </summary>
public class TextChangedEventArgs : RoutedEventArgs
{
public TextChangedEventArgs(RoutedEvent? routedEvent)
: base (routedEvent)
{
}
public TextChangedEventArgs(RoutedEvent? routedEvent, IInteractive? source)
: base(routedEvent, source)
{
}
}
}

20
src/Avalonia.Controls/TextChangingEventArgs.cs

@ -0,0 +1,20 @@
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data specific to a TextChanging event.
/// </summary>
public class TextChangingEventArgs : RoutedEventArgs
{
public TextChangingEventArgs(RoutedEvent? routedEvent)
: base (routedEvent)
{
}
public TextChangingEventArgs(RoutedEvent? routedEvent, IInteractive? source)
: base(routedEvent, source)
{
}
}
}

2
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@ -235,7 +235,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
public bool FillContains(Point point) public bool FillContains(Point point)
{ {
// Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation) // to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++) for (int i = 0; i < points.Count; i++)
{ {

2
tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs

@ -148,7 +148,7 @@ namespace Avalonia.UnitTests
public bool FillContains(Point point) public bool FillContains(Point point)
{ {
// Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation) // to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++) for (int i = 0; i < points.Count; i++)
{ {

Loading…
Cancel
Save