From 26863755d8628957f0a675077d897c3a259e1cb2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 23 Jun 2020 22:26:48 +0300 Subject: [PATCH 1/3] Synchronize native control size even if its not visible (X11/Win32) --- src/Avalonia.Controls/NativeControlHost.cs | 89 +++++++++++++++---- .../Platform/INativeControlHostImpl.cs | 4 +- src/Avalonia.Native/NativeControlHostImpl.cs | 6 +- src/Avalonia.Visuals/Rect.cs | 15 ++++ src/Avalonia.X11/X11NativeControlHost.cs | 22 +++-- .../Avalonia.Win32/Win32NativeControlHost.cs | 12 ++- 6 files changed, 116 insertions(+), 32 deletions(-) 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..4045ce8816 100644 --- a/src/Avalonia.Native/NativeControlHostImpl.cs +++ b/src/Avalonia.Native/NativeControlHostImpl.cs @@ -114,16 +114,16 @@ namespace Avalonia.Native public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl; - public void Hide() + public void HideWithSize(Size size) { + //TODO _native?.Hide(); } - 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); 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)); From cba236d9a35758952eac4ab7989ce11b534a3d2f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 29 Jun 2020 18:43:01 +0300 Subject: [PATCH 2/3] OSX impl for HideWithSize --- native/Avalonia.Native/inc/avalonia-native.h | 4 ++-- native/Avalonia.Native/src/OSX/controlhost.mm | 21 ++++++++++++++++--- src/Avalonia.Native/NativeControlHostImpl.cs | 5 ++--- 3 files changed, 22 insertions(+), 8 deletions(-) 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.Native/NativeControlHostImpl.cs b/src/Avalonia.Native/NativeControlHostImpl.cs index 4045ce8816..a46528dc48 100644 --- a/src/Avalonia.Native/NativeControlHostImpl.cs +++ b/src/Avalonia.Native/NativeControlHostImpl.cs @@ -116,8 +116,7 @@ namespace Avalonia.Native public void HideWithSize(Size size) { - //TODO - _native?.Hide(); + _native.HideWithSize(Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height)); } public void ShowInBounds(Rect bounds) @@ -126,7 +125,7 @@ namespace Avalonia.Native throw new InvalidOperationException("Native control isn't attached to a toplevel"); 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) From bd29bc333afc9eb9858fd3f3730dcb6fdf2cd0b9 Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Mon, 29 Jun 2020 23:44:45 -0400 Subject: [PATCH 3/3] Remove duplicated colors from fluent theme editor with non-standard values --- .../Accents/FluentBaseDark.xaml | 38 +----------------- .../Accents/FluentBaseLight.xaml | 39 +------------------ 2 files changed, 4 insertions(+), 73 deletions(-) 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