mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-26 03:00:28 +08:00
1324 lines
68 KiB
C#
1324 lines
68 KiB
C#
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<ControlTheme>();
|
|
var host = new Avalonia.Controls.Window
|
|
{
|
|
Width = 100,
|
|
Height = 100,
|
|
Content = resizer
|
|
};
|
|
host.Show();
|
|
resizer.ApplyTemplate();
|
|
|
|
var grips = resizer.GetVisualDescendants()
|
|
.OfType<Border>()
|
|
.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>((window, args) =>");
|
|
windowSource.ShouldContain("window.HandleFrameShadowPropertyChanged(args.GetNewValue<BoxShadows>())");
|
|
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<WindowsCaptionButton>");
|
|
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");
|
|
}
|
|
}
|