From 35f64af761bc80e7efbeffd0633c61afb39e95cb Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 27 Jul 2019 10:37:24 +0300 Subject: [PATCH] Make toplevels responsible of creating popups --- .../Avalonia.Android/AndroidPlatform.cs | 5 --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 + .../Offscreen/OffscreenTopLevelImpl.cs | 1 + .../Platform/ITopLevelImpl.cs | 2 + .../Platform/IWindowingPlatform.cs | 1 - .../Platform/PlatformManager.cs | 5 --- src/Avalonia.Controls/Primitives/Popup.cs | 37 +++++++++---------- src/Avalonia.Controls/Primitives/PopupRoot.cs | 8 ++-- src/Avalonia.Controls/ToolTip.cs | 3 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 + src/Avalonia.Native/AvaloniaNativePlatform.cs | 5 --- src/Avalonia.Native/PopupImpl.cs | 6 +++ src/Avalonia.Native/WindowImpl.cs | 5 +++ src/Avalonia.Native/WindowImplBase.cs | 3 +- src/Avalonia.X11/X11Platform.cs | 7 +--- src/Avalonia.X11/X11Window.cs | 10 +++-- .../FramebufferToplevelImpl.cs | 2 + .../Wpf/WpfTopLevelImpl.cs | 2 + src/Windows/Avalonia.Win32/Win32Platform.cs | 5 --- src/iOS/Avalonia.iOS/TopLevelImpl.cs | 2 + .../Primitives/PopupRootTests.cs | 12 +++--- 21 files changed, 63 insertions(+), 62 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 4e48811c35..c91b58311b 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -71,10 +71,5 @@ namespace Avalonia.Android { throw new NotSupportedException(); } - - public IPopupImpl CreatePopup() - { - return new PopupImpl(); - } } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index f42faeaa63..0d0d9db252 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -191,6 +191,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform } } + public IPopupImpl CreatePopup() => null; + ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(_view.Holder.Surface); } } diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 9c53dc0c10..29f0374301 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -61,5 +61,6 @@ namespace Avalonia.Controls.Embedding.Offscreen public Action Closed { get; set; } public abstract IMouseDevice MouseDevice { get; } + public IPopupImpl CreatePopup() => null; } } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 8d8ce35c38..cfbc0b1c4b 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -107,5 +107,7 @@ namespace Avalonia.Platform /// [CanBeNull] IMouseDevice MouseDevice { get; } + + IPopupImpl CreatePopup(); } } diff --git a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs index 5c2c1a8da3..a55bd63c6a 100644 --- a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs +++ b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs @@ -4,6 +4,5 @@ namespace Avalonia.Platform { IWindowImpl CreateWindow(); IEmbeddableWindowImpl CreateEmbeddableWindow(); - IPopupImpl CreatePopup(); } } diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index fa01b9e839..ef453274b8 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -41,10 +41,5 @@ namespace Avalonia.Controls.Platform throw new Exception("Could not CreateEmbeddableWindow(): IWindowingPlatform is not registered."); return platform.CreateEmbeddableWindow(); } - - public static IPopupImpl CreatePopup() - { - return AvaloniaLocator.Current.GetService().CreatePopup(); - } } } diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 058658357f..895094eded 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -218,9 +218,16 @@ namespace Avalonia.Controls.Primitives /// public void Open() { + if (PlacementTarget == null) + throw new InvalidOperationException("It's not valid to show a popup without a PlacementTarget"); + + + if (_topLevel == null && PlacementTarget != null) + _topLevel = PlacementTarget.GetSelfAndLogicalAncestors().First(x => x is TopLevel) as TopLevel; + if (_popupRoot == null) { - _popupRoot = new PopupRoot(DependencyResolver) + _popupRoot = new PopupRoot(_topLevel, DependencyResolver) { [~ContentControl.ContentProperty] = this[~ChildProperty], [~WidthProperty] = this[~WidthProperty], @@ -236,30 +243,22 @@ namespace Avalonia.Controls.Primitives _popupRoot.Position = GetPosition(); - if (_topLevel == null && PlacementTarget != null) + var window = _topLevel as Window; + if (window != null) { - _topLevel = PlacementTarget.GetSelfAndLogicalAncestors().First(x => x is TopLevel) as TopLevel; + window.Deactivated += WindowDeactivated; } - - if (_topLevel != null) + else { - var window = _topLevel as Window; - if (window != null) + var parentPopuproot = _topLevel as PopupRoot; + if (parentPopuproot?.Parent is Popup popup) { - window.Deactivated += WindowDeactivated; + popup.Closed += ParentClosed; } - else - { - var parentPopuproot = _topLevel as PopupRoot; - if (parentPopuproot?.Parent is Popup popup) - { - popup.Closed += ParentClosed; - } - } - _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel); - _nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick); } - + _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel); + _nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick); + PopupRootCreated?.Invoke(this, EventArgs.Empty); _popupRoot.Show(); diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index d2e8f1ab92..47863932d1 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -31,8 +31,8 @@ namespace Avalonia.Controls.Primitives /// /// Initializes a new instance of the class. /// - public PopupRoot() - : this(null) + public PopupRoot(TopLevel parent) + : this(parent, null) { } @@ -42,8 +42,8 @@ namespace Avalonia.Controls.Primitives /// /// The dependency resolver to use. If null the default dependency resolver will be used. /// - public PopupRoot(IAvaloniaDependencyResolver dependencyResolver) - : base(PlatformManager.CreatePopup(), dependencyResolver) + public PopupRoot(TopLevel parent, IAvaloniaDependencyResolver dependencyResolver) + : base(parent.PlatformImpl.CreatePopup(), dependencyResolver) { } diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index 28d1ba5e0f..8c23f4abdc 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -4,6 +4,7 @@ using System; using System.Reactive.Linq; using Avalonia.Controls.Primitives; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -234,7 +235,7 @@ namespace Avalonia.Controls { Close(); - _popup = new PopupRoot { Content = this, }; + _popup = new PopupRoot((TopLevel)control.GetVisualRoot()) {Content = this}; ((ISetLogicalParent)_popup).SetParent(control); _popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup, GetHorizontalOffset(control), GetVerticalOffset(control)); diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 9c547279d6..ddb8b62b6a 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -29,6 +29,8 @@ namespace Avalonia.DesignerSupport.Remote public Func Closing { get; set; } public Action Closed { get; set; } public IMouseDevice MouseDevice { get; } = new MouseDevice(); + public IPopupImpl CreatePopup() => null; + public PixelPoint Position { get; set; } public Action PositionChanged { get; set; } public WindowState WindowState { get; set; } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index adb27d348d..edde2176bd 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -97,11 +97,6 @@ namespace Avalonia.Native { throw new NotImplementedException(); } - - public IPopupImpl CreatePopup() - { - return new PopupImpl(_factory, _options); - } } public class AvaloniaNativeMacOptions diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index a470caa80e..976208b058 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -9,8 +9,12 @@ namespace Avalonia.Native { public class PopupImpl : WindowBaseImpl, IPopupImpl { + private readonly IAvaloniaNativeFactory _factory; + private readonly AvaloniaNativePlatformOptions _opts; public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(opts) { + _factory = factory; + _opts = opts; using (var e = new PopupEvents(this)) { Init(factory.CreatePopup(e), factory.CreateScreens()); @@ -35,5 +39,7 @@ namespace Avalonia.Native { } } + + public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts); } } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 076fe9ccae..c7857898d2 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -11,9 +11,13 @@ namespace Avalonia.Native { public class WindowImpl : WindowBaseImpl, IWindowImpl { + private readonly IAvaloniaNativeFactory _factory; + private readonly AvaloniaNativePlatformOptions _opts; IAvnWindow _native; public WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(opts) { + _factory = factory; + _opts = opts; using (var e = new WindowEvents(this)) { Init(_native = factory.CreateWindow(e), factory.CreateScreens()); @@ -100,5 +104,6 @@ namespace Avalonia.Native } public Func Closing { get; set; } + public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts); } } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 638879ba14..ae0a2f535b 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -15,7 +15,7 @@ using Avalonia.Threading; namespace Avalonia.Native { - public class WindowBaseImpl : IWindowBaseImpl, + public abstract class WindowBaseImpl : IWindowBaseImpl, IFramebufferPlatformSurface { IInputRoot _inputRoot; @@ -91,6 +91,7 @@ namespace Avalonia.Native public Action Resized { get; set; } public Action Closed { get; set; } public IMouseDevice MouseDevice => AvaloniaNativePlatform.MouseDevice; + public abstract IPopupImpl CreatePopup(); class FramebufferWrapper : ILockedFramebuffer diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 7bdc61eb28..9bdcaab82b 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -74,18 +74,13 @@ namespace Avalonia.X11 public IntPtr Display { get; set; } public IWindowImpl CreateWindow() { - return new X11Window(this, false); + return new X11Window(this, null); } public IEmbeddableWindowImpl CreateEmbeddableWindow() { throw new NotSupportedException(); } - - public IPopupImpl CreatePopup() - { - return new X11Window(this, true); - } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 18c23aa31e..a1e386892b 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -21,6 +21,7 @@ namespace Avalonia.X11 unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client { private readonly AvaloniaX11Platform _platform; + private readonly IWindowImpl _popupParent; private readonly bool _popup; private readonly X11Info _x11; private bool _invalidated; @@ -47,10 +48,10 @@ namespace Avalonia.X11 private readonly Queue _inputQueue = new Queue(); private InputEventContainer _lastEvent; private bool _useRenderWindow = false; - public X11Window(AvaloniaX11Platform platform, bool popup) + public X11Window(AvaloniaX11Platform platform, IWindowImpl popupParent) { _platform = platform; - _popup = popup; + _popup = popupParent != null; _x11 = platform.Info; _mouse = platform.MouseDevice; _keyboard = platform.KeyboardDevice; @@ -66,7 +67,7 @@ namespace Avalonia.X11 | SetWindowValuemask.BackPixmap | SetWindowValuemask.BackingStore | SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity; - if (popup) + if (_popup) { attr.override_redirect = true; valueMask |= SetWindowValuemask.OverrideRedirect; @@ -793,7 +794,8 @@ namespace Avalonia.X11 } public IMouseDevice MouseDevice => _mouse; - + public IPopupImpl CreatePopup() => new X11Window(_platform, this); + public void Activate() { if (_x11.Atoms._NET_ACTIVE_WINDOW != IntPtr.Zero) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 5e2ba51caf..ebaad81fa1 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -59,6 +59,8 @@ namespace Avalonia.LinuxFramebuffer public Size ClientSize => ScaledSize; public IMouseDevice MouseDevice => new MouseDevice(); + public IPopupImpl CreatePopup() => null; + public double Scaling => 1; public IEnumerable Surfaces => new object[] {_outputBackend}; public Action Input { get; set; } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index c89d0a15cf..7798452f10 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -240,5 +240,7 @@ namespace Avalonia.Win32.Interop.Wpf return new Vector(1, 1); return new Vector(src.TransformToDevice.M11, src.TransformToDevice.M22); } + + IPopupImpl CreatePopup() => null; } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index c45bf6389e..56a7e356b6 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -210,11 +210,6 @@ namespace Avalonia.Win32 return embedded; } - public IPopupImpl CreatePopup() - { - return new PopupImpl(); - } - public IWindowIconImpl LoadIcon(string fileName) { using (var stream = File.OpenRead(fileName)) diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs index 15e8b35056..d5f456409f 100644 --- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs +++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs @@ -134,5 +134,7 @@ namespace Avalonia.iOS } public ILockedFramebuffer Lock() => new EmulatedFramebuffer(this); + + public IPopupImpl CreatePopup() => null; } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs index 059146f17d..44bb7cb69b 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs @@ -21,7 +21,7 @@ namespace Avalonia.Controls.UnitTests.Primitives { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var target = CreateTarget(); + var target = CreateTarget(new Window()); Assert.True(((ILogical)target).IsAttachedToLogicalTree); } @@ -32,7 +32,7 @@ namespace Avalonia.Controls.UnitTests.Primitives { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var target = CreateTarget(); + var target = CreateTarget(new Window()); Assert.True(target.Presenter.IsAttachedToLogicalTree); } @@ -63,8 +63,8 @@ namespace Avalonia.Controls.UnitTests.Primitives using (UnitTestApplication.Start(TestServices.StyledWindow)) { var child = new Decorator(); - var target = CreateTarget(); var window = new Window(); + var target = CreateTarget(window); var detachedCount = 0; var attachedCount = 0; @@ -88,8 +88,8 @@ namespace Avalonia.Controls.UnitTests.Primitives using (UnitTestApplication.Start(TestServices.StyledWindow)) { var child = new Decorator(); - var target = CreateTarget(); var window = new Window(); + var target = CreateTarget(window); var detachedCount = 0; var attachedCount = 0; @@ -130,9 +130,9 @@ namespace Avalonia.Controls.UnitTests.Primitives } } - private PopupRoot CreateTarget() + private PopupRoot CreateTarget(TopLevel popupParent) { - var result = new PopupRoot + var result = new PopupRoot(popupParent) { Template = new FuncControlTemplate((parent, scope) => new ContentPresenter