diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs index b6b2ac7181..089d1af197 100644 --- a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs @@ -32,13 +32,13 @@ namespace ControlCatalog.Pages 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); } 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(); } @@ -46,7 +46,7 @@ namespace ControlCatalog.Pages 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(); dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty); @@ -56,7 +56,7 @@ namespace ControlCatalog.Pages 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; } @@ -64,7 +64,7 @@ namespace ControlCatalog.Pages 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 filesPath = (ClipboardContent.Text ?? string.Empty) @@ -110,7 +110,7 @@ namespace ControlCatalog.Pages 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; @@ -120,7 +120,7 @@ namespace ControlCatalog.Pages 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(); ClipboardContent.Text = string.Join(Environment.NewLine, formats); @@ -129,7 +129,7 @@ namespace ControlCatalog.Pages private async void Clear(object sender, RoutedEventArgs args) { - if (Application.Current!.Clipboard is { } clipboard) + if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard) { await clipboard.ClearAsync(); } diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index 27dcfe8645..eaf09850d1 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -6,9 +6,7 @@ using Android.Views; using Android.Views.InputMethods; using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Controls.Presenters; -using Avalonia.Input; using Avalonia.Input.TextInput; -using Avalonia.Reactive; namespace Avalonia.Android { diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index daecb58a60..984d3ed5fc 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -38,7 +38,6 @@ namespace Avalonia.Android Options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions(); AvaloniaLocator.CurrentMutable - .Bind().ToTransient() .Bind().ToTransient() .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToSingleton() diff --git a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs index d1a116345b..8564363889 100644 --- a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs +++ b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs @@ -2,32 +2,26 @@ using System; using System.Threading.Tasks; using Android.Content; -using Android.Runtime; -using Android.Views; using Avalonia.Input; using Avalonia.Input.Platform; -using Avalonia.Platform; namespace Avalonia.Android.Platform { internal class ClipboardImpl : IClipboard { - private Context context = (AvaloniaLocator.Current.GetService() as View).Context; + private ClipboardManager? _clipboardManager; - private ClipboardManager ClipboardManager + internal ClipboardImpl(ClipboardManager? value) { - get - { - return this.context.GetSystemService(Context.ClipboardService).JavaCast(); - } + _clipboardManager = value; } public Task GetTextAsync() { - if (ClipboardManager.HasPrimaryClip) + if (_clipboardManager?.HasPrimaryClip == true) { - return Task.FromResult(ClipboardManager.PrimaryClip.GetItemAt(0).Text); + return Task.FromResult(_clipboardManager.PrimaryClip.GetItemAt(0).Text); } return Task.FromResult(null); @@ -35,15 +29,25 @@ namespace Avalonia.Android.Platform public Task SetTextAsync(string text) { + if(_clipboardManager == null) + { + return Task.CompletedTask; + } + ClipData clip = ClipData.NewPlainText("text", text); - ClipboardManager.PrimaryClip = clip; + _clipboardManager.PrimaryClip = clip; return Task.FromResult(null); } public Task ClearAsync() { - ClipboardManager.PrimaryClip = null; + if (_clipboardManager == null) + { + return Task.CompletedTask; + } + + _clipboardManager.PrimaryClip = null; return Task.FromResult(null); } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index b8d80a50ff..17726e6353 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -3,7 +3,10 @@ using System.Collections.Generic; using Android.App; using Android.Content; using Android.Graphics; +using Android.Graphics.Drawables; +using Android.OS; using Android.Runtime; +using Android.Text; using Android.Views; using Android.Views.InputMethods; using Avalonia.Android.Platform.Specific; @@ -13,6 +16,7 @@ using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.OpenGL.Egl; @@ -22,13 +26,7 @@ using Avalonia.Platform.Storage; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Java.Lang; -using Java.Util; -using Math = System.Math; -using AndroidRect = Android.Graphics.Rect; -using Window = Android.Views.Window; -using Android.Graphics.Drawables; -using Android.OS; -using Android.Text; +using ClipboardManager = Android.Content.ClipboardManager; namespace Avalonia.Android.Platform.SkiaPlatform { @@ -44,6 +42,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform private readonly IStorageProvider _storageProvider; private readonly ISystemNavigationManagerImpl _systemNavigationManager; private readonly AndroidInsetsManager _insetsManager; + private readonly ClipboardImpl _clipboard; private ViewImpl _view; public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false) @@ -54,6 +53,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _pointerHelper = new AndroidMotionEventsHelper(this); _gl = new EglGlPlatformSurface(this); _framebuffer = new FramebufferManager(this); + _clipboard = new ClipboardImpl(avaloniaView.Context?.GetSystemService(Context.ClipboardService).JavaCast()); RenderScaling = _view.Scaling; @@ -408,6 +408,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform return _insetsManager; } + if(featureType == typeof(IClipboard)) + { + return _clipboard; + } + return null; } } diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 72ebf10984..944e7b737a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -6133,8 +6133,10 @@ namespace Avalonia.Controls private async void CopyToClipboard(string text) { - var clipboard = ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))); - await clipboard.SetTextAsync(text); + var clipboard = TopLevel.GetTopLevel(this)?.Clipboard; + + if (clipboard != null) + await clipboard.SetTextAsync(text); } /// diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 6d9a6bd493..be3d5424fb 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -36,8 +36,6 @@ namespace Avalonia /// private DataTemplates? _dataTemplates; - private readonly Lazy _clipboard = - new Lazy(() => (IClipboard?)AvaloniaLocator.Current.GetService(typeof(IClipboard))); private Styles? _styles; private IResourceDictionary? _resources; private bool _notifyingResourcesChanged; @@ -141,11 +139,6 @@ namespace Avalonia private set; } - /// - /// Gets the application clipboard. - /// - public IClipboard? Clipboard => _clipboard.Value; - /// /// Gets the application's global resource dictionary. /// diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs index 8fc9477dc7..f54e8b19db 100644 --- a/src/Avalonia.Controls/MaskedTextBox.cs +++ b/src/Avalonia.Controls/MaskedTextBox.cs @@ -210,7 +210,7 @@ namespace Avalonia.Controls 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) return; diff --git a/src/Avalonia.Controls/SelectableTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs index 1007001f05..60b0a77f30 100644 --- a/src/Avalonia.Controls/SelectableTextBlock.cs +++ b/src/Avalonia.Controls/SelectableTextBlock.cs @@ -8,6 +8,7 @@ using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Media.TextFormatting; +using Avalonia.Platform; using Avalonia.Utilities; namespace Avalonia.Controls @@ -120,8 +121,10 @@ namespace Avalonia.Controls if (!eventArgs.Handled) { - await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard))) - .SetTextAsync(text); + var clipboard = TopLevel.GetTopLevel(this)?.Clipboard; + + if (clipboard != null) + await clipboard.SetTextAsync(text); } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index d244290c89..9bb68ba419 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -18,6 +18,7 @@ using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Automation.Peers; using Avalonia.Threading; +using Avalonia.Platform; namespace Avalonia.Controls { @@ -1044,8 +1045,13 @@ namespace Avalonia.Controls if (!eventArgs.Handled) { 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(); } } @@ -1066,8 +1072,10 @@ namespace Avalonia.Controls RaiseEvent(eventArgs); if (!eventArgs.Handled) { - await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard))) - .SetTextAsync(text); + var clipboard = TopLevel.GetTopLevel(this)?.Clipboard; + + if (clipboard != null) + await clipboard.SetTextAsync(text); } } @@ -1083,7 +1091,12 @@ namespace Avalonia.Controls 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)) { diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index bf0de1d79f..8781884f9c 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -394,6 +394,11 @@ namespace Avalonia.Controls public IInsetsManager? InsetsManager => PlatformImpl?.TryGetFeature(); + /// + /// Gets the platform's clipboard implementation + /// + public IClipboard? Clipboard => PlatformImpl?.TryGetFeature(); + /// Point IRenderRoot.PointToClient(PixelPoint p) { diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs index 808153cc4a..ab0a668607 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs @@ -48,7 +48,6 @@ namespace Avalonia.DesignerSupport.Remote s_transport = transport; var instance = new PreviewerWindowingPlatform(); AvaloniaLocator.CurrentMutable - .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(Keyboard) .Bind().ToSingleton() diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index a8f609c507..5bc9909ef3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -76,12 +76,6 @@ namespace Avalonia.Diagnostics.Controls public Input.InputManager? InputManager => _application.InputManager; - /// - /// Gets the application clipboard. - /// - public Input.Platform.IClipboard? Clipboard => - _application.Clipboard; - /// /// Gets the application's global resource dictionary. /// diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs index 16973a96ef..e3a67806fe 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Data; +using Avalonia.Input.Platform; using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Media; @@ -7,7 +8,7 @@ namespace Avalonia.Diagnostics.ViewModels { 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) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 1951914273..b9b4ccb0b4 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -59,6 +59,8 @@ namespace Avalonia.Diagnostics.ViewModels 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. foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator)) { @@ -91,7 +93,7 @@ namespace Avalonia.Diagnostics.ViewModels var resourceKey = resourceInfo.Value.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 { @@ -99,11 +101,11 @@ namespace Avalonia.Diagnostics.ViewModels if (isBinding) { - setterVm = new BindingSetterViewModel(regularSetter.Property, setterValue); + setterVm = new BindingSetterViewModel(regularSetter.Property, setterValue, clipboard); } else { - setterVm = new SetterViewModel(regularSetter.Property, setterValue); + setterVm = new SetterViewModel(regularSetter.Property, setterValue, clipboard); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs index 5202ac963e..2d9fe8e8bd 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs +++ b/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 { @@ -10,7 +11,7 @@ namespace Avalonia.Diagnostics.ViewModels 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; Tint = isDynamic ? Brushes.Orange : Brushes.Brown; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs index 559ed49911..ee64bce78c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs @@ -25,13 +25,17 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _isVisible, value); } - public SetterViewModel(AvaloniaProperty property, object? value) + private IClipboard? _clipboard; + + public SetterViewModel(AvaloniaProperty property, object? value, IClipboard? clipboard) { Property = property; Name = property.Name; Value = value; IsActive = true; IsVisible = true; + + _clipboard = clipboard; } public virtual void CopyValue() @@ -51,11 +55,9 @@ namespace Avalonia.Diagnostics.ViewModels CopyToClipboard(Property.Name); } - protected static void CopyToClipboard(string value) + protected void CopyToClipboard(string value) { - var clipboard = AvaloniaLocator.Current.GetService(); - - clipboard?.SetTextAsync(value); + _clipboard?.SetTextAsync(value); } } } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 15e2c696ac..a801474f21 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using Avalonia.Automation.Peers; using Avalonia.Controls; -using Avalonia.Controls.Platform; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Input.Raw; using Avalonia.Media.Imaging; using Avalonia.Platform; @@ -254,6 +253,11 @@ namespace Avalonia.Headless return new NoopStorageProvider(); } + if(featureType == typeof(IClipboard)) + { + return AvaloniaLocator.Current.GetRequiredService(); + } + return null; } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 4357f3b705..0dff46057e 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -7,6 +7,7 @@ using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Input.Raw; using Avalonia.Native.Interop; using Avalonia.OpenGL; @@ -528,6 +529,11 @@ namespace Avalonia.Native return _platformBehaviorInhibition; } + if (featureType == typeof(IClipboard)) + { + return AvaloniaLocator.Current.GetRequiredService(); + } + return null; } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index e04a53dcab..8bd84215ed 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -23,6 +23,7 @@ using Avalonia.Threading; using Avalonia.X11.Glx; using Avalonia.X11.NativeDialogs; using static Avalonia.X11.XLib; +using Avalonia.Input.Platform; // ReSharper disable IdentifierTypo // ReSharper disable StringLiteralTypo @@ -828,6 +829,11 @@ namespace Avalonia.X11 return _nativeControlHost; } + if (featureType == typeof(IClipboard)) + { + return AvaloniaLocator.Current.GetRequiredService(); + } + return null; } diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 7c5418dbeb..d33f773bfa 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -8,6 +8,7 @@ using Avalonia.Browser.Storage; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Platform; @@ -31,6 +32,7 @@ namespace Avalonia.Browser private readonly INativeControlHostImpl _nativeControlHost; private readonly IStorageProvider _storageProvider; private readonly ISystemNavigationManagerImpl _systemNavigationManager; + private readonly ClipboardImpl _clipboard; private readonly IInsetsManager? _insetsManager; public BrowserTopLevelImpl(AvaloniaView avaloniaView) @@ -46,7 +48,7 @@ namespace Avalonia.Browser _nativeControlHost = _avaloniaView.GetNativeControlHostImpl(); _storageProvider = new BrowserStorageProvider(); _systemNavigationManager = new BrowserSystemNavigationManagerImpl(); - + _clipboard = new ClipboardImpl(); } public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds; @@ -282,6 +284,11 @@ namespace Avalonia.Browser return _insetsManager; } + if (featureType == typeof(IClipboard)) + { + return _clipboard; + } + return null; } } diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs index 0578a08ec3..88e378bd79 100644 --- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs +++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs @@ -36,7 +36,6 @@ namespace Avalonia.Browser s_keyboard = new KeyboardDevice(); AvaloniaLocator.CurrentMutable .Bind().ToSingleton() - .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(s_keyboard) .Bind().ToSingleton() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 419cb57e87..e066bbe5f6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -24,6 +24,7 @@ using Avalonia.Win32.OpenGl; using Avalonia.Win32.WinRT.Composition; using Avalonia.Win32.WinRT; using static Avalonia.Win32.Interop.UnmanagedMethods; +using Avalonia.Input.Platform; namespace Avalonia.Win32 { @@ -332,6 +333,11 @@ namespace Avalonia.Win32 return _storageProvider; } + if (featureType == typeof(IClipboard)) + { + return AvaloniaLocator.Current.GetRequiredService(); + } + return null; } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 09721ad181..ec6ea56d9d 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -4,6 +4,7 @@ using Avalonia.Controls; using Avalonia.Controls.Embedding; using Avalonia.Controls.Platform; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.iOS.Storage; @@ -87,6 +88,8 @@ namespace Avalonia.iOS private readonly INativeControlHostImpl _nativeControlHost; private readonly IStorageProvider _storageProvider; internal readonly InsetsManager _insetsManager; + private readonly ClipboardImpl _clipboard; + public AvaloniaView View => _view; public TopLevelImpl(AvaloniaView view) @@ -99,6 +102,7 @@ namespace Avalonia.iOS { view._topLevel.Padding = b ? default : _insetsManager.SafeAreaPadding; }; + _clipboard = new ClipboardImpl(); } public void Dispose() @@ -196,6 +200,11 @@ namespace Avalonia.iOS return _insetsManager; } + if (featureType == typeof(IClipboard)) + { + return _clipboard; + } + return null; } } diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index 5bcd1eae84..9187187648 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -39,7 +39,6 @@ namespace Avalonia.iOS .Bind().ToConstant(GlFeature) .Bind().ToConstant(new CursorFactoryStub()) .Bind().ToConstant(new WindowingPlatformStub()) - .Bind().ToConstant(new ClipboardImpl()) .Bind().ToSingleton() .Bind().ToConstant(new PlatformIconLoaderStub()) .Bind().ToSingleton() diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index 2732f6a5fb..19009416ef 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -9,8 +9,10 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; +using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Rendering; using Avalonia.UnitTests; using Moq; using Xunit; @@ -820,18 +822,25 @@ namespace Avalonia.Controls.UnitTests SelectionStart = selectionStart, SelectionEnd = selectionEnd }; - + + var impl = CreateMockTopLevelImpl(); + var topLevel = new TestTopLevel(impl.Object) + { + Template = CreateTopLevelTemplate() + }; + topLevel.Content = target; + + topLevel.ApplyTemplate(); + topLevel.LayoutManager.ExecuteInitialLayoutPass(); + target.ApplyTemplate(); if (fromClipboard) { - AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - - var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService(); - clipboard.SetTextAsync(textInput).GetAwaiter().GetResult(); + topLevel.Clipboard?.SetTextAsync(textInput).GetAwaiter().GetResult(); RaiseKeyEvent(target, Key.V, KeyModifiers.Control); - clipboard.ClearAsync().GetAwaiter().GetResult(); + topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult(); } else { @@ -859,10 +868,18 @@ namespace Avalonia.Controls.UnitTests AcceptsReturn = 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.SelectionEnd = 3; - AvaloniaLocator.CurrentMutable - .Bind().ToSingleton(); RaiseKeyEvent(target, key, modifiers); 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; @@ -976,6 +993,40 @@ namespace Avalonia.Controls.UnitTests public Task 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 CreateMockTopLevelImpl() + { + var clipboard = new Mock(); + clipboard.Setup(r => r.CreateRenderer(It.IsAny())) + .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 CreateTopLevelTemplate() + { + return new FuncControlTemplate((x, scope) => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], + }.RegisterInNameScope(scope)); + } + private class TestContextMenu : ContextMenu { public TestContextMenu() diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 3d8f823621..0f0fd8f6c4 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -11,6 +11,7 @@ using Avalonia.Input.Platform; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Rendering; using Avalonia.UnitTests; using Moq; using Xunit; @@ -760,18 +761,24 @@ namespace Avalonia.Controls.UnitTests SelectionStart = selectionStart, 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); if (fromClipboard) { - AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - - var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService(); - clipboard.SetTextAsync(textInput).GetAwaiter().GetResult(); + topLevel.Clipboard?.SetTextAsync(textInput).GetAwaiter().GetResult(); RaiseKeyEvent(target, Key.V, KeyModifiers.Control); - clipboard.ClearAsync().GetAwaiter().GetResult(); + topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult(); } else { @@ -799,11 +806,19 @@ namespace Avalonia.Controls.UnitTests AcceptsReturn = 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.SelectionStart = 1; target.SelectionEnd = 3; - AvaloniaLocator.CurrentMutable - .Bind().ToSingleton(); RaiseKeyEvent(target, key, modifiers); RaiseKeyEvent(target, Key.Z, KeyModifiers.Control); // undo @@ -872,15 +887,21 @@ namespace Avalonia.Controls.UnitTests 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().ToSingleton(); + target.Measure(Size.Infinity); - var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService(); - clipboard.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult(); + topLevel.Clipboard?.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult(); RaiseKeyEvent(target, Key.V, KeyModifiers.Control); - clipboard.ClearAsync().GetAwaiter().GetResult(); + topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult(); RaiseTextEvent(target, Environment.NewLine); @@ -1176,7 +1197,41 @@ namespace Avalonia.Controls.UnitTests public Task 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 CreateMockTopLevelImpl() + { + var clipboard = new Mock(); + clipboard.Setup(r => r.CreateRenderer(It.IsAny())) + .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 CreateTopLevelTemplate() + { + return new FuncControlTemplate((x, scope) => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], + }.RegisterInNameScope(scope)); + } + private class TestContextMenu : ContextMenu { public TestContextMenu() diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 2644e7184a..62b5d889a8 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -2,6 +2,7 @@ using System; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.LogicalTree; @@ -11,6 +12,7 @@ using Avalonia.Styling; using Avalonia.UnitTests; using Moq; using Xunit; +using static Avalonia.Controls.UnitTests.MaskedTextBoxTests; namespace Avalonia.Controls.UnitTests {