diff --git a/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs b/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs index bb9dc66f5a..4ed91c248d 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs +++ b/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 BackRequested; - public AndroidSystemNavigationManager(IActivityNavigationService? navigationService) + public AndroidSystemNavigationManagerImpl(IActivityNavigationService? navigationService) { if(navigationService != null) { diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index f451fddeb7..693a26f3bd 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/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 _keyboardHelper; private readonly AndroidMotionEventsHelper _pointerHelper; private readonly AndroidInputMethod _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 diff --git a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs b/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs index 66d4f083c2..1778031e5b 100644 --- a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs +++ b/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs @@ -23,5 +23,4 @@ public static class OptionalFeatureProviderExtensions rv = provider.TryGetFeature(); return rv != null; } - } diff --git a/src/Avalonia.Base/Platform/SystemNavigationManager.cs b/src/Avalonia.Base/Platform/SystemNavigationManagerImpl.cs similarity index 50% rename from src/Avalonia.Base/Platform/SystemNavigationManager.cs rename to src/Avalonia.Base/Platform/SystemNavigationManagerImpl.cs index 6165b2bb77..080cd8083e 100644 --- a/src/Avalonia.Base/Platform/SystemNavigationManager.cs +++ b/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? BackRequested; } diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index c976ad6255..3a4ae80cf4 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/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; } } diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index 18dc1b1264..6b9e378d3d 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/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(); if (_currentHost != null) { diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index c556ce7b02..9c1fb93a48 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/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(); if (Exporter != null) { Exporter.OnIsNativeMenuExportedChanged += delegate diff --git a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs index ffa79aa8d6..a2805e69e9 100644 --- a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs +++ b/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; } - } } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 5a27b70ba2..29156f4030 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -51,7 +51,7 @@ namespace Avalonia.Platform /// . /// [Unstable] - public interface ITopLevelImpl : IDisposable + public interface ITopLevelImpl : IOptionalFeatureProvider, IDisposable { /// /// Gets the client size of the toplevel. diff --git a/src/Avalonia.Controls/Platform/ITopLevelImplWithStorageProvider.cs b/src/Avalonia.Controls/Platform/ITopLevelImplWithStorageProvider.cs deleted file mode 100644 index b42040f3c3..0000000000 --- a/src/Avalonia.Controls/Platform/ITopLevelImplWithStorageProvider.cs +++ /dev/null @@ -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; } -} diff --git a/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs b/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs deleted file mode 100644 index a2e426ca08..0000000000 --- a/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs +++ /dev/null @@ -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; } - } -} diff --git a/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs b/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs index 149a978c54..3093169f04 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs +++ b/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; } - } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 5691fda19f..06a829c418 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/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() 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()?.CreateProvider(this) - ?? (PlatformImpl as ITopLevelImplWithStorageProvider)?.StorageProvider + ?? PlatformImpl?.TryGetFeature() ?? throw new InvalidOperationException("StorageProvider platform implementation is not available."); /// @@ -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(); } } diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 020e09526e..2da8f38ea9 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/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() { } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index effec9cc58..ea427e4c92 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/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 diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 6818929ef8..15e2c696ac 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/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) { diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 880a385744..f27d94b61a 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/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 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); + } } } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 41de2356e6..1f290acd86 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/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(); _mouse = new MouseDevice(); _cursorFactory = AvaloniaLocator.Current.GetService(); - 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; } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 88486d36bd..c78ef2350e 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/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>[] + _storageProvider = new CompositeStorageProvider(new Func>[] { () => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreate(Handle) : Task.FromResult(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; diff --git a/src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs b/src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs index cc7d8c3af1..bc38067f4a 100644 --- a/src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs +++ b/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? BackRequested; - public BrowserSystemNavigationManager() + public BrowserSystemNavigationManagerImpl() { NavigationHelper.AddBackHandler(() => { diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 3affcfb4c2..f1cd441f45 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/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; + } } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 5705d14edb..9dc7f08064 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/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; } } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 76fc4fa21d..aff533c443 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/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; } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index de1b463a07..1e0d92d442 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -35,10 +35,7 @@ namespace Avalonia.Win32 /// Window implementation for Win32 platform. /// [Unstable] - public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo, - ITopLevelImplWithNativeControlHost, - ITopLevelImplWithTextInputMethod, - ITopLevelImplWithStorageProvider + public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo { private static readonly List s_instances = new List(); @@ -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; diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 7442861597..2d6b93f818 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/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")] diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs index 510adb37b5..2d45c699f1 100644 --- a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs +++ b/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())).Returns(null); impl.Setup(r => r.CreateRenderer(It.IsAny())).Returns(renderer); impl.Setup(r => r.PointToScreen(It.IsAny())).Returns(p => new PixelPoint((int)p.X, (int)p.Y)); impl.Setup(r => r.PointToClient(It.IsAny())).Returns(p => new Point(p.X, p.Y)); diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index ca08f3bcb2..678fb5c163 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -465,6 +465,7 @@ namespace Avalonia.LeakTests var renderer = new Mock(); renderer.Setup(x => x.Dispose()); var impl = new Mock(); + impl.Setup(r => r.TryGetFeature(It.IsAny())).Returns(null); impl.SetupGet(x => x.RenderScaling).Returns(1); impl.SetupProperty(x => x.Closed); impl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs index 486177c61a..5ef09a4d0f 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -203,5 +203,7 @@ public class CompositorTestServices : IDisposable } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } + + public object TryGetFeature(Type featureType) => null; } -} \ No newline at end of file +} diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index e30f0fa5f3..7f28477d09 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/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())).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())).Returns(null); popupImpl.Setup(x => x.Dispose()).Callback(() => { popupImpl.Object.Closed?.Invoke();