diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 1a11778db2..c59c7c11d9 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -44,7 +44,7 @@ namespace Avalonia.Controls.Primitives /// The dependency resolver to use. If null the default dependency resolver will be used. /// public PopupRoot(TopLevel parent, IPopupImpl impl, IAvaloniaDependencyResolver dependencyResolver) - : base(impl, dependencyResolver) + : base(ValidatingPopupImpl.Wrap(impl), dependencyResolver) { _parent = parent; } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index eaee5bdb50..9eb35e0548 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -134,6 +134,8 @@ namespace Avalonia.Controls "Could not create window implementation: maybe no windowing subsystem was initialized?"); } + impl = ValidatingToplevelImpl.Wrap(impl); + PlatformImpl = impl; _actualTransparencyLevel = PlatformImpl.TransparencyLevel; @@ -367,14 +369,15 @@ namespace Avalonia.Controls Renderer?.Dispose(); Renderer = null; + (this as IInputRoot).MouseDevice?.TopLevelClosed(this); + PlatformImpl = null; + var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); var visualArgs = new VisualTreeAttachmentEventArgs(this, this); OnDetachedFromVisualTreeCore(visualArgs); - - (this as IInputRoot).MouseDevice?.TopLevelClosed(this); - PlatformImpl = null; + OnClosed(EventArgs.Empty); LayoutManager?.Dispose(); diff --git a/src/Avalonia.Controls/ValidatingToplevel.cs b/src/Avalonia.Controls/ValidatingToplevel.cs new file mode 100644 index 0000000000..6af3cc7d0d --- /dev/null +++ b/src/Avalonia.Controls/ValidatingToplevel.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls.Platform; +using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Input.TextInput; +using Avalonia.Platform; +using Avalonia.Rendering; + +namespace Avalonia.Controls; + +internal class ValidatingToplevelImpl : ITopLevelImpl, ITopLevelImplWithNativeControlHost, + ITopLevelImplWithNativeMenuExporter, ITopLevelImplWithTextInputMethod +{ + private readonly ITopLevelImpl _impl; + private bool _disposed; + + public ValidatingToplevelImpl(ITopLevelImpl impl) + { + _impl = impl ?? throw new InvalidOperationException( + "Could not create TopLevel implementation: maybe no windowing subsystem was initialized?"); + } + + public void Dispose() + { + _disposed = true; + _impl.Dispose(); + } + + protected void CheckDisposed() + { + if (_disposed) + throw new ObjectDisposedException(_impl.GetType().FullName); + } + + protected ITopLevelImpl Inner + { + get + { + CheckDisposed(); + return _impl; + } + } + + public static ITopLevelImpl Wrap(ITopLevelImpl impl) + { +#if DEBUG + if (impl is ValidatingToplevelImpl) + return impl; + return new ValidatingToplevelImpl(impl); +#endif + } + + public Size ClientSize => Inner.ClientSize; + public Size? FrameSize => Inner.FrameSize; + public double RenderScaling => Inner.RenderScaling; + public IEnumerable Surfaces => Inner.Surfaces; + + public Action Input + { + get => Inner.Input; + set => Inner.Input = value; + } + + public Action Paint + { + get => Inner.Paint; + set => Inner.Paint = value; + } + + public Action Resized + { + get => Inner.Resized; + set => Inner.Resized = value; + } + + public Action ScalingChanged + { + get => Inner.ScalingChanged; + set => Inner.ScalingChanged = value; + } + + public Action TransparencyLevelChanged + { + get => Inner.TransparencyLevelChanged; + set => Inner.TransparencyLevelChanged = value; + } + + public IRenderer CreateRenderer(IRenderRoot root) => Inner.CreateRenderer(root); + + public void Invalidate(Rect rect) => Inner.Invalidate(rect); + + public void SetInputRoot(IInputRoot inputRoot) => Inner.SetInputRoot(inputRoot); + + public Point PointToClient(PixelPoint point) => Inner.PointToClient(point); + + public PixelPoint PointToScreen(Point point) => Inner.PointToScreen(point); + + public void SetCursor(ICursorImpl cursor) => Inner.SetCursor(cursor); + + public Action Closed + { + get => Inner.Closed; + set => Inner.Closed = value; + } + + public Action LostFocus + { + get => Inner.LostFocus; + set => Inner.LostFocus = value; + } + + // Exception: for some reason we are notifying platform mouse device from TopLevel.cs + public IMouseDevice MouseDevice => _impl.MouseDevice; + public IPopupImpl CreatePopup() => Inner.CreatePopup(); + + public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) => + Inner.SetTransparencyLevelHint(transparencyLevel); + + + public WindowTransparencyLevel TransparencyLevel => Inner.TransparencyLevel; + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => Inner.AcrylicCompensationLevels; + public INativeControlHostImpl NativeControlHost => (Inner as ITopLevelImplWithNativeControlHost)?.NativeControlHost; + + public ITopLevelNativeMenuExporter NativeMenuExporter => + (Inner as ITopLevelImplWithNativeMenuExporter)?.NativeMenuExporter; + + public ITextInputMethodImpl TextInputMethod => (Inner as ITopLevelImplWithTextInputMethod)?.TextInputMethod; +} + +internal class ValidatingWindowBaseImpl : ValidatingToplevelImpl, IWindowBaseImpl +{ + private readonly IWindowBaseImpl _impl; + + public ValidatingWindowBaseImpl(IWindowBaseImpl impl) : base(impl) + { + _impl = impl; + } + + protected new IWindowBaseImpl Inner + { + get + { + CheckDisposed(); + return _impl; + } + } + + public static IWindowBaseImpl Wrap(IWindowBaseImpl impl) + { +#if DEBUG + if (impl is ValidatingToplevelImpl) + return impl; + return new ValidatingWindowBaseImpl(impl); +#endif + } + + public void Show(bool activate, bool isDialog) => Inner.Show(activate, isDialog); + + public void Hide() => Inner.Hide(); + + public double DesktopScaling => Inner.DesktopScaling; + public PixelPoint Position => Inner.Position; + + public Action PositionChanged + { + get => Inner.PositionChanged; + set => Inner.PositionChanged = value; + } + + public void Activate() => Inner.Activate(); + + public Action Deactivated + { + get => Inner.Deactivated; + set => Inner.Deactivated = value; + } + + public Action Activated + { + get => Inner.Activated; + set => Inner.Deactivated = value; + } + + public IPlatformHandle Handle => Inner.Handle; + public Size MaxAutoSizeHint => Inner.MaxAutoSizeHint; + public void SetTopmost(bool value) => Inner.SetTopmost(value); + public IScreenImpl Screen => Inner.Screen; +} + +internal class ValidatingWindowImpl : ValidatingWindowBaseImpl, IWindowImpl +{ + private readonly IWindowImpl _impl; + + public ValidatingWindowImpl(IWindowImpl impl) : base(impl) + { + _impl = impl; + } + + protected new IWindowImpl Inner + { + get + { + CheckDisposed(); + return _impl; + } + } + + public static IWindowImpl Wrap(IWindowImpl impl) + { +#if DEBUG + if (impl is ValidatingToplevelImpl) + return impl; + return new ValidatingWindowImpl(impl); +#endif + } + + public WindowState WindowState + { + get => Inner.WindowState; + set => Inner.WindowState = value; + } + + public Action WindowStateChanged + { + get => Inner.WindowStateChanged; + set => Inner.WindowStateChanged = value; + } + + public void SetTitle(string title) => Inner.SetTitle(title); + + public void SetParent(IWindowImpl parent) => Inner.SetParent(parent); + + public void SetEnabled(bool enable) => Inner.SetEnabled(enable); + + public Action GotInputWhenDisabled + { + get => Inner.GotInputWhenDisabled; + set => Inner.GotInputWhenDisabled = value; + } + + public void SetSystemDecorations(SystemDecorations enabled) => Inner.SetSystemDecorations(enabled); + + public void SetIcon(IWindowIconImpl icon) => Inner.SetIcon(icon); + + public void ShowTaskbarIcon(bool value) => Inner.ShowTaskbarIcon(value); + + public void CanResize(bool value) => Inner.CanResize(value); + + public Func Closing + { + get => Inner.Closing; + set => Inner.Closing = value; + } + + public bool IsClientAreaExtendedToDecorations => Inner.IsClientAreaExtendedToDecorations; + + public Action ExtendClientAreaToDecorationsChanged + { + get => Inner.ExtendClientAreaToDecorationsChanged; + set => Inner.ExtendClientAreaToDecorationsChanged = value; + } + + public bool NeedsManagedDecorations => Inner.NeedsManagedDecorations; + public Thickness ExtendedMargins => Inner.ExtendedMargins; + public Thickness OffScreenMargin => Inner.OffScreenMargin; + public void BeginMoveDrag(PointerPressedEventArgs e) => Inner.BeginMoveDrag(e); + + public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) => Inner.BeginResizeDrag(edge, e); + + public void Resize(Size clientSize, PlatformResizeReason reason) => + Inner.Resize(clientSize, reason); + + public void Move(PixelPoint point) => Inner.Move(point); + + public void SetMinMaxSize(Size minSize, Size maxSize) => Inner.SetMinMaxSize(minSize, maxSize); + + public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint) => + Inner.SetExtendClientAreaToDecorationsHint(extendIntoClientAreaHint); + + public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) => + Inner.SetExtendClientAreaChromeHints(hints); + + public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight) => + Inner.SetExtendClientAreaTitleBarHeightHint(titleBarHeight); +} + +internal class ValidatingPopupImpl : ValidatingWindowBaseImpl, IPopupImpl +{ + private readonly IPopupImpl _impl; + + public ValidatingPopupImpl(IPopupImpl impl) : base(impl) + { + _impl = impl; + } + + protected new IPopupImpl Inner + { + get + { + CheckDisposed(); + return _impl; + } + } + + public static IPopupImpl Wrap(IPopupImpl impl) + { +#if DEBUG + if (impl is ValidatingToplevelImpl) + return impl; + return new ValidatingPopupImpl(impl); +#endif + } + + public IPopupPositioner PopupPositioner => Inner.PopupPositioner; + public void SetWindowManagerAddShadowHint(bool enabled) => Inner.SetWindowManagerAddShadowHint(enabled); +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 4c94b725ea..b9ca594efa 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -237,13 +237,14 @@ namespace Avalonia.Controls /// /// The window implementation. public Window(IWindowImpl impl) - : base(impl) + : base(ValidatingWindowImpl.Wrap(impl)) { - impl.Closing = HandleClosing; - impl.GotInputWhenDisabled = OnGotInputWhenDisabled; - impl.WindowStateChanged = HandleWindowStateChanged; + var wrapped = (IWindowImpl)base.PlatformImpl!; + wrapped.Closing = HandleClosing; + wrapped.GotInputWhenDisabled = OnGotInputWhenDisabled; + wrapped.WindowStateChanged = HandleWindowStateChanged; _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size); - impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; + wrapped.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, PlatformResizeReason.Application)); PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar); diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 5861d0452d..2207d0550a 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -57,12 +57,13 @@ namespace Avalonia.Controls { } - public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver) + public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(ValidatingWindowBaseImpl.Wrap(impl), dependencyResolver) { Screens = new Screens(PlatformImpl?.Screen); - impl.Activated = HandleActivated; - impl.Deactivated = HandleDeactivated; - impl.PositionChanged = HandlePositionChanged; + var wrapped = PlatformImpl!; + wrapped.Activated = HandleActivated; + wrapped.Deactivated = HandleDeactivated; + wrapped.PositionChanged = HandlePositionChanged; } ///