diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index bd7927bed7..9bd2708855 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -465,8 +465,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 1F) : IUnknown virtual void* GetParentHandle() = 0; virtual HRESULT InitializeWithChildHandle(void* child) = 0; virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0; - virtual void MoveTo(float x, float y, float width, float height) = 0; - virtual void Hide() = 0; + virtual void ShowInBounds(float x, float y, float width, float height) = 0; + virtual void HideWithSize(float width, float height) = 0; virtual void ReleaseChild() = 0; }; diff --git a/native/Avalonia.Native/src/OSX/controlhost.mm b/native/Avalonia.Native/src/OSX/controlhost.mm index 315ec2f310..5ee2344ac7 100644 --- a/native/Avalonia.Native/src/OSX/controlhost.mm +++ b/native/Avalonia.Native/src/OSX/controlhost.mm @@ -97,7 +97,7 @@ public: return S_OK; }; - virtual void MoveTo(float x, float y, float width, float height) override + virtual void ShowInBounds(float x, float y, float width, float height) override { if(_child == nil) return; @@ -106,7 +106,7 @@ public: IAvnNativeControlHostTopLevelAttachment* slf = this; slf->AddRef(); dispatch_async(dispatch_get_main_queue(), ^{ - slf->MoveTo(x, y, width, height); + slf->ShowInBounds(x, y, width, height); slf->Release(); }); return; @@ -122,9 +122,24 @@ public: [[_holder superview] setNeedsDisplay:true]; } - virtual void Hide() override + virtual void HideWithSize(float width, float height) override { + if(_child == nil) + return; + if(AvnInsidePotentialDeadlock::IsInside()) + { + IAvnNativeControlHostTopLevelAttachment* slf = this; + slf->AddRef(); + dispatch_async(dispatch_get_main_queue(), ^{ + slf->HideWithSize(width, height); + slf->Release(); + }); + return; + } + + NSRect frame = {0, 0, width, height}; [_holder setHidden: true]; + [_child setFrame: frame]; } virtual void ReleaseChild() override diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index 94ef0b2284..20eac11c2c 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/src/Avalonia.Controls/NativeControlHost.cs @@ -1,7 +1,9 @@ +using System; +using System.Collections.Generic; using Avalonia.Controls.Platform; -using Avalonia.LogicalTree; using Avalonia.Platform; using Avalonia.Threading; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -12,14 +14,18 @@ namespace Avalonia.Controls private INativeControlHostControlTopLevelAttachment _attachment; private IPlatformHandle _nativeControlHandle; private bool _queuedForDestruction; + private bool _queuedForMoveResize; + private readonly List _propertyChangedSubscriptions = new List(); + private readonly EventHandler _propertyChangedHandler; static NativeControlHost() { IsVisibleProperty.Changed.AddClassHandler(OnVisibleChanged); - TransformedBoundsProperty.Changed.AddClassHandler(OnBoundsChanged); } - private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) - => host.UpdateHost(); + public NativeControlHost() + { + _propertyChangedHandler = PropertyChangedHandler; + } private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) => host.UpdateHost(); @@ -27,21 +33,46 @@ namespace Avalonia.Controls protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = e.Root as TopLevel; + var visual = (IVisual)this; + while (visual != _currentRoot) + { + + if (visual is Visual v) + { + v.PropertyChanged += _propertyChangedHandler; + _propertyChangedSubscriptions.Add(v); + } + + visual = visual.GetVisualParent(); + } + UpdateHost(); } + private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.IsEffectiveValueChange && e.Property == BoundsProperty) + EnqueueForMoveResize(); + } + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = null; + if (_propertyChangedSubscriptions != null) + { + foreach (var v in _propertyChangedSubscriptions) + v.PropertyChanged -= _propertyChangedHandler; + _propertyChangedSubscriptions.Clear(); + } UpdateHost(); } - void UpdateHost() + private void UpdateHost() { + _queuedForMoveResize = false; _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost; var needsAttachment = _currentHost != null; - var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue; if (needsAttachment) { @@ -93,22 +124,46 @@ namespace Avalonia.Controls } } - if (needsShow) - _attachment?.ShowInBounds(TransformedBounds.Value); - else if (needsAttachment) - _attachment?.Hide(); + if (_attachment?.AttachedTo != _currentHost) + return; + + TryUpdateNativeControlPosition(); + } + + + private Rect? GetAbsoluteBounds() + { + var bounds = Bounds; + var position = this.TranslatePoint(bounds.Position, _currentRoot); + if (position == null) + return null; + return new Rect(position.Value, bounds.Size); + } + + void EnqueueForMoveResize() + { + if(_queuedForMoveResize) + return; + _queuedForMoveResize = true; + Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render); } public bool TryUpdateNativeControlPosition() { - var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue; + if (_currentHost == null) + return false; + + var bounds = GetAbsoluteBounds(); + var needsShow = IsEffectivelyVisible && bounds.HasValue; - if(needsShow) - _attachment?.ShowInBounds(TransformedBounds.Value); - return needsShow; + if (needsShow) + _attachment?.ShowInBounds(bounds.Value); + else + _attachment?.HideWithSize(Bounds.Size); + return false; } - void CheckDestruction() + private void CheckDestruction() { _queuedForDestruction = false; if (_currentRoot == null) @@ -117,10 +172,12 @@ namespace Avalonia.Controls protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent) { + if (_currentHost == null) + throw new InvalidOperationException(); return _currentHost.CreateDefaultChild(parent); } - void DestroyNativeControl() + private void DestroyNativeControl() { if (_nativeControlHandle != null) { diff --git a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs index 7a4568abc6..c6b1d09849 100644 --- a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs +++ b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs @@ -21,8 +21,8 @@ namespace Avalonia.Controls.Platform { INativeControlHostImpl AttachedTo { get; set; } bool IsCompatibleWith(INativeControlHostImpl host); - void Hide(); - void ShowInBounds(TransformedBounds transformedBounds); + void HideWithSize(Size size); + void ShowInBounds(Rect rect); } public interface ITopLevelImplWithNativeControlHost diff --git a/src/Avalonia.Native/NativeControlHostImpl.cs b/src/Avalonia.Native/NativeControlHostImpl.cs index 0777c6416b..a46528dc48 100644 --- a/src/Avalonia.Native/NativeControlHostImpl.cs +++ b/src/Avalonia.Native/NativeControlHostImpl.cs @@ -114,19 +114,18 @@ namespace Avalonia.Native public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl; - public void Hide() + public void HideWithSize(Size size) { - _native?.Hide(); + _native.HideWithSize(Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height)); } - public void ShowInBounds(TransformedBounds transformedBounds) + public void ShowInBounds(Rect bounds) { if (_attachedTo == null) throw new InvalidOperationException("Native control isn't attached to a toplevel"); - var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform); bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width), Math.Max(1, bounds.Height)); - _native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height); + _native.ShowInBounds((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height); } public void InitWithChild(IPlatformHandle handle) diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 4dfd641525..5bbb33542c 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -204,6 +204,21 @@ namespace Avalonia rect.Width * scale.X, rect.Height * scale.Y); } + + /// + /// Multiplies a rectangle by a scale. + /// + /// The rectangle. + /// The scale. + /// The scaled rectangle. + public static Rect operator *(Rect rect, double scale) + { + return new Rect( + rect.X * scale, + rect.Y * scale, + rect.Width * scale, + rect.Height * scale); + } /// /// Divides a rectangle by a vector. diff --git a/src/Avalonia.X11/X11NativeControlHost.cs b/src/Avalonia.X11/X11NativeControlHost.cs index add3bc6585..23fb27f72b 100644 --- a/src/Avalonia.X11/X11NativeControlHost.cs +++ b/src/Avalonia.X11/X11NativeControlHost.cs @@ -157,21 +157,30 @@ namespace Avalonia.X11 public bool IsCompatibleWith(INativeControlHostImpl host) => host is X11NativeControlHost; - public void Hide() + public void HideWithSize(Size size) { if(_attachedTo == null || _child == null) return; - _mapped = false; - XUnmapWindow(_display, _holder.Handle); + if (_mapped) + { + _mapped = false; + XUnmapWindow(_display, _holder.Handle); + } + + size *= _attachedTo.Window.Scaling; + XResizeWindow(_display, _child.Handle, + Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height)); } - public void ShowInBounds(TransformedBounds transformedBounds) + + + public void ShowInBounds(Rect bounds) { CheckDisposed(); if (_attachedTo == null) throw new InvalidOperationException("The control isn't currently attached to a toplevel"); - var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) * - new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling); + bounds *= _attachedTo.Window.Scaling; + var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height)); XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height); @@ -183,7 +192,6 @@ namespace Avalonia.X11 XRaiseWindow(_display, _holder.Handle); _mapped = true; } - Console.WriteLine($"Moved {_child.Handle} to {pixelRect}"); } } } diff --git a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs index 69fbe30068..d7bb2c037e 100644 --- a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs +++ b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs @@ -168,21 +168,25 @@ namespace Avalonia.Win32 public bool IsCompatibleWith(INativeControlHostImpl host) => host is Win32NativeControlHost; - public void Hide() + public void HideWithSize(Size size) { UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero, -100, -100, 1, 1, UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); + if (_attachedTo == null || _child == null) + return; + size *= _attachedTo.Window.Scaling; + UnmanagedMethods.MoveWindow(_child.Handle, 0, 0, + Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height), false); } - public unsafe void ShowInBounds(TransformedBounds transformedBounds) + public unsafe void ShowInBounds(Rect bounds) { CheckDisposed(); if (_attachedTo == null) throw new InvalidOperationException("The control isn't currently attached to a toplevel"); - var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) * - new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling); + bounds *= _attachedTo.Window.Scaling; var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height));