diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 0baa6589c6..3f1ec289d1 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -29,11 +29,19 @@ namespace ControlCatalog { new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") }, new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") }, }; @@ -41,11 +49,19 @@ namespace ControlCatalog { new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseDark.xaml") }, new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") }, }; diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index fa4fd7dd07..af95e3c356 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -2,7 +2,6 @@ xmlns:pages="clr-namespace:ControlCatalog.Pages" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.MainView" - Background="Black" Foreground="{DynamicResource ThemeForegroundBrush}" FontSize="{DynamicResource FontSizeNormal}"> diff --git a/samples/RenderDemo/App.xaml b/samples/RenderDemo/App.xaml index ccc3f54cc0..61e4d2385b 100644 --- a/samples/RenderDemo/App.xaml +++ b/samples/RenderDemo/App.xaml @@ -3,8 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="RenderDemo.App"> - - + - \ No newline at end of file + diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml index 07fdb91a16..fd23067f61 100644 --- a/samples/RenderDemo/SideBar.xaml +++ b/samples/RenderDemo/SideBar.xaml @@ -1,65 +1,68 @@ - + - - - - - + + + + + + diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs index 38a23f918f..40cf81358f 100644 --- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs +++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs @@ -1,3 +1,5 @@ +using System; +using System.Runtime.ConstrainedExecution; using System.Threading; namespace Avalonia.Threading @@ -7,6 +9,20 @@ namespace Avalonia.Threading /// public class AvaloniaSynchronizationContext : SynchronizationContext { + public interface INonPumpingPlatformWaitProvider + { + int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout); + } + + private readonly INonPumpingPlatformWaitProvider _waitProvider; + + public AvaloniaSynchronizationContext(INonPumpingPlatformWaitProvider waitProvider) + { + _waitProvider = waitProvider; + if (_waitProvider != null) + SetWaitNotificationRequired(); + } + /// /// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer. /// @@ -22,7 +38,8 @@ namespace Avalonia.Threading return; } - SetSynchronizationContext(new AvaloniaSynchronizationContext()); + SetSynchronizationContext(new AvaloniaSynchronizationContext(AvaloniaLocator.Current + .GetService())); } /// @@ -39,5 +56,13 @@ namespace Avalonia.Threading else Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait(); } + + [PrePrepareMethod] + public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) + { + if (_waitProvider != null) + return _waitProvider.Wait(waitHandles, waitAll, millisecondsTimeout); + return base.Wait(waitHandles, waitAll, millisecondsTimeout); + } } } diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index a3ad30db76..27f13a3f57 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -54,7 +54,7 @@ namespace Avalonia.Controls } _childGraph.Measure(availableSize); - _childGraph.Reset(); + _childGraph.Reset(false); var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN()); _childGraph.Reset(); _childGraph.Measure(boundingSize); @@ -119,17 +119,22 @@ namespace Avalonia.Controls public void Arrange(Size arrangeSize) => Element.Arrange(new Rect(Left, Top, Math.Max(arrangeSize.Width - Left - Right, 0), Math.Max(arrangeSize.Height - Top - Bottom, 0))); - public void Reset() + public void Reset(bool clearPos) { - Left = double.NaN; - Top = double.NaN; - Right = double.NaN; - Bottom = double.NaN; + if (clearPos) + { + Left = double.NaN; + Top = double.NaN; + Right = double.NaN; + Bottom = double.NaN; + } + Measured = false; } public Size GetBoundingSize() { + if (Left < 0 || Top < 0) return default; if (Measured) return BoundingSize; @@ -209,7 +214,7 @@ namespace Avalonia.Controls _nodeDic.Clear(); } - public void Reset() => _nodeDic.Values.Do(node => node.Reset()); + public void Reset(bool clearPos = true) => _nodeDic.Values.Do(node => node.Reset(clearPos)); public GraphNode? AddLink(GraphNode from, Layoutable? to) { @@ -255,28 +260,21 @@ namespace Avalonia.Controls foreach (var node in nodes) { - /* - * 该节点无任何依赖,所以从这里开始计算元素位置。 - * 因为无任何依赖,所以忽略同级元素 - */ if (!node.Measured && !node.OutgoingNodes.Any()) { MeasureChild(node); continue; } - - // 判断依赖元素是否全部排列完毕 + if (node.OutgoingNodes.All(item => item.Measured)) { MeasureChild(node); continue; } - - // 判断是否有循环 + if (!set.Add(node.Element)) throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); - - // 没有循环,且有依赖,则继续往下 + Measure(node.OutgoingNodes, set); if (!node.Measured) diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index f5c673d5f9..931c27c575 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -192,7 +192,7 @@ namespace Avalonia.Controls.Remote.Server GetAvaloniaInputModifiers(pressed.Modifiers))); }, DispatcherPriority.Input); } - if (obj is PointerPressedEventMessage released) + if (obj is PointerReleasedEventMessage released) { Dispatcher.UIThread.Post(() => { diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index d90729e8a5..569697304f 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -28,14 +28,22 @@ namespace Avalonia.Controls { control.PointerEnter -= ControlPointerEnter; control.PointerLeave -= ControlPointerLeave; + control.DetachedFromVisualTree -= ControlDetaching; } if (e.NewValue != null) { control.PointerEnter += ControlPointerEnter; control.PointerLeave += ControlPointerLeave; + control.DetachedFromVisualTree += ControlDetaching; } } + + private void ControlDetaching(object sender, VisualTreeAttachmentEventArgs e) + { + var control = (Control)sender; + Close(control); + } /// /// Called when the pointer enters a control with an attached tooltip. diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs index b445f86613..91c4c28995 100644 --- a/src/Avalonia.FreeDesktop/DBusHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -43,7 +43,7 @@ namespace Avalonia.FreeDesktop public void Initialized() { lock (_lock) - _ctx = new AvaloniaSynchronizationContext(); + _ctx = new AvaloniaSynchronizationContext(null); } } public static Connection Connection { get; private set; } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 1aec4f0016..b7c68c4b95 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -1059,9 +1060,21 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool SetFocus(IntPtr hWnd); [DllImport("user32.dll")] + public static extern IntPtr GetFocus(); + [DllImport("user32.dll")] public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent); [DllImport("user32.dll")] public static extern IntPtr GetParent(IntPtr hWnd); + + public enum GetAncestorFlags + { + GA_PARENT = 1, + GA_ROOT = 2, + GA_ROOTOWNER = 3 + } + + [DllImport("user32.dll")] + public static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags gaFlags); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommand nCmdShow); @@ -1382,6 +1395,22 @@ namespace Avalonia.Win32.Interop throw new Exception("RtlGetVersion failed!"); } } + + [DllImport("kernel32", EntryPoint="WaitForMultipleObjectsEx", SetLastError = true, CharSet = CharSet.Auto)] + private static extern int IntWaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable); + + public const int WAIT_FAILED = unchecked((int)0xFFFFFFFF); + + internal static int WaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable) + { + int result = IntWaitForMultipleObjectsEx(nCount, pHandles, bWaitAll, dwMilliseconds, bAlertable); + if(result == WAIT_FAILED) + { + throw new Win32Exception(); + } + + return result; + } [DllImport("user32.dll")] internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); diff --git a/src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs b/src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs new file mode 100644 index 0000000000..a0160fcfbd --- /dev/null +++ b/src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs @@ -0,0 +1,15 @@ +using System; +using Avalonia.Threading; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + internal class NonPumpingWaitProvider : AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider + { + public int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) + { + return UnmanagedMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll, + millisecondsTimeout, false); + } + } +} diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index cd25b32ed9..525e5e0d52 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -7,6 +7,7 @@ namespace Avalonia.Win32 { class PopupImpl : WindowImpl, IPopupImpl { + private readonly IWindowBaseImpl _parent; private bool _dropShadowHint = true; private Size? _maxAutoSize; @@ -19,18 +20,25 @@ namespace Avalonia.Win32 public override void Show() { UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate); - var parent = UnmanagedMethods.GetParent(Handle.Handle); - if (parent != IntPtr.Zero) - { - IntPtr nextParent = parent; - while (nextParent != IntPtr.Zero) - { - parent = nextParent; - nextParent = UnmanagedMethods.GetParent(parent); - } - UnmanagedMethods.SetFocus(parent); + // We need to steal focus if it's held by a child window of our toplevel window + var parent = _parent; + while(parent != null) + { + if(parent is PopupImpl pi) + parent = pi._parent; + else + break; } + + if(parent == null) + return; + + var focusOwner = UnmanagedMethods.GetFocus(); + if (focusOwner != IntPtr.Zero && + UnmanagedMethods.GetAncestor(focusOwner, UnmanagedMethods.GetAncestorFlags.GA_ROOT) + == parent.Handle.Handle) + UnmanagedMethods.SetFocus(parent.Handle.Handle); } protected override bool ShouldTakeFocusOnClick => false; @@ -118,6 +126,7 @@ namespace Avalonia.Win32 private PopupImpl(IWindowBaseImpl parent, bool dummy) : base() { + _parent = parent; PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index b7bb0e19ba..af6058d197 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -93,6 +93,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToSingleton() .Bind().ToConstant(s_instance) + .Bind().ToConstant(new NonPumpingWaitProvider()) .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()); if (options.AllowEglInitialization) diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 7b1f6d07b7..6e171a58e7 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -82,5 +82,55 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds); } + + [Fact] + public void LeftOf_Measures_Correctly() + { + var rect1 = new Rectangle { Height = 20, Width = 20 }; + var rect2 = new Rectangle { Height = 20, Width = 20 }; + + var target = new RelativePanel + { + VerticalAlignment = Layout.VerticalAlignment.Center, + HorizontalAlignment = Layout.HorizontalAlignment.Center, + Children = + { + rect1, rect2 + } + }; + + RelativePanel.SetLeftOf(rect2, rect1); + target.Measure(new Size(400, 400)); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Size(20, 20), target.Bounds.Size); + Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); + Assert.Equal(new Rect(-20, 0, 20, 20), target.Children[1].Bounds); + } + + [Fact] + public void Above_Measures_Correctly() + { + var rect1 = new Rectangle { Height = 20, Width = 20 }; + var rect2 = new Rectangle { Height = 20, Width = 20 }; + + var target = new RelativePanel + { + VerticalAlignment = Layout.VerticalAlignment.Center, + HorizontalAlignment = Layout.HorizontalAlignment.Center, + Children = + { + rect1, rect2 + } + }; + + RelativePanel.SetAbove(rect2, rect1); + target.Measure(new Size(400, 400)); + target.Arrange(new Rect(target.DesiredSize)); + + Assert.Equal(new Size(20, 20), target.Bounds.Size); + Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds); + Assert.Equal(new Rect(0, -20, 20, 20), target.Children[1].Bounds); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs index 34b37e7635..9d7bc6af74 100644 --- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs @@ -30,6 +30,40 @@ namespace Avalonia.Controls.UnitTests Assert.False(ToolTip.GetIsOpen(control)); } + + [Fact] + public void Should_Close_When_Control_Detaches() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = new Window(); + + var panel = new Panel(); + + var target = new Decorator() + { + [ToolTip.TipProperty] = "Tip", + [ToolTip.ShowDelayProperty] = 0 + }; + + panel.Children.Add(target); + + window.Content = panel; + + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + + Assert.True((target as IVisual).IsAttachedToVisualTree); + + _mouseHelper.Enter(target); + + Assert.True(ToolTip.GetIsOpen(target)); + + panel.Children.Remove(target); + + Assert.False(ToolTip.GetIsOpen(target)); + } + } [Fact] public void Should_Open_On_Pointer_Enter()