瀏覽代碼

Merge pull request #10129 from AvaloniaUI/toplevelimpl-getfeature

Introduce TopLevelImpl.TryGetFeature instead of having multiple interfaces per feature
Nikita Tsukanov 2 年之前
父節點
當前提交
0b6e62a15f
共有 29 個文件被更改,包括 217 次插入121 次删除
  1. 2 2
      src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
  2. 32 14
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  3. 0 1
      src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
  4. 1 7
      src/Avalonia.Base/Platform/SystemNavigationManagerImpl.cs
  5. 2 0
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  6. 1 1
      src/Avalonia.Controls/NativeControlHost.cs
  7. 2 1
      src/Avalonia.Controls/NativeMenu.Export.cs
  8. 0 6
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  9. 1 1
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  10. 0 11
      src/Avalonia.Controls/Platform/ITopLevelImplWithStorageProvider.cs
  11. 0 13
      src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs
  12. 0 6
      src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs
  13. 4 5
      src/Avalonia.Controls/TopLevel.cs
  14. 11 3
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  15. 1 0
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  16. 9 2
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  17. 13 5
      src/Avalonia.Native/WindowImpl.cs
  18. 17 4
      src/Avalonia.Native/WindowImplBase.cs
  19. 32 13
      src/Avalonia.X11/X11Window.cs
  20. 2 2
      src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs
  21. 29 7
      src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
  22. 1 0
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  23. 2 0
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  24. 23 9
      src/Windows/Avalonia.Win32/WindowImpl.cs
  25. 24 7
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  26. 1 0
      tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
  27. 1 0
      tests/Avalonia.LeakTests/ControlTests.cs
  28. 3 1
      tests/Avalonia.UnitTests/CompositorTestServices.cs
  29. 3 0
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs

+ 2 - 2
src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs

@@ -4,11 +4,11 @@ using Avalonia.Platform;
 
 namespace Avalonia.Android.Platform
 {
-    internal class AndroidSystemNavigationManager : ISystemNavigationManager
+    internal class AndroidSystemNavigationManagerImpl : ISystemNavigationManagerImpl
     {
         public event EventHandler<RoutedEventArgs> BackRequested;
 
-        public AndroidSystemNavigationManager(IActivityNavigationService? navigationService)
+        public AndroidSystemNavigationManagerImpl(IActivityNavigationService? navigationService)
         {
             if(navigationService != null)
             {

+ 32 - 14
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -31,9 +31,7 @@ using Android.Graphics.Drawables;
 
 namespace Avalonia.Android.Platform.SkiaPlatform
 {
-    class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
-        ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
-        ITopLevelWithSystemNavigationManager
+    class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
     {
         private readonly IGlPlatformSurface _gl;
         private readonly IFramebufferPlatformSurface _framebuffer;
@@ -41,6 +39,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
         private readonly AndroidMotionEventsHelper _pointerHelper;
         private readonly AndroidInputMethod<ViewImpl> _textInputMethod;
+        private readonly INativeControlHostImpl _nativeControlHost;
+        private readonly IStorageProvider _storageProvider;
+        private readonly ISystemNavigationManagerImpl _systemNavigationManager;
         private ViewImpl _view;
 
         public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
@@ -57,10 +58,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
                 _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
 
-            NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
-            StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
+            _nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
+            _storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
 
-            SystemNavigationManager = new AndroidSystemNavigationManager(avaloniaView.Context as IActivityNavigationService);
+            _systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService);
         }
 
         public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@@ -294,14 +295,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
         public double Scaling => RenderScaling;
 
-        public ITextInputMethodImpl TextInputMethod => _textInputMethod;
-
-        public INativeControlHostImpl NativeControlHost { get; }
-        
-        public IStorageProvider StorageProvider { get; }
-
-        public ISystemNavigationManager SystemNavigationManager { get; }
-
         public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
         {
             if (TransparencyLevel != transparencyLevel)
@@ -386,6 +379,31 @@ namespace Avalonia.Android.Platform.SkiaPlatform
                 }
             }
         }
+        
+        public virtual object TryGetFeature(Type featureType)
+        {
+            if (featureType == typeof(IStorageProvider))
+            {
+                return _storageProvider;
+            }
+
+            if (featureType == typeof(ITextInputMethodImpl))
+            {
+                return _textInputMethod;
+            }
+
+            if (featureType == typeof(ISystemNavigationManagerImpl))
+            {
+                return _systemNavigationManager;
+            }
+
+            if (featureType == typeof(INativeControlHostImpl))
+            {
+                return _nativeControlHost;
+            }
+
+            return null;
+        }
     }
 
     internal class AvaloniaInputConnection : BaseInputConnection

+ 0 - 1
src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs

@@ -23,5 +23,4 @@ public static class OptionalFeatureProviderExtensions
         rv = provider.TryGetFeature<T>();
         return rv != null;
     }
-        
 }

+ 1 - 7
src/Avalonia.Base/Platform/SystemNavigationManager.cs → src/Avalonia.Base/Platform/SystemNavigationManagerImpl.cs

@@ -5,13 +5,7 @@ using Avalonia.Metadata;
 namespace Avalonia.Platform
 {
     [Unstable]
-    public interface ITopLevelWithSystemNavigationManager
-    {
-        ISystemNavigationManager SystemNavigationManager { get; }
-    }
-
-    [Unstable]
-    public interface ISystemNavigationManager
+    public interface ISystemNavigationManagerImpl
     {
         public event EventHandler<RoutedEventArgs>? BackRequested;
     }

+ 2 - 0
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@@ -94,5 +94,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
         public WindowTransparencyLevel TransparencyLevel { get; private set; }
 
         public IPopupImpl? CreatePopup() => null;
+        
+        public virtual object? TryGetFeature(Type featureType) => null;
     }
 }

+ 1 - 1
src/Avalonia.Controls/NativeControlHost.cs

@@ -58,7 +58,7 @@ namespace Avalonia.Controls
         private void UpdateHost()
         {
             _queuedForMoveResize = false;
-            _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost;
+            _currentHost = _currentRoot?.PlatformImpl?.TryGetFeature<INativeControlHostImpl>();
             
             if (_currentHost != null)
             {

+ 2 - 1
src/Avalonia.Controls/NativeMenu.Export.cs

@@ -1,6 +1,7 @@
 using System;
 using Avalonia.Controls.Platform;
 using Avalonia.Reactive;
+using Avalonia.Platform;
 
 namespace Avalonia.Controls
 {
@@ -21,7 +22,7 @@ namespace Avalonia.Controls
 
             public NativeMenuInfo(TopLevel target)
             {
-                Exporter = (target.PlatformImpl as ITopLevelImplWithNativeMenuExporter)?.NativeMenuExporter;
+                Exporter = target.PlatformImpl?.TryGetFeature<ITopLevelNativeMenuExporter>();
                 if (Exporter != null)
                 {
                     Exporter.OnIsNativeMenuExportedChanged += delegate

+ 0 - 6
src/Avalonia.Controls/Platform/INativeControlHostImpl.cs

@@ -29,10 +29,4 @@ namespace Avalonia.Controls.Platform
         void HideWithSize(Size size);
         void ShowInBounds(Rect rect);
     }
-
-    [Unstable]
-    public interface ITopLevelImplWithNativeControlHost
-    {
-        INativeControlHostImpl? NativeControlHost { get; }
-    }
 }

+ 1 - 1
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@@ -51,7 +51,7 @@ namespace Avalonia.Platform
     /// <see cref="IPopupImpl"/>.
     /// </remarks>
     [Unstable]
-    public interface ITopLevelImpl : IDisposable
+    public interface ITopLevelImpl : IOptionalFeatureProvider, IDisposable
     {
         /// <summary>
         /// Gets the client size of the toplevel.

+ 0 - 11
src/Avalonia.Controls/Platform/ITopLevelImplWithStorageProvider.cs

@@ -1,11 +0,0 @@
-using Avalonia.Metadata;
-using Avalonia.Platform;
-using Avalonia.Platform.Storage;
-
-namespace Avalonia.Controls.Platform;
-
-[Unstable]
-public interface ITopLevelImplWithStorageProvider : ITopLevelImpl
-{
-    public IStorageProvider StorageProvider { get; }
-}

+ 0 - 13
src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs

@@ -1,13 +0,0 @@
-using Avalonia.Input;
-using Avalonia.Input.TextInput;
-using Avalonia.Metadata;
-using Avalonia.Platform;
-
-namespace Avalonia.Controls.Platform
-{
-    [Unstable]
-    public interface ITopLevelImplWithTextInputMethod : ITopLevelImpl
-    {
-        public ITextInputMethodImpl? TextInputMethod { get; }
-    }
-}

+ 0 - 6
src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs

@@ -23,10 +23,4 @@ namespace Avalonia.Controls.Platform
     {
         INativeMenuExporter? NativeMenuExporter { get; }
     }
-
-    [Unstable]
-    public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl
-    {
-        ITopLevelNativeMenuExporter? NativeMenuExporter { get; }
-    }
 }

+ 4 - 5
src/Avalonia.Controls/TopLevel.cs

@@ -216,9 +216,9 @@ namespace Avalonia.Controls
             _pointerOverPreProcessor = new PointerOverPreProcessor(this);
             _pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor);
 
-            if(impl is ITopLevelWithSystemNavigationManager topLevelWithSystemNavigation)
+            if(impl.TryGetFeature<ISystemNavigationManagerImpl>() is {} systemNavigationManager)
             {
-                topLevelWithSystemNavigation.SystemNavigationManager.BackRequested += (s, e) =>
+                systemNavigationManager.BackRequested += (s, e) =>
                 {
                     e.RoutedEvent = BackRequestedEvent;
                     RaiseEvent(e);
@@ -382,7 +382,7 @@ namespace Avalonia.Controls
         
         public IStorageProvider StorageProvider => _storageProvider
             ??= AvaloniaLocator.Current.GetService<IStorageProviderFactory>()?.CreateProvider(this)
-            ?? (PlatformImpl as ITopLevelImplWithStorageProvider)?.StorageProvider
+            ?? PlatformImpl?.TryGetFeature<IStorageProvider>()
             ?? throw new InvalidOperationException("StorageProvider platform implementation is not available.");
         
         /// <inheritdoc/>
@@ -616,7 +616,6 @@ namespace Avalonia.Controls
             // Do nothing becuase TopLevel should't apply MirrorTransform on himself.
         }
 
-        ITextInputMethodImpl? ITextInputMethodRoot.InputMethod =>
-            (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod;
+        ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => PlatformImpl?.TryGetFeature<ITextInputMethodImpl>();
     }
 }

+ 11 - 3
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@@ -11,7 +11,7 @@ using Avalonia.Threading;
 
 namespace Avalonia.DesignerSupport.Remote
 {
-    class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl, ITopLevelImplWithStorageProvider
+    class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl
     {
         private readonly IAvaloniaRemoteTransportConnection _transport;
 
@@ -92,8 +92,16 @@ namespace Avalonia.DesignerSupport.Remote
 
         public bool NeedsManagedDecorations => false;
 
-        public IStorageProvider StorageProvider => new NoopStorageProvider();
-
+        public override object TryGetFeature(Type featureType)
+        {
+            if (featureType == typeof(IStorageProvider))
+            {
+                return new NoopStorageProvider();
+            }
+            
+            return base.TryGetFeature(featureType);
+        }
+        
         public void Activate()
         {
         }

+ 1 - 0
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@@ -194,6 +194,7 @@ namespace Avalonia.DesignerSupport.Remote
         public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
 
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
+        public object TryGetFeature(Type featureType) => null;
     }
 
     class ClipboardStub : IClipboard

+ 9 - 2
src/Avalonia.Headless/HeadlessWindowImpl.cs

@@ -18,7 +18,7 @@ using Avalonia.Utilities;
 
 namespace Avalonia.Headless
 {
-    class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow, ITopLevelImplWithStorageProvider
+    class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow
     {
         private IKeyboardDevice _keyboard;
         private Stopwatch _st = Stopwatch.StartNew();
@@ -247,8 +247,15 @@ namespace Avalonia.Headless
         public Action LostFocus { get; set; }
 
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
+        public object TryGetFeature(Type featureType)
+        {
+            if (featureType == typeof(IStorageProvider))
+            {
+                return new NoopStorageProvider();
+            }
 
-        public IStorageProvider StorageProvider => new NoopStorageProvider();
+            return null;
+        }
 
         void IHeadlessWindow.KeyPress(Key key, RawInputModifiers modifiers)
         {

+ 13 - 5
src/Avalonia.Native/WindowImpl.cs

@@ -11,14 +11,14 @@ using Avalonia.Platform.Interop;
 
 namespace Avalonia.Native
 {
-    internal class WindowImpl : WindowBaseImpl, IWindowImpl, ITopLevelImplWithNativeMenuExporter
+    internal class WindowImpl : WindowBaseImpl, IWindowImpl
     {
         private readonly AvaloniaNativePlatformOptions _opts;
         private readonly AvaloniaNativeGlPlatformGraphics _glFeature;
         IAvnWindow _native;
         private double _extendTitleBarHeight = -1;
         private DoubleClickHelper _doubleClickHelper;
-        
+        private readonly ITopLevelNativeMenuExporter _nativeMenuExporter;
 
         internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
             AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, opts, glFeature)
@@ -32,7 +32,7 @@ namespace Avalonia.Native
                 Init(_native = factory.CreateWindow(e, glFeature.SharedContext.Context), factory.CreateScreens());
             }
 
-            NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
+            _nativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
         }
 
         class WindowEvents : WindowBaseEvents, IAvnWindowEvents
@@ -209,8 +209,6 @@ namespace Avalonia.Native
 
         public Func<WindowCloseReason, bool> Closing { get; set; }
 
-        public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
-
         public void Move(PixelPoint point) => Position = point;
 
         public override IPopupImpl CreatePopup() =>
@@ -227,5 +225,15 @@ namespace Avalonia.Native
         {
             _native.SetEnabled(enable.AsComBool());
         }
+
+        public override object TryGetFeature(Type featureType)
+        {
+            if (featureType == typeof(ITopLevelNativeMenuExporter))
+            {
+                return _nativeMenuExporter;
+            }
+            
+            return base.TryGetFeature(featureType);
+        }
     }
 }

+ 17 - 4
src/Avalonia.Native/WindowImplBase.cs

@@ -47,7 +47,7 @@ namespace Avalonia.Native
     }
 
     internal abstract class WindowBaseImpl : IWindowBaseImpl,
-        IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
+        IFramebufferPlatformSurface
     {
         protected readonly IAvaloniaNativeFactory _factory;
         protected IInputRoot _inputRoot;
@@ -62,6 +62,7 @@ namespace Avalonia.Native
         private double _savedScaling;
         private GlPlatformSurface _glSurface;
         private NativeControlHostImpl _nativeControlHost;
+        private IStorageProvider _storageProvider;
 
         internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
             AvaloniaNativeGlPlatformGraphics glFeature)
@@ -72,7 +73,6 @@ namespace Avalonia.Native
             _keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
             _mouse = new MouseDevice();
             _cursorFactory = AvaloniaLocator.Current.GetService<ICursorFactory>();
-            StorageProvider = new SystemDialogs(this, _factory.CreateSystemDialogs());
         }
 
         protected void Init(IAvnWindowBase window, IAvnScreens screens)
@@ -87,6 +87,7 @@ namespace Avalonia.Native
             _savedLogicalSize = ClientSize;
             _savedScaling = RenderScaling;
             _nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
+            _storageProvider = new SystemDialogs(this, _factory.CreateSystemDialogs());
 
             var monitor = Screen.AllScreens.OrderBy(x => x.Scaling)
                     .FirstOrDefault(m => m.Bounds.Contains(Position));
@@ -508,9 +509,21 @@ namespace Avalonia.Native
         }
 
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0);
+        public virtual object TryGetFeature(Type featureType)
+        {
+            if (featureType == typeof(INativeControlHostImpl))
+            {
+                return _nativeControlHost;
+            }
+            
+            if (featureType == typeof(IStorageProvider))
+            {
+                return _storageProvider;
+            }
 
-        public IPlatformHandle Handle { get; private set; }
+            return null;
+        }
 
-        public IStorageProvider StorageProvider { get; }
+        public IPlatformHandle Handle { get; private set; }
     }
 }

+ 32 - 13
src/Avalonia.X11/X11Window.cs

@@ -27,11 +27,7 @@ using static Avalonia.X11.XLib;
 // ReSharper disable StringLiteralTypo
 namespace Avalonia.X11
 {
-    unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client,
-        ITopLevelImplWithNativeMenuExporter,
-        ITopLevelImplWithNativeControlHost,
-        ITopLevelImplWithTextInputMethod,
-        ITopLevelImplWithStorageProvider
+    unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client
     {
         private readonly AvaloniaX11Platform _platform;
         private readonly bool _popup;
@@ -43,6 +39,9 @@ namespace Avalonia.X11
         private readonly MouseDevice _mouse;
         private readonly TouchDevice _touch;
         private readonly IKeyboardDevice _keyboard;
+        private readonly ITopLevelNativeMenuExporter _nativeMenuExporter;
+        private readonly IStorageProvider _storageProvider;
+        private readonly X11NativeControlHost _nativeControlHost;
         private PixelPoint? _position;
         private PixelSize _realSize;
         private IntPtr _handle;
@@ -199,8 +198,8 @@ namespace Avalonia.X11
             if(_popup)
                 PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
             if (platform.Options.UseDBusMenu)
-                NativeMenuExporter = DBusMenuExporter.TryCreateTopLevelNativeMenu(_handle);
-            NativeControlHost = new X11NativeControlHost(_platform, this);
+                _nativeMenuExporter = DBusMenuExporter.TryCreateTopLevelNativeMenu(_handle);
+            _nativeControlHost = new X11NativeControlHost(_platform, this);
             InitializeIme();
             
             XChangeProperty(_x11.Display, _handle, _x11.Atoms.WM_PROTOCOLS, _x11.Atoms.XA_ATOM, 32,
@@ -213,7 +212,7 @@ namespace Avalonia.X11
                     _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref _xSyncCounter, 1);
             }
 
-            StorageProvider = new CompositeStorageProvider(new Func<Task<IStorageProvider>>[]
+            _storageProvider = new CompositeStorageProvider(new Func<Task<IStorageProvider>>[]
             {
                 () => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreate(Handle) : Task.FromResult<IStorageProvider>(null),
                 () => GtkSystemDialog.TryCreate(this),
@@ -796,6 +795,31 @@ namespace Avalonia.X11
             Cleanup();            
         }
 
+        public virtual object TryGetFeature(Type featureType)
+        {
+            if (featureType == typeof(ITopLevelNativeMenuExporter))
+            {
+                return _nativeMenuExporter;
+            }
+            
+            if (featureType == typeof(IStorageProvider))
+            {
+                return _storageProvider;
+            }
+
+            if (featureType == typeof(ITextInputMethodImpl))
+            {
+                return _ime;
+            }
+
+            if (featureType == typeof(INativeControlHostImpl))
+            {
+                return _nativeControlHost;
+            }
+
+            return null;
+        }
+        
         void Cleanup()
         {
             if (_rawEventGrouper != null)
@@ -1195,9 +1219,6 @@ namespace Avalonia.X11
         }
 
         public IPopupPositioner PopupPositioner { get; }
-        public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
-        public INativeControlHostImpl NativeControlHost { get; }
-        public ITextInputMethodImpl TextInputMethod => _ime;
 
         public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) =>
             _transparencyHelper?.SetTransparencyRequest(transparencyLevel);
@@ -1215,8 +1236,6 @@ namespace Avalonia.X11
 
         public bool NeedsManagedDecorations => false;
 
-        public IStorageProvider StorageProvider { get; }
-
         public class SurfacePlatformHandle : IPlatformNativeSurfaceHandle
         {
             private readonly X11Window _owner;

+ 2 - 2
src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs

@@ -5,11 +5,11 @@ using Avalonia.Platform;
 
 namespace Avalonia.Browser
 {
-    internal class BrowserSystemNavigationManager : ISystemNavigationManager
+    internal class BrowserSystemNavigationManagerImpl : ISystemNavigationManagerImpl
     {
         public event EventHandler<RoutedEventArgs>? BackRequested;
 
-        public BrowserSystemNavigationManager()
+        public BrowserSystemNavigationManagerImpl()
         {
             NavigationHelper.AddBackHandler(() =>
             {

+ 29 - 7
src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs

@@ -19,8 +19,7 @@ using Avalonia.Rendering.Composition;
 
 namespace Avalonia.Browser
 {
-    internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
-        ITopLevelWithSystemNavigationManager
+    internal class BrowserTopLevelImpl : ITopLevelImpl
     {
         private Size _clientSize;
         private IInputRoot? _inputRoot;
@@ -29,6 +28,9 @@ namespace Avalonia.Browser
         private readonly TouchDevice _touchDevice;
         private readonly PenDevice _penDevice;
         private string _currentCursor = CssCursor.Default;
+        private readonly INativeControlHostImpl _nativeControlHost;
+        private readonly IStorageProvider _storageProvider;
+        private readonly ISystemNavigationManagerImpl _systemNavigationManager;
 
         public BrowserTopLevelImpl(AvaloniaView avaloniaView)
         {
@@ -38,7 +40,9 @@ namespace Avalonia.Browser
             AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1);
             _touchDevice = new TouchDevice();
             _penDevice = new PenDevice();
-            NativeControlHost = _avaloniaView.GetNativeControlHostImpl();
+            _nativeControlHost = _avaloniaView.GetNativeControlHostImpl();
+            _storageProvider = new BrowserStorageProvider();
+            _systemNavigationManager = new BrowserSystemNavigationManagerImpl();
         }
 
         public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds;
@@ -236,11 +240,29 @@ namespace Avalonia.Browser
 
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
 
-        public ITextInputMethodImpl TextInputMethod => _avaloniaView;
+        public object? TryGetFeature(Type featureType)
+        {
+            if (featureType == typeof(IStorageProvider))
+            {
+                return _storageProvider;
+            }
 
-        public INativeControlHostImpl? NativeControlHost { get; }
-        public IStorageProvider StorageProvider { get; } = new BrowserStorageProvider();
+            if (featureType == typeof(ITextInputMethodImpl))
+            {
+                return _avaloniaView;
+            }
 
-        public ISystemNavigationManager SystemNavigationManager { get; } = new BrowserSystemNavigationManager();
+            if (featureType == typeof(ISystemNavigationManagerImpl))
+            {
+                return _systemNavigationManager;
+            }
+
+            if (featureType == typeof(INativeControlHostImpl))
+            {
+                return _nativeControlHost;
+            }
+
+            return null;
+        }
     }
 }

+ 1 - 0
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@@ -77,5 +77,6 @@ namespace Avalonia.LinuxFramebuffer
         public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
 
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
+        public object TryGetFeature(Type featureType) => null;
     }
 }

+ 2 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@@ -261,5 +261,7 @@ namespace Avalonia.Win32.Interop.Wpf
         public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
 
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
+        
+        public object TryGetFeature(Type featureType) => null;
     }
 }

+ 23 - 9
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -35,10 +35,7 @@ namespace Avalonia.Win32
     /// Window implementation for Win32 platform.
     /// </summary>
     [Unstable]
-    public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
-        ITopLevelImplWithNativeControlHost,
-        ITopLevelImplWithTextInputMethod,
-        ITopLevelImplWithStorageProvider
+    public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
     {
         private static readonly List<WindowImpl> s_instances = new List<WindowImpl>();
 
@@ -83,6 +80,7 @@ namespace Avalonia.Win32
         private readonly bool _wmPointerEnabled;
 
         private Win32NativeControlHost _nativeControlHost;
+        private IStorageProvider _storageProvider;
         private WndProc _wndProcDelegate;
         private string _className;
         private IntPtr _hwnd;
@@ -183,7 +181,7 @@ namespace Avalonia.Win32
             }
 
             Screen = new ScreenImpl();
-            StorageProvider = new Win32StorageProvider(this);
+            _storageProvider = new Win32StorageProvider(this);
 
             _nativeControlHost = new Win32NativeControlHost(this, _isUsingComposition);
             s_instances.Add(this);
@@ -322,6 +320,26 @@ namespace Avalonia.Win32
 
         private bool IsMouseInPointerEnabled => _wmPointerEnabled && IsMouseInPointerEnabled();
 
+        public object TryGetFeature(Type featureType)
+        {
+            if (featureType == typeof(ITextInputMethodImpl))
+            {
+                return Imm32InputMethod.Current;
+            }
+
+            if (featureType == typeof(INativeControlHostImpl))
+            {
+                return _nativeControlHost;
+            }
+            
+            if (featureType == typeof(IStorageProvider))
+            {
+                return _storageProvider;
+            }
+
+            return null;
+        }
+        
         public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
         {
             TransparencyLevel = EnableBlur(transparencyLevel);
@@ -1465,10 +1483,6 @@ namespace Avalonia.Win32
             public void Dispose() => _owner._resizeReason = _restore;
         }
 
-        public ITextInputMethodImpl TextInputMethod => Imm32InputMethod.Current;
-
-        public IStorageProvider StorageProvider { get; }
-
         private class WindowImplPlatformHandle : IPlatformNativeSurfaceHandle
         {
             private readonly WindowImpl _owner;

+ 24 - 7
src/iOS/Avalonia.iOS/AvaloniaView.cs

@@ -68,17 +68,18 @@ namespace Avalonia.iOS
             settings?.TraitCollectionDidChange();
         }
 
-        internal class TopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost,
-            ITopLevelImplWithStorageProvider
+        internal class TopLevelImpl : ITopLevelImpl
         {
             private readonly AvaloniaView _view;
+            private readonly INativeControlHostImpl _nativeControlHost;
+            private readonly IStorageProvider _storageProvider;
             public AvaloniaView View => _view;
 
             public TopLevelImpl(AvaloniaView view)
             {
                 _view = view;
-                NativeControlHost = new NativeControlHostImpl(_view);
-                StorageProvider = new IOSStorageProvider(view);
+                _nativeControlHost = new NativeControlHostImpl(_view);
+                _storageProvider = new IOSStorageProvider(view);
             }
 
             public void Dispose()
@@ -157,9 +158,25 @@ namespace Avalonia.iOS
             public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } =
                 new AcrylicPlatformCompensationLevels();
 
-            public ITextInputMethodImpl? TextInputMethod => _view;
-            public INativeControlHostImpl NativeControlHost { get; }
-            public IStorageProvider StorageProvider { get; }
+            public object? TryGetFeature(Type featureType)
+            {
+                if (featureType == typeof(IStorageProvider))
+                {
+                    return _storageProvider;
+                }
+
+                if (featureType == typeof(ITextInputMethodImpl))
+                {
+                    return _view;
+                }
+
+                if (featureType == typeof(INativeControlHostImpl))
+                {
+                    return _nativeControlHost;
+                }
+
+                return null;
+            }
         }
 
         [Export("layerClass")]

+ 1 - 0
tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs

@@ -35,6 +35,7 @@ public abstract class PointerTestsBase
         impl.DefaultValue = DefaultValue.Mock;
         impl.SetupAllProperties();
         impl.SetupGet(r => r.RenderScaling).Returns(1);
+        impl.Setup(r => r.TryGetFeature(It.IsAny<Type>())).Returns(null);
         impl.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer);
         impl.Setup(r => r.PointToScreen(It.IsAny<Point>())).Returns<Point>(p => new PixelPoint((int)p.X, (int)p.Y));
         impl.Setup(r => r.PointToClient(It.IsAny<PixelPoint>())).Returns<PixelPoint>(p => new Point(p.X, p.Y));

+ 1 - 0
tests/Avalonia.LeakTests/ControlTests.cs

@@ -465,6 +465,7 @@ namespace Avalonia.LeakTests
                 var renderer = new Mock<IRenderer>();
                 renderer.Setup(x => x.Dispose());
                 var impl = new Mock<IWindowImpl>();
+                impl.Setup(r => r.TryGetFeature(It.IsAny<Type>())).Returns(null);
                 impl.SetupGet(x => x.RenderScaling).Returns(1);
                 impl.SetupProperty(x => x.Closed);
                 impl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);

+ 3 - 1
tests/Avalonia.UnitTests/CompositorTestServices.cs

@@ -203,5 +203,7 @@ public class CompositorTestServices : IDisposable
         }
 
         public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
+        
+        public object TryGetFeature(Type featureType) => null;
     }
-}
+}

+ 3 - 0
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@@ -35,6 +35,8 @@ namespace Avalonia.UnitTests
             windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object);
             windowImpl.Setup(x => x.Position).Returns(() => position);
 
+            windowImpl.Setup(r => r.TryGetFeature(It.IsAny<Type>())).Returns(null);
+
             windowImpl.Setup(x => x.CreatePopup()).Returns(() =>
             {
                 return CreatePopupMock(windowImpl.Object).Object;
@@ -95,6 +97,7 @@ namespace Avalonia.UnitTests
             popupImpl.Setup(x => x.RenderScaling).Returns(1);
             popupImpl.Setup(x => x.PopupPositioner).Returns(positioner);
 
+            popupImpl.Setup(r => r.TryGetFeature(It.IsAny<Type>())).Returns(null);
             popupImpl.Setup(x => x.Dispose()).Callback(() =>
             {
                 popupImpl.Object.Closed?.Invoke();