From dcd3aa5e4f73d5ce2e92260349403bbec3d99a68 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 29 Jun 2022 15:49:23 +0200 Subject: [PATCH 1/3] Backport IntegrationTestApp to 0.10.x. Even though we don't have automation there and so can't do integration testing, it's still useful for manual testing. --- Avalonia.sln | 43 ++++-- samples/IntegrationTestApp/App.axaml | 7 + samples/IntegrationTestApp/App.axaml.cs | 24 ++++ .../IntegrationTestApp.csproj | 28 ++++ samples/IntegrationTestApp/MainWindow.axaml | 120 ++++++++++++++++ .../IntegrationTestApp/MainWindow.axaml.cs | 135 ++++++++++++++++++ samples/IntegrationTestApp/Program.cs | 22 +++ .../IntegrationTestApp/ShowWindowTest.axaml | 35 +++++ .../ShowWindowTest.axaml.cs | 40 ++++++ samples/IntegrationTestApp/bundle.sh | 5 + samples/IntegrationTestApp/nuget.config | 11 ++ src/Avalonia.Controls/ControlExtensions.cs | 23 +++ 12 files changed, 485 insertions(+), 8 deletions(-) create mode 100644 samples/IntegrationTestApp/App.axaml create mode 100644 samples/IntegrationTestApp/App.axaml.cs create mode 100644 samples/IntegrationTestApp/IntegrationTestApp.csproj create mode 100644 samples/IntegrationTestApp/MainWindow.axaml create mode 100644 samples/IntegrationTestApp/MainWindow.axaml.cs create mode 100644 samples/IntegrationTestApp/Program.cs create mode 100644 samples/IntegrationTestApp/ShowWindowTest.axaml create mode 100644 samples/IntegrationTestApp/ShowWindowTest.axaml.cs create mode 100644 samples/IntegrationTestApp/bundle.sh create mode 100644 samples/IntegrationTestApp/nuget.config diff --git a/Avalonia.sln b/Avalonia.sln index 7037a9721b2..6c065a12e07 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -237,15 +237,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "sampl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestApp", "samples\IntegrationTestApp\IntegrationTestApp.csproj", "{D3867680-B9C7-43D6-BF2C-697EC9CF1151}" +EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 - src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5 - src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5 - src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5 - src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5 - src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU Ad-Hoc|iPhone = Ad-Hoc|iPhone @@ -2195,6 +2189,30 @@ Global {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhone.Build.0 = Release|Any CPU {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhone.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhone.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|Any CPU.Build.0 = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhone.ActiveCfg = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhone.Build.0 = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2258,8 +2276,17 @@ Global {C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098} {26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {D3867680-B9C7-43D6-BF2C-697EC9CF1151} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 + src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5 + src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5 + src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5 + src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5 + src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 + EndGlobalSection EndGlobal diff --git a/samples/IntegrationTestApp/App.axaml b/samples/IntegrationTestApp/App.axaml new file mode 100644 index 00000000000..a833e096dfe --- /dev/null +++ b/samples/IntegrationTestApp/App.axaml @@ -0,0 +1,7 @@ + + + + + diff --git a/samples/IntegrationTestApp/App.axaml.cs b/samples/IntegrationTestApp/App.axaml.cs new file mode 100644 index 00000000000..022931366d3 --- /dev/null +++ b/samples/IntegrationTestApp/App.axaml.cs @@ -0,0 +1,24 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +namespace IntegrationTestApp +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } + } +} diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj new file mode 100644 index 00000000000..42843993572 --- /dev/null +++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj @@ -0,0 +1,28 @@ + + + WinExe + net6.0 + enable + + + + IntegrationTestApp + net.avaloniaui.avalonia.integrationtestapp + true + 1.0.0 + + + + + + + + + + + + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml new file mode 100644 index 00000000000..f4d6ad3ace9 --- /dev/null +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + TextBlockWithName + + TextBlockWithNameAndAutomationId + + Label for TextBox + + Foo + + + + + + + + + + + + + + + + Unchecked + Checked + ThreeState + + + + + + + Item 0 + Item 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + None + + + + + + + + + + + NonOwned + Owned + Modal + + + Manual + CenterScreen + CenterOwner + + + + + + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs new file mode 100644 index 00000000000..9e180b12c57 --- /dev/null +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.VisualTree; + +namespace IntegrationTestApp +{ + public class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + InitializeViewMenu(); + this.AttachDevTools(); + AddHandler(Button.ClickEvent, OnButtonClick); + ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList(); + DataContext = this; + } + + public List ListBoxItems { get; } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void InitializeViewMenu() + { + var mainTabs = this.FindControl("MainTabs"); + var viewMenu = (NativeMenuItem)NativeMenu.GetMenu(this).Items[1]; + + foreach (TabItem tabItem in mainTabs.Items) + { + var menuItem = new NativeMenuItem + { + Header = (string)tabItem.Header!, + IsChecked = tabItem.IsSelected, + ToggleType = NativeMenuItemToggleType.Radio, + }; + + menuItem.Click += (s, e) => tabItem.IsSelected = true; + viewMenu.Menu.Items.Add(menuItem); + } + } + + private void ShowWindow() + { + var sizeTextBox = this.GetControl("ShowWindowSize"); + var modeComboBox = this.GetControl("ShowWindowMode"); + var locationComboBox = this.GetControl("ShowWindowLocation"); + var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null; + var owner = (Window)this.GetVisualRoot()!; + + var window = new ShowWindowTest + { + WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex, + }; + + if (size.HasValue) + { + window.Width = size.Value.Width; + window.Height = size.Value.Height; + } + + sizeTextBox.Text = string.Empty; + + switch (modeComboBox.SelectedIndex) + { + case 0: + window.Show(); + break; + case 1: + window.Show(owner); + break; + case 2: + window.ShowDialog(owner); + break; + } + } + + private void SendToBack() + { + var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; + + foreach (var window in lifetime.Windows) + { + window.Activate(); + } + } + + private void RestoreAll() + { + var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; + + foreach (var window in lifetime.Windows) + { + if (window.WindowState == WindowState.Minimized) + window.WindowState = WindowState.Normal; + } + } + + private void MenuClicked(object? sender, RoutedEventArgs e) + { + var clickedMenuItemTextBlock = this.FindControl("ClickedMenuItem"); + clickedMenuItemTextBlock.Text = ((MenuItem)sender!).Header.ToString(); + } + + private void OnButtonClick(object? sender, RoutedEventArgs e) + { + var source = e.Source as Button; + + if (source?.Name == "ComboBoxSelectionClear") + this.FindControl("BasicComboBox").SelectedIndex = -1; + if (source?.Name == "ComboBoxSelectFirst") + this.FindControl("BasicComboBox").SelectedIndex = 0; + if (source?.Name == "ListBoxSelectionClear") + this.FindControl("BasicListBox").SelectedIndex = -1; + if (source?.Name == "MenuClickedMenuItemReset") + this.FindControl("ClickedMenuItem").Text = "None"; + if (source?.Name == "ShowWindow") + ShowWindow(); + if (source?.Name == "SendToBack") + SendToBack(); + if (source?.Name == "ExitFullscreen") + WindowState = WindowState.Normal; + if (source?.Name == "RestoreAll") + RestoreAll(); + } + } +} diff --git a/samples/IntegrationTestApp/Program.cs b/samples/IntegrationTestApp/Program.cs new file mode 100644 index 00000000000..c09b249cfae --- /dev/null +++ b/samples/IntegrationTestApp/Program.cs @@ -0,0 +1,22 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; + +namespace IntegrationTestApp +{ + class Program + { + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .LogToTrace(); + } +} diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml new file mode 100644 index 00000000000..40c1642e919 --- /dev/null +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + Normal + Minimized + Maximized + Fullscreen + + + diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs new file mode 100644 index 00000000000..001f1867619 --- /dev/null +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -0,0 +1,40 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.Rendering; + +namespace IntegrationTestApp +{ + public class ShowWindowTest : Window + { + public ShowWindowTest() + { + InitializeComponent(); + DataContext = this; + PositionChanged += (s, e) => this.GetControl("Position").Text = $"{Position}"; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); + var scaling = PlatformImpl!.DesktopScaling; + this.GetControl("Position").Text = $"{Position}"; + this.GetControl("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}"; + this.GetControl("Scaling").Text = $"{scaling}"; + + if (Owner is not null) + { + var ownerRect = this.GetControl("OwnerRect"); + var owner = (Window)Owner; + ownerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}"; + } + } + } +} diff --git a/samples/IntegrationTestApp/bundle.sh b/samples/IntegrationTestApp/bundle.sh new file mode 100644 index 00000000000..505991582e8 --- /dev/null +++ b/samples/IntegrationTestApp/bundle.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +cd $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) +dotnet restore -r osx-arm64 +dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-arm64 -p:_AvaloniaUseExternalMSBuild=false \ No newline at end of file diff --git a/samples/IntegrationTestApp/nuget.config b/samples/IntegrationTestApp/nuget.config new file mode 100644 index 00000000000..6c273ab3d9b --- /dev/null +++ b/samples/IntegrationTestApp/nuget.config @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index c3cab0a729e..86ad335e263 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -66,6 +66,29 @@ public static T FindControl(this IControl control, string name) where T : cla return nameScope.Find(name); } + /// + /// Finds the named control in the scope of the specified control and throws if not found. + /// + /// The type of the control to find. + /// The control to look in. + /// The name of the control to find. + /// The control. + public static T GetControl(this IControl control, string name) where T : class, IControl + { + _ = control ?? throw new ArgumentNullException(nameof(control)); + _ = name ?? throw new ArgumentNullException(nameof(name)); + + var nameScope = control.FindNameScope(); + + if (nameScope == null) + { + throw new InvalidOperationException("Could not find parent name scope."); + } + + return nameScope.Find(name) ?? + throw new ArgumentException($"Could not find control named '{name}'."); + } + /// /// Sets a pseudoclass depending on an observable trigger. /// From 3b8499a356a5dae32676f36fa1a4de9b662e6e9e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 28 Jun 2022 17:45:47 +0100 Subject: [PATCH 2/3] Merge pull request #8405 from AvaloniaUI/fixes/8335-more-macos-window-issues Fix more macos window issues and add more integration tests. --- .../Avalonia.Native/src/OSX/WindowBaseImpl.mm | 2 +- native/Avalonia.Native/src/OSX/WindowImpl.mm | 18 +++++++++++------- src/Avalonia.Controls/Window.cs | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 0877c9b4b84..1d44c431c09 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -223,7 +223,7 @@ if (ret == nullptr) return E_POINTER; - if(Window != nullptr){ + if(Window != nullptr && _shown){ auto frame = [Window frame]; ret->Width = frame.size.width; ret->Height = frame.size.height; diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index cc276d19b20..27768101d17 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -118,13 +118,16 @@ { if(Window != nullptr) { - if(IsDialog()) + if (![Window isMiniaturized]) { - Activate(); - } - else - { - [Window orderFront:nullptr]; + if(IsDialog()) + { + Activate(); + } + else + { + [Window orderFront:nullptr]; + } } [Window invalidateShadow]; @@ -487,6 +490,8 @@ } if (_shown) { + _actualWindowState = _lastWindowState; + switch (state) { case Maximized: if (currentState == FullScreen) { @@ -544,7 +549,6 @@ break; } - _actualWindowState = _lastWindowState; WindowEvents->WindowStateChanged(_actualWindowState); } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ed8a86445f5..5242c50eaf0 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -902,10 +902,10 @@ Owner is Window ownerWindow && { if (owner != null) { - // TODO: We really need non-client size here. + var ownerSize = owner.FrameSize ?? owner.ClientSize; var ownerRect = new PixelRect( owner.Position, - PixelSize.FromSize(owner.ClientSize, scaling)); + PixelSize.FromSize(ownerSize, scaling)); Position = ownerRect.CenterRect(rect).Position; } } From af05fce02e44421752f91ab7f5fe263cae7bc26b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 28 Jun 2022 10:35:34 +0200 Subject: [PATCH 3/3] Merge pull request #8232 from AvaloniaUI/feature/window-integration-tests Feature/window integration tests --- src/Avalonia.Controls/TopLevel.cs | 6 ++- src/Avalonia.Controls/Window.cs | 48 +++++++++---------- src/Avalonia.Controls/WindowBase.cs | 12 +++-- .../WindowTests.cs | 27 +++++++++++ 4 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 2bc3b10328d..e30740e4e08 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -465,7 +465,11 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) /// Raises the event. /// /// The event args. - protected virtual void OnOpened(EventArgs e) => Opened?.Invoke(this, e); + protected virtual void OnOpened(EventArgs e) + { + FrameSize = PlatformImpl?.FrameSize; + Opened?.Invoke(this, e); + } /// /// Raises the event. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 5242c50eaf0..b358763c1d3 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -872,10 +872,10 @@ Owner is Window ownerWindow && var scaling = owner?.DesktopScaling ?? PlatformImpl?.DesktopScaling ?? 1; - // TODO: We really need non-client size here. - var rect = new PixelRect( - PixelPoint.Origin, - PixelSize.FromSize(ClientSize, scaling)); + // Use frame size, falling back to client size if the platform can't give it to us. + var rect = FrameSize.HasValue ? + new PixelRect(PixelSize.FromSize(FrameSize.Value, scaling)) : + new PixelRect(PixelSize.FromSize(ClientSize, scaling)); if (startupLocation == WindowStartupLocation.CenterScreen) { @@ -992,28 +992,28 @@ protected sealed override void HandleClosed() /// protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason) { - if (ClientSize == clientSize) - return; - - var sizeToContent = SizeToContent; - - // If auto-sizing is enabled, and the resize came from a user resize (or the reason was - // unspecified) then turn off auto-resizing for any window dimension that is not equal - // to the requested size. - if (sizeToContent != SizeToContent.Manual && - CanResize && - reason == PlatformResizeReason.Unspecified || - reason == PlatformResizeReason.User) + if (ClientSize != clientSize || double.IsNaN(Width) || double.IsNaN(Height)) { - if (clientSize.Width != ClientSize.Width) - sizeToContent &= ~SizeToContent.Width; - if (clientSize.Height != ClientSize.Height) - sizeToContent &= ~SizeToContent.Height; - SizeToContent = sizeToContent; - } + var sizeToContent = SizeToContent; + + // If auto-sizing is enabled, and the resize came from a user resize (or the reason was + // unspecified) then turn off auto-resizing for any window dimension that is not equal + // to the requested size. + if (sizeToContent != SizeToContent.Manual && + CanResize && + reason == PlatformResizeReason.Unspecified || + reason == PlatformResizeReason.User) + { + if (clientSize.Width != ClientSize.Width) + sizeToContent &= ~SizeToContent.Width; + if (clientSize.Height != ClientSize.Height) + sizeToContent &= ~SizeToContent.Height; + SizeToContent = sizeToContent; + } - Width = clientSize.Width; - Height = clientSize.Height; + Width = clientSize.Width; + Height = clientSize.Height; + } base.HandleResized(clientSize, reason); } diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 6333b418b29..4d42e9200ef 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -217,10 +217,14 @@ protected override void HandleClosed() /// The reason for the resize. protected override void HandleResized(Size clientSize, PlatformResizeReason reason) { - ClientSize = clientSize; - FrameSize = PlatformImpl.FrameSize; - LayoutManager.ExecuteLayoutPass(); - Renderer?.Resized(clientSize); + FrameSize = PlatformImpl?.FrameSize; + + if (ClientSize != clientSize) + { + ClientSize = clientSize; + LayoutManager.ExecuteLayoutPass(); + Renderer?.Resized(clientSize); + } } /// diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 63ccf74c2b1..a8c9b68d12c 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -695,6 +695,31 @@ public void Should_Not_Have_Offset_On_Bounds_When_Content_Larger_Than_Max_Window } } + [Fact] + public void Width_Height_Should_Not_Be_NaN_After_Show_With_SizeToContent_Manual() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var child = new Canvas + { + Width = 400, + Height = 800, + }; + + var target = new Window() + { + SizeToContent = SizeToContent.Manual, + Content = child + }; + + Show(target); + + // Values come from MockWindowingPlatform defaults. + Assert.Equal(800, target.Width); + Assert.Equal(600, target.Height); + } + } + [Fact] public void Width_Height_Should_Not_Be_NaN_After_Show_With_SizeToContent_WidthAndHeight() { @@ -712,6 +737,8 @@ public void Width_Height_Should_Not_Be_NaN_After_Show_With_SizeToContent_WidthAn Content = child }; + target.GetObservable(Window.WidthProperty).Subscribe(x => { }); + Show(target); Assert.Equal(400, target.Width);