using System.Runtime.Versioning; using System.Xml.Linq; using AtomUI.Controls; using AtomUI.Media; using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Styling; using Avalonia.VisualTree; using Shouldly; using Xunit; namespace AtomUI.Desktop.Controls.Tests.Window; public class WindowResizeArtifactTests { [Fact] public void Visual_Layers_Use_One_Window_Frame_Clip_Without_Changing_Their_Coordinate_System() { var clientSize = new Size(900, 650); var shadow = new Thickness(36, 27, 36, 45); var document = XDocument.Load(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Themes/WindowTheme.axaml")); XNamespace av = "https://github.com/avaloniaui"; XNamespace atom = "https://atomui.net"; WindowVisualLayerClip.CalculateClipBounds(clientSize, shadow) .ShouldBe(new Rect(36, 27, 828, 578)); WindowVisualLayerClip.CalculateClipBounds(clientSize, default) .ShouldBe(new Rect(clientSize)); var layerManagers = document.Descendants(av + "VisualLayerManager") .Where(element => (string?)element.Attribute("Name") == "PART_VisualLayerManager") .ToList(); layerManagers.Count.ShouldBe(3); layerManagers.Count(manager => manager.Parent?.Name == atom + "WindowVisualLayerClip").ShouldBe(2); } [Theory] [InlineData(1.0)] [InlineData(1.25)] [InlineData(1.5)] [InlineData(1.6666666666666667)] public void Visual_Layer_Clip_Edges_Are_Aligned_To_Physical_Pixels(double renderScaling) { var surfaceSize = new Size(641, 480); var shadow = new Thickness(36, 27, 36, 45); var rawBounds = WindowVisualLayerClip.CalculateClipBounds(surfaceSize, shadow); var clipBounds = WindowVisualLayerClip.CalculatePixelAlignedClipBounds( surfaceSize, shadow, renderScaling); IsPhysicalPixelAligned(clipBounds.Left, renderScaling).ShouldBeTrue(); IsPhysicalPixelAligned(clipBounds.Top, renderScaling).ShouldBeTrue(); IsPhysicalPixelAligned(clipBounds.Right, renderScaling).ShouldBeTrue(); IsPhysicalPixelAligned(clipBounds.Bottom, renderScaling).ShouldBeTrue(); clipBounds.Right.ShouldBeGreaterThanOrEqualTo(rawBounds.Right); clipBounds.Bottom.ShouldBeGreaterThanOrEqualTo(rawBounds.Bottom); (clipBounds.Right - rawBounds.Right).ShouldBeGreaterThanOrEqualTo( 1 / renderScaling - 0.000001); (clipBounds.Bottom - rawBounds.Bottom).ShouldBeGreaterThanOrEqualTo( 1 / renderScaling - 0.000001); } [Fact] public void Visual_Layer_Clip_Preserves_Empty_Bounds_When_Frame_Is_Larger_Than_Surface() { WindowVisualLayerClip.CalculatePixelAlignedClipBounds( new Size(20, 18), new Thickness(12, 10, 12, 10), 1.25) .ShouldBe(new Rect(12, 10, 0, 0)); } [Theory] [InlineData(1.0)] [InlineData(1.25)] [InlineData(1.6666666666666667)] public void Visual_Layer_Clip_Does_Not_Bleed_Past_Surface_Without_Shadow(double renderScaling) { var surfaceSize = new Size(641.4, 480); WindowVisualLayerClip.CalculatePixelAlignedClipBounds( surfaceSize, default, renderScaling) .ShouldBe(new Rect( 0, 0, Avalonia.Layout.LayoutHelper.RoundLayoutValueUp(surfaceSize.Width, renderScaling), Avalonia.Layout.LayoutHelper.RoundLayoutValueUp(surfaceSize.Height, renderScaling))); } [Theory] [InlineData(901.81, 480.2, 1.6666666666666667, 902.4, 480.6)] [InlineData(901.4, 479.6, 1.5, 902, 480)] [InlineData(640, 480, 1, 640, 480)] public void Wayland_Overlay_Mask_Covers_The_Complete_Physical_Buffer( double logicalWidth, double logicalHeight, double renderScaling, double expectedWidth, double expectedHeight) { var maskSize = WindowVisualLayerClip.CalculateWaylandMaskSize( new Size(logicalWidth, logicalHeight), renderScaling); maskSize.Width.ShouldBe(expectedWidth, 0.000001); maskSize.Height.ShouldBe(expectedHeight, 0.000001); } [Fact] public void Linux_Window_Preserves_Shadow_And_Scales_Only_The_Managed_Resize_Grip() { var tokenSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/WindowToken.cs")); var chromeSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Chrome/AbstractLinuxWindowChromeManager.cs")); var reflectionSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Utils/WindowDrawnDecorationsReflectionExtensions.cs")); var waylandSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Chrome/WaylandWindowChromeManager.cs")); tokenSource.ShouldContain("FrameShadows = EffectiveGlobalToken.BoxShadowsSecondary;"); tokenSource.ShouldNotContain("ScaleFrameShadows"); waylandSource.ShouldContain("private const double ManagedResizeGripScale = 1.0 / 3.0;"); waylandSource.ShouldContain("Window.TryTakeOverManagedResizeGrip("); waylandSource.ShouldContain("ManagedResizeGripScale,"); reflectionSource.ShouldContain("decorations.FrameThickness"); reflectionSource.ShouldContain("decorations.ShadowThickness"); reflectionSource.ShouldContain("(frame.Left + shadow.Left) * scale"); reflectionSource.ShouldContain("(frame.Top + shadow.Top) * scale"); reflectionSource.ShouldContain("(frame.Right + shadow.Right) * scale"); reflectionSource.ShouldContain("(frame.Bottom + shadow.Bottom) * scale"); reflectionSource.ShouldContain("gripThicknessProperty.SetValue(resizeGrips, default(Thickness));"); } [Fact] [SupportedOSPlatform("linux")] public void Linux_Resize_Minimum_Does_Not_Grow_From_The_Current_TitleBar_Width() { var minimumSize = AbstractLinuxWindowChromeManager.CalculateResizeMinimumSize( new Thickness(36, 27, 36, 45), new CornerRadius(12), titleBarHeight: 40); minimumSize.Width.ShouldBe(98); minimumSize.Height.ShouldBe(194); } [Fact] public void Wayland_Resize_Uses_The_Platform_Reported_Size_Unmodified() { var waylandSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Chrome/WaylandWindowChromeManager.cs")); var windowSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Window.cs")); var resizerSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Utils/WindowResizer.cs")); waylandSource.ShouldNotContain("platformImpl.Resized ="); waylandSource.ShouldNotContain("CorrectPlatformResize"); windowSource.ShouldNotContain("NotifyResizeStarted"); windowSource.ShouldNotContain("NotifyResizeFinished"); resizerSource.ShouldNotContain("NotifyResizeStarted"); resizerSource.ShouldContain("TargetWindow.BeginResizeDrag(windowEdge, e);"); } [Theory] [SupportedOSPlatform("linux")] [InlineData(36, 26.666666666666668, 36, 45.333333333333336, 36, 27, 36, 45)] [InlineData(35.5, 26.4, 36.49, 45.5, 36, 26, 36, 46)] public void Wayland_Shadow_Extents_Are_Integral_At_The_Protocol_Boundary( double left, double top, double right, double bottom, double expectedLeft, double expectedTop, double expectedRight, double expectedBottom) { var normalized = WaylandWindowChromeManager.NormalizeShadowExtents( new Thickness(left, top, right, bottom)); normalized.ShouldBe(new Thickness(expectedLeft, expectedTop, expectedRight, expectedBottom)); const int surfaceWidth = 1000; const int surfaceHeight = 900; var geometryWidth = (int)Math.Ceiling(surfaceWidth - normalized.Right) - (int)normalized.Left; var geometryHeight = (int)Math.Ceiling(surfaceHeight - normalized.Bottom) - (int)normalized.Top; (geometryWidth + normalized.Left + normalized.Right).ShouldBe(surfaceWidth); (geometryHeight + normalized.Top + normalized.Bottom).ShouldBe(surfaceHeight); } [Fact] [SupportedOSPlatform("linux")] public void Wayland_Min_Max_Hints_Use_Window_Geometry_Excluding_Shadow() { var geometryConstraint = WaylandWindowChromeManager.CalculateGeometryConstraint( new Size(594, 474), new Thickness(36, 27, 36, 45)); geometryConstraint.ShouldBe(new Size(522, 402)); WaylandWindowChromeManager.CalculateGeometryConstraint( new Size(double.PositiveInfinity, double.PositiveInfinity), new Thickness(36, 27, 36, 45)) .ShouldBe(new Size(double.PositiveInfinity, double.PositiveInfinity)); WaylandWindowChromeManager.CalculateGeometryConstraint( default, new Thickness(36, 27, 36, 45)) .ShouldBe(default); } [Fact] [SupportedOSPlatform("linux")] public void Wayland_Shadow_Input_Region_Preserves_Only_The_Managed_Resize_Band() { var region = WaylandWindowChromeManager.CalculateInputRegion( new Avalonia.Size(900, 600), new Avalonia.Thickness(24), new Avalonia.Thickness(8), canResize: true, isCsdEnabled: true, Avalonia.Controls.WindowState.Normal); region.ShouldBe(new Avalonia.PixelRect(16, 16, 868, 568)); WaylandWindowChromeManager.CalculateInputRegion( new Avalonia.Size(900, 600), new Avalonia.Thickness(24), new Avalonia.Thickness(8), canResize: false, isCsdEnabled: true, Avalonia.Controls.WindowState.Normal) .ShouldBe(new Avalonia.PixelRect(24, 24, 852, 552)); WaylandWindowChromeManager.CalculateInputRegion( new Avalonia.Size(900, 600), new Avalonia.Thickness(24), new Avalonia.Thickness(8), canResize: true, isCsdEnabled: true, Avalonia.Controls.WindowState.Maximized) .ShouldBe(new Avalonia.PixelRect(0, 0, 900, 600)); WaylandWindowChromeManager.CalculateInputRegion( new Avalonia.Size(900, 600), new Avalonia.Thickness(24), new Avalonia.Thickness(8), canResize: true, isCsdEnabled: false, Avalonia.Controls.WindowState.Normal) .ShouldBe(new Avalonia.PixelRect(0, 0, 900, 600)); } [Fact] public void Wayland_Shadow_Input_Region_Uses_WlSurface_Protocol_And_Tracks_Size() { var chromeSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Chrome/WaylandWindowChromeManager.cs")); var reflectionSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Native/Linux/WaylandWindowReflectionExtensions.cs")); var nativeSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Native/Linux/WaylandWindowUtils.cs")); chromeSource.ShouldContain("e.Property == Visual.BoundsProperty"); chromeSource.ShouldContain("RequestFrameGeometryUpdate();"); chromeSource.ShouldContain("Window.TrySetWaylandInputRectangle("); chromeSource.ShouldContain("shadowThickness.Left - effectiveGrip.Left"); chromeSource.ShouldContain("Math.Ceiling(insetLeft)"); chromeSource.ShouldContain("Math.Floor(surfaceWidth - insetRight)"); chromeSource.ShouldNotContain("RenderScaling"); reflectionSource.ShouldContain("WaylandWindowUtils.SetInputRectangle"); nativeSource.ShouldContain("wlCompositor.CreateRegion()"); nativeSource.ShouldContain("region.Add(x, y, width, height);"); nativeSource.ShouldContain("wlSurface.SetInputRegion(region);"); nativeSource.ShouldContain("region.Destroy();"); nativeSource.IndexOf("SetInputRegion", StringComparison.Ordinal) .ShouldBeLessThan(nativeSource.IndexOf("region.Destroy", StringComparison.Ordinal)); } [Fact] public void Wayland_Input_Region_Destroys_The_Protocol_Object_Before_Disposing_The_Local_Proxy() { var nativeSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Native/Linux/WaylandWindowUtils.cs")); nativeSource.ShouldContain("region.Destroy();"); nativeSource.ShouldContain("region.Dispose();"); nativeSource.IndexOf("region.Destroy();", StringComparison.Ordinal) .ShouldBeLessThan(nativeSource.IndexOf("region.Dispose();", StringComparison.Ordinal)); } [Fact] public void Browser_Wasm_Excludes_Wayland_Input_Region_Protocol_Assemblies() { var browserProject = File.ReadAllText(GetRepoFile( "controlgallery/AtomUIGallery.Browser/AtomUIGallery.Browser.csproj")); browserProject.ShouldContain("ExcludeDesktopPlatformAssembliesFromBrowserWasm"); browserProject.ShouldContain("'%(FileName)' == 'Avalonia.Wayland'"); browserProject.ShouldContain("'%(FileName)' == 'NWayland'"); } [Fact] public void Window_Resizer_Hover_Cursors_Preserve_Directional_Semantics() { var source = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Themes/WindowResizerTheme.axaml")); var builderSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/DesktopAppBuilderExtensions.cs")); source.ShouldContain("Cursor=\"TopSide\""); source.ShouldContain("Cursor=\"BottomSide\""); source.ShouldContain("Cursor=\"LeftSide\""); source.ShouldContain("Cursor=\"RightSide\""); source.ShouldContain("Cursor=\"TopLeftCorner\""); source.ShouldContain("Cursor=\"TopRightCorner\""); source.ShouldContain("Cursor=\"BottomLeftCorner\""); source.ShouldContain("Cursor=\"BottomRightCorner\""); builderSource.ShouldNotContain("PreferSymmetricResizeCursorNames"); } [Fact] public void Window_Resizer_Compiled_Theme_Uses_The_Expected_Hover_Cursors() { AvaloniaTestApp.EnsureInitialized(); var resizer = new WindowResizer(); Application.Current!.TryFindResource(typeof(WindowResizer), out var resource).ShouldBeTrue(); resizer.Theme = resource.ShouldBeAssignableTo(); var host = new Avalonia.Controls.Window { Width = 100, Height = 100, Content = resizer }; host.Show(); resizer.ApplyTemplate(); var grips = resizer.GetVisualDescendants() .OfType() .Where(border => border.Tag is ResizeHandleLocation) .ToDictionary( border => (ResizeHandleLocation)border.Tag!, border => border.Cursor?.ToString()); grips[ResizeHandleLocation.North].ShouldBe("TopSide"); grips[ResizeHandleLocation.South].ShouldBe("BottomSide"); grips[ResizeHandleLocation.West].ShouldBe("LeftSide"); grips[ResizeHandleLocation.East].ShouldBe("RightSide"); grips[ResizeHandleLocation.NorthWest].ShouldBe("TopLeftCorner"); grips[ResizeHandleLocation.NorthEast].ShouldBe("TopRightCorner"); grips[ResizeHandleLocation.SouthWest].ShouldBe("BottomLeftCorner"); grips[ResizeHandleLocation.SouthEast].ShouldBe("BottomRightCorner"); host.Close(); } [Fact] public void Window_Prepares_Linux_Initial_Client_Size_Before_Show() { var windowSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Window.cs")); var chromeSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Chrome/AbstractLinuxWindowChromeManager.cs")); var x11Source = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Chrome/X11WindowChromeManager.cs")); windowSource.ShouldContain("public override void Show()"); windowSource.ShouldContain("_platformChromeManager?.PrepareInitialShowState();"); windowSource.ShouldContain("base.Show();"); windowSource.IndexOf("_platformChromeManager?.PrepareInitialShowState();", StringComparison.Ordinal) .ShouldBeLessThan(windowSource.IndexOf("base.Show();", StringComparison.Ordinal)); chromeSource.ShouldContain("PrepareInitialShowState()"); x11Source.ShouldContain("SizeToContent != SizeToContent.Manual"); x11Source.ShouldContain("WindowState is WindowState.Minimized or WindowState.Maximized or WindowState.FullScreen"); x11Source.ShouldContain("SetPlatformChromeClientSize(clientSize);"); x11Source.ShouldContain("Window.Width = clientSize.Width;"); x11Source.ShouldContain("Window.Height = clientSize.Height;"); x11Source.ShouldContain("TryGetInitialStartupPosition"); x11Source.ShouldContain("WindowStartupLocation.Manual"); x11Source.ShouldContain("ConfigureLinuxInitialWindowGeometry"); chromeSource.ShouldContain("_initialShowStatePrepared"); } [Fact] public void Linux_Initial_Geometry_Updates_X11_Size_Hints_Before_Map() { var extensionSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/WindowExtensions.cs")); var linuxSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/Linux/WindowUtils.Linux.cs")); var interopSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/Linux/WindowUtils.Interop.cs")); extensionSource.ShouldContain("ConfigureLinuxInitialWindowGeometry"); extensionSource.ShouldContain("WindowUtilsLinux.ConfigureInitialWindowGeometry"); linuxSource.ShouldContain("ConfigureInitialWindowGeometry"); linuxSource.ShouldContain("XSetWMNormalHints"); linuxSource.ShouldContain("XMoveResizeWindow"); linuxSource.IndexOf("XSetWMNormalHints", StringComparison.Ordinal) .ShouldBeLessThan(linuxSource.IndexOf("XMoveResizeWindow", StringComparison.Ordinal)); interopSource.ShouldContain("USPosition"); interopSource.ShouldContain("USSize"); interopSource.ShouldContain("PSize"); } [Fact] public void Linux_Client_Drawn_Shadow_Publishes_X11_Csd_Frame_Extents() { var windowSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Window.cs")); var chromeSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Chrome/X11WindowChromeManager.cs")); var linuxChromeSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Chrome/AbstractLinuxWindowChromeManager.cs")); var extensionSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/WindowExtensions.cs")); var linuxSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/Linux/WindowUtils.Linux.cs")); var interopSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/Linux/WindowUtils.Interop.cs")); windowSource.ShouldContain("_platformChromeManager = WindowChromeManager.Attach(this);"); linuxChromeSource.ShouldContain("_window.ScalingChanged += HandleScalingChanged;"); chromeSource.ShouldContain("ApplyCsdFrameExtents();"); chromeSource.ShouldContain("Window.WindowState is WindowState.Normal ? Window.FrameShadowThickness : default"); chromeSource.ShouldContain("Window.SetLinuxX11CsdFrameExtents(frameExtents);"); windowSource.ShouldNotContain("EnsureMinSizeForDecorations"); windowSource.IndexOf("_platformChromeManager?.PrepareInitialShowState();", StringComparison.Ordinal) .ShouldBeLessThan(windowSource.IndexOf("base.Show();", StringComparison.Ordinal)); extensionSource.ShouldContain("SetLinuxX11CsdFrameExtents"); extensionSource.ShouldContain("handle.HandleDescriptor != \"XID\""); extensionSource.ShouldContain("ToPixelMargin(frameExtents.Left, scaling)"); extensionSource.ShouldContain("ToPixelMargin(frameExtents.Top, scaling)"); extensionSource.ShouldContain("ToPixelMargin(frameExtents.Right, scaling)"); extensionSource.ShouldContain("ToPixelMargin(frameExtents.Bottom, scaling)"); linuxSource.ShouldContain("X11CsdFrameExtentsPropertyName = \"_GTK_FRAME_EXTENTS\""); linuxSource.ShouldContain("SetX11CsdFrameExtents"); linuxSource.ShouldContain("Mutter and KWin"); linuxSource.ShouldContain("left, right, top, bottom"); linuxSource.ShouldContain("XInternAtom"); linuxSource.ShouldContain("XChangeProperty"); linuxSource.IndexOf("ToCardinal(left)", StringComparison.Ordinal) .ShouldBeLessThan(linuxSource.IndexOf("ToCardinal(right)", StringComparison.Ordinal)); linuxSource.IndexOf("ToCardinal(right)", StringComparison.Ordinal) .ShouldBeLessThan(linuxSource.IndexOf("ToCardinal(top)", StringComparison.Ordinal)); linuxSource.IndexOf("ToCardinal(top)", StringComparison.Ordinal) .ShouldBeLessThan(linuxSource.IndexOf("ToCardinal(bottom)", StringComparison.Ordinal)); interopSource.ShouldContain("PropModeReplace"); interopSource.ShouldContain("XInternAtom"); interopSource.ShouldContain("XChangeProperty"); interopSource.ShouldContain("IntPtr[] data"); windowSource.ShouldNotContain("GtkFrameExtents"); chromeSource.ShouldNotContain("GtkFrameExtents"); extensionSource.ShouldNotContain("GtkFrameExtents"); linuxSource.ShouldNotContain("GtkFrameExtents"); } [Fact] public void Linux_Window_Frame_Geometry_Updates_Are_Coalesced_Before_Render() { var windowSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Window.cs")); var chromeSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Chrome/AbstractLinuxWindowChromeManager.cs")); chromeSource.ShouldContain("HandleFrameShadowChanged"); chromeSource.ShouldContain("ConfigureTitleBarHeightHint"); chromeSource.ShouldContain("_hasPendingFrameShadowThickness"); chromeSource.ShouldContain("_hasPendingTitleBarHeightHint"); chromeSource.ShouldContain("RequestFrameGeometryUpdate"); chromeSource.ShouldContain("ApplyPendingFrameGeometryUpdate"); chromeSource.ShouldContain("ApplyPendingFrameGeometryInputs"); chromeSource.ShouldContain("UpdateFrameGeometry"); chromeSource.ShouldContain("Avalonia.Threading.DispatcherPriority.Render"); windowSource.ShouldContain("FrameShadowProperty.Changed.AddClassHandler((window, args) =>"); windowSource.ShouldContain("window.HandleFrameShadowPropertyChanged(args.GetNewValue())"); windowSource.ShouldContain("_platformChromeManager.HandleFrameShadowChanged(frameShadow);"); windowSource.ShouldContain("_platformChromeManager.ConfigureTitleBarHeightHint(e.NewSize.Height);"); windowSource.ShouldContain("_platformChromeManager?.HandlePropertyChanged(change.Property);"); chromeSource.ShouldContain("RequestFrameGeometryUpdate();"); chromeSource.IndexOf("protected void RequestFrameGeometryUpdate()", StringComparison.Ordinal) .ShouldBeLessThan(chromeSource.IndexOf("private void ApplyPendingFrameGeometryUpdate()", StringComparison.Ordinal)); chromeSource.IndexOf("private void ApplyPendingFrameGeometryUpdate()", StringComparison.Ordinal) .ShouldBeLessThan(chromeSource.IndexOf("private void ApplyPendingFrameGeometryInputs()", StringComparison.Ordinal)); chromeSource.ShouldContain("ApplyPendingFrameGeometryInputs();\n UpdateFrameGeometry();"); chromeSource.IndexOf("Dispatcher.Post(ApplyPendingFrameGeometryUpdate", StringComparison.Ordinal) .ShouldBeLessThan(chromeSource.IndexOf("private void ApplyPendingFrameGeometryUpdate()", StringComparison.Ordinal)); windowSource.ShouldNotContain("RequestLinuxFrameGeometryUpdate"); windowSource.ShouldNotContain("ApplyPendingLinuxFrameGeometryUpdate"); windowSource.ShouldNotContain("UpdateLinuxFrameGeometry"); } [Fact] public void Linux_Shadow_Input_Region_Keeps_Only_Ten_Dip_Resize_Band() { var chromeSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Chrome/X11WindowChromeManager.cs")); var nativeInputSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/WindowExtensions.cs")); var nativeLinuxSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/Linux/WindowUtils.Linux.cs")); File.Exists(GetRepoFileOrNull("src/AtomUI.Native/Linux/ClickThroughShadowExtensions.cs") ?? string.Empty) .ShouldBeFalse(); chromeSource.ShouldContain("AttachClickThroughShadow();"); chromeSource.ShouldContain("s_shadowInputRegionAffectsProperties"); chromeSource.ShouldContain("ShadowInputRegionResizeBand"); chromeSource.ShouldContain("CanResizeProperty"); chromeSource.ShouldContain("FrameShadowThicknessProperty"); chromeSource.ShouldContain("IsCsdEnabledProperty"); chromeSource.ShouldContain("Window.Opened +="); chromeSource.ShouldContain("Window.PropertyChanged +="); chromeSource.ShouldContain("Visual.BoundsProperty"); chromeSource.ShouldContain("AvaloniaWindow.WindowDecorationMarginProperty"); chromeSource.ShouldContain("TopLevel.TransparencyLevelHintProperty"); chromeSource.ShouldContain("private const double ShadowInputRegionResizeBand = 10.0;"); chromeSource.ShouldContain("var effectiveResizeBand = Window.CanResize ? ShadowInputRegionResizeBand : 0;"); chromeSource.ShouldContain("shadowThickness.Left - effectiveResizeBand"); chromeSource.ShouldContain("shadowThickness.Top - effectiveResizeBand"); chromeSource.ShouldContain("Window.SetWindowInputRectangle(x, y, w, h);"); chromeSource.ShouldContain("Window.ResetWindowInputRegion(fullW, fullH);"); nativeInputSource.ShouldContain("SetWindowInputRectangle"); nativeInputSource.ShouldContain("ResetWindowInputRegion"); nativeLinuxSource.ShouldContain("SetInputRectangle"); } [Fact] public void Native_Linux_Window_Utilities_Do_Not_Keep_Legacy_Ignore_Mouse_Shape_Query_Code() { var extensionSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/WindowExtensions.cs")); var linuxSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/Linux/WindowUtils.Linux.cs")); var interopSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/Linux/WindowUtils.Interop.cs")); extensionSource.ShouldNotContain("SetWindowIgnoreMouseEventsLinux"); extensionSource.ShouldNotContain("IsWindowIgnoreMouseEventsLinux"); linuxSource.ShouldNotContain("SetWindowIgnoreMouseEventsLinux"); linuxSource.ShouldNotContain("IsWindowIgnoreMouseEventsLinux"); linuxSource.ShouldNotContain("GetWindowGeometry"); interopSource.ShouldNotContain("xcb_get_geometry"); interopSource.ShouldNotContain("xcb_get_geometry_reply"); interopSource.ShouldNotContain("xcb_shape_get_rectangles"); interopSource.ShouldNotContain("xcb_shape_get_rectangles_reply"); interopSource.ShouldNotContain("xcb_shape_get_rectangles_rectangles_length"); interopSource.ShouldNotContain("xcb_get_geometry_reply_t"); interopSource.ShouldNotContain("xcb_shape_get_rectangles_reply_t"); interopSource.ShouldNotContain("xcb_shape_query_version_reply_t"); } [Fact] public void Linux_NonCsd_Window_Template_Clips_Content_To_Window_CornerRadius() { var document = XDocument.Load(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Themes/WindowTheme.axaml")); XNamespace av = "https://github.com/avaloniaui"; XNamespace atom = "https://atomui.net"; var linuxTemplateStyle = document.Descendants(av + "Style") .Single(element => (string?)element.Attribute("Selector") == "^[OsType=Linux][IsCsdEnabled=False]" && element.Descendants(av + "ControlTemplate").Any()); var contentClip = linuxTemplateStyle.Descendants(av + "Border") .Single(element => (string?)element.Attribute("Name") == "WindowContentClip"); contentClip.Attribute("CornerRadius").ShouldNotBeNull().Value.ShouldBe("{TemplateBinding CornerRadius}"); contentClip.Attribute("ClipToBounds").ShouldNotBeNull().Value.ShouldBe("True"); contentClip.Attribute("Margin").ShouldNotBeNull().Value.ShouldBe("{TemplateBinding FrameShadowThickness}"); contentClip.Ancestors(atom + "WindowVisualLayerClip").Count().ShouldBe(1); var dockPanel = contentClip.Elements(av + "DockPanel").Single(); dockPanel.Attribute("LastChildFill").ShouldNotBeNull().Value.ShouldBe("True"); dockPanel.Attribute("Margin").ShouldBeNull(); } [Fact] public void Linux_Csd_Window_Template_Clips_Content_Surface_To_Bottom_Window_CornerRadius() { var document = XDocument.Load(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Themes/WindowTheme.axaml")); XNamespace av = "https://github.com/avaloniaui"; XNamespace atom = "https://atomui.net"; document.Descendants() .ShouldContain(element => element.Name.LocalName == "CornerRadiusFilterConverter" && element.Attributes().Any(attribute => attribute.Name.LocalName == "Key" && (string?)attribute == "WindowContentBottomCornerRadiusFilter") && (string?)element.Attribute("Filter") == "BottomLeft, BottomRight"); var csdTemplateStyle = document.Descendants(av + "Style") .Single(element => (string?)element.Attribute("Selector") == "^[IsCsdEnabled=True]" && element.Descendants(av + "ControlTemplate").Any()); csdTemplateStyle.Descendants(av + "Border") .ShouldNotContain(element => (string?)element.Attribute("Name") == "WindowContentClip"); var layerClip = csdTemplateStyle.Descendants(atom + "WindowVisualLayerClip").Single(); layerClip.Attribute("ShadowThickness").ShouldNotBeNull().Value.ShouldBe( "{TemplateBinding FrameShadowThickness}"); layerClip.Attribute("CornerRadius").ShouldNotBeNull().Value.ShouldBe( "{TemplateBinding CornerRadius}"); var visualLayerManager = layerClip.Elements(av + "VisualLayerManager") .Single(element => (string?)element.Attribute("Name") == "PART_VisualLayerManager"); var contentPanel = visualLayerManager.Elements(av + "Panel").Single(); contentPanel.Attribute("Margin").ShouldNotBeNull().Value.ShouldBe( "{Binding $parent[atom:Window].EffectiveContentFrameMargin}"); contentPanel.Attribute("ClipToBounds").ShouldBeNull(); var contentFrameLayer = contentPanel.Elements(av + "ContentPresenter") .Single(element => (string?)element.Attribute("Name") == "ContentFrameLayer"); contentFrameLayer.Attribute("CornerRadius").ShouldNotBeNull().Value.ShouldBe( "{TemplateBinding CornerRadius, Converter={StaticResource WindowContentBottomCornerRadiusFilter}}"); contentFrameLayer.Attribute("ClipToBounds").ShouldNotBeNull().Value.ShouldBe("True"); var contentFrame = contentPanel.Elements(av + "Border") .Single(element => (string?)element.Attribute("Name") == "ContentFrame"); contentFrame.Attribute("CornerRadius").ShouldNotBeNull().Value.ShouldBe( "{TemplateBinding CornerRadius, Converter={StaticResource WindowContentBottomCornerRadiusFilter}}"); contentFrame.Attribute("ClipToBounds").ShouldNotBeNull().Value.ShouldBe("True"); } [Theory] [InlineData(OsType.Windows)] [InlineData(OsType.Linux)] [InlineData(OsType.macOS)] public void Csd_Window_Hides_The_Managed_TitleBar_Without_Dropping_Full_Window_Decorations(OsType osType) { AvaloniaTestApp.EnsureInitialized(); var window = new AtomUI.Desktop.Controls.Window(); window.SetValue(AtomUI.Desktop.Controls.Window.OsTypeProperty, osType); window.IsCsdEnabled = true; window.IsTitleBarVisible = false; window.PreparePlatformChromeInitialShowLayout(); window.WindowDecorations.ShouldBe(WindowDecorations.Full); window.IsTitleBarVisible = true; window.WindowDecorations.ShouldBe(WindowDecorations.Full); window.IsTitleBarVisible = false; window.WindowDecorations.ShouldBe(WindowDecorations.Full); } [Fact] public void Drawn_Decorations_Combine_Platform_Title_Bar_Capability_With_AtomUI_Visibility() { var document = XDocument.Load(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Themes/WindowDrawnDecorationsTheme.axaml")); AssertTitleBarVisibilityContract(document, "PART_TitleBar"); AssertTitleBarVisibilityContract(document, "PART_TitleBarPresenter"); AssertTitleBarVisibilityContract(document, "WindowTitleBarShadowBackground"); } [Fact] public void MacOs_NonCsd_Window_Removes_The_Native_TitleBar_When_TitleBar_Visibility_Is_Disabled() { AvaloniaTestApp.EnsureInitialized(); var window = new AtomUI.Desktop.Controls.Window(); window.SetValue(AtomUI.Desktop.Controls.Window.OsTypeProperty, OsType.macOS); window.IsCsdEnabled = false; window.IsTitleBarVisible = false; window.PreparePlatformChromeInitialShowLayout(); window.WindowDecorations.ShouldBe(WindowDecorations.BorderOnly); window.IsTitleBarVisible = true; window.WindowDecorations.ShouldBe(WindowDecorations.Full); window.IsTitleBarVisible = false; window.WindowDecorations.ShouldBe(WindowDecorations.BorderOnly); } [Fact] public void MacOs_Window_Decoration_Changes_Reapply_And_Defer_Traffic_Light_Layout() { var windowSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Window.cs")); var relayoutBlockStart = windowSource.IndexOf( "if (change.Property == WindowStateProperty ||", StringComparison.Ordinal); var relayoutBlockEnd = windowSource.IndexOf( "if (change.Property == ExtendClientAreaTitleBarHeightHintProperty ||", relayoutBlockStart + 1, StringComparison.Ordinal); var relayoutBlock = windowSource[relayoutBlockStart..relayoutBlockEnd]; (relayoutBlock.Split( "change.Property == WindowDecorationsProperty", StringSplitOptions.None).Length - 1) .ShouldBe(2); relayoutBlock.ShouldContain("QueueMacOsWindowConfiguration"); relayoutBlock.ShouldContain("forceFollowUp: true"); windowSource.ShouldContain("DispatcherPriority.Render"); } [Fact] public void MacOs_TitleBar_Height_Changes_Defer_Traffic_Light_Layout_Until_Render() { var windowSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Window.cs")); var relayoutBlockStart = windowSource.IndexOf( "if (change.Property == WindowStateProperty ||", StringComparison.Ordinal); var relayoutBlockEnd = windowSource.IndexOf( "if (change.Property == ExtendClientAreaTitleBarHeightHintProperty ||", relayoutBlockStart + 1, StringComparison.Ordinal); var relayoutBlock = windowSource[relayoutBlockStart..relayoutBlockEnd]; relayoutBlock.ShouldContain("change.Property == ExtendClientAreaTitleBarHeightHintProperty"); relayoutBlock.ShouldContain("QueueMacOsWindowConfiguration"); windowSource.ShouldContain("DispatcherPriority.Render"); windowSource.ShouldContain("ApplyQueuedMacOsWindowConfiguration"); } [Fact] public void MacOs_Window_Sharing_Transitions_Recheck_Native_Button_Frame() { var windowSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Window.cs")); var nativeSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/MacOS/WindowUtils.MacOS.cs")); var interopSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/MacOS/WindowUtils.Interop.cs")); windowSource.ShouldNotContain("DispatcherTimer"); windowSource.ShouldContain("ObserveStandardWindowButtonChanges"); windowSource.ShouldContain("_macOsWindowButtonObserver"); windowSource.ShouldContain("StopMacOsWindowButtonObserver"); nativeSource.ShouldContain("IsStandardWindowButtonsVisible"); nativeSource.ShouldContain("ObserveStandardWindowButtonChanges"); nativeSource.ShouldContain("NSNotificationCenter"); nativeSource.ShouldContain("GetStandardWindowButtonFrame"); interopSource.ShouldContain("isHidden"); interopSource.ShouldContain("postsFrameChangedNotifications"); interopSource.ShouldContain("NSViewFrameDidChangeNotification"); nativeSource.ShouldContain("observeValueForKeyPath"); nativeSource.ShouldContain("HiddenKeyPath"); interopSource.ShouldContain("removeObserver:forKeyPath:"); windowSource.ShouldContain("_macOsCachedButtonY"); } [Fact] public void MacOs_Window_Button_Observer_Releases_All_Native_Resources() { var nativeSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/MacOS/WindowUtils.MacOS.cs")); var interopSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/MacOS/WindowUtils.Interop.cs")); interopSource.ShouldContain("dlclose"); nativeSource.ShouldContain("_hiddenKeyPath"); nativeSource.ShouldNotContain("private static readonly IntPtr HiddenKeyPath"); nativeSource.ShouldContain("RemoveNativeRegistrations"); nativeSource.ShouldContain("_keyValueObserversRegistered"); nativeSource.ShouldContain("_frameNotificationStateCaptured"); nativeSource.ShouldContain("WindowUtilsInterop.ReleaseSelector"); } [Fact] public void MacOs_NonCsd_Window_Preserves_The_Native_Resize_Cursor_Hit_Test_Layer() { var document = XDocument.Load(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Themes/WindowTheme.axaml")); var managerSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Chrome/WindowChromeManager.cs")); var macOsManagerSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Chrome/MacOSWindowChromeManager.cs")); XNamespace av = "https://github.com/avaloniaui"; XNamespace atom = "https://atomui.net"; var macOsTemplateStyle = document.Descendants(av + "Style") .Single(element => (string?)element.Attribute("Selector") == "^[OsType=macOS][IsCsdEnabled=False]" && element.Descendants(av + "ControlTemplate").Any()); macOsTemplateStyle.Descendants(atom + "WindowResizer").ShouldBeEmpty(); managerSource.ShouldContain("MacOSWindowChromeManager.Attach(window)"); macOsManagerSource.ShouldContain("public bool UsesCustomResizer => false;"); macOsManagerSource.ShouldNotContain("Window.TryTakeOverManagedResizeGrip("); macOsManagerSource.ShouldNotContain("Window.ConfigureManagedResizeGrip("); macOsManagerSource.ShouldContain("_window.SetMacOsResizeIndicatorVisible(false);"); } [Fact] public void MacOs_Resize_Cursor_Manager_Does_Not_Replace_Other_Platform_Managers() { var managerSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Chrome/WindowChromeManager.cs")); var linuxBranchStart = managerSource.IndexOf( "if (OperatingSystem.IsLinux())", StringComparison.Ordinal); var windowsBranchStart = managerSource.IndexOf( "if (OperatingSystem.IsWindows())", linuxBranchStart + 1, StringComparison.Ordinal); var macOsBranchStart = managerSource.IndexOf( "if (OperatingSystem.IsMacOS())", windowsBranchStart + 1, StringComparison.Ordinal); linuxBranchStart.ShouldBeGreaterThanOrEqualTo(0); windowsBranchStart.ShouldBeGreaterThan(linuxBranchStart); macOsBranchStart.ShouldBeGreaterThan(windowsBranchStart); managerSource[linuxBranchStart..windowsBranchStart].ShouldContain( "AbstractLinuxWindowChromeManager.Attach(window)"); managerSource[windowsBranchStart..macOsBranchStart].ShouldContain( "WindowsWindowChromeManager.Attach(window)"); managerSource[macOsBranchStart..].ShouldContain("MacOSWindowChromeManager.Attach(window)"); } [Fact] public void MacOs_Window_Chrome_Manager_Preserves_Default_Frame_Geometry_Updates() { AvaloniaTestApp.EnsureInitialized(); var window = new AtomUI.Desktop.Controls.Window(); var manager = MacOSWindowChromeManager.Attach(window); var frameShadow = new BoxShadows(new BoxShadow { OffsetX = 3, OffsetY = 4, Blur = 10, Spread = 2 }); manager.HandleFrameShadowChanged(frameShadow); manager.ConfigureTitleBarHeightHint(42); manager.UsesCustomResizer.ShouldBeFalse(); window.FrameShadowThickness.ShouldBe(frameShadow.Thickness()); window.ExtendClientAreaTitleBarHeightHint.ShouldBe(42); } [Fact] public void Linux_NonCsd_Window_Hides_Only_The_AtomUI_TitleBar_And_Keeps_Native_Decorations_Disabled() { var document = XDocument.Load(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Themes/WindowTheme.axaml")); XNamespace av = "https://github.com/avaloniaui"; var linuxTemplateStyle = document.Descendants(av + "Style") .Single(element => (string?)element.Attribute("Selector") == "^[OsType=Linux][IsCsdEnabled=False]" && element.Descendants(av + "ControlTemplate").Any()); var titleBarPanel = linuxTemplateStyle.Descendants(av + "Panel") .Single(element => (string?)element.Attribute("Name") == "TitleBarPanel"); titleBarPanel.Attribute("IsVisible").ShouldNotBeNull().Value.ShouldBe( "{TemplateBinding IsTitleBarVisible}"); var linuxDecorationStyle = document.Descendants(av + "Style") .Single(element => (string?)element.Attribute("Selector") == "^[OsType=Linux][IsCsdEnabled=False]" && element.Elements(av + "Setter").Any(setter => (string?)setter.Attribute("Property") == "WindowDecorations")); linuxDecorationStyle.Elements(av + "Setter").ShouldContain(setter => (string?)setter.Attribute("Property") == "WindowDecorations" && (string?)setter.Attribute("Value") == "None"); } [Fact] public void Wayland_Csd_Drawn_TitleBar_Layers_Clip_To_Top_Window_CornerRadius() { var document = XDocument.Load(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Themes/WindowDrawnDecorationsTheme.axaml")); XNamespace av = "https://github.com/avaloniaui"; var titleBarFrame = document.Descendants(av + "ContentPresenter") .Single(element => (string?)element.Attribute("Name") == "PART_TitleBar"); var titleBarContent = document.Descendants(av + "ContentPresenter") .Single(element => (string?)element.Attribute("Name") == "PART_TitleBarPresenter"); var titleBarClip = titleBarContent.Parent.ShouldNotBeNull(); titleBarFrame.Attribute("ClipToBounds").ShouldNotBeNull().Value.ShouldBe("True"); titleBarClip.Name.ShouldBe(av + "Border"); titleBarClip.Attribute("ClipToBounds").ShouldNotBeNull().Value.ShouldBe("True"); titleBarClip.Attribute("CornerRadius").ShouldNotBeNull().Value.ShouldBe( "{Binding $parent[atom:Window].CornerRadius, Converter={StaticResource TitleBarCornerRadiusFilter}}"); titleBarContent.Attribute("ClipToBounds").ShouldBeNull(); titleBarContent.Attribute("CornerRadius").ShouldBeNull(); } [Fact] public void Windows_Window_Has_No_NonCsd_Template_And_Uses_Opaque_Csd_Background() { var document = XDocument.Load(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Themes/WindowTheme.axaml")); var decorationsDocument = XDocument.Load(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Themes/WindowDrawnDecorationsTheme.axaml")); XNamespace av = "https://github.com/avaloniaui"; document.Descendants(av + "Style") .ShouldNotContain(element => (string?)element.Attribute("Selector") == "^[OsType=Windows][IsCsdEnabled=False]"); var windowsStyle = document.Descendants(av + "Style") .Single(element => (string?)element.Attribute("Selector") == "^[OsType=Windows]"); windowsStyle.Elements(av + "Setter").ShouldContain(setter => (string?)setter.Attribute("Property") == "TransparencyLevelHint" && (string?)setter.Attribute("Value") == "None"); decorationsDocument.Descendants(av + "Style") .Single(style => (string?)style.Attribute("Selector") == "^/template/ Border#PART_WindowFrame") .Elements(av + "Setter") .ShouldContain(setter => (string?)setter.Attribute("Property") == "Background" && (string?)setter.Attribute("Value") == "{atom:WindowTokenResource DefaultBackground}"); } [Fact] public void BuiltIn_TitleBars_Use_One_Full_Frame_LayoutPanel_With_Three_Roles() { var document = XDocument.Load(GetRepoFile( "src/AtomUI.Desktop.Controls/WindowTitleBar/Themes/WindowTitleBarTheme.axaml")); XNamespace av = "https://github.com/avaloniaui"; XNamespace atom = "https://atomui.net"; var baseStyle = document.Descendants(av + "Style") .Single(style => (string?)style.Attribute("Selector") == "^:is(atom|WindowTitleBar)"); baseStyle.Elements(av + "Setter").ShouldContain(setter => (string?)setter.Attribute("Property") == "Background" && (string?)setter.Attribute("Value") == "{atom:SharedTokenResource ColorBgContainer}"); baseStyle.Elements(av + "Setter").ShouldNotContain(setter => (string?)setter.Attribute("Property") == "Background" && (string?)setter.Attribute("Value") == "Transparent"); var templates = document.Descendants(av + "ControlTemplate").ToList(); templates.Count.ShouldBe(3); foreach (var template in templates) { var frame = template.Elements(av + "Border").Single(); var layoutPanel = frame.Elements(atom + "WindowTitleBarLayoutPanel").Single(); var directChildren = layoutPanel.Elements().ToList(); frame.Attribute("Name")?.Value.ShouldBe("Frame"); frame.Attribute("Background")?.Value.ShouldBe("{TemplateBinding Background}"); layoutPanel.Attribute("TitleAlignment")?.Value.ShouldBe("{TemplateBinding TitleAlignment}"); layoutPanel.Attribute("OsType")?.Value.ShouldBe("{TemplateBinding OsType}"); layoutPanel.Attribute("NativeChromeInsets")?.Value.ShouldBe("{TemplateBinding NativeChromeInsets}"); layoutPanel.Attribute("IsCsdEnabled")?.Value.ShouldBe("{TemplateBinding IsCsdEnabled}"); layoutPanel.Attribute("WindowState")?.Value.ShouldBe("{TemplateBinding HostWindowState}"); layoutPanel.Attribute("HorizontalSpacing")?.Value.ShouldBe( "{atom:WindowTitleBarTokenResource HeaderHorizontalSpacing}"); directChildren.Count(child => (string?)child.Attribute(atom + "WindowTitleBarLayoutPanel.Role") == "Leading") .ShouldBe(1); directChildren.Count(child => (string?)child.Attribute(atom + "WindowTitleBarLayoutPanel.Role") == "Title") .ShouldBe(1); directChildren.Count(child => (string?)child.Attribute(atom + "WindowTitleBarLayoutPanel.Role") == "Trailing") .ShouldBe(1); layoutPanel.Descendants(atom + "CaptionButtonGroup").Single() .Attribute("Name")?.Value.ShouldBe("PART_CaptionButtonGroup"); } } [Fact] public void Fullscreen_Title_Hosts_Use_The_Shared_Full_Frame_Layout_With_Managed_Operations() { var themePaths = new[] { "src/AtomUI.Desktop.Controls/Window/Themes/WindowDrawnDecorationsTheme.axaml", "src/AtomUI.Desktop.Controls/Window/Themes/FullscreenPopoverLayerTheme.axaml" }; XNamespace atom = "https://atomui.net"; foreach (var themePath in themePaths) { var document = XDocument.Load(GetRepoFile(themePath)); var layoutPanel = document.Descendants(atom + "WindowTitleBarLayoutPanel").Single(); var directChildren = layoutPanel.Elements().ToList(); layoutPanel.Attribute("TitleAlignment")?.Value.ShouldBe( "{Binding $parent[atom:Window].TitleAlignment}"); layoutPanel.Attribute("OsType")?.Value.ShouldBe("{Binding $parent[atom:Window].OsType}"); layoutPanel.Attribute("NativeChromeInsets")?.Value.ShouldBe("0"); layoutPanel.Attribute("WindowState")?.Value.ShouldBe("FullScreen"); layoutPanel.Attribute("Padding")?.Value.ShouldBe( "{atom:WindowTokenResource FullscreenHeaderFramePadding}"); layoutPanel.Attribute("HorizontalSpacing")?.Value.ShouldBe( "{atom:WindowTitleBarTokenResource HeaderHorizontalSpacing}"); directChildren.Count(child => GetLayoutRole(child) == "Leading").ShouldBe(1); directChildren.Count(child => GetLayoutRole(child) == "Title").ShouldBe(1); directChildren.Count(child => GetLayoutRole(child) == "Trailing").ShouldBe(1); directChildren.Single(child => GetLayoutRole(child) == "Leading") .Descendants() .ShouldBeEmpty(); var title = directChildren.Single(child => GetLayoutRole(child) == "Title"); title.Name.LocalName.ShouldBe("DockPanel"); title.Attribute("LastChildFill")?.Value.ShouldBe("True"); title.Attribute("HorizontalSpacing")?.Value.ShouldBe( "{atom:WindowTitleBarTokenResource LogoAndTitleSpacing}"); title.Attribute("IsHitTestVisible")?.Value.ShouldBe("False"); title.Attribute("ClipToBounds")?.Value.ShouldBe("True"); var logoPresenter = title.Descendants().Single(element => (string?)element.Attribute("Name") == "FullscreenLogoPresenter"); logoPresenter.Attribute("DockPanel.Dock")?.Value.ShouldBe("Left"); var titleText = title.Descendants().Single(element => (string?)element.Attribute("Name") == "FullscreenTitleText"); titleText.Attribute("TextWrapping")?.Value.ShouldBe("NoWrap"); titleText.Attribute("TextTrimming")?.Value.ShouldBe("CharacterEllipsis"); var trailing = directChildren.Single(child => GetLayoutRole(child) == "Trailing"); trailing.Descendants(atom + "CaptionButton").Count().ShouldBe(2); } } [Fact] public void Windows_Window_Uses_Avalonia_Csd_Without_The_Legacy_Chrome_Hook() { var managerSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Chrome/WindowChromeManager.cs")); var windowsChromeSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Chrome/WindowsWindowChromeManager.cs")); var reflectionSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Utils/WindowDrawnDecorationsReflectionExtensions.cs")); var windowSource = File.ReadAllText(GetRepoFile("src/AtomUI.Desktop.Controls/Window/Window.cs")); var nativeSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/WindowExtensions.cs")); var interopSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/Windows/WindowUtils.Interop.cs")); var windowsNativeSource = File.ReadAllText(GetRepoFile("src/AtomUI.Native/Windows/WindowUtils.Windows.cs")); var captionSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/WindowTitleBar/CaptionButtonGroup.cs")); var document = XDocument.Load(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Themes/WindowTheme.axaml")); var decorationsDocument = XDocument.Load(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Themes/WindowDrawnDecorationsTheme.axaml")); var captionDocument = XDocument.Load(GetRepoFile( "src/AtomUI.Desktop.Controls/WindowTitleBar/Themes/CaptionButtonGroupTheme.axaml")); XNamespace av = "https://github.com/avaloniaui"; XNamespace atom = "https://atomui.net"; string? GetCaptionButtonRole(string name) { return (string?)captionDocument.Descendants(atom + "WindowsCaptionButton") .Single(element => (string?)element.Attribute("Name") == name) .Attribute("WindowDecorationProperties.ElementRole"); } managerSource.ShouldContain("WindowsWindowChromeManager.Attach(window)"); File.Exists(Path.Combine( Path.GetDirectoryName(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Chrome/WindowChromeManager.cs"))!, "WindowsWindowChromeManager.cs")).ShouldBeTrue(); File.Exists(Path.Combine( Path.GetDirectoryName(GetRepoFile( "src/AtomUI.Desktop.Controls/Window/Chrome/WindowChromeManager.cs"))!, "WindowsInactiveFramePolicy.cs")).ShouldBeFalse(); windowSource.ShouldContain("else if (OperatingSystem.IsWindows())"); windowSource.ShouldContain("IsCsdEnabled = true;"); windowSource.ShouldContain("CalculateWindowsCsdMinimumHeight"); windowSource.ShouldContain("SetCurrentValue(MinHeightProperty, minimumHeight);"); windowSource.ShouldContain("PointerCaptureLost"); windowSource.ShouldContain("ResetTitleBarMoveDragState();"); windowSource.ShouldContain("ApplyCurrentWindowsCsdFrameTheme();"); windowSource.ShouldContain("change.Property == ActualThemeVariantProperty"); windowSource.ShouldContain("(PlatformThemeVariant?)ActualThemeVariant"); windowSource.ShouldContain("platformThemeVariant == PlatformThemeVariant.Dark"); windowSource.ShouldContain("private void ApplyWindowsCsdFrameTheme(bool isDarkMode)"); windowSource.ShouldNotContain("EnsureWindowsCsdFrameThemeSubscription"); windowSource.ShouldNotContain("themeManager.ThemeChanged += handler;"); windowSource.ShouldNotContain("TryResolveCurrentWindowDarkMode"); windowSource.ShouldNotContain("_isDragging"); windowSource.ShouldNotContain("IsWindowsDrawnDecorationsEnabledProperty"); windowSource.ShouldNotContain("WindowsInactiveFramePolicy.Apply(this)"); windowSource.ShouldNotContain("AddWndProcHookCallback"); windowsChromeSource.ShouldContain("PreparePlatformChromeInitialShowHandle();"); windowsChromeSource.ShouldContain("_backgroundHook?.PaintClientArea();"); windowsChromeSource.ShouldNotContain("_initialShowStatePrepared || _window.IsVisible || _window.PlatformImpl is null"); nativeSource.ShouldContain("SetWindowsCsdFrameDarkMode"); windowsNativeSource.ShouldContain("SetWindowFrameDarkModeWindows"); windowsNativeSource.ShouldContain("OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000)"); windowsNativeSource.ShouldContain("ReapplyActiveNonClientFrame"); windowsNativeSource.ShouldContain("WindowUtilsInterop.WM_NCACTIVATE"); windowsNativeSource.ShouldContain("WindowUtilsInterop.DefWindowProc"); windowsNativeSource.ShouldNotContain("WindowUtilsInterop.RedrawWindow"); interopSource.ShouldContain("DWMWA_USE_IMMERSIVE_DARK_MODE"); interopSource.ShouldNotContain("RDW_FRAME"); interopSource.ShouldNotContain("DWMWA_BORDER_COLOR"); reflectionSource.ShouldNotContain("TryUpdateDrawnDecorations"); reflectionSource.ShouldNotContain("\"UpdateDrawnDecorations\""); reflectionSource.ShouldNotContain("\"UpdateDrawnDecorationMargins\""); nativeSource.ShouldNotContain("WinWndProcHook"); nativeSource.ShouldNotContain("ForceWinNonClientFrameChanged"); interopSource.ShouldNotContain("WM_NCCALCSIZE"); interopSource.ShouldNotContain("WM_NCHITTEST"); interopSource.ShouldNotContain("HTMAXBUTTON"); captionSource.ShouldNotContain("Win32Properties.AddWndProcHookCallback"); captionSource.ShouldNotContain("EnableWindowsSnapLayout"); GetCaptionButtonRole("PART_FullScreenButton").ShouldBe("FullScreenButton"); GetCaptionButtonRole("PART_PinButton").ShouldBe("DecorationsElement"); GetCaptionButtonRole("PART_MinimizeButton").ShouldBe("MinimizeButton"); GetCaptionButtonRole("PART_MaximizeButton").ShouldBe("MaximizeButton"); GetCaptionButtonRole("PART_CloseButton").ShouldBe("CloseButton"); document.Descendants(av + "Setter").ShouldContain(setter => (string?)setter.Attribute("Property") == "ExtendClientAreaToDecorationsHint" && (string?)setter.Attribute("Value") == "True"); document.Descendants(av + "Setter").ShouldNotContain(setter => (string?)setter.Attribute("Property") == "ExtendClientAreaToDecorationsHint" && (string?)setter.Attribute("Value") == "False"); var hasWindowsManagedFrameStyle = document.Descendants(av + "Style").Any(style => ((string?)style.Attribute("Selector"))?.Contains("IsWindowsDrawnDecorationsEnabled=True") == true && style.Elements(av + "Setter").Any(setter => (string?)setter.Attribute("Property") is "TransparencyLevelHint" or "FrameShadow")); hasWindowsManagedFrameStyle.ShouldBeFalse(); decorationsDocument.Descendants(av + "Setter").ShouldContain(setter => (string?)setter.Attribute("Property") == "DefaultFrameThickness" && (string?)setter.Attribute("Value") == "1"); decorationsDocument.Descendants(av + "Style").Single(style => (string?)style.Attribute("Selector") == "^/template/ Border#PART_WindowFrame") .Elements(av + "Setter") .ShouldNotContain(setter => (string?)setter.Attribute("Property") == "BoxShadow"); decorationsDocument.Descendants(av + "Style").Single(style => (string?)style.Attribute("Selector") == "^:has-shadow /template/ Border#PART_WindowFrame") .Elements(av + "Setter") .ShouldContain(setter => (string?)setter.Attribute("Property") == "BoxShadow" && (string?)setter.Attribute("Value") == "{atom:WindowTokenResource FrameShadows}"); } [Theory] [InlineData(40, 80)] [InlineData(0, 0)] [InlineData(-1, 0)] public void Windows_Csd_Minimum_Height_Always_Leaves_A_Nonzero_Content_Surface( double titleBarHeight, double expectedMinimumHeight) { AtomUI.Desktop.Controls.Window.CalculateWindowsCsdMinimumHeight(titleBarHeight) .ShouldBe(expectedMinimumHeight); } [Fact] public void Hidden_Csd_TitleBar_Removes_Only_The_Drawn_TitleBar_From_Content_Margin() { var decorationMargin = new Thickness(3, 44, 5, 7); AtomUI.Desktop.Controls.Window.CalculateEffectiveContentFrameMargin( decorationMargin, isCsdEnabled: true, isTitleBarVisible: false, drawnTitleBarHeight: 40) .ShouldBe(new Thickness(3, 4, 5, 7)); AtomUI.Desktop.Controls.Window.CalculateEffectiveContentFrameMargin( decorationMargin, isCsdEnabled: true, isTitleBarVisible: false, drawnTitleBarHeight: 80) .ShouldBe(new Thickness(3, 0, 5, 7)); } [Theory] [InlineData(false, false, 40)] [InlineData(true, true, 40)] [InlineData(true, false, 0)] [InlineData(true, false, -1)] [InlineData(true, false, double.NaN)] public void Content_Margin_Remains_Unchanged_Without_A_Valid_Hidden_Csd_TitleBar( bool isCsdEnabled, bool isTitleBarVisible, double drawnTitleBarHeight) { var decorationMargin = new Thickness(3, 44, 5, 7); AtomUI.Desktop.Controls.Window.CalculateEffectiveContentFrameMargin( decorationMargin, isCsdEnabled, isTitleBarVisible, drawnTitleBarHeight) .ShouldBe(decorationMargin); } [Fact] [SupportedOSPlatform("windows")] public void Windows_Csd_Window_Rejects_A_Minimum_Height_Below_Its_Safe_Content_Surface() { if (!OperatingSystem.IsWindows()) { return; } AvaloniaTestApp.EnsureInitialized(); var window = new AtomUI.Desktop.Controls.Window { TitleBarHeight = 40 }; window.MinHeight.ShouldBe(80); window.MinHeight = 20; window.MinHeight.ShouldBe(80); window.MinHeight = 160; window.MinHeight.ShouldBe(160); } [Fact] public void Windows_Caption_Buttons_Suppress_Stale_Hover_After_Window_State_Transitions() { var buttonSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/WindowTitleBar/WindowsCaptionButton.cs")); var captionThemeSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/WindowTitleBar/Themes/CaptionButtonGroupTheme.axaml")); var themeSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/WindowTitleBar/Themes/WindowsCaptionButtonTheme.axaml")); buttonSource.ShouldContain("HostWindowStateProperty.Changed.AddClassHandler"); buttonSource.ShouldContain("button.InvalidatePointerOverVisualState()"); captionThemeSource.ShouldContain("HostWindowState=\"{TemplateBinding HostWindowState}\""); themeSource.ShouldContain("^[IsPointerOverSuppressed=False]:pointerover"); } [Fact] public void AtomUI_Defaults_Use_Ordered_Windows_Composition_Fallbacks_For_Live_Resize() { var source = File.ReadAllText(GetRepoFile("src/AtomUI.Core/AppBuilderExtensions.cs")); source.ShouldContain(".With(new Win32PlatformOptions"); source.ShouldContain("Win32RenderingMode.AngleEgl"); source.ShouldContain("Win32RenderingMode.Software"); source.ShouldContain("Win32CompositionMode.WinUIComposition"); source.ShouldContain("Win32CompositionMode.DirectComposition"); source.ShouldContain("Win32CompositionMode.LowLatencyDxgiSwapChain"); source.ShouldContain("Win32CompositionMode.RedirectionSurface"); source.IndexOf("Win32CompositionMode.WinUIComposition", StringComparison.Ordinal) .ShouldBeLessThan(source.IndexOf("Win32CompositionMode.DirectComposition", StringComparison.Ordinal)); source.IndexOf("Win32CompositionMode.DirectComposition", StringComparison.Ordinal) .ShouldBeLessThan(source.IndexOf("Win32CompositionMode.LowLatencyDxgiSwapChain", StringComparison.Ordinal)); source.IndexOf("Win32CompositionMode.LowLatencyDxgiSwapChain", StringComparison.Ordinal) .ShouldBeLessThan(source.IndexOf("Win32CompositionMode.RedirectionSurface", StringComparison.Ordinal)); source.ShouldNotContain("WindowsAppBuilderDefaults"); source.ShouldNotContain("ShouldRenderOnUIThread = true"); } private static string GetRepoFile(string relativePath) { var directory = new DirectoryInfo(AppContext.BaseDirectory); while (directory is not null) { var candidate = Path.Combine(directory.FullName, relativePath); if (File.Exists(candidate)) { return candidate; } directory = directory.Parent; } throw new FileNotFoundException($"Could not find repository file: {relativePath}"); } private static string? GetRepoFileOrNull(string relativePath) { var directory = new DirectoryInfo(AppContext.BaseDirectory); while (directory is not null) { var candidate = Path.Combine(directory.FullName, relativePath); if (File.Exists(candidate)) { return candidate; } directory = directory.Parent; } return null; } private static string? GetLayoutRole(XElement element) { return element.Attributes() .SingleOrDefault(attribute => attribute.Name.LocalName == "WindowTitleBarLayoutPanel.Role") ?.Value; } private static bool IsPhysicalPixelAligned(double value, double renderScaling) { var physicalValue = value * renderScaling; return Math.Abs(physicalValue - Math.Round(physicalValue)) < 0.000001; } private static void AssertTitleBarVisibilityContract(XDocument document, string elementName) { var element = document.Descendants() .Single(candidate => (string?)candidate.Attribute("Name") == elementName || candidate.Name.LocalName == elementName); var multiBinding = element.Elements() .Single(property => property.Name.LocalName.EndsWith(".IsVisible")) .Elements() .Single(binding => binding.Name.LocalName == "MultiBinding"); multiBinding.Attribute("Converter").ShouldNotBeNull().Value.ShouldBe("{x:Static BoolConverters.And}"); var bindings = multiBinding.Elements() .Where(binding => binding.Name.LocalName == "Binding") .ToArray(); bindings.ShouldContain(binding => (string?)binding.Attribute("RelativeSource") == "{RelativeSource TemplatedParent}" && (string?)binding.Attribute("Path") == "HasTitleBar"); bindings.ShouldContain(binding => (string?)binding.Attribute("Path") == "$parent[atom:Window].IsTitleBarVisible"); } }