mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-18 14:37:19 +08:00
547 lines
18 KiB
C#
547 lines
18 KiB
C#
using System.Xml.Linq;
|
|
using AtomUI.Controls;
|
|
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.Presenters;
|
|
using Avalonia.Headless;
|
|
using Avalonia.Input;
|
|
using Avalonia.Layout;
|
|
using Avalonia.Media;
|
|
using Avalonia.Styling;
|
|
using Avalonia.Threading;
|
|
using Avalonia.VisualTree;
|
|
using AtomUI.Icons.AntDesign;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
using AtomUIWindow = AtomUI.Desktop.Controls.Window;
|
|
|
|
namespace AtomUI.Desktop.Controls.Tests.Window;
|
|
|
|
public class WindowTitleBarButtonTests
|
|
{
|
|
static WindowTitleBarButtonTests()
|
|
{
|
|
AvaloniaTestApp.EnsureInitialized();
|
|
}
|
|
|
|
[Fact]
|
|
public void Public_AddOn_Controls_Reuse_The_Icon_Button_Family()
|
|
{
|
|
var button = new WindowTitleBarButton();
|
|
var toggleButton = new WindowTitleBarToggleButton();
|
|
|
|
button.ShouldBeAssignableTo<IconButton>();
|
|
toggleButton.ShouldBeAssignableTo<ToggleIconButton>();
|
|
button.IsWindowActive.ShouldBeTrue();
|
|
button.HostMotionEnabled.ShouldBeTrue();
|
|
button.HostOsType.ShouldBe(OsType.Unknown);
|
|
toggleButton.IsWindowActive.ShouldBeTrue();
|
|
toggleButton.HostMotionEnabled.ShouldBeTrue();
|
|
toggleButton.HostOsType.ShouldBe(OsType.Unknown);
|
|
}
|
|
|
|
[Fact]
|
|
public void Windows_AddOn_Button_Uses_Full_Height_Square_Caption_Geometry()
|
|
{
|
|
var button = new WindowTitleBarButton
|
|
{
|
|
Icon = new SearchOutlined()
|
|
};
|
|
var titleBar = new WindowTitleBar
|
|
{
|
|
RightAddOn = button,
|
|
IsWindowActive = true,
|
|
IsMotionEnabled = false,
|
|
Title = "Title",
|
|
Height = 44
|
|
};
|
|
titleBar.SetValue(WindowTitleBar.OsTypeProperty, OsType.Windows);
|
|
titleBar.Theme = Application.Current!
|
|
.FindResource(typeof(WindowTitleBar))
|
|
.ShouldBeAssignableTo<ControlTheme>();
|
|
|
|
var host = new Avalonia.Controls.Window
|
|
{
|
|
Width = 400,
|
|
Height = 100,
|
|
Content = titleBar
|
|
};
|
|
|
|
try
|
|
{
|
|
host.Show();
|
|
titleBar.ApplyTemplate();
|
|
button.ApplyTemplate();
|
|
host.UpdateLayout();
|
|
|
|
button.DesiredSize.ShouldBe(new Size(44, 44));
|
|
button.Bounds.Size.ShouldBe(new Size(44, 44));
|
|
button.CornerRadius.ShouldBe(new CornerRadius(0));
|
|
button.VerticalAlignment.ShouldBe(VerticalAlignment.Stretch);
|
|
button.Cursor.ShouldNotBeNull();
|
|
button.Cursor!.ToString().ShouldContain("Arrow");
|
|
button.Background!
|
|
.ShouldBeAssignableTo<ISolidColorBrush>()
|
|
.Color.ShouldBe(Colors.Transparent);
|
|
|
|
var point = button.TranslatePoint(
|
|
new Point(button.Bounds.Width / 2, button.Bounds.Height / 2),
|
|
host).ShouldNotBeNull();
|
|
host.MouseMove(point);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
button.IsPointerOver.ShouldBeTrue();
|
|
var hoverColor = button.Background!
|
|
.ShouldBeAssignableTo<ISolidColorBrush>()
|
|
.Color;
|
|
hoverColor.ShouldNotBe(Colors.Transparent);
|
|
|
|
host.MouseDown(point, MouseButton.Left);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
button.Background!
|
|
.ShouldBeAssignableTo<ISolidColorBrush>()
|
|
.Color.ShouldNotBe(hoverColor);
|
|
|
|
host.MouseUp(point, MouseButton.Left);
|
|
host.MouseMove(new Point(10, 90));
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
button.IsPointerOver.ShouldBeFalse();
|
|
button.Background!
|
|
.ShouldBeAssignableTo<ISolidColorBrush>()
|
|
.Color.ShouldBe(Colors.Transparent);
|
|
|
|
titleBar.IsWindowActive = false;
|
|
host.MouseMove(point);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
var inactiveHoverColor = button.Background!
|
|
.ShouldBeAssignableTo<ISolidColorBrush>()
|
|
.Color;
|
|
inactiveHoverColor.ShouldNotBe(Colors.Transparent);
|
|
|
|
host.MouseDown(point, MouseButton.Left);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
button.Background!
|
|
.ShouldBeAssignableTo<ISolidColorBrush>()
|
|
.Color.ShouldNotBe(inactiveHoverColor);
|
|
|
|
host.MouseUp(point, MouseButton.Left);
|
|
button.IsEnabled = false;
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
button.Background!
|
|
.ShouldBeAssignableTo<ISolidColorBrush>()
|
|
.Color.ShouldBe(Colors.Transparent);
|
|
|
|
button.IsEnabled = true;
|
|
titleBar.IsWindowActive = true;
|
|
host.MouseMove(new Point(10, 90));
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
titleBar.SetValue(WindowTitleBar.OsTypeProperty, OsType.Linux);
|
|
host.UpdateLayout();
|
|
|
|
button.HostOsType.ShouldBe(OsType.Linux);
|
|
button.DesiredSize.ShouldBe(new Size(30, 30));
|
|
|
|
titleBar.SetValue(WindowTitleBar.OsTypeProperty, OsType.Windows);
|
|
host.UpdateLayout();
|
|
|
|
button.HostOsType.ShouldBe(OsType.Windows);
|
|
button.DesiredSize.ShouldBe(new Size(44, 44));
|
|
}
|
|
finally
|
|
{
|
|
host.Close();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Windows_AddOn_Toggle_Button_Uses_Full_Height_Square_Caption_Geometry()
|
|
{
|
|
var button = new WindowTitleBarToggleButton
|
|
{
|
|
CheckedIcon = new SearchOutlined(),
|
|
UnCheckedIcon = new SettingOutlined()
|
|
};
|
|
var titleBar = new WindowTitleBar
|
|
{
|
|
LeftAddOn = button,
|
|
IsWindowActive = true,
|
|
Title = "Title"
|
|
};
|
|
titleBar.SetValue(WindowTitleBar.OsTypeProperty, OsType.Windows);
|
|
titleBar.Theme = Application.Current!
|
|
.FindResource(typeof(WindowTitleBar))
|
|
.ShouldBeAssignableTo<ControlTheme>();
|
|
|
|
var host = new Avalonia.Controls.Window
|
|
{
|
|
Width = 400,
|
|
Height = 100,
|
|
Content = titleBar
|
|
};
|
|
|
|
try
|
|
{
|
|
host.Show();
|
|
titleBar.ApplyTemplate();
|
|
button.ApplyTemplate();
|
|
host.UpdateLayout();
|
|
|
|
button.DesiredSize.ShouldBe(new Size(40, 40));
|
|
button.Bounds.Size.ShouldBe(new Size(40, 40));
|
|
button.CornerRadius.ShouldBe(new CornerRadius(0));
|
|
button.VerticalAlignment.ShouldBe(VerticalAlignment.Stretch);
|
|
button.Cursor.ShouldNotBeNull();
|
|
button.Cursor!.ToString().ShouldContain("Arrow");
|
|
button.Background!
|
|
.ShouldBeAssignableTo<ISolidColorBrush>()
|
|
.Color.ShouldBe(Colors.Transparent);
|
|
}
|
|
finally
|
|
{
|
|
host.Close();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Non_Windows_AddOn_Buttons_Preserve_Managed_Button_Geometry()
|
|
{
|
|
var button = new WindowTitleBarButton
|
|
{
|
|
Icon = new SearchOutlined()
|
|
};
|
|
var toggleButton = new WindowTitleBarToggleButton
|
|
{
|
|
CheckedIcon = new SearchOutlined(),
|
|
UnCheckedIcon = new SettingOutlined()
|
|
};
|
|
var titleBar = new WindowTitleBar
|
|
{
|
|
LeftAddOn = new StackPanel
|
|
{
|
|
Orientation = Orientation.Horizontal,
|
|
Children = { button, toggleButton }
|
|
},
|
|
IsWindowActive = true,
|
|
Title = "Title"
|
|
};
|
|
titleBar.SetValue(WindowTitleBar.OsTypeProperty, OsType.Linux);
|
|
titleBar.Theme = Application.Current!
|
|
.FindResource(typeof(WindowTitleBar))
|
|
.ShouldBeAssignableTo<ControlTheme>();
|
|
|
|
var host = new Avalonia.Controls.Window
|
|
{
|
|
Width = 400,
|
|
Height = 100,
|
|
Content = titleBar
|
|
};
|
|
|
|
try
|
|
{
|
|
host.Show();
|
|
titleBar.ApplyTemplate();
|
|
button.ApplyTemplate();
|
|
toggleButton.ApplyTemplate();
|
|
host.UpdateLayout();
|
|
|
|
button.DesiredSize.ShouldBe(new Size(30, 30));
|
|
toggleButton.DesiredSize.ShouldBe(new Size(30, 30));
|
|
button.CornerRadius.ShouldNotBe(new CornerRadius(0));
|
|
toggleButton.CornerRadius.ShouldNotBe(new CornerRadius(0));
|
|
button.VerticalAlignment.ShouldBe(VerticalAlignment.Center);
|
|
toggleButton.VerticalAlignment.ShouldBe(VerticalAlignment.Center);
|
|
button.Cursor!.ToString().ShouldContain("Hand");
|
|
toggleButton.Cursor!.ToString().ShouldContain("Hand");
|
|
}
|
|
finally
|
|
{
|
|
host.Close();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOn_Content_Receives_TitleBar_Host_State()
|
|
{
|
|
var button = new WindowTitleBarButton();
|
|
var titleBar = new WindowTitleBar
|
|
{
|
|
LeftAddOn = button,
|
|
IsWindowActive = false,
|
|
IsMotionEnabled = false,
|
|
Title = "Title"
|
|
};
|
|
titleBar.SetValue(WindowTitleBar.OsTypeProperty, OsType.Linux);
|
|
titleBar.Theme = Application.Current!
|
|
.FindResource(typeof(WindowTitleBar))
|
|
.ShouldBeAssignableTo<ControlTheme>();
|
|
|
|
var host = new Avalonia.Controls.Window
|
|
{
|
|
Width = 400,
|
|
Height = 100,
|
|
Content = titleBar
|
|
};
|
|
|
|
try
|
|
{
|
|
host.Show();
|
|
titleBar.ApplyTemplate();
|
|
host.UpdateLayout();
|
|
|
|
var projectedButton = titleBar.GetVisualDescendants()
|
|
.OfType<WindowTitleBarButton>()
|
|
.Single();
|
|
|
|
projectedButton.ShouldBeSameAs(button);
|
|
projectedButton.IsWindowActive.ShouldBeFalse();
|
|
projectedButton.HostMotionEnabled.ShouldBeFalse();
|
|
projectedButton.HostOsType.ShouldBe(OsType.Linux);
|
|
|
|
titleBar.IsWindowActive = true;
|
|
titleBar.IsMotionEnabled = true;
|
|
host.UpdateLayout();
|
|
|
|
projectedButton.IsWindowActive.ShouldBeTrue();
|
|
projectedButton.HostMotionEnabled.ShouldBeTrue();
|
|
}
|
|
finally
|
|
{
|
|
host.Close();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Toggle_AddOn_Preserves_Checked_State_And_Icon_Pair()
|
|
{
|
|
var checkedIcon = new SearchOutlined();
|
|
var uncheckedIcon = new SettingOutlined();
|
|
var toggleButton = new WindowTitleBarToggleButton
|
|
{
|
|
CheckedIcon = checkedIcon,
|
|
UnCheckedIcon = uncheckedIcon,
|
|
IsChecked = false
|
|
};
|
|
toggleButton.Theme = Application.Current!
|
|
.FindResource(typeof(WindowTitleBarToggleButton))
|
|
.ShouldBeAssignableTo<ControlTheme>();
|
|
|
|
var host = new Avalonia.Controls.Window
|
|
{
|
|
Width = 180,
|
|
Height = 80,
|
|
Content = toggleButton
|
|
};
|
|
|
|
try
|
|
{
|
|
host.Show();
|
|
toggleButton.ApplyTemplate();
|
|
host.UpdateLayout();
|
|
|
|
var presenters = toggleButton.GetVisualDescendants()
|
|
.OfType<IconPresenter>()
|
|
.Where(presenter => presenter.Name is not null)
|
|
.ToDictionary(presenter => presenter.Name!);
|
|
presenters["PART_CheckedIconPresenter"].Icon.ShouldBeSameAs(checkedIcon);
|
|
presenters["PART_UnCheckedIconPresenter"].Icon.ShouldBeSameAs(uncheckedIcon);
|
|
presenters["PART_CheckedIconPresenter"].IsVisible.ShouldBeFalse();
|
|
presenters["PART_UnCheckedIconPresenter"].IsVisible.ShouldBeTrue();
|
|
|
|
toggleButton.IsChecked = true;
|
|
host.UpdateLayout();
|
|
|
|
presenters["PART_CheckedIconPresenter"].IsVisible.ShouldBeTrue();
|
|
presenters["PART_UnCheckedIconPresenter"].IsVisible.ShouldBeFalse();
|
|
toggleButton.IsChecked.ShouldBe(true);
|
|
}
|
|
finally
|
|
{
|
|
host.Close();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOn_Content_Returns_To_Standalone_Defaults_When_Removed()
|
|
{
|
|
var button = new WindowTitleBarButton();
|
|
var titleBar = new WindowTitleBar
|
|
{
|
|
LeftAddOn = button,
|
|
IsWindowActive = false,
|
|
IsMotionEnabled = false,
|
|
Title = "Title"
|
|
};
|
|
titleBar.SetValue(WindowTitleBar.OsTypeProperty, OsType.Linux);
|
|
titleBar.Theme = Application.Current!
|
|
.FindResource(typeof(WindowTitleBar))
|
|
.ShouldBeAssignableTo<ControlTheme>();
|
|
|
|
var host = new Avalonia.Controls.Window
|
|
{
|
|
Width = 400,
|
|
Height = 100,
|
|
Content = titleBar
|
|
};
|
|
|
|
try
|
|
{
|
|
host.Show();
|
|
titleBar.ApplyTemplate();
|
|
host.UpdateLayout();
|
|
|
|
button.IsWindowActive.ShouldBeFalse();
|
|
button.HostMotionEnabled.ShouldBeFalse();
|
|
button.HostOsType.ShouldBe(OsType.Linux);
|
|
|
|
titleBar.LeftAddOn = null;
|
|
host.UpdateLayout();
|
|
|
|
button.IsWindowActive.ShouldBeTrue();
|
|
button.HostMotionEnabled.ShouldBeTrue();
|
|
button.HostOsType.ShouldBe(OsType.Unknown);
|
|
}
|
|
finally
|
|
{
|
|
host.Close();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOn_Pointer_Input_Does_Not_Bubble_As_TitleBar_Double_Click()
|
|
{
|
|
var button = new WindowTitleBarButton
|
|
{
|
|
Icon = new SearchOutlined()
|
|
};
|
|
var toggleButton = new WindowTitleBarToggleButton
|
|
{
|
|
CheckedIcon = new SearchOutlined(),
|
|
UnCheckedIcon = new SettingOutlined()
|
|
};
|
|
var titleBar = new WindowTitleBar
|
|
{
|
|
LeftAddOn = new StackPanel
|
|
{
|
|
Orientation = Orientation.Horizontal,
|
|
Children = { button, toggleButton }
|
|
},
|
|
Title = "Title"
|
|
};
|
|
titleBar.SetValue(WindowTitleBar.OsTypeProperty, OsType.Linux);
|
|
titleBar.Theme = Application.Current!
|
|
.FindResource(typeof(WindowTitleBar))
|
|
.ShouldBeAssignableTo<ControlTheme>();
|
|
|
|
var maximizeRequests = 0;
|
|
titleBar.MaximizeWindowRequested += (_, _) => maximizeRequests++;
|
|
|
|
var host = new AtomUIWindow
|
|
{
|
|
IsTitleBarVisible = false,
|
|
Content = titleBar
|
|
};
|
|
|
|
try
|
|
{
|
|
host.Show();
|
|
titleBar.ApplyTemplate();
|
|
host.UpdateLayout();
|
|
|
|
RaiseDoubleClick(button);
|
|
RaiseDoubleClick(toggleButton);
|
|
|
|
maximizeRequests.ShouldBe(0);
|
|
}
|
|
finally
|
|
{
|
|
host.Close();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void AddOn_Themes_Are_Registered_As_Independent_Assets_And_Stay_Out_Of_System_Caption_Contract()
|
|
{
|
|
Application.Current!
|
|
.FindResource(typeof(WindowTitleBarButton))
|
|
.ShouldBeAssignableTo<ControlTheme>();
|
|
Application.Current!
|
|
.FindResource(typeof(WindowTitleBarToggleButton))
|
|
.ShouldBeAssignableTo<ControlTheme>();
|
|
|
|
var manifest = File.ReadAllText(GetRepoFile(
|
|
"src/AtomUI.Desktop.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.ThemeAssetManifestGenerator/GeneratedControlThemeAssetManifest.g.cs"));
|
|
manifest.ShouldContain("WindowTitleBarButtonTheme.axaml");
|
|
manifest.ShouldContain("WindowTitleBarToggleButtonTheme.axaml");
|
|
|
|
var buttonTheme = XDocument.Load(GetRepoFile(
|
|
"src/AtomUI.Desktop.Controls/WindowTitleBar/Themes/WindowTitleBarButtonTheme.axaml"));
|
|
var toggleTheme = XDocument.Load(GetRepoFile(
|
|
"src/AtomUI.Desktop.Controls/WindowTitleBar/Themes/WindowTitleBarToggleButtonTheme.axaml"));
|
|
XNamespace xaml = "http://schemas.microsoft.com/winfx/2006/xaml";
|
|
foreach (var theme in new[] { buttonTheme, toggleTheme })
|
|
{
|
|
theme.Root.ShouldNotBeNull();
|
|
theme.Root!.Name.LocalName.ShouldBe("ControlTheme");
|
|
theme.Root.Attribute(xaml + "Class").ShouldNotBeNull();
|
|
theme.Root.Descendants()
|
|
.ShouldAllBe(element => element.Attribute(xaml + "Class") == null);
|
|
theme.ToString().ShouldNotContain("CaptionButtonAction");
|
|
theme.ToString().ShouldNotContain("ElementRole");
|
|
}
|
|
}
|
|
|
|
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 void RaiseDoubleClick(Control source)
|
|
{
|
|
var pointer = new Pointer(
|
|
Pointer.GetNextFreeId(),
|
|
PointerType.Mouse,
|
|
true);
|
|
source.RaiseEvent(new PointerPressedEventArgs(
|
|
source,
|
|
pointer,
|
|
source,
|
|
default,
|
|
0,
|
|
new PointerPointProperties(
|
|
RawInputModifiers.LeftMouseButton,
|
|
PointerUpdateKind.LeftButtonPressed),
|
|
KeyModifiers.None,
|
|
2));
|
|
source.RaiseEvent(new PointerReleasedEventArgs(
|
|
source,
|
|
pointer,
|
|
source,
|
|
default,
|
|
1,
|
|
new PointerPointProperties(
|
|
RawInputModifiers.None,
|
|
PointerUpdateKind.LeftButtonReleased),
|
|
KeyModifiers.None,
|
|
MouseButton.Left));
|
|
}
|
|
}
|