diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 1cf3bc75b0..22247df9b2 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/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; }; 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.Themes.Fluent/Accents/FluentBaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml index b12a639d31..b5d502787d 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml @@ -2,35 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=netstandard"> - #FF0078D7 - #FF000000 - #FF000000 - #FF000000 - #FF000000 - #FF000000 - #FFFFFFFF - #FF333333 - #FF9A9A9A - #FFB4B4B4 - #FF676767 - #FFB4B4B4 - #FF000000 - #FFB4B4B4 - #FF000000 - #FF000000 - #FF333333 - #FF808080 - #FF808080 - #FF151515 - #FF1D1D1D - #FF2C2C2C - #FFFFFFFF - #FF1D1D1D - #FF333333 - #CC000000 - #FF333333 - #FF1D1D1D - #FF333333 + #18FFFFFF + #30FFFFFF @@ -67,13 +40,6 @@ 1,1,1,1 1 - - #FF005A9E - #FF004275 - #FF002642 - #FF429CE3 - #FF76B9ED - #FFA6D8FF #FF000000 diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml index 31c2f592b9..0806a6e9ef 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml @@ -2,36 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=netstandard"> - #FF0078D7 - #FFFFFFFF - #FFFFFFFF - #FFFFFFFF - #FFFFFFFF - #FFFFFFFF - #FF000000 - #FFCCCCCC - #FF898989 - #FF5D5D5D - #FF737373 - #FF5D5D5D - #FF000000 - #FFCCCCCC - #FF5D5D5D - #FF898989 - #FFCCCCCC - #FF898989 - #FF737373 - #FFCCCCCC - #FFECECEC - #FFE6E6E6 - #FFECECEC - #FFFFFFFF - #FFE6E6E6 - #FFCCCCCC - #CCFFFFFF - #FFCCCCCC - #FFE6E6E6 - #FFCCCCCC + #17000000 + #2E000000 @@ -68,13 +40,6 @@ 1,1,1,1 1 - - #FF005A9E - #FF004275 - #FF002642 - #FF429CE3 - #FF76B9ED - #FFA6D8FF #FFFFFFFF diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index d1110e0613..d2a72db6ae 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -211,6 +211,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));