Browse Source

move clipboard to TopLevel

pull/10762/head
Emmanuel Hansen 3 years ago
parent
commit
04c8b652c8
  1. 16
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  2. 2
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  3. 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  4. 30
      src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
  5. 19
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  6. 6
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  7. 7
      src/Avalonia.Controls/Application.cs
  8. 2
      src/Avalonia.Controls/MaskedTextBox.cs
  9. 7
      src/Avalonia.Controls/SelectableTextBlock.cs
  10. 23
      src/Avalonia.Controls/TextBox.cs
  11. 5
      src/Avalonia.Controls/TopLevel.cs
  12. 1
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  13. 6
      src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
  14. 3
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs
  15. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  16. 5
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs
  17. 12
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs
  18. 8
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  19. 6
      src/Avalonia.Native/WindowImplBase.cs
  20. 6
      src/Avalonia.X11/X11Window.cs
  21. 9
      src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
  22. 1
      src/Browser/Avalonia.Browser/WindowingPlatform.cs
  23. 6
      src/Windows/Avalonia.Win32/WindowImpl.cs
  24. 9
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  25. 1
      src/iOS/Avalonia.iOS/Platform.cs
  26. 69
      tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
  27. 83
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  28. 2
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

16
samples/ControlCatalog/Pages/ClipboardPage.xaml.cs

@ -32,13 +32,13 @@ namespace ControlCatalog.Pages
private async void CopyText(object? sender, RoutedEventArgs args) private async void CopyText(object? sender, RoutedEventArgs args)
{ {
if (Application.Current!.Clipboard is { } clipboard && ClipboardContent is { } clipboardContent) if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard && ClipboardContent is { } clipboardContent)
await clipboard.SetTextAsync(clipboardContent.Text ?? String.Empty); await clipboard.SetTextAsync(clipboardContent.Text ?? String.Empty);
} }
private async void PasteText(object? sender, RoutedEventArgs args) private async void PasteText(object? sender, RoutedEventArgs args)
{ {
if (Application.Current!.Clipboard is { } clipboard) if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{ {
ClipboardContent.Text = await clipboard.GetTextAsync(); ClipboardContent.Text = await clipboard.GetTextAsync();
} }
@ -46,7 +46,7 @@ namespace ControlCatalog.Pages
private async void CopyTextDataObject(object? sender, RoutedEventArgs args) private async void CopyTextDataObject(object? sender, RoutedEventArgs args)
{ {
if (Application.Current!.Clipboard is { } clipboard) if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{ {
var dataObject = new DataObject(); var dataObject = new DataObject();
dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty); dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
@ -56,7 +56,7 @@ namespace ControlCatalog.Pages
private async void PasteTextDataObject(object? sender, RoutedEventArgs args) private async void PasteTextDataObject(object? sender, RoutedEventArgs args)
{ {
if (Application.Current!.Clipboard is { } clipboard) if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{ {
ClipboardContent.Text = await clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty; ClipboardContent.Text = await clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty;
} }
@ -64,7 +64,7 @@ namespace ControlCatalog.Pages
private async void CopyFilesDataObject(object? sender, RoutedEventArgs args) private async void CopyFilesDataObject(object? sender, RoutedEventArgs args)
{ {
if (Application.Current!.Clipboard is { } clipboard) if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{ {
var storageProvider = TopLevel.GetTopLevel(this)!.StorageProvider; var storageProvider = TopLevel.GetTopLevel(this)!.StorageProvider;
var filesPath = (ClipboardContent.Text ?? string.Empty) var filesPath = (ClipboardContent.Text ?? string.Empty)
@ -110,7 +110,7 @@ namespace ControlCatalog.Pages
private async void PasteFilesDataObject(object? sender, RoutedEventArgs args) private async void PasteFilesDataObject(object? sender, RoutedEventArgs args)
{ {
if (Application.Current!.Clipboard is { } clipboard) if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{ {
var files = await clipboard.GetDataAsync(DataFormats.Files) as IEnumerable<Avalonia.Platform.Storage.IStorageItem>; var files = await clipboard.GetDataAsync(DataFormats.Files) as IEnumerable<Avalonia.Platform.Storage.IStorageItem>;
@ -120,7 +120,7 @@ namespace ControlCatalog.Pages
private async void GetFormats(object sender, RoutedEventArgs args) private async void GetFormats(object sender, RoutedEventArgs args)
{ {
if (Application.Current!.Clipboard is { } clipboard) if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{ {
var formats = await clipboard.GetFormatsAsync(); var formats = await clipboard.GetFormatsAsync();
ClipboardContent.Text = string.Join(Environment.NewLine, formats); ClipboardContent.Text = string.Join(Environment.NewLine, formats);
@ -129,7 +129,7 @@ namespace ControlCatalog.Pages
private async void Clear(object sender, RoutedEventArgs args) private async void Clear(object sender, RoutedEventArgs args)
{ {
if (Application.Current!.Clipboard is { } clipboard) if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{ {
await clipboard.ClearAsync(); await clipboard.ClearAsync();
} }

2
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -6,9 +6,7 @@ using Android.Views;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Input.TextInput; using Avalonia.Input.TextInput;
using Avalonia.Reactive;
namespace Avalonia.Android namespace Avalonia.Android
{ {

1
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -38,7 +38,6 @@ namespace Avalonia.Android
Options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions(); Options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions();
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToTransient<ClipboardImpl>()
.Bind<ICursorFactory>().ToTransient<CursorFactory>() .Bind<ICursorFactory>().ToTransient<CursorFactory>()
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub()) .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>() .Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()

30
src/Android/Avalonia.Android/Platform/ClipboardImpl.cs

@ -2,32 +2,26 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.Content; using Android.Content;
using Android.Runtime;
using Android.Views;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Platform;
namespace Avalonia.Android.Platform namespace Avalonia.Android.Platform
{ {
internal class ClipboardImpl : IClipboard internal class ClipboardImpl : IClipboard
{ {
private Context context = (AvaloniaLocator.Current.GetService<IWindowImpl>() as View).Context; private ClipboardManager? _clipboardManager;
private ClipboardManager ClipboardManager internal ClipboardImpl(ClipboardManager? value)
{ {
get _clipboardManager = value;
{
return this.context.GetSystemService(Context.ClipboardService).JavaCast<ClipboardManager>();
}
} }
public Task<string> GetTextAsync() public Task<string> GetTextAsync()
{ {
if (ClipboardManager.HasPrimaryClip) if (_clipboardManager?.HasPrimaryClip == true)
{ {
return Task.FromResult<string>(ClipboardManager.PrimaryClip.GetItemAt(0).Text); return Task.FromResult<string>(_clipboardManager.PrimaryClip.GetItemAt(0).Text);
} }
return Task.FromResult<string>(null); return Task.FromResult<string>(null);
@ -35,15 +29,25 @@ namespace Avalonia.Android.Platform
public Task SetTextAsync(string text) public Task SetTextAsync(string text)
{ {
if(_clipboardManager == null)
{
return Task.CompletedTask;
}
ClipData clip = ClipData.NewPlainText("text", text); ClipData clip = ClipData.NewPlainText("text", text);
ClipboardManager.PrimaryClip = clip; _clipboardManager.PrimaryClip = clip;
return Task.FromResult<object>(null); return Task.FromResult<object>(null);
} }
public Task ClearAsync() public Task ClearAsync()
{ {
ClipboardManager.PrimaryClip = null; if (_clipboardManager == null)
{
return Task.CompletedTask;
}
_clipboardManager.PrimaryClip = null;
return Task.FromResult<object>(null); return Task.FromResult<object>(null);
} }

19
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -3,7 +3,10 @@ using System.Collections.Generic;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Text;
using Android.Views; using Android.Views;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific;
@ -13,6 +16,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Input.TextInput; using Avalonia.Input.TextInput;
using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Egl;
@ -22,13 +26,7 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Java.Lang; using Java.Lang;
using Java.Util; using ClipboardManager = Android.Content.ClipboardManager;
using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Window = Android.Views.Window;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Text;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
{ {
@ -44,6 +42,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly IStorageProvider _storageProvider; private readonly IStorageProvider _storageProvider;
private readonly ISystemNavigationManagerImpl _systemNavigationManager; private readonly ISystemNavigationManagerImpl _systemNavigationManager;
private readonly AndroidInsetsManager _insetsManager; private readonly AndroidInsetsManager _insetsManager;
private readonly ClipboardImpl _clipboard;
private ViewImpl _view; private ViewImpl _view;
public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false) public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
@ -54,6 +53,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_pointerHelper = new AndroidMotionEventsHelper(this); _pointerHelper = new AndroidMotionEventsHelper(this);
_gl = new EglGlPlatformSurface(this); _gl = new EglGlPlatformSurface(this);
_framebuffer = new FramebufferManager(this); _framebuffer = new FramebufferManager(this);
_clipboard = new ClipboardImpl(avaloniaView.Context?.GetSystemService(Context.ClipboardService).JavaCast<ClipboardManager>());
RenderScaling = _view.Scaling; RenderScaling = _view.Scaling;
@ -408,6 +408,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return _insetsManager; return _insetsManager;
} }
if(featureType == typeof(IClipboard))
{
return _clipboard;
}
return null; return null;
} }
} }

6
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -6133,8 +6133,10 @@ namespace Avalonia.Controls
private async void CopyToClipboard(string text) private async void CopyToClipboard(string text)
{ {
var clipboard = ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))); var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
await clipboard.SetTextAsync(text);
if (clipboard != null)
await clipboard.SetTextAsync(text);
} }
/// <summary> /// <summary>

7
src/Avalonia.Controls/Application.cs

@ -36,8 +36,6 @@ namespace Avalonia
/// </summary> /// </summary>
private DataTemplates? _dataTemplates; private DataTemplates? _dataTemplates;
private readonly Lazy<IClipboard?> _clipboard =
new Lazy<IClipboard?>(() => (IClipboard?)AvaloniaLocator.Current.GetService(typeof(IClipboard)));
private Styles? _styles; private Styles? _styles;
private IResourceDictionary? _resources; private IResourceDictionary? _resources;
private bool _notifyingResourcesChanged; private bool _notifyingResourcesChanged;
@ -141,11 +139,6 @@ namespace Avalonia
private set; private set;
} }
/// <summary>
/// Gets the application clipboard.
/// </summary>
public IClipboard? Clipboard => _clipboard.Value;
/// <summary> /// <summary>
/// Gets the application's global resource dictionary. /// Gets the application's global resource dictionary.
/// </summary> /// </summary>

2
src/Avalonia.Controls/MaskedTextBox.cs

@ -210,7 +210,7 @@ namespace Avalonia.Controls
if (keymap is not null && Match(keymap.Paste)) if (keymap is not null && Match(keymap.Paste))
{ {
var clipboard = (IClipboard?)AvaloniaLocator.Current.GetService(typeof(IClipboard)); var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard is null) if (clipboard is null)
return; return;

7
src/Avalonia.Controls/SelectableTextBlock.cs

@ -8,6 +8,7 @@ using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -120,8 +121,10 @@ namespace Avalonia.Controls
if (!eventArgs.Handled) if (!eventArgs.Handled)
{ {
await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard))) var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
.SetTextAsync(text);
if (clipboard != null)
await clipboard.SetTextAsync(text);
} }
} }

23
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 Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Platform;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -1044,8 +1045,13 @@ namespace Avalonia.Controls
if (!eventArgs.Handled) if (!eventArgs.Handled)
{ {
SnapshotUndoRedo(); SnapshotUndoRedo();
await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard)))
.SetTextAsync(text); var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard == null)
return;
await clipboard.SetTextAsync(text);
DeleteSelection(); DeleteSelection();
} }
} }
@ -1066,8 +1072,10 @@ namespace Avalonia.Controls
RaiseEvent(eventArgs); RaiseEvent(eventArgs);
if (!eventArgs.Handled) if (!eventArgs.Handled)
{ {
await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard))) var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
.SetTextAsync(text);
if (clipboard != null)
await clipboard.SetTextAsync(text);
} }
} }
@ -1083,7 +1091,12 @@ namespace Avalonia.Controls
return; return;
} }
var text = await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard))).GetTextAsync(); string? text = null;
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard != null)
text = await clipboard.GetTextAsync();
if (string.IsNullOrEmpty(text)) if (string.IsNullOrEmpty(text))
{ {

5
src/Avalonia.Controls/TopLevel.cs

@ -394,6 +394,11 @@ namespace Avalonia.Controls
public IInsetsManager? InsetsManager => PlatformImpl?.TryGetFeature<IInsetsManager>(); public IInsetsManager? InsetsManager => PlatformImpl?.TryGetFeature<IInsetsManager>();
/// <summary>
/// Gets the platform's clipboard implementation
/// </summary>
public IClipboard? Clipboard => PlatformImpl?.TryGetFeature<IClipboard>();
/// <inheritdoc/> /// <inheritdoc/>
Point IRenderRoot.PointToClient(PixelPoint p) Point IRenderRoot.PointToClient(PixelPoint p)
{ {

1
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -48,7 +48,6 @@ namespace Avalonia.DesignerSupport.Remote
s_transport = transport; s_transport = transport;
var instance = new PreviewerWindowingPlatform(); var instance = new PreviewerWindowingPlatform();
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
.Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>() .Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(Keyboard) .Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>() .Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()

6
src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs

@ -76,12 +76,6 @@ namespace Avalonia.Diagnostics.Controls
public Input.InputManager? InputManager => public Input.InputManager? InputManager =>
_application.InputManager; _application.InputManager;
/// <summary>
/// Gets the application clipboard.
/// </summary>
public Input.Platform.IClipboard? Clipboard =>
_application.Clipboard;
/// <summary> /// <summary>
/// Gets the application's global resource dictionary. /// Gets the application's global resource dictionary.
/// </summary> /// </summary>

3
src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs

@ -1,5 +1,6 @@
using System; using System;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input.Platform;
using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Media; using Avalonia.Media;
@ -7,7 +8,7 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
internal class BindingSetterViewModel : SetterViewModel internal class BindingSetterViewModel : SetterViewModel
{ {
public BindingSetterViewModel(AvaloniaProperty property, object? value) : base(property, value) public BindingSetterViewModel(AvaloniaProperty property, object? value, IClipboard? clipboard) : base(property, value, clipboard)
{ {
switch (value) switch (value)
{ {

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -59,6 +59,8 @@ namespace Avalonia.Diagnostics.ViewModels
var styleDiagnostics = styledElement.GetStyleDiagnostics(); var styleDiagnostics = styledElement.GetStyleDiagnostics();
var clipboard = TopLevel.GetTopLevel(_avaloniaObject as Visual)?.Clipboard;
// We need to place styles without activator first, such styles will be overwritten by ones with activators. // We need to place styles without activator first, such styles will be overwritten by ones with activators.
foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator)) foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator))
{ {
@ -91,7 +93,7 @@ namespace Avalonia.Diagnostics.ViewModels
var resourceKey = resourceInfo.Value.resourceKey; var resourceKey = resourceInfo.Value.resourceKey;
var resourceValue = styledElement.FindResource(resourceKey); var resourceValue = styledElement.FindResource(resourceKey);
setterVm = new ResourceSetterViewModel(regularSetter.Property, resourceKey, resourceValue, resourceInfo.Value.isDynamic); setterVm = new ResourceSetterViewModel(regularSetter.Property, resourceKey, resourceValue, resourceInfo.Value.isDynamic, clipboard);
} }
else else
{ {
@ -99,11 +101,11 @@ namespace Avalonia.Diagnostics.ViewModels
if (isBinding) if (isBinding)
{ {
setterVm = new BindingSetterViewModel(regularSetter.Property, setterValue); setterVm = new BindingSetterViewModel(regularSetter.Property, setterValue, clipboard);
} }
else else
{ {
setterVm = new SetterViewModel(regularSetter.Property, setterValue); setterVm = new SetterViewModel(regularSetter.Property, setterValue, clipboard);
} }
} }

5
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs

@ -1,4 +1,5 @@
using Avalonia.Media; using Avalonia.Input.Platform;
using Avalonia.Media;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
@ -10,7 +11,7 @@ namespace Avalonia.Diagnostics.ViewModels
public string ValueTypeTooltip { get; } public string ValueTypeTooltip { get; }
public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object? resourceValue, bool isDynamic) : base(property, resourceValue) public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object? resourceValue, bool isDynamic, IClipboard? clipboard) : base(property, resourceValue, clipboard)
{ {
Key = resourceKey; Key = resourceKey;
Tint = isDynamic ? Brushes.Orange : Brushes.Brown; Tint = isDynamic ? Brushes.Orange : Brushes.Brown;

12
src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs

@ -25,13 +25,17 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _isVisible, value); set => RaiseAndSetIfChanged(ref _isVisible, value);
} }
public SetterViewModel(AvaloniaProperty property, object? value) private IClipboard? _clipboard;
public SetterViewModel(AvaloniaProperty property, object? value, IClipboard? clipboard)
{ {
Property = property; Property = property;
Name = property.Name; Name = property.Name;
Value = value; Value = value;
IsActive = true; IsActive = true;
IsVisible = true; IsVisible = true;
_clipboard = clipboard;
} }
public virtual void CopyValue() public virtual void CopyValue()
@ -51,11 +55,9 @@ namespace Avalonia.Diagnostics.ViewModels
CopyToClipboard(Property.Name); CopyToClipboard(Property.Name);
} }
protected static void CopyToClipboard(string value) protected void CopyToClipboard(string value)
{ {
var clipboard = AvaloniaLocator.Current.GetService<IClipboard>(); _clipboard?.SetTextAsync(value);
clipboard?.SetTextAsync(value);
} }
} }
} }

8
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -1,12 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using Avalonia.Automation.Peers;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
@ -254,6 +253,11 @@ namespace Avalonia.Headless
return new NoopStorageProvider(); return new NoopStorageProvider();
} }
if(featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService<IClipboard>();
}
return null; return null;
} }

6
src/Avalonia.Native/WindowImplBase.cs

@ -7,6 +7,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.OpenGL; using Avalonia.OpenGL;
@ -528,6 +529,11 @@ namespace Avalonia.Native
return _platformBehaviorInhibition; return _platformBehaviorInhibition;
} }
if (featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService<IClipboard>();
}
return null; return null;
} }

6
src/Avalonia.X11/X11Window.cs

@ -23,6 +23,7 @@ using Avalonia.Threading;
using Avalonia.X11.Glx; using Avalonia.X11.Glx;
using Avalonia.X11.NativeDialogs; using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.XLib; using static Avalonia.X11.XLib;
using Avalonia.Input.Platform;
// ReSharper disable IdentifierTypo // ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo // ReSharper disable StringLiteralTypo
@ -828,6 +829,11 @@ namespace Avalonia.X11
return _nativeControlHost; return _nativeControlHost;
} }
if (featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService<IClipboard>();
}
return null; return null;
} }

9
src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs

@ -8,6 +8,7 @@ using Avalonia.Browser.Storage;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Input.TextInput; using Avalonia.Input.TextInput;
using Avalonia.Platform; using Avalonia.Platform;
@ -31,6 +32,7 @@ namespace Avalonia.Browser
private readonly INativeControlHostImpl _nativeControlHost; private readonly INativeControlHostImpl _nativeControlHost;
private readonly IStorageProvider _storageProvider; private readonly IStorageProvider _storageProvider;
private readonly ISystemNavigationManagerImpl _systemNavigationManager; private readonly ISystemNavigationManagerImpl _systemNavigationManager;
private readonly ClipboardImpl _clipboard;
private readonly IInsetsManager? _insetsManager; private readonly IInsetsManager? _insetsManager;
public BrowserTopLevelImpl(AvaloniaView avaloniaView) public BrowserTopLevelImpl(AvaloniaView avaloniaView)
@ -46,7 +48,7 @@ namespace Avalonia.Browser
_nativeControlHost = _avaloniaView.GetNativeControlHostImpl(); _nativeControlHost = _avaloniaView.GetNativeControlHostImpl();
_storageProvider = new BrowserStorageProvider(); _storageProvider = new BrowserStorageProvider();
_systemNavigationManager = new BrowserSystemNavigationManagerImpl(); _systemNavigationManager = new BrowserSystemNavigationManagerImpl();
_clipboard = new ClipboardImpl();
} }
public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds; public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds;
@ -282,6 +284,11 @@ namespace Avalonia.Browser
return _insetsManager; return _insetsManager;
} }
if (featureType == typeof(IClipboard))
{
return _clipboard;
}
return null; return null;
} }
} }

1
src/Browser/Avalonia.Browser/WindowingPlatform.cs

@ -36,7 +36,6 @@ namespace Avalonia.Browser
s_keyboard = new KeyboardDevice(); s_keyboard = new KeyboardDevice();
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IRuntimePlatform>().ToSingleton<BrowserRuntimePlatform>() .Bind<IRuntimePlatform>().ToSingleton<BrowserRuntimePlatform>()
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<ICursorFactory>().ToSingleton<CssCursorFactory>() .Bind<ICursorFactory>().ToSingleton<CssCursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(s_keyboard) .Bind<IKeyboardDevice>().ToConstant(s_keyboard)
.Bind<IPlatformSettings>().ToSingleton<BrowserPlatformSettings>() .Bind<IPlatformSettings>().ToSingleton<BrowserPlatformSettings>()

6
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -24,6 +24,7 @@ using Avalonia.Win32.OpenGl;
using Avalonia.Win32.WinRT.Composition; using Avalonia.Win32.WinRT.Composition;
using Avalonia.Win32.WinRT; using Avalonia.Win32.WinRT;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
using Avalonia.Input.Platform;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
@ -332,6 +333,11 @@ namespace Avalonia.Win32
return _storageProvider; return _storageProvider;
} }
if (featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService<IClipboard>();
}
return null; return null;
} }

9
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Embedding; using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Input.TextInput; using Avalonia.Input.TextInput;
using Avalonia.iOS.Storage; using Avalonia.iOS.Storage;
@ -87,6 +88,8 @@ namespace Avalonia.iOS
private readonly INativeControlHostImpl _nativeControlHost; private readonly INativeControlHostImpl _nativeControlHost;
private readonly IStorageProvider _storageProvider; private readonly IStorageProvider _storageProvider;
internal readonly InsetsManager _insetsManager; internal readonly InsetsManager _insetsManager;
private readonly ClipboardImpl _clipboard;
public AvaloniaView View => _view; public AvaloniaView View => _view;
public TopLevelImpl(AvaloniaView view) public TopLevelImpl(AvaloniaView view)
@ -99,6 +102,7 @@ namespace Avalonia.iOS
{ {
view._topLevel.Padding = b ? default : _insetsManager.SafeAreaPadding; view._topLevel.Padding = b ? default : _insetsManager.SafeAreaPadding;
}; };
_clipboard = new ClipboardImpl();
} }
public void Dispose() public void Dispose()
@ -196,6 +200,11 @@ namespace Avalonia.iOS
return _insetsManager; return _insetsManager;
} }
if (featureType == typeof(IClipboard))
{
return _clipboard;
}
return null; return null;
} }
} }

1
src/iOS/Avalonia.iOS/Platform.cs

@ -39,7 +39,6 @@ namespace Avalonia.iOS
.Bind<IPlatformGraphics>().ToConstant(GlFeature) .Bind<IPlatformGraphics>().ToConstant(GlFeature)
.Bind<ICursorFactory>().ToConstant(new CursorFactoryStub()) .Bind<ICursorFactory>().ToConstant(new CursorFactoryStub())
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub()) .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IClipboard>().ToConstant(new ClipboardImpl())
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>() .Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoaderStub()) .Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoaderStub())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>() .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()

69
tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs

@ -9,8 +9,10 @@ using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq; using Moq;
using Xunit; using Xunit;
@ -820,18 +822,25 @@ namespace Avalonia.Controls.UnitTests
SelectionStart = selectionStart, SelectionStart = selectionStart,
SelectionEnd = selectionEnd SelectionEnd = selectionEnd
}; };
var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
target.ApplyTemplate(); target.ApplyTemplate();
if (fromClipboard) if (fromClipboard)
{ {
AvaloniaLocator.CurrentMutable.Bind<IClipboard>().ToSingleton<ClipboardStub>(); topLevel.Clipboard?.SetTextAsync(textInput).GetAwaiter().GetResult();
var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService<IClipboard>();
clipboard.SetTextAsync(textInput).GetAwaiter().GetResult();
RaiseKeyEvent(target, Key.V, KeyModifiers.Control); RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
clipboard.ClearAsync().GetAwaiter().GetResult(); topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult();
} }
else else
{ {
@ -859,10 +868,18 @@ namespace Avalonia.Controls.UnitTests
AcceptsReturn = true, AcceptsReturn = true,
AcceptsTab = true AcceptsTab = true
}; };
var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
target.SelectionStart = 1; target.SelectionStart = 1;
target.SelectionEnd = 3; target.SelectionEnd = 3;
AvaloniaLocator.CurrentMutable
.Bind<Input.Platform.IClipboard>().ToSingleton<ClipboardStub>();
RaiseKeyEvent(target, key, modifiers); RaiseKeyEvent(target, key, modifiers);
RaiseKeyEvent(target, Key.Z, KeyModifiers.Control); // undo RaiseKeyEvent(target, Key.Z, KeyModifiers.Control); // undo
@ -951,7 +968,7 @@ namespace Avalonia.Controls.UnitTests
} }
} }
private class ClipboardStub : IClipboard // in order to get tests working that use the clipboard internal class ClipboardStub : IClipboard // in order to get tests working that use the clipboard
{ {
private string _text; private string _text;
@ -976,6 +993,40 @@ namespace Avalonia.Controls.UnitTests
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null); public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
} }
private class TestTopLevel : TopLevel
{
private readonly ILayoutManager _layoutManager;
public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null)
: base(impl)
{
_layoutManager = layoutManager ?? new LayoutManager(this);
}
protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
private static Mock<ITopLevelImpl> CreateMockTopLevelImpl()
{
var clipboard = new Mock<ITopLevelImpl>();
clipboard.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>()))
.Returns(RendererMocks.CreateRenderer().Object);
clipboard.Setup(r => r.TryGetFeature(typeof(IClipboard)))
.Returns(new ClipboardStub());
clipboard.SetupGet(x => x.RenderScaling).Returns(1);
return clipboard;
}
private static FuncControlTemplate<TestTopLevel> CreateTopLevelTemplate()
{
return new FuncControlTemplate<TestTopLevel>((x, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
}.RegisterInNameScope(scope));
}
private class TestContextMenu : ContextMenu private class TestContextMenu : ContextMenu
{ {
public TestContextMenu() public TestContextMenu()

83
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -11,6 +11,7 @@ using Avalonia.Input.Platform;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq; using Moq;
using Xunit; using Xunit;
@ -760,18 +761,24 @@ namespace Avalonia.Controls.UnitTests
SelectionStart = selectionStart, SelectionStart = selectionStart,
SelectionEnd = selectionEnd SelectionEnd = selectionEnd
}; };
var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
if (fromClipboard) if (fromClipboard)
{ {
AvaloniaLocator.CurrentMutable.Bind<IClipboard>().ToSingleton<ClipboardStub>(); topLevel.Clipboard?.SetTextAsync(textInput).GetAwaiter().GetResult();
var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService<IClipboard>();
clipboard.SetTextAsync(textInput).GetAwaiter().GetResult();
RaiseKeyEvent(target, Key.V, KeyModifiers.Control); RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
clipboard.ClearAsync().GetAwaiter().GetResult(); topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult();
} }
else else
{ {
@ -799,11 +806,19 @@ namespace Avalonia.Controls.UnitTests
AcceptsReturn = true, AcceptsReturn = true,
AcceptsTab = true AcceptsTab = true
}; };
var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
target.ApplyTemplate(); target.ApplyTemplate();
target.SelectionStart = 1; target.SelectionStart = 1;
target.SelectionEnd = 3; target.SelectionEnd = 3;
AvaloniaLocator.CurrentMutable
.Bind<Input.Platform.IClipboard>().ToSingleton<ClipboardStub>();
RaiseKeyEvent(target, key, modifiers); RaiseKeyEvent(target, key, modifiers);
RaiseKeyEvent(target, Key.Z, KeyModifiers.Control); // undo RaiseKeyEvent(target, Key.Z, KeyModifiers.Control); // undo
@ -872,15 +887,21 @@ namespace Avalonia.Controls.UnitTests
AcceptsReturn= true AcceptsReturn= true
}; };
target.Measure(Size.Infinity); var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<IClipboard>().ToSingleton<ClipboardStub>(); target.Measure(Size.Infinity);
var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService<IClipboard>(); topLevel.Clipboard?.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult();
clipboard.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult();
RaiseKeyEvent(target, Key.V, KeyModifiers.Control); RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
clipboard.ClearAsync().GetAwaiter().GetResult(); topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult();
RaiseTextEvent(target, Environment.NewLine); RaiseTextEvent(target, Environment.NewLine);
@ -1176,7 +1197,41 @@ namespace Avalonia.Controls.UnitTests
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null); public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
} }
private class TestTopLevel : TopLevel
{
private readonly ILayoutManager _layoutManager;
public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null)
: base(impl)
{
_layoutManager = layoutManager ?? new LayoutManager(this);
}
protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
private static Mock<ITopLevelImpl> CreateMockTopLevelImpl()
{
var clipboard = new Mock<ITopLevelImpl>();
clipboard.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>()))
.Returns(RendererMocks.CreateRenderer().Object);
clipboard.Setup(r => r.TryGetFeature(typeof(IClipboard)))
.Returns(new ClipboardStub());
clipboard.SetupGet(x => x.RenderScaling).Returns(1);
return clipboard;
}
private static FuncControlTemplate<TestTopLevel> CreateTopLevelTemplate()
{
return new FuncControlTemplate<TestTopLevel>((x, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
}.RegisterInNameScope(scope));
}
private class TestContextMenu : ContextMenu private class TestContextMenu : ContextMenu
{ {
public TestContextMenu() public TestContextMenu()

2
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -2,6 +2,7 @@ using System;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
@ -11,6 +12,7 @@ using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq; using Moq;
using Xunit; using Xunit;
using static Avalonia.Controls.UnitTests.MaskedTextBoxTests;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
{ {

Loading…
Cancel
Save