Browse Source

Merge branch 'master' into rxui-events

pull/4535/head
Artyom V. Gorchakov 6 years ago
committed by GitHub
parent
commit
16288af93e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 103
      src/Avalonia.Controls/TextBox.cs
  2. 8
      src/Avalonia.Themes.Default/TextBox.xaml
  3. 6
      src/Avalonia.Themes.Fluent/TextBox.xaml
  4. 26
      tests/Avalonia.LeakTests/ControlTests.cs

103
src/Avalonia.Controls/TextBox.cs

@ -18,6 +18,15 @@ namespace Avalonia.Controls
{ {
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
{ {
public static KeyGesture CutGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Cut.FirstOrDefault();
public static KeyGesture CopyGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Copy.FirstOrDefault();
public static KeyGesture PasteGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Paste.FirstOrDefault();
public static readonly StyledProperty<bool> AcceptsReturnProperty = public static readonly StyledProperty<bool> AcceptsReturnProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn)); AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn));
@ -103,6 +112,21 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> RevealPasswordProperty = public static readonly StyledProperty<bool> RevealPasswordProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword)); AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword));
public static readonly DirectProperty<TextBox, bool> CanCutProperty =
AvaloniaProperty.RegisterDirect<TextBox, bool>(
nameof(CanCut),
o => o.CanCut);
public static readonly DirectProperty<TextBox, bool> CanCopyProperty =
AvaloniaProperty.RegisterDirect<TextBox, bool>(
nameof(CanCopy),
o => o.CanCopy);
public static readonly DirectProperty<TextBox, bool> CanPasteProperty =
AvaloniaProperty.RegisterDirect<TextBox, bool>(
nameof(CanPaste),
o => o.CanPaste);
struct UndoRedoState : IEquatable<UndoRedoState> struct UndoRedoState : IEquatable<UndoRedoState>
{ {
@ -126,6 +150,9 @@ namespace Avalonia.Controls
private UndoRedoHelper<UndoRedoState> _undoRedoHelper; private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing; private bool _isUndoingRedoing;
private bool _ignoreTextChanges; private bool _ignoreTextChanges;
private bool _canCut;
private bool _canCopy;
private bool _canPaste;
private string _newLine = Environment.NewLine; private string _newLine = Environment.NewLine;
private static readonly string[] invalidCharacters = new String[1] { "\u007f" }; private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
@ -378,6 +405,33 @@ namespace Avalonia.Controls
SelectionStart = SelectionEnd = CaretIndex; SelectionStart = SelectionEnd = CaretIndex;
} }
/// <summary>
/// Property for determining if the Cut command can be executed.
/// </summary>
public bool CanCut
{
get { return _canCut; }
private set { SetAndRaise(CanCutProperty, ref _canCut, value); }
}
/// <summary>
/// Property for determining if the Copy command can be executed.
/// </summary>
public bool CanCopy
{
get { return _canCopy; }
private set { SetAndRaise(CanCopyProperty, ref _canCopy, value); }
}
/// <summary>
/// Property for determining if the Paste command can be executed.
/// </summary>
public bool CanPaste
{
get { return _canPaste; }
private set { SetAndRaise(CanPasteProperty, ref _canPaste, 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");
@ -395,9 +449,19 @@ namespace Avalonia.Controls
if (change.Property == TextProperty) if (change.Property == TextProperty)
{ {
UpdatePseudoclasses(); UpdatePseudoclasses();
UpdateCommandStates();
} }
} }
private void UpdateCommandStates()
{
var text = GetSelection();
var isNullOrEmpty = string.IsNullOrEmpty(text);
CanCopy = !isNullOrEmpty;
CanCut = !isNullOrEmpty && !IsReadOnly;
CanPaste = !IsReadOnly;
}
protected override void OnGotFocus(GotFocusEventArgs e) protected override void OnGotFocus(GotFocusEventArgs e)
{ {
base.OnGotFocus(e); base.OnGotFocus(e);
@ -412,6 +476,8 @@ namespace Avalonia.Controls
SelectAll(); SelectAll();
} }
UpdateCommandStates();
_presenter?.ShowCaret(); _presenter?.ShowCaret();
} }
@ -424,7 +490,9 @@ namespace Avalonia.Controls
ClearSelection(); ClearSelection();
RevealPassword = false; RevealPassword = false;
} }
UpdateCommandStates();
_presenter?.HideCaret(); _presenter?.HideCaret();
} }
@ -467,19 +535,31 @@ namespace Avalonia.Controls
return text; return text;
} }
private async void Copy() public async void Cut()
{ {
var text = GetSelection();
if (text is null) return;
_undoRedoHelper.Snapshot();
Copy();
DeleteSelection();
_undoRedoHelper.Snapshot();
}
public async void Copy()
{
var text = GetSelection();
if (text is null) return;
await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))) await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)))
.SetTextAsync(GetSelection()); .SetTextAsync(text);
} }
private async void Paste() public async void Paste()
{ {
var text = await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))).GetTextAsync(); var text = await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))).GetTextAsync();
if (text == null)
{ if (text is null) return;
return;
}
_undoRedoHelper.Snapshot(); _undoRedoHelper.Snapshot();
HandleTextInput(text); HandleTextInput(text);
@ -518,23 +598,18 @@ namespace Avalonia.Controls
{ {
if (!IsPasswordBox) if (!IsPasswordBox)
{ {
_undoRedoHelper.Snapshot(); Cut();
Copy();
DeleteSelection();
_undoRedoHelper.Snapshot();
} }
handled = true; handled = true;
} }
else if (Match(keymap.Paste)) else if (Match(keymap.Paste))
{ {
Paste(); Paste();
handled = true; handled = true;
} }
else if (Match(keymap.Undo)) else if (Match(keymap.Undo))
{ {
try try
{ {
_isUndoingRedoing = true; _isUndoingRedoing = true;

8
src/Avalonia.Themes.Default/TextBox.xaml

@ -1,4 +1,11 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<ContextMenu x:Key="DefaultTextBoxContextMenu" x:Name="TextBoxContextMenu">
<MenuItem x:Name="TextBoxContextMenuCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
<MenuItem x:Name="TextBoxContextMenuCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
<MenuItem x:Name="TextBoxContextMenuPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/>
</ContextMenu>
</Styles.Resources>
<Style Selector="TextBox"> <Style Selector="TextBox">
<Setter Property="CaretBrush" Value="{DynamicResource ThemeForegroundBrush}" /> <Setter Property="CaretBrush" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/> <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
@ -7,6 +14,7 @@
<Setter Property="SelectionBrush" Value="{DynamicResource HighlightBrush}"/> <Setter Property="SelectionBrush" Value="{DynamicResource HighlightBrush}"/>
<Setter Property="SelectionForegroundBrush" Value="{DynamicResource HighlightForegroundBrush}"/> <Setter Property="SelectionForegroundBrush" Value="{DynamicResource HighlightForegroundBrush}"/>
<Setter Property="Padding" Value="4"/> <Setter Property="Padding" Value="4"/>
<Setter Property="ContextMenu" Value="{StaticResource DefaultTextBoxContextMenu}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Name="border" <Border Name="border"

6
src/Avalonia.Themes.Fluent/TextBox.xaml

@ -14,6 +14,11 @@
<StreamGeometry x:Key="TextBoxClearButtonData">M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z</StreamGeometry> <StreamGeometry x:Key="TextBoxClearButtonData">M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry> <StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry> <StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry>
<ContextMenu x:Key="DefaultTextBoxContextMenu" x:Name="TextBoxContextMenu">
<MenuItem x:Name="TextBoxContextMenuCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
<MenuItem x:Name="TextBoxContextMenuCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
<MenuItem x:Name="TextBoxContextMenuPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/>
</ContextMenu>
</Styles.Resources> </Styles.Resources>
<Style Selector="TextBox"> <Style Selector="TextBox">
@ -28,6 +33,7 @@
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" /> <Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" /> <Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
<Setter Property="FocusAdorner" Value="{x:Null}" /> <Setter Property="FocusAdorner" Value="{x:Null}" />
<Setter Property="ContextMenu" Value="{StaticResource DefaultTextBoxContextMenu}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<DockPanel> <DockPanel>

26
tests/Avalonia.LeakTests/ControlTests.cs

@ -449,13 +449,22 @@ namespace Avalonia.LeakTests
Assert.Same(window, FocusManager.Instance.Current); Assert.Same(window, FocusManager.Instance.Current);
// Context menu in resources means the baseline may not be 0.
var initialMenuCount = 0;
var initialMenuItemCount = 0;
dotMemory.Check(memory =>
{
initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
});
AttachShowAndDetachContextMenu(window); AttachShowAndDetachContextMenu(window);
Mock.Get(window.PlatformImpl).ResetCalls(); Mock.Get(window.PlatformImpl).ResetCalls();
dotMemory.Check(memory => dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount)); Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
dotMemory.Check(memory => dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount)); Assert.Equal(initialMenuItemCount, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
} }
} }
@ -484,14 +493,23 @@ namespace Avalonia.LeakTests
Assert.Same(window, FocusManager.Instance.Current); Assert.Same(window, FocusManager.Instance.Current);
// Context menu in resources means the baseline may not be 0.
var initialMenuCount = 0;
var initialMenuItemCount = 0;
dotMemory.Check(memory =>
{
initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
});
BuildAndShowContextMenu(window); BuildAndShowContextMenu(window);
BuildAndShowContextMenu(window); BuildAndShowContextMenu(window);
Mock.Get(window.PlatformImpl).ResetCalls(); Mock.Get(window.PlatformImpl).ResetCalls();
dotMemory.Check(memory => dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount)); Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
dotMemory.Check(memory => dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount)); Assert.Equal(initialMenuItemCount, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
} }
} }

Loading…
Cancel
Save