using System; using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Chrome; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Platform; using Avalonia.VisualTree; using Xunit; namespace Avalonia.IntegrationTests.Win32; public abstract class ExtendClientAreaWindowTests : IDisposable { private const double ClientWidth = 200; private const double ClientHeight = 200; private Window? _window; private Window Window { get { Assert.NotNull(_window); return _window; } } protected abstract SystemDecorations Decorations { get; } public static MatrixTheoryData States => new([true, false], Enum.GetValues()); private async Task InitWindowAsync(WindowState state, bool canResize) { Assert.Null(_window); _window = new Window { CanResize = canResize, WindowState = state, SystemDecorations = Decorations, ExtendClientAreaToDecorationsHint = true, ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.PreferSystemChrome, Width = ClientWidth, Height = ClientHeight, WindowStartupLocation = WindowStartupLocation.Manual, Position = new PixelPoint(50, 50), Content = new Border { Background = Brushes.DodgerBlue, BorderBrush = Brushes.Yellow, BorderThickness = new Thickness(1) } }; _window.Show(); await Window.WhenLoadedAsync(); } [Theory] [MemberData(nameof(States))] public async Task Normal_State_Respects_Client_Size(bool canResize, WindowState initialState) { await InitWindowAsync(initialState, canResize); if (initialState != WindowState.Normal) Window.WindowState = WindowState.Normal; // The client size should have been kept var expected = PixelSize.FromSize(new Size(ClientWidth, ClientHeight), Window.RenderScaling); var clientSize = Window.GetWin32ClientSize(); Assert.Equal(expected, clientSize); VerifyNormalState(canResize); } protected abstract void VerifyNormalState(bool canResize); [Theory] [MemberData(nameof(States))] public async Task Maximized_State_Fills_Screen_Working_Area(bool canResize, WindowState initialState) { await InitWindowAsync(initialState, canResize); if (initialState != WindowState.Maximized) Window.WindowState = WindowState.Maximized; // The client size should match the screen working area var clientSize = Window.GetWin32ClientSize(); var screenWorkingArea = Window.GetScreen().WorkingArea; Assert.Equal(screenWorkingArea.Size, clientSize); VerifyMaximizedState(); } protected abstract void VerifyMaximizedState(); [Theory] [MemberData(nameof(States))] public async Task FullScreen_State_Fills_Screen(bool canResize, WindowState initialState) { await InitWindowAsync(initialState, canResize); if (initialState != WindowState.FullScreen) Window.WindowState = WindowState.FullScreen; // The client size should match the screen bounds var clientSize = Window.GetWin32ClientSize(); var screenBounds = Window.GetScreen().Bounds; Assert.Equal(screenBounds.Width, clientSize.Width); Assert.Equal(screenBounds.Height, clientSize.Height); // The window size should also match the screen bounds var windowBounds = Window.GetWin32WindowBounds(); Assert.Equal(screenBounds, windowBounds); // And no visible title bar AssertNoTitleBar(); } protected void AssertHasBorder() { var clientSize = Window.GetWin32ClientSize(); var windowBounds = Window.GetWin32WindowBounds(); Assert.NotEqual(clientSize.Width, windowBounds.Width); Assert.NotEqual(clientSize.Height, windowBounds.Height); } protected void AssertNoBorder() { var clientSize = Window.GetWin32ClientSize(); var windowBounds = Window.GetWin32WindowBounds(); Assert.Equal(clientSize.Width, windowBounds.Width); Assert.Equal(clientSize.Height, windowBounds.Height); } protected (double TitleBarHeight, double ButtonsHeight) GetTitleBarInfo() { var titleBar = Window.GetVisualDescendants().OfType().FirstOrDefault(); Assert.NotNull(titleBar); var buttons = titleBar.GetVisualDescendants().OfType().FirstOrDefault(); Assert.NotNull(buttons); return (titleBar.Height, buttons.Height); } private void AssertNoTitleBar() { var (titleBarHeight, buttonsHeight) = GetTitleBarInfo(); Assert.Equal(0, titleBarHeight); Assert.Equal(0, buttonsHeight); } public void Dispose() => _window?.Close(); public sealed class DecorationsFull : ExtendClientAreaWindowTests { protected override SystemDecorations Decorations => SystemDecorations.Full; protected override void VerifyNormalState(bool canResize) { AssertHasBorder(); AssertLargeTitleBarWithButtons(); } protected override void VerifyMaximizedState() => AssertLargeTitleBarWithButtons(); private void AssertLargeTitleBarWithButtons() { var (titleBarHeight, buttonsHeight) = GetTitleBarInfo(); Assert.True(titleBarHeight > 20); Assert.True(buttonsHeight > 20); } } public sealed class DecorationsBorderOnly : ExtendClientAreaWindowTests { protected override SystemDecorations Decorations => SystemDecorations.BorderOnly; protected override void VerifyNormalState(bool canResize) { AssertHasBorder(); if (canResize) AssertSmallTitleBarWithoutButtons(); else AssertNoTitleBar(); } protected override void VerifyMaximizedState() => AssertNoTitleBar(); private void AssertSmallTitleBarWithoutButtons() { var (titleBarHeight, buttonsHeight) = GetTitleBarInfo(); Assert.True(titleBarHeight < 10); Assert.NotEqual(0, titleBarHeight); Assert.Equal(0, buttonsHeight); } } public sealed class DecorationsNone : ExtendClientAreaWindowTests { protected override SystemDecorations Decorations => SystemDecorations.None; protected override void VerifyNormalState(bool canResize) { AssertNoBorder(); AssertNoTitleBar(); } protected override void VerifyMaximizedState() => AssertNoTitleBar(); } }