Prechádzať zdrojové kódy

Merge pull request #4193 from AvaloniaUI/foreign-embed-createahead

Create and resize the native control even if it's not currently effectively visible
danwalmsley 5 rokov pred
rodič
commit
5cc8bed8c6

+ 2 - 2
native/Avalonia.Native/inc/avalonia-native.h

@@ -493,8 +493,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : 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;
 };
 

+ 18 - 3
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

+ 73 - 16
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<Visual> _propertyChangedSubscriptions = new List<Visual>();
+        private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
         static NativeControlHost()
         {
             IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged);
-            TransformedBoundsProperty.Changed.AddClassHandler<NativeControlHost>(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)
             {

+ 2 - 2
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

+ 4 - 5
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) 

+ 15 - 0
src/Avalonia.Visuals/Rect.cs

@@ -211,6 +211,21 @@ namespace Avalonia
                 rect.Width * scale.X,
                 rect.Height * scale.Y);
         }
+        
+        /// <summary>
+        /// Multiplies a rectangle by a scale.
+        /// </summary>
+        /// <param name="rect">The rectangle.</param>
+        /// <param name="scale">The scale.</param>
+        /// <returns>The scaled rectangle.</returns>
+        public static Rect operator *(Rect rect, double scale)
+        {
+            return new Rect(
+                rect.X * scale,
+                rect.Y * scale,
+                rect.Width * scale,
+                rect.Height * scale);
+        }
 
         /// <summary>
         /// Divides a rectangle by a vector.

+ 15 - 7
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}");
             }
         }
     }

+ 8 - 4
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));