Browse Source

Merge branch 'master' into fixes/window-resized-sizechanged-order

pull/11095/head
Max Katz 3 years ago
committed by GitHub
parent
commit
6b18f9e976
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      Avalonia.Desktop.slnf
  2. 22
      Avalonia.sln
  3. 1
      nukebuild/Build.cs
  4. 17
      packages/Avalonia/Avalonia.csproj
  5. 2
      packages/Avalonia/Avalonia.props
  6. 2
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  7. 6
      samples/ControlCatalog.NetCore/Properties/launchSettings.json
  8. 2
      samples/ControlCatalog/App.xaml
  9. 48
      samples/ControlCatalog/MainView.xaml.cs
  10. 2
      samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj
  11. 2
      src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs
  12. 2
      src/Avalonia.Base/Controls/IResourceDictionary.cs
  13. 22
      src/Avalonia.Base/Controls/IThemeVariantProvider.cs
  14. 12
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  15. 46
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  16. 13
      src/Avalonia.Base/Input/Cursor.cs
  17. 23
      src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs
  18. 27
      src/Avalonia.Base/Platform/Storage/NoopStorageProvider.cs
  19. 1
      src/Avalonia.Base/Styling/IThemeVariantHost.cs
  20. 39
      src/Avalonia.Controls/AppBuilder.cs
  21. 6
      src/Avalonia.Controls/Avalonia.Controls.csproj
  22. 2
      src/Avalonia.Controls/DefinitionList.cs
  23. 3
      src/Avalonia.Controls/Platform/ScreenHelper.cs
  24. 13
      src/Avalonia.Controls/TopLevel.cs
  25. 5
      src/Avalonia.Controls/WindowBase.cs
  26. 12
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  27. 107
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  28. 14
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
  29. 11
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
  30. 78
      src/Avalonia.FreeDesktop/DBusPlatformSettings.cs
  31. 13
      src/Avalonia.Headless/Avalonia.Headless.csproj
  32. 4
      src/Avalonia.Native/Avalonia.Native.csproj
  33. 12
      src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml
  34. 69
      src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml
  35. 158
      src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml
  36. 114
      src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml
  37. 163
      src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs
  38. 158
      src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs
  39. 118
      src/Avalonia.Themes.Fluent/ColorPaletteResources.cs
  40. 65
      src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs
  41. 2
      src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml
  42. 2
      src/Avalonia.Themes.Fluent/Controls/Window.xaml
  43. 14
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  44. 6
      src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
  45. 6
      src/Headless/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj
  46. 36
      src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
  47. 5
      src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
  48. 19
      src/Headless/Avalonia.Headless.XUnit/Avalonia.Headless.XUnit.csproj
  49. 35
      src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFramework.cs
  50. 45
      src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkAttribute.cs
  51. 61
      src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunner.cs
  52. 18
      src/Headless/Avalonia.Headless/Avalonia.Headless.csproj
  53. 20
      src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  54. 46
      src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  55. 73
      src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
  56. 101
      src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs
  57. 124
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  58. 10
      src/Headless/Avalonia.Headless/IHeadlessWindow.cs
  59. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  60. 14
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  61. 14
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
  62. 31
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlThemeVariantProviderTransformer.cs
  63. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  64. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  65. 6
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  66. 22
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  67. 4
      src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
  68. 22
      tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs
  69. 4
      tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
  70. 19
      tests/Avalonia.Headless.UnitTests/Avalonia.Headless.UnitTests.csproj
  71. 36
      tests/Avalonia.Headless.UnitTests/InputTests.cs
  72. 33
      tests/Avalonia.Headless.UnitTests/RenderingTests.cs
  73. 24
      tests/Avalonia.Headless.UnitTests/TestApplication.cs
  74. 32
      tests/Avalonia.Headless.UnitTests/ThreadingTests.cs
  75. 31
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  76. 28
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs
  77. 136
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs

6
Avalonia.Desktop.slnf

@ -23,8 +23,6 @@
"src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
"src\\Avalonia.Fonts.Inter\\Avalonia.Fonts.Inter.csproj",
"src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj",
"src\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
"src\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj",
"src\\Avalonia.Native\\Avalonia.Native.csproj",
"src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj",
@ -33,6 +31,8 @@
"src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj",
"src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
"src\\Avalonia.X11\\Avalonia.X11.csproj",
"src\\Headless\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
"src\\Headless\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj",
"src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj",
"src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj",
@ -65,4 +65,4 @@
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
]
}
}
}

22
Avalonia.sln

@ -181,9 +181,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Headless\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "src\Headless\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
EndProject
@ -260,6 +260,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.Desktop", "sam
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.iOS", "samples\SafeAreaDemo.iOS\SafeAreaDemo.iOS.csproj", "{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF237916-7150-496B-89ED-6CA3292896E7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.UnitTests", "tests\Avalonia.Headless.UnitTests\Avalonia.Headless.UnitTests.csproj", "{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -599,6 +605,14 @@ Global
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.Build.0 = Release|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -690,6 +704,10 @@ Global
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7}
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7}
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}

1
nukebuild/Build.cs

@ -212,6 +212,7 @@ partial class Build : NukeBuild
RunCoreTest("Avalonia.Markup.Xaml.UnitTests");
RunCoreTest("Avalonia.Skia.UnitTests");
RunCoreTest("Avalonia.ReactiveUI.UnitTests");
RunCoreTest("Avalonia.Headless.UnitTests");
});
Target RunRenderTests => _ => _

17
packages/Avalonia/Avalonia.csproj

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.BuildServices" Version="0.0.12" />
<ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj">
<PrivateAssets>all</PrivateAssets>
@ -60,4 +61,20 @@
<Import Project="..\..\build\NetFX.props" />
<Import Project="..\..\build\CoreLibraries.props" />
<Import Project="..\..\build\SourceLink.props" Condition="'$(DisableSourceLink)' == ''" />
<Target Name="WriteCurrentPackageVersionProps" BeforeTargets="_GetPackageFiles">
<PropertyGroup>
<PackageVersionPropsPath>$(IntermediateOutputPath)/AvaloniaVersion.props</PackageVersionPropsPath>
</PropertyGroup>
<WriteLinesToFile
File="$(PackageVersionPropsPath)"
Overwrite="true"
Lines="&lt;Project&gt;&lt;PropertyGroup&gt;&lt;AvaloniaMainPackageVersion&gt;$(PackageVersion)&lt;/AvaloniaMainPackageVersion&gt;&lt;/PropertyGroup&gt;&lt;/Project&gt;" />
<ItemGroup>
<Content Include="$(PackageVersionPropsPath)">
<Pack>true</Pack>
<PackagePath>build</PackagePath>
</Content>
</ItemGroup>
</Target>
</Project>

2
packages/Avalonia/Avalonia.props

@ -4,9 +4,11 @@
<AvaloniaPreviewerNetFullToolPath>$(MSBuildThisFileDirectory)\..\tools\net461\designer\Avalonia.Designer.HostApp.exe</AvaloniaPreviewerNetFullToolPath>
<AvaloniaBuildTasksLocation>$(MSBuildThisFileDirectory)\..\tools\netstandard2.0\Avalonia.Build.Tasks.dll</AvaloniaBuildTasksLocation>
<AvaloniaUseExternalMSBuild>false</AvaloniaUseExternalMSBuild>
<UsedAvaloniaProducts>$(UsedAvaloniaProducts);AvaloniaUI</UsedAvaloniaProducts>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.props"/>
<Import Project="$(MSBuildThisFileDirectory)\Avalonia.Generators.props"/>
<Import Project="$(MSBuildThisFileDirectory)..\build\AvaloniaVersion.props" />
<!-- Allow loading the AvaloniaVS extension when referencing the Avalonia nuget package -->
<ItemGroup>

2
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -26,7 +26,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
<ProjectReference Include="..\..\src\Headless\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />

6
samples/ControlCatalog.NetCore/Properties/launchSettings.json

@ -6,6 +6,10 @@
"Dxgi": {
"commandName": "Project",
"commandLineArgs": "--dxgi"
},
"VNC": {
"commandName": "Project",
"commandLineArgs": "--vnc"
}
}
}
}

2
samples/ControlCatalog/App.xaml

@ -26,8 +26,6 @@
<Color x:Key="CatalogBaseHighColor">#FFFFFFFF</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Color x:Key="SystemAccentColor">#FF0078D7</Color>
<Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
<!-- Styles attached dynamically depending on current theme (simple or fluent) -->
<StyleInclude x:Key="DataGridFluent" Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />

48
samples/ControlCatalog/MainView.xaml.cs

@ -19,13 +19,9 @@ namespace ControlCatalog
{
public class MainView : UserControl
{
private readonly IPlatformSettings _platformSettings;
public MainView()
{
AvaloniaXamlLoader.Load(this);
_platformSettings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
var sideBar = this.Get<TabControl>("Sidebar");
@ -141,50 +137,6 @@ namespace ControlCatalog
ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true;
};
}
_platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
_platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged;
}
private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
{
Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1;
Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, 0.3);
Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, 0.5);
Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, 0.7);
static Color ChangeColorLuminosity(Color color, double luminosityFactor)
{
var red = (double)color.R;
var green = (double)color.G;
var blue = (double)color.B;
if (luminosityFactor < 0)
{
luminosityFactor = 1 + luminosityFactor;
red *= luminosityFactor;
green *= luminosityFactor;
blue *= luminosityFactor;
}
else if (luminosityFactor >= 0)
{
red = (255 - red) * luminosityFactor + red;
green = (255 - green) * luminosityFactor + green;
blue = (255 - blue) * luminosityFactor + blue;
}
return new Color(color.A, (byte)red, (byte)green, (byte)blue);
}
}
}
}

2
samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj

@ -19,7 +19,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
<ProjectReference Include="..\..\src\Headless\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MobileSandbox\MobileSandbox.csproj" />

2
src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs

@ -35,7 +35,7 @@ namespace Avalonia.Collections
/// Indicates if a weak subscription should be used to track changes to the collection.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
internal static IDisposable ForEachItem<TKey, TValue>(
public static IDisposable ForEachItem<TKey, TValue>(
this IAvaloniaReadOnlyDictionary<TKey, TValue> collection,
Action<TKey, TValue> added,
Action<TKey, TValue> removed,

2
src/Avalonia.Base/Controls/IResourceDictionary.cs

@ -18,6 +18,6 @@ namespace Avalonia.Controls
/// <summary>
/// Gets a collection of merged resource dictionaries that are specifically keyed and composed to address theme scenarios.
/// </summary>
IDictionary<ThemeVariant, IResourceProvider> ThemeDictionaries { get; }
IDictionary<ThemeVariant, IThemeVariantProvider> ThemeDictionaries { get; }
}
}

22
src/Avalonia.Base/Controls/IThemeVariantProvider.cs

@ -0,0 +1,22 @@
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Controls;
/// <summary>
/// Resource provider with theme variant awareness.
/// Can be used with <see cref="IResourceDictionary.ThemeDictionaries"/>.
/// </summary>
/// <remarks>
/// This is a helper interface for the XAML compiler to make Key property accessibly by the markup extensions.
/// Which means, it can only be used with ResourceDictionaries and markup extensions in the XAML code.
/// This API might be removed in the future minor updates.
/// </remarks>
[Unstable]
public interface IThemeVariantProvider : IResourceProvider
{
/// <summary>
/// Key property set by the compiler.
/// </summary>
ThemeVariant? Key { get; set; }
}

12
src/Avalonia.Base/Controls/ResourceDictionary.cs

@ -13,13 +13,13 @@ namespace Avalonia.Controls
/// <summary>
/// An indexed dictionary of resources.
/// </summary>
public class ResourceDictionary : IResourceDictionary
public class ResourceDictionary : IResourceDictionary, IThemeVariantProvider
{
private object? lastDeferredItemKey;
private Dictionary<object, object?>? _inner;
private IResourceHost? _owner;
private AvaloniaList<IResourceProvider>? _mergedDictionaries;
private AvaloniaDictionary<ThemeVariant, IResourceProvider>? _themeDictionary;
private AvaloniaDictionary<ThemeVariant, IThemeVariantProvider>? _themeDictionary;
/// <summary>
/// Initializes a new instance of the <see cref="ResourceDictionary"/> class.
@ -93,13 +93,13 @@ namespace Avalonia.Controls
}
}
public IDictionary<ThemeVariant, IResourceProvider> ThemeDictionaries
public IDictionary<ThemeVariant, IThemeVariantProvider> ThemeDictionaries
{
get
{
if (_themeDictionary == null)
{
_themeDictionary = new AvaloniaDictionary<ThemeVariant, IResourceProvider>(2);
_themeDictionary = new AvaloniaDictionary<ThemeVariant, IThemeVariantProvider>(2);
_themeDictionary.ForEachItem(
(_, x) =>
{
@ -120,6 +120,8 @@ namespace Avalonia.Controls
return _themeDictionary;
}
}
ThemeVariant? IThemeVariantProvider.Key { get; set; }
bool IResourceNode.HasResources
{
@ -192,7 +194,7 @@ namespace Avalonia.Controls
if (_themeDictionary is not null)
{
IResourceProvider? themeResourceProvider;
IThemeVariantProvider? themeResourceProvider;
if (theme is not null && theme != ThemeVariant.Default)
{
if (_themeDictionary.TryGetValue(theme, out themeResourceProvider)

46
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@ -119,7 +119,19 @@ namespace Avalonia.Controls
resourceProvider = resourceProvider ?? throw new ArgumentNullException(nameof(resourceProvider));
key = key ?? throw new ArgumentNullException(nameof(key));
return new FloatingResourceObservable(resourceProvider, key, converter);
return new FloatingResourceObservable(resourceProvider, key, null, converter);
}
public static IObservable<object?> GetResourceObservable(
this IResourceProvider resourceProvider,
object key,
ThemeVariant? defaultThemeVariant,
Func<object?, object?>? converter = null)
{
resourceProvider = resourceProvider ?? throw new ArgumentNullException(nameof(resourceProvider));
key = key ?? throw new ArgumentNullException(nameof(key));
return new FloatingResourceObservable(resourceProvider, key, defaultThemeVariant, converter);
}
private class ResourceObservable : LightweightObservableBase<object?>
@ -128,7 +140,10 @@ namespace Avalonia.Controls
private readonly object _key;
private readonly Func<object?, object?>? _converter;
public ResourceObservable(IResourceHost target, object key, Func<object?, object?>? converter)
public ResourceObservable(
IResourceHost target,
object key,
Func<object?, object?>? converter)
{
_target = target;
_key = key;
@ -170,11 +185,8 @@ namespace Avalonia.Controls
private object? GetValue()
{
if (_target is not IThemeVariantHost themeVariantHost
|| !_target.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value))
{
value = _target.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
}
var theme = (_target as IThemeVariantHost)?.ActualThemeVariant;
var value = _target.FindResource(theme, _key) ?? AvaloniaProperty.UnsetValue;
return _converter?.Invoke(value) ?? value;
}
@ -183,14 +195,20 @@ namespace Avalonia.Controls
private class FloatingResourceObservable : LightweightObservableBase<object?>
{
private readonly IResourceProvider _target;
private readonly ThemeVariant? _overrideThemeVariant;
private readonly object _key;
private readonly Func<object?, object?>? _converter;
private IResourceHost? _owner;
public FloatingResourceObservable(IResourceProvider target, object key, Func<object?, object?>? converter)
public FloatingResourceObservable(
IResourceProvider target,
object key,
ThemeVariant? overrideThemeVariant,
Func<object?, object?>? converter)
{
_target = target;
_key = key;
_overrideThemeVariant = overrideThemeVariant;
_converter = converter;
}
@ -233,7 +251,7 @@ namespace Avalonia.Controls
{
_owner.ResourcesChanged -= ResourcesChanged;
}
if (_owner is IThemeVariantHost themeVariantHost)
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
{
themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged;
}
@ -244,12 +262,11 @@ namespace Avalonia.Controls
{
_owner.ResourcesChanged += ResourcesChanged;
}
if (_owner is IThemeVariantHost themeVariantHost2)
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost2)
{
themeVariantHost2.ActualThemeVariantChanged -= ActualThemeVariantChanged;
}
PublishNext();
}
@ -265,11 +282,8 @@ namespace Avalonia.Controls
private object? GetValue()
{
if (!(_target.Owner is IThemeVariantHost themeVariantHost)
|| !_target.Owner.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value))
{
value = _target.Owner?.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
}
var theme = _overrideThemeVariant ?? (_target.Owner as IThemeVariantHost)?.ActualThemeVariant;
var value = _target.Owner?.FindResource(theme, _key) ?? AvaloniaProperty.UnsetValue;
return _converter?.Invoke(value) ?? value;
}

13
src/Avalonia.Base/Input/Cursor.cs

@ -42,19 +42,21 @@ namespace Avalonia.Input
public class Cursor : IDisposable
{
public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow);
private string _name;
internal Cursor(ICursorImpl platformImpl)
private Cursor(ICursorImpl platformImpl, string name)
{
PlatformImpl = platformImpl;
_name = name;
}
public Cursor(StandardCursorType cursorType)
: this(GetCursorFactory().GetCursor(cursorType))
: this(GetCursorFactory().GetCursor(cursorType), cursorType.ToString())
{
}
public Cursor(IBitmap cursor, PixelPoint hotSpot)
: this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot))
: this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot), "BitmapCursor")
{
}
@ -73,5 +75,10 @@ namespace Avalonia.Input
{
return AvaloniaLocator.Current.GetRequiredService<ICursorFactory>();
}
public override string ToString()
{
return _name;
}
}
}

23
src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs

@ -0,0 +1,23 @@
using System;
namespace Avalonia.Metadata;
/// <summary>
/// Defines how compiler should split avalonia list string value before parsing individual items.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class AvaloniaListAttribute : Attribute
{
/// <summary>
/// Separator used to split input string.
/// Default value is ','.
/// </summary>
public string[]? Separators { get; init; }
/// <summary>
/// Split options used to split input string.
/// Default value is RemoveEmptyEntries with TrimEntries.
/// </summary>
// StringSplitOptions.TrimEntries = 2, but only on net6 target.
public StringSplitOptions SplitOptions { get; init; } = StringSplitOptions.RemoveEmptyEntries | (StringSplitOptions)2;
}

27
src/Avalonia.Base/Platform/Storage/NoopStorageProvider.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Platform.Storage.FileIO;
namespace Avalonia.Platform.Storage;
internal class NoopStorageProvider : BclStorageProvider
{
public override bool CanOpen => false;
public override Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
{
return Task.FromResult<IReadOnlyList<IStorageFile>>(Array.Empty<IStorageFile>());
}
public override bool CanSave => false;
public override Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
{
return Task.FromResult<IStorageFile?>(null);
}
public override bool CanPickFolder => false;
public override Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
{
return Task.FromResult<IReadOnlyList<IStorageFolder>>(Array.Empty<IStorageFolder>());
}
}

1
src/Avalonia.Base/Styling/IThemeVariantHost.cs

@ -7,7 +7,6 @@ namespace Avalonia.Styling;
/// <summary>
/// Interface for the host element with a theme variant.
/// </summary>
[Unstable]
public interface IThemeVariantHost : IResourceHost
{
/// <summary>

39
src/Avalonia.Controls/AppBuilder.cs

@ -118,6 +118,43 @@ namespace Avalonia
};
}
/// <summary>
/// Begin configuring an <see cref="Application"/>.
/// Should only be used for testing and design purposes, as it relies on dynamic code.
/// </summary>
/// <param name="entryPointType">
/// Parameter from which <see cref="AppBuilder"/> should be created.
/// It either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application.
/// </param>
/// <returns>An <see cref="AppBuilder"/> instance. If can't be created, thrown an exception.</returns>
internal static AppBuilder Configure(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type entryPointType)
{
var appBuilderObj = entryPointType
.GetMethod(
"BuildAvaloniaApp",
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy,
null,
Array.Empty<Type>(),
null)?
.Invoke(null, Array.Empty<object?>());
if (appBuilderObj is AppBuilder appBuilder)
{
return appBuilder;
}
if (typeof(Application).IsAssignableFrom(entryPointType))
{
return Configure(() => (Application)Activator.CreateInstance(entryPointType)!);
}
throw new InvalidOperationException(
$"Unable to create AppBuilder from type {entryPointType.Name}." +
$"Input type either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application type.");
}
protected AppBuilder Self => this;
public AppBuilder AfterSetup(Action<AppBuilder> callback)
@ -206,7 +243,7 @@ namespace Avalonia
_optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind<T>().ToFunc(options); };
return Self;
}
/// <summary>
/// Registers an action that is executed with the current font manager.
/// </summary>

6
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -16,5 +16,11 @@
<InternalsVisibleTo Include="Avalonia.Controls.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.DesignerSupport, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.LeakTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Headless, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Headless.XUnit, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Native, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.DesignerSupport.Remote, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Browser, PublicKey=$(AvaloniaPublicKey)" />
</ItemGroup>
</Project>

2
src/Avalonia.Controls/DefinitionList.cs

@ -1,9 +1,11 @@
using System.Collections;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
[AvaloniaList(Separators = new [] { ",", " " })]
public abstract class DefinitionList<T> : AvaloniaList<T> where T : DefinitionBase
{
public DefinitionList()

3
src/Avalonia.Controls/Platform/ScreenHelper.cs

@ -1,11 +1,12 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.Platform
{
public static class ScreenHelper
internal static class ScreenHelper
{
public static Screen? ScreenFromPoint(PixelPoint point, IReadOnlyList<Screen> screens)
{

13
src/Avalonia.Controls/TopLevel.cs

@ -285,6 +285,11 @@ namespace Avalonia.Controls
/// </summary>
public event EventHandler? Closed;
/// <summary>
/// Gets or sets a method called when the TopLevel's scaling changes.
/// </summary>
public event EventHandler? ScalingChanged;
/// <summary>
/// Gets or sets the client size of the window.
/// </summary>
@ -428,14 +433,17 @@ namespace Avalonia.Controls
double ILayoutRoot.LayoutScaling => PlatformImpl?.RenderScaling ?? 1;
/// <inheritdoc/>
double IRenderRoot.RenderScaling => PlatformImpl?.RenderScaling ?? 1;
public double RenderScaling => PlatformImpl?.RenderScaling ?? 1;
IStyleHost IStyleHost.StylingParent => _globalStyles!;
/// <summary>
/// File System storage service used for file pickers and bookmarks.
/// </summary>
public IStorageProvider StorageProvider => _storageProvider
??= AvaloniaLocator.Current.GetService<IStorageProviderFactory>()?.CreateProvider(this)
?? PlatformImpl?.TryGetFeature<IStorageProvider>()
?? throw new InvalidOperationException("StorageProvider platform implementation is not available.");
?? new NoopStorageProvider();
public IInsetsManager? InsetsManager => PlatformImpl?.TryGetFeature<IInsetsManager>();
@ -587,6 +595,7 @@ namespace Avalonia.Controls
protected virtual void HandleScalingChanged(double scaling)
{
LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
ScalingChanged?.Invoke(this, EventArgs.Empty);
}
private static bool TransparencyLevelsMatch (WindowTransparencyLevel requested, WindowTransparencyLevel received)

5
src/Avalonia.Controls/WindowBase.cs

@ -129,6 +129,11 @@ namespace Avalonia.Controls
set { SetValue(TopmostProperty, value); }
}
/// <summary>
/// Gets the scaling factor for Window positioning and sizing.
/// </summary>
public double DesktopScaling => PlatformImpl?.DesktopScaling ?? 1;
/// <summary>
/// Activates the window.
/// </summary>

12
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@ -179,17 +179,9 @@ namespace Avalonia.DesignerSupport.Remote
var entryPoint = asm.EntryPoint;
if (entryPoint == null)
throw Die($"Assembly {args.AppPath} doesn't have an entry point");
var builderMethod = entryPoint.DeclaringType.GetMethod(
BuilderMethodName,
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy,
null,
Array.Empty<Type>(),
null);
if (builderMethod == null)
throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}");
Log($"Obtaining AppBuilder instance from {entryPoint.DeclaringType!.FullName}");
var appBuilder = AppBuilder.Configure(entryPoint.DeclaringType);
Design.IsDesignMode = true;
Log($"Obtaining AppBuilder instance from {builderMethod.DeclaringType.FullName}.{builderMethod.Name}");
var appBuilder = builderMethod.Invoke(null, null);
Log($"Initializing application in design mode");
var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer));
transport = initializer.ConfigureApp(transport, args, appBuilder);

107
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -1,5 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
@ -21,6 +27,8 @@ namespace Avalonia.Diagnostics.ViewModels
SettersFilter.RefreshFilter += (s, e) => Details?.UpdateStyleFilters();
}
public event EventHandler<string>? ClipboardCopyRequested;
public MainViewModel MainView { get; }
public FilterViewModel PropertiesFilter { get; }
@ -106,6 +114,105 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public void CopySelector()
{
var currentVisual = SelectedNode?.Visual as Visual;
if (currentVisual is not null)
{
var selector = GetVisualSelector(currentVisual);
ClipboardCopyRequested?.Invoke(this, selector);
}
}
public void CopySelectorFromTemplateParent()
{
var parts = new List<string>();
var currentVisual = SelectedNode?.Visual as Visual;
while (currentVisual is not null)
{
parts.Add(GetVisualSelector(currentVisual));
currentVisual = currentVisual.TemplatedParent as Visual;
}
if (parts.Any())
{
parts.Reverse();
var selector = string.Join(" /template/ ", parts);
ClipboardCopyRequested?.Invoke(this, selector);
}
}
public void ExpandRecursively()
{
if (SelectedNode is { } selectedNode)
{
ExpandNode(selectedNode);
var stack = new Stack<TreeNode>();
stack.Push(selectedNode);
while (stack.Count > 0)
{
var item = stack.Pop();
item.IsExpanded = true;
foreach (var child in item.Children)
{
stack.Push(child);
}
}
}
}
public void CollapseChildren()
{
if (SelectedNode is { } selectedNode)
{
var stack = new Stack<TreeNode>();
stack.Push(selectedNode);
while (stack.Count > 0)
{
var item = stack.Pop();
item.IsExpanded = false;
foreach (var child in item.Children)
{
stack.Push(child);
}
}
}
}
public void CaptureNodeScreenshot()
{
MainView.Shot(null);
}
public void BringIntoView()
{
(SelectedNode?.Visual as Control)?.BringIntoView();
}
public void Focus()
{
(SelectedNode?.Visual as Control)?.Focus();
}
private static string GetVisualSelector(Visual visual)
{
var name = string.IsNullOrEmpty(visual.Name) ? "" : $"#{visual.Name}";
var classes = string.Concat(visual.Classes
.Where(c => !c.StartsWith(":"))
.Select(c => '.' + c));
var typeName = ((IStyleable)visual).StyleKey.Name;
return $"{typeName}{name}{classes}";
}
private void ExpandNode(TreeNode? node)
{
if (node != null)

14
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml

@ -26,6 +26,20 @@
<Setter Property="Background" Value="Transparent" />
</Style>
</TreeView.Styles>
<TreeView.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Copy">
<MenuItem Header="Copy individual node selector" Command="{Binding CopySelector}" />
<MenuItem Header="Copy selector from template parent" Command="{Binding CopySelectorFromTemplateParent}" />
</MenuItem>
<MenuItem Header="-" />
<MenuItem Header="Expand recursively" Command="{Binding ExpandRecursively}" />
<MenuItem Header="Collapse children" Command="{Binding CollapseChildren}" />
<MenuItem Header="Capture node screenshot" Command="{Binding CaptureNodeScreenshot}" />
<MenuItem Header="Bring into view" Command="{Binding BringIntoView}" />
<MenuItem Header="Focus" Command="{Binding Focus}" />
</MenuFlyout>
</TreeView.ContextFlyout>
</TreeView>
<GridSplitter Background="{DynamicResource ThemeControlMidBrush}" Width="1" Grid.Column="1"/>

11
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs

@ -1,3 +1,4 @@
using System;
using System.Diagnostics;
using System.Linq;
using Avalonia.Controls;
@ -38,6 +39,16 @@ namespace Avalonia.Diagnostics.Views
AdornerLayer.SetIsClipEnabled(_adorner, false);
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
((TreePageViewModel)DataContext!).ClipboardCopyRequested += (sender, s) =>
{
TopLevel.GetTopLevel(this)?.Clipboard?.SetTextAsync(s);
};
}
protected void AddAdorner(object? sender, PointerEventArgs e)
{
var node = (TreeNode?)((Control)sender!).DataContext;

78
src/Avalonia.FreeDesktop/DBusPlatformSettings.cs

@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Platform;
using Tmds.DBus.SourceGenerator;
@ -9,7 +10,10 @@ namespace Avalonia.FreeDesktop
internal class DBusPlatformSettings : DefaultPlatformSettings
{
private readonly OrgFreedesktopPortalSettings? _settings;
private PlatformColorValues? _lastColorValues;
private PlatformThemeVariant? _themeVariant;
private Color? _accentColor;
public DBusPlatformSettings()
{
@ -21,24 +25,33 @@ namespace Avalonia.FreeDesktop
_ = TryGetInitialValueAsync();
}
public override PlatformColorValues GetColorValues()
{
return _lastColorValues ?? base.GetColorValues();
}
public override PlatformColorValues GetColorValues() => _lastColorValues ?? base.GetColorValues();
private async Task TryGetInitialValueAsync()
{
try
{
var value = await _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme");
_lastColorValues = GetColorValuesFromSetting(value);
OnColorValuesChanged(_lastColorValues);
_themeVariant = ReadAsColorScheme(value);
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get org.freedesktop.appearance.color-scheme value", ex);
}
try
{
var value = await _settings!.ReadAsync("org.kde.kdeglobals.General", "AccentColor");
_accentColor = ReadAsAccentColor(value);
}
catch (Exception ex)
{
_lastColorValues = base.GetColorValues();
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get setting value", ex);
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get org.kde.kdeglobals.General.AccentColor value", ex);
}
_lastColorValues = BuildPlatformColorValues();
if (_lastColorValues is not null)
OnColorValuesChanged(_lastColorValues);
}
private void SettingsChangedHandler(Exception? exception, (string @namespace, string key, DBusVariantItem value) valueTuple)
@ -46,25 +59,48 @@ namespace Avalonia.FreeDesktop
if (exception is not null)
return;
if (valueTuple is ("org.freedesktop.appearance", "color-scheme", { } value))
switch (valueTuple)
{
/*
<member>0: No preference</member>
<member>1: Prefer dark appearance</member>
<member>2: Prefer light appearance</member>
*/
_lastColorValues = GetColorValuesFromSetting(value);
OnColorValuesChanged(_lastColorValues);
case ("org.freedesktop.appearance", "color-scheme", { } colorScheme):
_themeVariant = ReadAsColorScheme(colorScheme);
_lastColorValues = BuildPlatformColorValues();
OnColorValuesChanged(_lastColorValues!);
break;
case ("org.kde.kdeglobals.General", "AccentColor", { } accentColor):
_accentColor = ReadAsAccentColor(accentColor);
_lastColorValues = BuildPlatformColorValues();
OnColorValuesChanged(_lastColorValues!);
break;
}
}
private static PlatformColorValues GetColorValuesFromSetting(DBusVariantItem value)
private PlatformColorValues? BuildPlatformColorValues()
{
if (_themeVariant is { } themeVariant && _accentColor is { } accentColor)
return new PlatformColorValues { ThemeVariant = themeVariant, AccentColor1 = accentColor };
if (_themeVariant is { } themeVariant1)
return new PlatformColorValues { ThemeVariant = themeVariant1 };
if (_accentColor is { } accentColor1)
return new PlatformColorValues { AccentColor1 = accentColor1 };
return null;
}
private static PlatformThemeVariant ReadAsColorScheme(DBusVariantItem value)
{
/*
<member>0: No preference</member>
<member>1: Prefer dark appearance</member>
<member>2: Prefer light appearance</member>
*/
var isDark = ((value.Value as DBusVariantItem)!.Value as DBusUInt32Item)!.Value == 1;
return new PlatformColorValues
{
ThemeVariant = isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light
};
return isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light;
}
private static Color ReadAsAccentColor(DBusVariantItem value)
{
var colorStr = ((value.Value as DBusVariantItem)!.Value as DBusStringItem)!.Value;
var rgb = colorStr.Split(',');
return new Color(255, byte.Parse(rgb[0]), byte.Parse(rgb[1]), byte.Parse(rgb[2]));
}
}
}

13
src/Avalonia.Headless/Avalonia.Headless.csproj

@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
</ItemGroup>
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
</Project>

4
src/Avalonia.Native/Avalonia.Native.csproj

@ -26,8 +26,4 @@
<Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<ItemGroup>
<Compile Remove="..\Shared\ModuleInitializer.cs" />
</ItemGroup>
</Project>

12
src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml

@ -1,12 +0,0 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Accent Colours -->
<!-- TODO pull accents from system... algorithm to generate shades -->
<Color x:Key="SystemAccentColor">#FF0078D7</Color>
<Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
<Color x:Key="SystemAccentColorDark2">#FF004275</Color>
<Color x:Key="SystemAccentColorDark3">#FF002642</Color>
<Color x:Key="SystemAccentColorLight1">#FF429CE3</Color>
<Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color>
<Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color>
</ResourceDictionary>

69
src/Avalonia.Themes.Fluent/Accents/BaseColorsPalette.xaml

@ -0,0 +1,69 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- https://docs.microsoft.com/en-us/previous-versions/windows/apps/dn518235(v=win.10)?redirectedfrom=MSDN -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<Color x:Key="SystemAltHighColor">#FFFFFFFF</Color>
<Color x:Key="SystemAltLowColor">#33FFFFFF</Color>
<Color x:Key="SystemAltMediumColor">#99FFFFFF</Color>
<Color x:Key="SystemAltMediumHighColor">#CCFFFFFF</Color>
<Color x:Key="SystemAltMediumLowColor">#66FFFFFF</Color>
<Color x:Key="SystemBaseHighColor">#FF000000</Color>
<Color x:Key="SystemBaseLowColor">#33000000</Color>
<Color x:Key="SystemBaseMediumColor">#99000000</Color>
<Color x:Key="SystemBaseMediumHighColor">#CC000000</Color>
<Color x:Key="SystemBaseMediumLowColor">#66000000</Color>
<Color x:Key="SystemChromeAltLowColor">#FF171717</Color>
<Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
<Color x:Key="SystemChromeBlackLowColor">#33000000</Color>
<Color x:Key="SystemChromeBlackMediumLowColor">#66000000</Color>
<Color x:Key="SystemChromeBlackMediumColor">#CC000000</Color>
<Color x:Key="SystemChromeDisabledHighColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeDisabledLowColor">#FF7A7A7A</Color>
<Color x:Key="SystemChromeHighColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeLowColor">#FFF2F2F2</Color>
<Color x:Key="SystemChromeMediumColor">#FFE6E6E6</Color>
<Color x:Key="SystemChromeMediumLowColor">#FFF2F2F2</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="SystemChromeGrayColor">#FF767676</Color>
<Color x:Key="SystemListLowColor">#19000000</Color>
<Color x:Key="SystemListMediumColor">#33000000</Color>
<Color x:Key="SystemErrorTextColor">#C50500</Color>
<Color x:Key="SystemRegionColor">#FFFFFFFF</Color>
<Color x:Key="SystemRevealListLowColor">#17000000</Color>
<Color x:Key="SystemRevealListMediumColor">#2E000000</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<Color x:Key="SystemAltHighColor">#FF000000</Color>
<Color x:Key="SystemAltLowColor">#33000000</Color>
<Color x:Key="SystemAltMediumColor">#99000000</Color>
<Color x:Key="SystemAltMediumHighColor">#CC000000</Color>
<Color x:Key="SystemAltMediumLowColor">#66000000</Color>
<Color x:Key="SystemBaseHighColor">#FFFFFFFF</Color>
<Color x:Key="SystemBaseLowColor">#33FFFFFF</Color>
<Color x:Key="SystemBaseMediumColor">#99FFFFFF</Color>
<Color x:Key="SystemBaseMediumHighColor">#CCFFFFFF</Color>
<Color x:Key="SystemBaseMediumLowColor">#66FFFFFF</Color>
<Color x:Key="SystemChromeAltLowColor">#FFF2F2F2</Color>
<Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
<Color x:Key="SystemChromeBlackLowColor">#33000000</Color>
<Color x:Key="SystemChromeBlackMediumLowColor">#66000000</Color>
<Color x:Key="SystemChromeBlackMediumColor">#CC000000</Color>
<Color x:Key="SystemChromeDisabledHighColor">#FF333333</Color>
<Color x:Key="SystemChromeDisabledLowColor">#FF858585</Color>
<Color x:Key="SystemChromeHighColor">#FF767676</Color>
<Color x:Key="SystemChromeLowColor">#FF171717</Color>
<Color x:Key="SystemChromeMediumColor">#FF1F1F1F</Color>
<Color x:Key="SystemChromeMediumLowColor">#FF2B2B2B</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="SystemChromeGrayColor">#FF767676</Color>
<Color x:Key="SystemListLowColor">#19FFFFFF</Color>
<Color x:Key="SystemListMediumColor">#33FFFFFF</Color>
<Color x:Key="SystemErrorTextColor">#FFF000</Color>
<Color x:Key="SystemRegionColor">#FF000000</Color>
<Color x:Key="SystemRevealListLowColor">#18FFFFFF</Color>
<Color x:Key="SystemRevealListMediumColor">#30FFFFFF</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

158
src/Avalonia.Themes.Fluent/Accents/Base.xaml → src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System"
xmlns:converters="using:Avalonia.Controls.Converters">
<!-- https://docs.microsoft.com/en-us/previous-versions/windows/apps/dn518235(v=win.10)?redirectedfrom=MSDN -->
<FontFamily x:Key="ContentControlThemeFontFamily">fonts:Inter#Inter, $Default</FontFamily>
<sys:Double x:Key="ControlContentThemeFontSize">14</sys:Double>
@ -28,39 +27,33 @@
<converters:CornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" Filter="BottomLeft, BottomRight" />
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft" />
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<!-- System Control Colors -->
<Color x:Key="SystemAltHighColor">#FFFFFFFF</Color>
<Color x:Key="SystemAltLowColor">#33FFFFFF</Color>
<Color x:Key="SystemAltMediumColor">#99FFFFFF</Color>
<Color x:Key="SystemAltMediumHighColor">#CCFFFFFF</Color>
<Color x:Key="SystemAltMediumLowColor">#66FFFFFF</Color>
<Color x:Key="SystemBaseHighColor">#FF000000</Color>
<Color x:Key="SystemBaseLowColor">#33000000</Color>
<Color x:Key="SystemBaseMediumColor">#99000000</Color>
<Color x:Key="SystemBaseMediumHighColor">#CC000000</Color>
<Color x:Key="SystemBaseMediumLowColor">#66000000</Color>
<Color x:Key="SystemChromeAltLowColor">#FF171717</Color>
<Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
<Color x:Key="SystemChromeBlackLowColor">#33000000</Color>
<Color x:Key="SystemChromeBlackMediumLowColor">#66000000</Color>
<Color x:Key="SystemChromeBlackMediumColor">#CC000000</Color>
<Color x:Key="SystemChromeDisabledHighColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeDisabledLowColor">#FF7A7A7A</Color>
<Color x:Key="SystemChromeHighColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeLowColor">#FFF2F2F2</Color>
<Color x:Key="SystemChromeMediumColor">#FFE6E6E6</Color>
<Color x:Key="SystemChromeMediumLowColor">#FFF2F2F2</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="SystemChromeGrayColor">#FF767676</Color>
<Color x:Key="SystemListLowColor">#19000000</Color>
<Color x:Key="SystemListMediumColor">#33000000</Color>
<Color x:Key="SystemErrorTextColor">#C50500</Color>
<x:Double x:Key="AutoCompleteListMaxHeight">374</x:Double>
<Thickness x:Key="AutoCompleteListMargin">0,2,0,2</Thickness>
<Thickness x:Key="AutoCompleteListBorderThemeThickness">1</Thickness>
<Thickness x:Key="AutoCompleteListPadding">-1,0,-1,0</Thickness>
<x:Double x:Key="TextControlThemeMinHeight">32</x:Double>
<x:Double x:Key="TextControlThemeMinWidth">64</x:Double>
<x:Double x:Key="FlyoutThemeMaxWidth">456</x:Double>
<Thickness x:Key="DateTimeFlyoutButtonBorderThickness">0</Thickness>
<Thickness x:Key="DateTimeFlyoutBorderThickness">1</Thickness>
<Thickness x:Key="DateTimeFlyoutBorderPadding">0</Thickness>
<Thickness x:Key="FlyoutContentThemePadding">12,11,12,12</Thickness>
<x:Double x:Key="FlyoutThemeMinWidth">96</x:Double>
<x:Double x:Key="FlyoutThemeMinHeight">40</x:Double>
<x:Double x:Key="FlyoutThemeMaxHeight">758</x:Double>
<Color x:Key="SystemRevealListLowColor">#17000000</Color>
<Color x:Key="SystemRevealListMediumColor">#2E000000</Color>
<!-- Moved from FlyoutPresenter.xaml -->
<Thickness x:Key="FlyoutBorderThemePadding">0</Thickness>
<!-- Moved from MenuItem.xaml -->
<Thickness x:Key="MenuFlyoutScrollerMargin">0,4,0,4</Thickness>
<!-- Moved from Menu.xaml -->
<Thickness x:Key="MenuBarItemPadding">12,0,12,0</Thickness>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="SystemControlBackgroundAccentBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="SystemControlBackgroundAltHighBrush" Color="{StaticResource SystemAltHighColor}" />
<SolidColorBrush x:Key="SystemControlBackgroundAltMediumHighBrush"
@ -211,82 +204,17 @@
Color="{StaticResource SystemChromeWhiteColor}" />
<SolidColorBrush x:Key="SystemControlHighlightAltTransparentRevealBorderBrush" Color="Transparent" />
<SolidColorBrush x:Key="SystemControlBackgroundTransparentRevealBorderBrush" Color="Transparent" />
<!-- TODO implement AcrylicBrush -->
<!--<AcrylicBrush x:Key="SystemControlTransientBackgroundBrush" BackgroundSource="HostBackdrop" TintColor="{StaticResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{StaticResource SystemChromeMediumLowColor}" />-->
<SolidColorBrush x:Key="SystemControlTransientBackgroundBrush"
Color="{StaticResource SystemChromeMediumLowColor}" />
<StaticResource x:Key="SystemControlDescriptionTextForegroundBrush"
ResourceKey="SystemControlPageTextBaseMediumBrush" />
<!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
<!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
<!--<RevealBorderBrush x:Key="SystemControlTransparentRevealBorderBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBorderBrush" Color="Transparent" />
<!--<RevealBackgroundBrush x:Key="SystemControlHighlightListLowRevealBackgroundBrush" TargetTheme="Light" Color="{ThemeResource SystemRevealListMediumColor}" FallbackColor="{ StaticResource SystemListMediumColor}" />-->
<Color x:Key="RegionColor">#FFFFFFFF</Color>
<SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
<x:Double x:Key="AutoCompleteListMaxHeight">374</x:Double>
<Thickness x:Key="AutoCompleteListMargin">0,2,0,2</Thickness>
<Thickness x:Key="AutoCompleteListBorderThemeThickness">1</Thickness>
<Thickness x:Key="AutoCompleteListPadding">-1,0,-1,0</Thickness>
<x:Double x:Key="TextControlThemeMinHeight">32</x:Double>
<x:Double x:Key="TextControlThemeMinWidth">64</x:Double>
<x:Double x:Key="FlyoutThemeMaxWidth">456</x:Double>
<Thickness x:Key="DateTimeFlyoutButtonBorderThickness">0</Thickness>
<Thickness x:Key="DateTimeFlyoutBorderThickness">1</Thickness>
<Thickness x:Key="DateTimeFlyoutBorderPadding">0</Thickness>
<Thickness x:Key="FlyoutContentThemePadding">12,11,12,12</Thickness>
<x:Double x:Key="FlyoutThemeMinWidth">96</x:Double>
<x:Double x:Key="FlyoutThemeMinHeight">40</x:Double>
<x:Double x:Key="FlyoutThemeMaxHeight">758</x:Double>
<!-- Moved from FlyoutPresenter.xaml -->
<Thickness x:Key="FlyoutBorderThemePadding">0</Thickness>
<!-- Moved from MenuItem.xaml -->
<Thickness x:Key="MenuFlyoutScrollerMargin">0,4,0,4</Thickness>
<!-- Moved from Menu.xaml -->
<Thickness x:Key="MenuBarItemPadding">12,0,12,0</Thickness>
<SolidColorBrush x:Key="SystemRegionBrush" Color="{StaticResource SystemRegionColor}" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<!-- System Control Colors -->
<Color x:Key="SystemAltHighColor">#FF000000</Color>
<Color x:Key="SystemAltLowColor">#33000000</Color>
<Color x:Key="SystemAltMediumColor">#99000000</Color>
<Color x:Key="SystemAltMediumHighColor">#CC000000</Color>
<Color x:Key="SystemAltMediumLowColor">#66000000</Color>
<Color x:Key="SystemBaseHighColor">#FFFFFFFF</Color>
<Color x:Key="SystemBaseLowColor">#33FFFFFF</Color>
<Color x:Key="SystemBaseMediumColor">#99FFFFFF</Color>
<Color x:Key="SystemBaseMediumHighColor">#CCFFFFFF</Color>
<Color x:Key="SystemBaseMediumLowColor">#66FFFFFF</Color>
<Color x:Key="SystemChromeAltLowColor">#FFF2F2F2</Color>
<Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
<Color x:Key="SystemChromeBlackLowColor">#33000000</Color>
<Color x:Key="SystemChromeBlackMediumLowColor">#66000000</Color>
<Color x:Key="SystemChromeBlackMediumColor">#CC000000</Color>
<Color x:Key="SystemChromeDisabledHighColor">#FF333333</Color>
<Color x:Key="SystemChromeDisabledLowColor">#FF858585</Color>
<Color x:Key="SystemChromeHighColor">#FF767676</Color>
<Color x:Key="SystemChromeLowColor">#FF171717</Color>
<Color x:Key="SystemChromeMediumColor">#FF1F1F1F</Color>
<Color x:Key="SystemChromeMediumLowColor">#FF2B2B2B</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="SystemChromeGrayColor">#FF767676</Color>
<Color x:Key="SystemListLowColor">#19FFFFFF</Color>
<Color x:Key="SystemListMediumColor">#33FFFFFF</Color>
<Color x:Key="SystemErrorTextColor">#FFF000</Color>
<Color x:Key="SystemRevealListLowColor">#18FFFFFF</Color>
<Color x:Key="SystemRevealListMediumColor">#30FFFFFF</Color>
<SolidColorBrush x:Key="SystemControlBackgroundAccentBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="SystemControlBackgroundAltHighBrush" Color="{StaticResource SystemAltHighColor}" />
<SolidColorBrush x:Key="SystemControlBackgroundAltMediumHighBrush"
@ -437,46 +365,14 @@
Color="{StaticResource SystemChromeWhiteColor}" />
<SolidColorBrush x:Key="SystemControlHighlightAltTransparentRevealBorderBrush" Color="Transparent" />
<SolidColorBrush x:Key="SystemControlBackgroundTransparentRevealBorderBrush" Color="Transparent" />
<!-- TODO implement AcrylicBrush -->
<!--<AcrylicBrush x:Key="SystemControlTransientBackgroundBrush" BackgroundSource="HostBackdrop" TintColor="{StaticResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{StaticResource SystemChromeMediumLowColor}" />-->
<SolidColorBrush x:Key="SystemControlTransientBackgroundBrush"
Color="{StaticResource SystemChromeMediumLowColor}" />
<StaticResource x:Key="SystemControlDescriptionTextForegroundBrush"
ResourceKey="SystemControlPageTextBaseMediumBrush" />
<!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
<!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
<!--<RevealBorderBrush x:Key="SystemControlTransparentRevealBorderBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBorderBrush" Color="Transparent" />
<Color x:Key="RegionColor">#FF000000</Color>
<SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
<x:Double x:Key="AutoCompleteListMaxHeight">374</x:Double>
<Thickness x:Key="AutoCompleteListMargin">0,2,0,2</Thickness>
<Thickness x:Key="AutoCompleteListBorderThemeThickness">1</Thickness>
<Thickness x:Key="AutoCompleteListPadding">-1,0,-1,0</Thickness>
<x:Double x:Key="TextControlThemeMinHeight">32</x:Double>
<x:Double x:Key="TextControlThemeMinWidth">64</x:Double>
<x:Double x:Key="FlyoutThemeMaxWidth">456</x:Double>
<Thickness x:Key="DateTimeFlyoutButtonBorderThickness">0</Thickness>
<Thickness x:Key="DateTimeFlyoutBorderThickness">1</Thickness>
<Thickness x:Key="DateTimeFlyoutBorderPadding">0</Thickness>
<Thickness x:Key="FlyoutContentThemePadding">12,11,12,12</Thickness>
<x:Double x:Key="FlyoutThemeMinWidth">96</x:Double>
<x:Double x:Key="FlyoutThemeMinHeight">40</x:Double>
<x:Double x:Key="FlyoutThemeMaxHeight">758</x:Double>
<!-- Moved from FlyoutPresenter.xaml -->
<Thickness x:Key="FlyoutBorderThemePadding">0</Thickness>
<!-- Moved from MenuItem.xaml -->
<Thickness x:Key="MenuFlyoutScrollerMargin">0,4,0,4</Thickness>
<!-- Moved from Menu.xaml -->
<Thickness x:Key="MenuBarItemPadding">12,0,12,0</Thickness>
<SolidColorBrush x:Key="SystemRegionBrush" Color="{StaticResource SystemRegionColor}" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

114
src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml

@ -4,8 +4,8 @@
<ResourceDictionary x:Key="Default">
<!-- Resources for Button.xaml -->
<StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlBackgroundAccentBrush" />
<StaticResource x:Key="AccentButtonBackgroundPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="AccentButtonBackgroundPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="AccentButtonBackgroundPointerOver" Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="AccentButtonBackgroundPressed" Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="AccentButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="AccentButtonForeground" ResourceKey="SystemControlBackgroundChromeWhiteBrush" />
<StaticResource x:Key="AccentButtonForegroundPointerOver" ResourceKey="SystemControlBackgroundChromeWhiteBrush" />
@ -52,7 +52,8 @@
<StaticResource x:Key="ToggleButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="ToggleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="SystemAccentColorLight1" />
<SolidColorBrush x:Key="ToggleButtonBackgroundCheckedPointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed"
ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
@ -291,15 +292,17 @@
ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeUncheckedDisabled"
ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeCheckedPointerOver" ResourceKey="SystemAccentColorLight1" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeCheckedPointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeCheckedPressed"
ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeCheckedDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeIndeterminate"
ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeIndeterminatePointerOver"
ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeIndeterminatePressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeIndeterminatePointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeIndeterminatePressed"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeIndeterminateDisabled"
ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillUnchecked" ResourceKey="SystemControlTransparentBrush" />
@ -309,13 +312,17 @@
ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillUncheckedDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillChecked" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillCheckedPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillCheckedPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillCheckedPointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillCheckedPressed"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillCheckedDisabled"
ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillIndeterminate" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillIndeterminatePointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillIndeterminatePressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillIndeterminatePointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillIndeterminatePressed"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillIndeterminateDisabled"
ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="CheckBoxCheckGlyphForegroundUnchecked"
@ -442,13 +449,17 @@
ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="RadioButtonOuterEllipseFillDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedStroke" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedStrokePointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedStrokePressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="RadioButtonOuterEllipseCheckedStrokePointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="RadioButtonOuterEllipseCheckedStrokePressed"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedStrokeDisabled"
ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedFill" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedFillPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedFillPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="RadioButtonOuterEllipseCheckedFillPointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="RadioButtonOuterEllipseCheckedFillPressed"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedFillDisabled"
ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="RadioButtonCheckGlyphFill" ResourceKey="SystemControlForegroundChromeWhiteBrush" />
@ -470,8 +481,8 @@
<StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderThumbBackground" ResourceKey="SystemControlForegroundAccentBrush" />
<StaticResource x:Key="SliderThumbBackgroundPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="SliderThumbBackgroundPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="SliderThumbBackgroundPointerOver" Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="SliderThumbBackgroundPressed" Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="SliderThumbBackgroundDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderTrackFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTrackFillPointerOver" ResourceKey="SystemControlForegroundBaseMediumBrush" />
@ -502,8 +513,8 @@
<StaticResource x:Key="ToggleSwitchStrokeOffPressed" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="ToggleSwitchStrokeOffDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="ToggleSwitchFillOn" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="ToggleSwitchFillOnPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="ToggleSwitchFillOnPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="ToggleSwitchFillOnPointerOver" Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="ToggleSwitchFillOnPressed" Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="ToggleSwitchFillOnDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="ToggleSwitchStrokeOn" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="ToggleSwitchStrokeOnPointerOver" ResourceKey="SystemControlHighlightListAccentHighBrush" />
@ -701,8 +712,9 @@
<StaticResource x:Key="SplitButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="SplitButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="SplitButtonBackgroundChecked" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SplitButtonBackgroundCheckedPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="SplitButtonBackgroundCheckedPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedPointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedPressed" Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="SplitButtonBackgroundCheckedDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="SplitButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="SplitButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
@ -775,8 +787,8 @@
<ResourceDictionary x:Key="Dark">
<!-- Resources for Button.xaml -->
<StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlBackgroundAccentBrush" />
<StaticResource x:Key="AccentButtonBackgroundPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="AccentButtonBackgroundPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="AccentButtonBackgroundPointerOver" Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="AccentButtonBackgroundPressed" Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="AccentButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="AccentButtonForeground" ResourceKey="SystemControlBackgroundChromeWhiteBrush" />
<StaticResource x:Key="AccentButtonForegroundPointerOver" ResourceKey="SystemControlBackgroundChromeWhiteBrush" />
@ -823,7 +835,8 @@
<StaticResource x:Key="ToggleButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="ToggleButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="ToggleButtonBackgroundCheckedPointerOver"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed"
ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
@ -1065,14 +1078,17 @@
ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeUncheckedDisabled"
ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeCheckedPointerOver" ResourceKey="SystemAccentColorDark1" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeCheckedPressed" ResourceKey="SystemAccentColorLight1" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeCheckedPointerOver"
Color="{DynamicResource SystemAccentColorDark1}" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeCheckedPressed"
Color="{DynamicResource SystemAccentColorLight1}" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeCheckedDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeIndeterminate"
ResourceKey="SystemControlForegroundAccentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeIndeterminatePointerOver"
ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeIndeterminatePressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeIndeterminatePointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeIndeterminatePressed"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="CheckBoxCheckBackgroundStrokeIndeterminateDisabled"
ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillUnchecked" ResourceKey="SystemControlTransparentBrush" />
@ -1082,13 +1098,17 @@
ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillUncheckedDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillChecked" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillCheckedPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillCheckedPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillCheckedPointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillCheckedPressed"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillCheckedDisabled"
ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillIndeterminate" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillIndeterminatePointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillIndeterminatePressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillIndeterminatePointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillIndeterminatePressed"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="CheckBoxCheckBackgroundFillIndeterminateDisabled"
ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="CheckBoxCheckGlyphForegroundUnchecked"
@ -1215,13 +1235,17 @@
ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="RadioButtonOuterEllipseFillDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedStroke" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedStrokePointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedStrokePressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="RadioButtonOuterEllipseCheckedStrokePointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="RadioButtonOuterEllipseCheckedStrokePressed"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedStrokeDisabled"
ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedFill" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedFillPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedFillPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="RadioButtonOuterEllipseCheckedFillPointerOver"
Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="RadioButtonOuterEllipseCheckedFillPressed"
Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="RadioButtonOuterEllipseCheckedFillDisabled"
ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="RadioButtonCheckGlyphFill" ResourceKey="SystemControlForegroundChromeWhiteBrush" />
@ -1243,8 +1267,8 @@
<StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderThumbBackground" ResourceKey="SystemControlForegroundAccentBrush" />
<StaticResource x:Key="SliderThumbBackgroundPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="SliderThumbBackgroundPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="SliderThumbBackgroundPointerOver" Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="SliderThumbBackgroundPressed" Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="SliderThumbBackgroundDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderTrackFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTrackFillPointerOver" ResourceKey="SystemControlForegroundBaseMediumBrush" />
@ -1275,12 +1299,12 @@
<StaticResource x:Key="ToggleSwitchStrokeOffPressed" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="ToggleSwitchStrokeOffDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="ToggleSwitchFillOn" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="ToggleSwitchFillOnPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="ToggleSwitchFillOnPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="ToggleSwitchFillOnPointerOver" Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="ToggleSwitchFillOnPressed" Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="ToggleSwitchFillOnDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="ToggleSwitchStrokeOn" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="ToggleSwitchStrokeOnPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="ToggleSwitchStrokeOnPressed" ResourceKey="SystemAccentColorDark1" />
<SolidColorBrush x:Key="ToggleSwitchStrokeOnPointerOver" Color="{DynamicResource SystemAccentColorLight1}" />
<SolidColorBrush x:Key="ToggleSwitchStrokeOnPressed" Color="{DynamicResource SystemAccentColorDark1}" />
<StaticResource x:Key="ToggleSwitchStrokeOnDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="ToggleSwitchKnobFillOff" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="ToggleSwitchKnobFillOffPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
@ -1476,8 +1500,8 @@
<StaticResource x:Key="SplitButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush" />
<StaticResource x:Key="SplitButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="SplitButtonBackgroundChecked" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SplitButtonBackgroundCheckedPointerOver" ResourceKey="SystemAccentColorDark1" />
<StaticResource x:Key="SplitButtonBackgroundCheckedPressed" ResourceKey="SystemAccentColorLight1" />
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedPointerOver" Color="{DynamicResource SystemAccentColorDark1}" />
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedPressed" Color="{DynamicResource SystemAccentColorLight1}" />
<StaticResource x:Key="SplitButtonBackgroundCheckedDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="SplitButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="SplitButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />

163
src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs

@ -0,0 +1,163 @@
using System;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
namespace Avalonia.Themes.Fluent.Accents;
internal class SystemAccentColors : IResourceProvider
{
public const string AccentKey = "SystemAccentColor";
public const string AccentDark1Key = "SystemAccentColorDark1";
public const string AccentDark2Key = "SystemAccentColorDark2";
public const string AccentDark3Key = "SystemAccentColorDark3";
public const string AccentLight1Key = "SystemAccentColorLight1";
public const string AccentLight2Key = "SystemAccentColorLight2";
public const string AccentLight3Key = "SystemAccentColorLight3";
private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215);
private readonly IPlatformSettings? _platformSettings;
private bool _invalidateColors = true;
private Color _systemAccentColor;
private Color _systemAccentColorDark1, _systemAccentColorDark2, _systemAccentColorDark3;
private Color _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3;
public SystemAccentColors()
{
_platformSettings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
}
public bool HasResources => true;
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{
if (key is string strKey)
{
if (strKey.Equals(AccentKey, StringComparison.InvariantCulture))
{
EnsureColors();
value = _systemAccentColor;
return true;
}
if (strKey.Equals(AccentDark1Key, StringComparison.InvariantCulture))
{
EnsureColors();
value = _systemAccentColorDark1;
return true;
}
if (strKey.Equals(AccentDark2Key, StringComparison.InvariantCulture))
{
EnsureColors();
value = _systemAccentColorDark2;
return true;
}
if (strKey.Equals(AccentDark3Key, StringComparison.InvariantCulture))
{
EnsureColors();
value = _systemAccentColorDark3;
return true;
}
if (strKey.Equals(AccentLight1Key, StringComparison.InvariantCulture))
{
EnsureColors();
value = _systemAccentColorLight1;
return true;
}
if (strKey.Equals(AccentLight2Key, StringComparison.InvariantCulture))
{
EnsureColors();
value = _systemAccentColorLight2;
return true;
}
if (strKey.Equals(AccentLight3Key, StringComparison.InvariantCulture))
{
EnsureColors();
value = _systemAccentColorLight3;
return true;
}
}
value = null;
return false;
}
public IResourceHost? Owner { get; private set; }
public event EventHandler? OwnerChanged;
public void AddOwner(IResourceHost owner)
{
if (Owner != owner)
{
Owner = owner;
OwnerChanged?.Invoke(this, EventArgs.Empty);
if (_platformSettings is not null)
{
_platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
}
}
}
public void RemoveOwner(IResourceHost owner)
{
if (Owner == owner)
{
Owner = null;
OwnerChanged?.Invoke(this, EventArgs.Empty);
if (_platformSettings is not null)
{
_platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged;
}
}
}
private void EnsureColors()
{
if (_invalidateColors)
{
_invalidateColors = false;
_systemAccentColor = _platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor;
(_systemAccentColorDark1,_systemAccentColorDark2, _systemAccentColorDark3,
_systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3) = CalculateAccentShades(_systemAccentColor);
}
}
public static (Color d1, Color d2, Color d3, Color l1, Color l2, Color l3) CalculateAccentShades(Color accentColor)
{
// dark1step = (hslAccent.L - SystemAccentColorDark1.L) * 255
const double dark1step = 28.5 / 255d;
const double dark2step = 49 / 255d;
const double dark3step = 74.5 / 255d;
// light1step = (SystemAccentColorLight1.L - hslAccent.L) * 255
const double light1step = 39 / 255d;
const double light2step = 70 / 255d;
const double light3step = 103 / 255d;
var hslAccent = accentColor.ToHsl();
return (
// Darker shades
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark1step).ToRgb(),
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark2step).ToRgb(),
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L - dark3step).ToRgb(),
// Lighter shades
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light1step).ToRgb(),
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light2step).ToRgb(),
new HslColor(hslAccent.A, hslAccent.H, hslAccent.S, hslAccent.L + light3step).ToRgb()
);
}
private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
{
_invalidateColors = true;
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}

158
src/Avalonia.Themes.Fluent/ColorPaletteResources.Properties.cs

@ -0,0 +1,158 @@
using Avalonia.Media;
namespace Avalonia.Themes.Fluent;
public partial class ColorPaletteResources
{
private bool _hasAccentColor;
private Color _accentColor;
private Color _accentColorDark1, _accentColorDark2, _accentColorDark3;
private Color _accentColorLight1, _accentColorLight2, _accentColorLight3;
public static readonly DirectProperty<ColorPaletteResources, Color> AccentProperty
= AvaloniaProperty.RegisterDirect<ColorPaletteResources, Color>(nameof(Accent), r => r.Accent, (r, v) => r.Accent = v);
/// <summary>
/// Gets or sets the Accent color value.
/// </summary>
public Color Accent
{
get => _accentColor;
set => SetAndRaise(AccentProperty, ref _accentColor, value);
}
/// <summary>
/// Gets or sets the AltHigh color value.
/// </summary>
public Color AltHigh { get => GetColor("SystemAltHighColor"); set => SetColor("SystemAltHighColor", value); }
/// <summary>
/// Gets or sets the AltLow color value.
/// </summary>
public Color AltLow { get => GetColor("SystemAltLowColor"); set => SetColor("SystemAltLowColor", value); }
/// <summary>
/// Gets or sets the AltMedium color value.
/// </summary>
public Color AltMedium { get => GetColor("SystemAltMediumColor"); set => SetColor("SystemAltMediumColor", value); }
/// <summary>
/// Gets or sets the AltMediumHigh color value.
/// </summary>
public Color AltMediumHigh { get => GetColor("SystemAltMediumHighColor"); set => SetColor("SystemAltMediumHighColor", value); }
/// <summary>
/// Gets or sets the AltMediumLow color value.
/// </summary>
public Color AltMediumLow { get => GetColor("SystemAltMediumLowColor"); set => SetColor("SystemAltMediumLowColor", value); }
/// <summary>
/// Gets or sets the BaseHigh color value.
/// </summary>
public Color BaseHigh { get => GetColor("SystemBaseHighColor"); set => SetColor("SystemBaseHighColor", value); }
/// <summary>
/// Gets or sets the BaseLow color value.
/// </summary>
public Color BaseLow { get => GetColor("SystemBaseLowColor"); set => SetColor("SystemBaseLowColor", value); }
/// <summary>
/// Gets or sets the BaseMedium color value.
/// </summary>
public Color BaseMedium { get => GetColor("SystemBaseMediumColor"); set => SetColor("SystemBaseMediumColor", value); }
/// <summary>
/// Gets or sets the BaseMediumHigh color value.
/// </summary>
public Color BaseMediumHigh { get => GetColor("SystemBaseMediumHighColor"); set => SetColor("SystemBaseMediumHighColor", value); }
/// <summary>
/// Gets or sets the BaseMediumLow color value.
/// </summary>
public Color BaseMediumLow { get => GetColor("SystemBaseMediumLowColor"); set => SetColor("SystemBaseMediumLowColor", value); }
/// <summary>
/// Gets or sets the ChromeAltLow color value.
/// </summary>
public Color ChromeAltLow { get => GetColor("SystemChromeAltLowColor"); set => SetColor("SystemChromeAltLowColor", value); }
/// <summary>
/// Gets or sets the ChromeBlackHigh color value.
/// </summary>
public Color ChromeBlackHigh { get => GetColor("SystemChromeBlackHighColor"); set => SetColor("SystemChromeBlackHighColor", value); }
/// <summary>
/// Gets or sets the ChromeBlackLow color value.
/// </summary>
public Color ChromeBlackLow { get => GetColor("SystemChromeBlackLowColor"); set => SetColor("SystemChromeBlackLowColor", value); }
/// <summary>
/// Gets or sets the ChromeBlackMedium color value.
/// </summary>
public Color ChromeBlackMedium { get => GetColor("SystemChromeBlackMediumColor"); set => SetColor("SystemChromeBlackMediumColor", value); }
/// <summary>
/// Gets or sets the ChromeBlackMediumLow color value.
/// </summary>
public Color ChromeBlackMediumLow { get => GetColor("SystemChromeBlackMediumLowColor"); set => SetColor("SystemChromeBlackMediumLowColor", value); }
/// <summary>
/// Gets or sets the ChromeDisabledHigh color value.
/// </summary>
public Color ChromeDisabledHigh { get => GetColor("SystemChromeDisabledHighColor"); set => SetColor("SystemChromeDisabledHighColor", value); }
/// <summary>
/// Gets or sets the ChromeDisabledLow color value.
/// </summary>
public Color ChromeDisabledLow { get => GetColor("SystemChromeDisabledLowColor"); set => SetColor("SystemChromeDisabledLowColor", value); }
/// <summary>
/// Gets or sets the ChromeGray color value.
/// </summary>
public Color ChromeGray { get => GetColor("SystemChromeGrayColor"); set => SetColor("SystemChromeGrayColor", value); }
/// <summary>
/// Gets or sets the ChromeHigh color value.
/// </summary>
public Color ChromeHigh { get => GetColor("SystemChromeHighColor"); set => SetColor("SystemChromeHighColor", value); }
/// <summary>
/// Gets or sets the ChromeLow color value.
/// </summary>
public Color ChromeLow { get => GetColor("SystemChromeLowColor"); set => SetColor("SystemChromeLowColor", value); }
/// <summary>
/// Gets or sets the ChromeMedium color value.
/// </summary>
public Color ChromeMedium { get => GetColor("SystemChromeMediumColor"); set => SetColor("SystemChromeMediumColor", value); }
/// <summary>
/// Gets or sets the ChromeMediumLow color value.
/// </summary>
public Color ChromeMediumLow { get => GetColor("SystemChromeMediumLowColor"); set => SetColor("SystemChromeMediumLowColor", value); }
/// <summary>
/// Gets or sets the ChromeWhite color value.
/// </summary>
public Color ChromeWhite { get => GetColor("SystemChromeWhiteColor"); set => SetColor("SystemChromeWhiteColor", value); }
/// <summary>
/// Gets or sets the ErrorText color value.
/// </summary>
public Color ErrorText { get => GetColor("SystemErrorTextColor"); set => SetColor("SystemErrorTextColor", value); }
/// <summary>
/// Gets or sets the ListLow color value.
/// </summary>
public Color ListLow { get => GetColor("SystemListLowColor"); set => SetColor("SystemListLowColor", value); }
/// <summary>
/// Gets or sets the ListMedium color value.
/// </summary>
public Color ListMedium { get => GetColor("SystemListMediumColor"); set => SetColor("SystemListMediumColor", value); }
/// <summary>
/// Gets or sets the RegionColor color value.
/// </summary>
public Color RegionColor { get => GetColor("SystemRegionColor"); set => SetColor("SystemRegionColor", value); }
}

118
src/Avalonia.Themes.Fluent/ColorPaletteResources.cs

@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Themes.Fluent.Accents;
namespace Avalonia.Themes.Fluent;
/// <summary>
/// Represents a specialized resource dictionary that contains color resources used by FluentTheme elements.
/// </summary>
/// <remarks>
/// This class can only be used in <see cref="FluentTheme.Palettes"/>.
/// </remarks>
public partial class ColorPaletteResources : AvaloniaObject, IResourceNode
{
private readonly Dictionary<string, Color> _colors = new(StringComparer.InvariantCulture);
public bool HasResources => _hasAccentColor || _colors.Count > 0;
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{
if (key is string strKey)
{
if (strKey.Equals(SystemAccentColors.AccentKey, StringComparison.InvariantCulture))
{
value = _accentColor;
return _hasAccentColor;
}
if (strKey.Equals(SystemAccentColors.AccentDark1Key, StringComparison.InvariantCulture))
{
value = _accentColorDark1;
return _hasAccentColor;
}
if (strKey.Equals(SystemAccentColors.AccentDark2Key, StringComparison.InvariantCulture))
{
value = _accentColorDark2;
return _hasAccentColor;
}
if (strKey.Equals(SystemAccentColors.AccentDark3Key, StringComparison.InvariantCulture))
{
value = _accentColorDark3;
return _hasAccentColor;
}
if (strKey.Equals(SystemAccentColors.AccentLight1Key, StringComparison.InvariantCulture))
{
value = _accentColorLight1;
return _hasAccentColor;
}
if (strKey.Equals(SystemAccentColors.AccentLight2Key, StringComparison.InvariantCulture))
{
value = _accentColorLight2;
return _hasAccentColor;
}
if (strKey.Equals(SystemAccentColors.AccentLight3Key, StringComparison.InvariantCulture))
{
value = _accentColorLight3;
return _hasAccentColor;
}
if (_colors.TryGetValue(strKey, out var color))
{
value = color;
return true;
}
}
value = null;
return false;
}
private Color GetColor(string key)
{
if (_colors.TryGetValue(key, out var color))
{
return color;
}
return default;
}
private void SetColor(string key, Color value)
{
if (value == default)
{
_colors.Remove(key);
}
else
{
_colors[key] = value;
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == AccentProperty)
{
_hasAccentColor = _accentColor != default;
if (_hasAccentColor)
{
(_accentColorDark1, _accentColorDark2, _accentColorDark3,
_accentColorLight1, _accentColorLight2, _accentColorLight3) =
SystemAccentColors.CalculateAccentShades(_accentColor);
}
}
}
}

65
src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs

@ -0,0 +1,65 @@
using System;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Styling;
namespace Avalonia.Themes.Fluent;
internal class ColorPaletteResourcesCollection : AvaloniaDictionary<ThemeVariant, ColorPaletteResources>, IResourceProvider
{
public ColorPaletteResourcesCollection() : base(2)
{
this.ForEachItem(
(_, x) =>
{
if (Owner is not null)
{
x.PropertyChanged += Palette_PropertyChanged;
}
},
(_, x) =>
{
if (Owner is not null)
{
x.PropertyChanged -= Palette_PropertyChanged;
}
},
() => throw new NotSupportedException("Dictionary reset not supported"));
}
public bool HasResources => Count > 0;
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{
theme ??= ThemeVariant.Default;
if (base.TryGetValue(theme, out var paletteResources)
&& paletteResources.TryGetResource(key, theme, out value))
{
return true;
}
value = null;
return false;
}
public IResourceHost? Owner { get; private set; }
public event EventHandler? OwnerChanged;
public void AddOwner(IResourceHost owner)
{
Owner = owner;
OwnerChanged?.Invoke(this, EventArgs.Empty);
}
public void RemoveOwner(IResourceHost owner)
{
Owner = null;
OwnerChanged?.Invoke(this, EventArgs.Empty);
}
private void Palette_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ColorPaletteResources.AccentProperty)
{
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}
}

2
src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml

@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTheme x:Key="{x:Type EmbeddableControlRoot}" TargetType="EmbeddableControlRoot">
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="Background" Value="{DynamicResource SystemRegionBrush}"/>
<Setter Property="TopLevel.SystemBarColor" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/>
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />

2
src/Avalonia.Themes.Fluent/Controls/Window.xaml

@ -1,7 +1,7 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTheme x:Key="{x:Type Window}" TargetType="Window">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="Background" Value="{DynamicResource SystemRegionBrush}"/>
<Setter Property="TransparencyBackgroundFallback" Value="{DynamicResource SystemControlBackgroundAltHighBrush}" />
<Setter Property="TopLevel.SystemBarColor" Value="{DynamicResource SystemControlBackgroundAltHighBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>

14
src/Avalonia.Themes.Fluent/FluentTheme.xaml

@ -1,11 +1,19 @@
<Styles x:Class="Avalonia.Themes.Fluent.FluentTheme"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fluent="using:Avalonia.Themes.Fluent"
xmlns:accents="clr-namespace:Avalonia.Themes.Fluent.Accents">
<Styles.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<MergeResourceInclude Source="/Accents/AccentColors.xaml" />
<MergeResourceInclude Source="/Accents/Base.xaml" />
<!-- Keep custom palettes higher priority than default BaseColorsPalette and SystemAccentColors
As that's an only place for user to redefine palette in a good way -->
<ResourceInclude Source="/Accents/BaseColorsPalette.xaml" />
<accents:SystemAccentColors />
<fluent:ColorPaletteResourcesCollection />
<!-- Resources and brushes will be merged into current dictionary for slightly better performance and possible optimizations -->
<MergeResourceInclude Source="/Accents/BaseResources.xaml" />
<MergeResourceInclude Source="/Accents/FluentControlResources.xaml" />
</ResourceDictionary.MergedDictionaries>

6
src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
@ -31,6 +32,9 @@ namespace Avalonia.Themes.Fluent
EnsureCompactStyles();
Palettes = Resources.MergedDictionaries.OfType<ColorPaletteResourcesCollection>().FirstOrDefault()
?? throw new InvalidOperationException("FluentTheme was initialized with missing ColorPaletteResourcesCollection.");
object GetAndRemove(string key)
{
var val = Resources[key]
@ -52,6 +56,8 @@ namespace Avalonia.Themes.Fluent
set => SetValue(DensityStyleProperty, value);
}
public IDictionary<ThemeVariant, ColorPaletteResources> Palettes { get; }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

6
src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj → src/Headless/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@ -9,5 +10,8 @@
<PackageReference Include="Quamotion.RemoteViewing" Version="1.1.21" />
</ItemGroup>
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\..\build\ApiDiff.props" />
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\TrimmingEnable.props" />
<Import Project="..\..\..\build\NullableEnable.props" />
</Project>

36
src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs → src/Headless/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs

@ -2,6 +2,7 @@
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Threading;
using RemoteViewing.Vnc;
using RemoteViewing.Vnc.Server;
@ -10,22 +11,28 @@ namespace Avalonia.Headless.Vnc
{
public class HeadlessVncFramebufferSource : IVncFramebufferSource
{
public IHeadlessWindow Window { get; set; }
public Window Window { get; set; }
private object _lock = new object();
public VncFramebuffer _framebuffer = new VncFramebuffer("Avalonia", 1, 1, VncPixelFormat.RGB32);
private VncButton _previousButtons;
public HeadlessVncFramebufferSource(VncServerSession session, Window window)
{
Window = (IHeadlessWindow)window.PlatformImpl;
Window = window;
session.PointerChanged += (_, args) =>
{
var pt = new Point(args.X, args.Y);
var buttons = (VncButton)args.PressedButtons;
int TranslateButton(VncButton vncButton) =>
vncButton == VncButton.Left ? 0 : vncButton == VncButton.Right ? 1 : 2;
MouseButton TranslateButton(VncButton vncButton) =>
vncButton switch
{
VncButton.Left => MouseButton.Left,
VncButton.Middle => MouseButton.Middle,
VncButton.Right => MouseButton.Right,
_ => MouseButton.None
};
var modifiers = (RawInputModifiers)(((int)buttons & 7) << 4);
@ -58,34 +65,25 @@ namespace Avalonia.Headless.Vnc
private static VncButton[] CheckedButtons = new[] {VncButton.Left, VncButton.Middle, VncButton.Right};
public VncFramebuffer Capture()
public unsafe VncFramebuffer Capture()
{
lock (_lock)
{
using (var bmpRef = Window.GetLastRenderedFrame())
{
if (bmpRef?.Item == null)
if (bmpRef == null)
return _framebuffer;
var bmp = bmpRef.Item;
var bmp = bmpRef;
if (bmp.PixelSize.Width != _framebuffer.Width || bmp.PixelSize.Height != _framebuffer.Height)
{
_framebuffer = new VncFramebuffer("Avalonia", bmp.PixelSize.Width, bmp.PixelSize.Height,
VncPixelFormat.RGB32);
}
using (var fb = bmp.Lock())
var buffer = _framebuffer.GetBuffer();
fixed (byte* bufferPtr = buffer)
{
var buf = _framebuffer.GetBuffer();
if (_framebuffer.Stride == fb.RowBytes)
Marshal.Copy(fb.Address, buf, 0, buf.Length);
else
for (var y = 0; y < fb.Size.Height; y++)
{
var sourceStart = fb.RowBytes * y;
var dstStart = _framebuffer.Stride * y;
var row = fb.Size.Width * 4;
Marshal.Copy(new IntPtr(sourceStart + fb.Address.ToInt64()), buf, dstStart, row);
}
bmp.CopyPixels(new PixelRect(default, bmp.PixelSize), (IntPtr)bufferPtr, buffer.Length, _framebuffer.Stride);
}
}
}

5
src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs → src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs

@ -1,3 +1,4 @@
using System;
using System.Net;
using System.Net.Sockets;
using Avalonia.Controls;
@ -25,7 +26,7 @@ namespace Avalonia
})
.AfterSetup(_ =>
{
var lt = ((IClassicDesktopStyleApplicationLifetime)builder.Instance.ApplicationLifetime);
var lt = ((IClassicDesktopStyleApplicationLifetime)builder.Instance!.ApplicationLifetime!);
lt.Startup += async delegate
{
while (true)
@ -38,7 +39,7 @@ namespace Avalonia
var session = new VncServerSession();
session.SetFramebufferSource(new HeadlessVncFramebufferSource(
session, lt.MainWindow));
session, lt.MainWindow ?? throw new InvalidOperationException("MainWindow wasn't initialized")));
session.Connect(client.GetStream(), options);
}

19
src/Headless/Avalonia.Headless.XUnit/Avalonia.Headless.XUnit.csproj

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.core" Version="2.4.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Headless\Avalonia.Headless.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\ApiDiff.props" />
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\NullableEnable.props" />
</Project>

35
src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFramework.cs

@ -0,0 +1,35 @@
using System.Reflection;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Avalonia.Headless.XUnit;
internal class AvaloniaTestFramework<TAppBuilderEntry> : XunitTestFramework
{
public AvaloniaTestFramework(IMessageSink messageSink) : base(messageSink)
{
}
protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
=> new Executor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
private class Executor : XunitTestFrameworkExecutor
{
public Executor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider,
IMessageSink diagnosticMessageSink) : base(assemblyName, sourceInformationProvider,
diagnosticMessageSink)
{
}
protected override async void RunTestCases(IEnumerable<IXunitTestCase> testCases,
IMessageSink executionMessageSink,
ITestFrameworkExecutionOptions executionOptions)
{
executionOptions.SetValue("xunit.execution.DisableParallelization", false);
using (var assemblyRunner = new AvaloniaTestRunner<TAppBuilderEntry>(
TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink,
executionOptions)) await assemblyRunner.RunAsync();
}
}
}

45
src/Headless/Avalonia.Headless.XUnit/AvaloniaTestFrameworkAttribute.cs

@ -0,0 +1,45 @@
using System.Diagnostics.CodeAnalysis;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Avalonia.Headless.XUnit;
/// <summary>
/// Sets up global avalonia test framework using avalonia application builder passed as a parameter.
/// </summary>
[TestFrameworkDiscoverer("Avalonia.Headless.XUnit.AvaloniaTestFrameworkTypeDiscoverer", "Avalonia.Headless.XUnit")]
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public sealed class AvaloniaTestFrameworkAttribute : Attribute, ITestFrameworkAttribute
{
/// <summary>
/// Creates instance of <see cref="AvaloniaTestFrameworkAttribute"/>.
/// </summary>
/// <param name="appBuilderEntryPointType">
/// Parameter from which <see cref="AppBuilder"/> should be created.
/// It either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application.
/// </param>
public AvaloniaTestFrameworkAttribute(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type appBuilderEntryPointType) { }
}
/// <summary>
/// Discoverer implementation for the Avalonia testing framework.
/// </summary>
public class AvaloniaTestFrameworkTypeDiscoverer : ITestFrameworkTypeDiscoverer
{
/// <summary>
/// Creates instance of <see cref="AvaloniaTestFrameworkTypeDiscoverer"/>.
/// </summary>
public AvaloniaTestFrameworkTypeDiscoverer(IMessageSink _)
{
}
/// <inheritdoc/>
public Type GetTestFrameworkType(IAttributeInfo attribute)
{
var builderType = attribute.GetConstructorArguments().First() as Type
?? throw new InvalidOperationException("AppBuilderEntryPointType parameter must be defined on the AvaloniaTestFrameworkAttribute attribute.");
return typeof(AvaloniaTestFramework<>).MakeGenericType(builderType);
}
}

61
src/Headless/Avalonia.Headless.XUnit/AvaloniaTestRunner.cs

@ -0,0 +1,61 @@
using Avalonia.Threading;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Avalonia.Headless.XUnit;
internal class AvaloniaTestRunner<TAppBuilderEntry> : XunitTestAssemblyRunner
{
private CancellationTokenSource? _cancellationTokenSource;
public AvaloniaTestRunner(ITestAssembly testAssembly, IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink, IMessageSink executionMessageSink,
ITestFrameworkExecutionOptions executionOptions) : base(testAssembly, testCases, diagnosticMessageSink,
executionMessageSink, executionOptions)
{
}
protected override void SetupSyncContext(int maxParallelThreads)
{
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = new CancellationTokenSource();
SynchronizationContext.SetSynchronizationContext(InitNewApplicationContext(_cancellationTokenSource.Token).Result);
}
public override void Dispose()
{
_cancellationTokenSource?.Cancel();
base.Dispose();
}
internal static Task<SynchronizationContext> InitNewApplicationContext(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<SynchronizationContext>();
new Thread(() =>
{
try
{
var appBuilder = AppBuilder.Configure(typeof(TAppBuilderEntry));
// If windowing subsystem wasn't initialized by user, force headless with default parameters.
if (appBuilder.WindowingSubsystemName != "Headless")
{
appBuilder = appBuilder.UseHeadless(new AvaloniaHeadlessPlatformOptions());
}
appBuilder.SetupWithoutStarting();
tcs.SetResult(SynchronizationContext.Current!);
}
catch (Exception e)
{
tcs.SetException(e);
}
Dispatcher.UIThread.MainLoop(cancellationToken);
}) { IsBackground = true }.Start();
return tcs.Task;
}
}

18
src/Headless/Avalonia.Headless/Avalonia.Headless.csproj

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\ApiDiff.props" />
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\TrimmingEnable.props" />
<Import Project="..\..\..\build\NullableEnable.props" />
<ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.Headless.Vnc, PublicKey=$(AvaloniaPublicKey)" />
</ItemGroup>
</Project>

20
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs → src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -1,8 +1,7 @@
using System;
using System.Diagnostics;
using Avalonia.Reactive;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Reactive;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
@ -14,11 +13,12 @@ namespace Avalonia.Headless
{
public static class AvaloniaHeadlessPlatform
{
internal static Compositor Compositor { get; private set; }
class RenderTimer : DefaultRenderTimer
internal static Compositor? Compositor { get; private set; }
private class RenderTimer : DefaultRenderTimer
{
private readonly int _framesPerSecond;
private Action _forceTick;
private Action? _forceTick;
protected override IDisposable StartCore(Action<TimeSpan> tick)
{
bool cancelled = false;
@ -48,7 +48,7 @@ namespace Avalonia.Headless
public void ForceTick() => _forceTick?.Invoke();
}
class HeadlessWindowingPlatform : IWindowingPlatform
private class HeadlessWindowingPlatform : IWindowingPlatform
{
public IWindowImpl CreateWindow() => new HeadlessWindowImpl(false);
@ -56,7 +56,7 @@ namespace Avalonia.Headless
public IPopupImpl CreatePopup() => new HeadlessWindowImpl(true);
public ITrayIconImpl CreateTrayIcon() => null;
public ITrayIconImpl? CreateTrayIcon() => null;
}
internal static void Initialize(AvaloniaHeadlessPlatformOptions opts)
@ -75,7 +75,11 @@ namespace Avalonia.Headless
Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(), null);
}
/// <summary>
/// Forces renderer to process a rendering timer tick.
/// Use this method before calling <see cref="HeadlessWindowExtensions.GetLastRenderedFrame"/>.
/// </summary>
/// <param name="count">Count of frames to be ticked on the timer.</param>
public static void ForceRenderTimerTick(int count = 1)
{
var timer = AvaloniaLocator.Current.GetService<IRenderTimer>() as RenderTimer;

46
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs → src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using System.Runtime.InteropServices;
@ -18,12 +19,13 @@ namespace Avalonia.Headless
public static void Initialize()
{
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToConstant(new HeadlessPlatformRenderInterface());
.Bind<IPlatformRenderInterface>().ToConstant(new HeadlessPlatformRenderInterface())
.Bind<IFontManagerImpl>().ToConstant(new HeadlessFontManagerStub());
}
public IEnumerable<string> InstalledFontNames { get; } = new[] { "Tahoma" };
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this;
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext? graphicsContext) => this;
public bool SupportsIndividualRoundRects => false;
@ -52,7 +54,7 @@ namespace Avalonia.Headless
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget();
public bool IsLost => false;
public object TryGetFeature(Type featureType) => null;
public object? TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{
@ -130,7 +132,7 @@ namespace Avalonia.Headless
return new HeadlessGlyphRunStub();
}
class HeadlessGlyphRunStub : IGlyphRunImpl
private class HeadlessGlyphRunStub : IGlyphRunImpl
{
public Rect Bounds => new Rect(new Size(8, 12));
@ -144,7 +146,7 @@ namespace Avalonia.Headless
=> Array.Empty<float>();
}
class HeadlessGeometryStub : IGeometryImpl
private class HeadlessGeometryStub : IGeometryImpl
{
public HeadlessGeometryStub(Rect bounds)
{
@ -157,7 +159,7 @@ namespace Avalonia.Headless
public virtual bool FillContains(Point point) => Bounds.Contains(point);
public Rect GetRenderBounds(IPen pen)
public Rect GetRenderBounds(IPen? pen)
{
if(pen is null)
{
@ -167,7 +169,7 @@ namespace Avalonia.Headless
return Bounds.Inflate(pen.Thickness / 2);
}
public bool StrokeContains(IPen pen, Point point)
public bool StrokeContains(IPen? pen, Point point)
{
return false;
}
@ -191,21 +193,21 @@ namespace Avalonia.Headless
return false;
}
public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, [NotNullWhen(true)] out IGeometryImpl? segmentGeometry)
{
segmentGeometry = null;
return false;
}
}
class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl
private class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl
{
public HeadlessTransformedGeometryStub(IGeometryImpl b, Matrix transform) : this(Fix(b, transform))
{
}
static (IGeometryImpl, Matrix, Rect) Fix(IGeometryImpl b, Matrix transform)
private static (IGeometryImpl, Matrix, Rect) Fix(IGeometryImpl b, Matrix transform)
{
if (b is HeadlessTransformedGeometryStub transformed)
{
@ -227,7 +229,7 @@ namespace Avalonia.Headless
public Matrix Transform { get; }
}
class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl
private class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl
{
public HeadlessStreamingGeometryStub() : base(default)
{
@ -243,7 +245,7 @@ namespace Avalonia.Headless
return new HeadlessStreamingGeometryContextStub(this);
}
class HeadlessStreamingGeometryContextStub : IStreamGeometryContextImpl
private class HeadlessStreamingGeometryContextStub : IStreamGeometryContextImpl
{
private readonly HeadlessStreamingGeometryStub _parent;
private double _x1, _y1, _x2, _y2;
@ -252,7 +254,7 @@ namespace Avalonia.Headless
_parent = parent;
}
void Track(Point pt)
private void Track(Point pt)
{
if (_x1 > pt.X)
_x1 = pt.X;
@ -301,7 +303,7 @@ namespace Avalonia.Headless
}
}
class HeadlessBitmapStub : IBitmapImpl, IDrawingContextLayerImpl, IWriteableBitmapImpl
private class HeadlessBitmapStub : IBitmapImpl, IDrawingContextLayerImpl, IWriteableBitmapImpl
{
public Size Size { get; }
@ -363,7 +365,7 @@ namespace Avalonia.Headless
}
}
class HeadlessDrawingContextStub : IDrawingContextImpl
private class HeadlessDrawingContextStub : IDrawingContextImpl
{
public void Dispose()
{
@ -437,16 +439,16 @@ namespace Avalonia.Headless
}
public object GetFeature(Type t)
public object? GetFeature(Type t)
{
return null;
}
public void DrawLine(IPen pen, Point p1, Point p2)
public void DrawLine(IPen? pen, Point p1, Point p2)
{
}
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
{
}
@ -464,16 +466,16 @@ namespace Avalonia.Headless
}
public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadow = default)
public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadow = default)
{
}
public void DrawEllipse(IBrush brush, IPen pen, Rect rect)
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
{
}
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
{
}
@ -484,7 +486,7 @@ namespace Avalonia.Headless
}
}
class HeadlessRenderTarget : IRenderTarget
private class HeadlessRenderTarget : IRenderTarget
{
public void Dispose()
{

73
src/Avalonia.Headless/HeadlessPlatformStubs.cs → src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -18,17 +18,17 @@ using Avalonia.Utilities;
namespace Avalonia.Headless
{
class HeadlessClipboardStub : IClipboard
internal class HeadlessClipboardStub : IClipboard
{
private string _text;
private IDataObject _data;
private string? _text;
private IDataObject? _data;
public Task<string> GetTextAsync()
public Task<string?> GetTextAsync()
{
return Task.Run(() => _text);
}
public Task SetTextAsync(string text)
public Task SetTextAsync(string? text)
{
return Task.Run(() => _text = text);
}
@ -45,16 +45,29 @@ namespace Avalonia.Headless
public Task<string[]> GetFormatsAsync()
{
throw new NotImplementedException();
return Task.Run(() =>
{
if (_data is not null)
{
return _data.GetDataFormats().ToArray();
}
if (_text is not null)
{
return new[] { DataFormats.Text };
}
return Array.Empty<string>();
});
}
public async Task<object> GetDataAsync(string format)
public async Task<object?> GetDataAsync(string format)
{
return await Task.Run(() => _data);
}
}
class HeadlessCursorFactoryStub : ICursorFactory
internal class HeadlessCursorFactoryStub : ICursorFactory
{
public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub();
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub();
@ -65,7 +78,7 @@ namespace Avalonia.Headless
}
}
class HeadlessGlyphTypefaceImpl : IGlyphTypeface
internal class HeadlessGlyphTypefaceImpl : IGlyphTypeface
{
public FontMetrics Metrics => new FontMetrics
{
@ -125,7 +138,7 @@ namespace Avalonia.Headless
public bool TryGetTable(uint tag, out byte[] table)
{
table = null;
table = null!;
return false;
}
@ -141,7 +154,7 @@ namespace Avalonia.Headless
}
}
class HeadlessTextShaperStub : ITextShaperImpl
internal class HeadlessTextShaperStub : ITextShaperImpl
{
public ShapedBuffer ShapeText(ReadOnlyMemory<char> text, TextShaperOptions options)
{
@ -153,7 +166,7 @@ namespace Avalonia.Headless
}
}
class HeadlessFontManagerStub : IFontManagerImpl
internal class HeadlessFontManagerStub : IFontManagerImpl
{
public string GetDefaultFontFamilyName()
{
@ -179,17 +192,16 @@ namespace Avalonia.Headless
return true;
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo culture, out Typeface typeface)
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, out Typeface typeface)
{
typeface = new Typeface("Arial", fontStyle, fontWeight, fontStretch);
return true;
}
}
class HeadlessIconLoaderStub : IPlatformIconLoader
internal class HeadlessIconLoaderStub : IPlatformIconLoader
{
class IconStub : IWindowIconImpl
private class IconStub : IWindowIconImpl
{
public void Save(Stream outputStream)
{
@ -212,7 +224,7 @@ namespace Avalonia.Headless
}
}
class HeadlessScreensStub : IScreenImpl
internal class HeadlessScreensStub : IScreenImpl
{
public int ScreenCount { get; } = 1;
@ -222,40 +234,19 @@ namespace Avalonia.Headless
new PixelRect(0, 0, 1920, 1280), true),
};
public Screen ScreenFromPoint(PixelPoint point)
public Screen? ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen ScreenFromRect(PixelRect rect)
public Screen? ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen ScreenFromWindow(IWindowBaseImpl window)
public Screen? ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
}
internal class NoopStorageProvider : BclStorageProvider
{
public override bool CanOpen => false;
public override Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
{
return Task.FromResult<IReadOnlyList<IStorageFile>>(Array.Empty<IStorageFile>());
}
public override bool CanSave => false;
public override Task<IStorageFile> SaveFilePickerAsync(FilePickerSaveOptions options)
{
return Task.FromResult<IStorageFile>(null);
}
public override bool CanPickFolder => false;
public override Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
{
return Task.FromResult<IReadOnlyList<IStorageFolder>>(Array.Empty<IStorageFolder>());
}
}
}

101
src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs

@ -0,0 +1,101 @@
using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Headless;
/// <summary>
/// Set of extension methods to simplify usage of Avalonia.Headless platform.
/// </summary>
public static class HeadlessWindowExtensions
{
/// <summary>
/// Triggers a renderer timer tick and captures last rendered frame.
/// </summary>
/// <returns>Bitmap with last rendered frame. Null, if nothing was rendered.</returns>
public static Bitmap? CaptureRenderedFrame(this TopLevel topLevel)
{
Dispatcher.UIThread.RunJobs();
AvaloniaHeadlessPlatform.ForceRenderTimerTick();
return topLevel.GetLastRenderedFrame();
}
/// <summary>
/// Reads last rendered frame.
/// Note, in order to trigger rendering timer, call <see cref="AvaloniaHeadlessPlatform.ForceRenderTimerTick"/> method.
/// </summary>
/// <returns>Bitmap with last rendered frame. Null, if nothing was rendered.</returns>
public static Bitmap? GetLastRenderedFrame(this TopLevel topLevel)
{
if (AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() is HeadlessPlatformRenderInterface)
{
throw new NotSupportedException(
"To capture a rendered frame, make sure that headless application was initialized with '.UseSkia()' and disabled 'UseHeadlessDrawing' in the 'AvaloniaHeadlessPlatformOptions'.");
}
return GetImpl(topLevel).GetLastRenderedFrame();
}
/// <summary>
/// Simulates keyboard press on the headless window/toplevel.
/// </summary>
public static void KeyPress(this TopLevel topLevel, Key key, RawInputModifiers modifiers) =>
RunJobsAndGetImpl(topLevel).KeyPress(key, modifiers);
/// <summary>
/// Simulates keyboard release on the headless window/toplevel.
/// </summary>
public static void KeyRelease(this TopLevel topLevel, Key key, RawInputModifiers modifiers) =>
RunJobsAndGetImpl(topLevel).KeyRelease(key, modifiers);
/// <summary>
/// Simulates mouse down on the headless window/toplevel.
/// </summary>
public static void MouseDown(this TopLevel topLevel, Point point, MouseButton button,
RawInputModifiers modifiers = RawInputModifiers.None) =>
RunJobsAndGetImpl(topLevel).MouseDown(point, button, modifiers);
/// <summary>
/// Simulates mouse move on the headless window/toplevel.
/// </summary>
public static void MouseMove(this TopLevel topLevel, Point point,
RawInputModifiers modifiers = RawInputModifiers.None) =>
RunJobsAndGetImpl(topLevel).MouseMove(point, modifiers);
/// <summary>
/// Simulates mouse up on the headless window/toplevel.
/// </summary>
public static void MouseUp(this TopLevel topLevel, Point point, MouseButton button,
RawInputModifiers modifiers = RawInputModifiers.None) =>
RunJobsAndGetImpl(topLevel).MouseUp(point, button, modifiers);
/// <summary>
/// Simulates mouse wheel on the headless window/toplevel.
/// </summary>
public static void MouseWheel(this TopLevel topLevel, Point point, Vector delta,
RawInputModifiers modifiers = RawInputModifiers.None) =>
RunJobsAndGetImpl(topLevel).MouseWheel(point, delta, modifiers);
/// <summary>
/// Simulates drag'n'drop target on the headless window/toplevel.
/// </summary>
public static void DragDrop(this TopLevel topLevel, Point point, RawDragEventType type, IDataObject data,
DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None) =>
RunJobsAndGetImpl(topLevel).DragDrop(point, type, data, effects, modifiers);
private static IHeadlessWindow RunJobsAndGetImpl(this TopLevel topLevel)
{
Dispatcher.UIThread.RunJobs();
return GetImpl(topLevel);
}
private static IHeadlessWindow GetImpl(this TopLevel topLevel)
{
return topLevel.PlatformImpl as IHeadlessWindow ??
throw new InvalidOperationException("TopLevel must be a headless window.");
}
}

124
src/Avalonia.Headless/HeadlessWindowImpl.cs → src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs

@ -17,20 +17,20 @@ using Avalonia.Utilities;
namespace Avalonia.Headless
{
class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow
internal class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow
{
private IKeyboardDevice _keyboard;
private Stopwatch _st = Stopwatch.StartNew();
private Pointer _mousePointer;
private WriteableBitmap _lastRenderedFrame;
private object _sync = new object();
private readonly IKeyboardDevice _keyboard;
private readonly Stopwatch _st = Stopwatch.StartNew();
private readonly Pointer _mousePointer;
private WriteableBitmap? _lastRenderedFrame;
private readonly object _sync = new object();
public bool IsPopup { get; }
public HeadlessWindowImpl(bool isPopup)
{
IsPopup = isPopup;
Surfaces = new object[] { this };
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
_keyboard = AvaloniaLocator.Current.GetRequiredService<IKeyboardDevice>();
_mousePointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
MouseDevice = new MouseDevice(_mousePointer);
ClientSize = new Size(1024, 768);
@ -48,13 +48,13 @@ namespace Avalonia.Headless
public double RenderScaling { get; } = 1;
public double DesktopScaling => RenderScaling;
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size, WindowResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action<RawInputEventArgs>? Input { get; set; }
public Action<Rect>? Paint { get; set; }
public Action<Size, WindowResizeReason>? Resized { get; set; }
public Action<double>? ScalingChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) =>
new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor, () => Surfaces);
new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor!, () => Surfaces);
public void Invalidate(Rect rect)
{
@ -65,18 +65,18 @@ namespace Avalonia.Headless
InputRoot = inputRoot;
}
public IInputRoot InputRoot { get; set; }
public IInputRoot? InputRoot { get; set; }
public Point PointToClient(PixelPoint point) => point.ToPoint(RenderScaling);
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling);
public void SetCursor(ICursorImpl cursor)
public void SetCursor(ICursorImpl? cursor)
{
}
public Action Closed { get; set; }
public Action? Closed { get; set; }
public IMouseDevice MouseDevice { get; }
public void Show(bool activate, bool isDialog)
@ -101,14 +101,14 @@ namespace Avalonia.Headless
}
public PixelPoint Position { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public Action<PixelPoint>? PositionChanged { get; set; }
public void Activate()
{
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
}
public Action Deactivated { get; set; }
public Action Activated { get; set; }
public Action? Deactivated { get; set; }
public Action? Activated { get; set; }
public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB");
public Size MaxClientSize { get; } = new Size(1920, 1280);
public void Resize(Size clientSize, WindowResizeReason reason)
@ -123,7 +123,7 @@ namespace Avalonia.Headless
});
}
void DoResize(Size clientSize)
private void DoResize(Size clientSize)
{
// Uncomment this check and experience a weird bug in layout engine
if (ClientSize != clientSize)
@ -145,8 +145,8 @@ namespace Avalonia.Headless
public IScreenImpl Screen { get; } = new HeadlessScreensStub();
public WindowState WindowState { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public void SetTitle(string title)
public Action<WindowState>? WindowStateChanged { get; set; }
public void SetTitle(string? title)
{
}
@ -156,7 +156,7 @@ namespace Avalonia.Headless
}
public void SetIcon(IWindowIconImpl icon)
public void SetIcon(IWindowIconImpl? icon)
{
}
@ -171,9 +171,9 @@ namespace Avalonia.Headless
}
public Func<WindowCloseReason, bool> Closing { get; set; }
public Func<WindowCloseReason, bool>? Closing { get; set; }
class FramebufferProxy : ILockedFramebuffer
private class FramebufferProxy : ILockedFramebuffer
{
private readonly ILockedFramebuffer _fb;
private readonly Action _onDispose;
@ -214,28 +214,37 @@ namespace Avalonia.Headless
});
}
public IRef<IWriteableBitmapImpl> GetLastRenderedFrame()
public Bitmap? GetLastRenderedFrame()
{
lock (_sync)
return _lastRenderedFrame?.PlatformImpl?.CloneAs<IWriteableBitmapImpl>();
{
if (_lastRenderedFrame is null)
{
return null;
}
using var lockedFramebuffer = _lastRenderedFrame.Lock();
return new Bitmap(lockedFramebuffer.Format, AlphaFormat.Opaque, lockedFramebuffer.Address,
lockedFramebuffer.Size, lockedFramebuffer.Dpi, lockedFramebuffer.RowBytes);
}
}
private ulong Timestamp => (ulong)_st.ElapsedMilliseconds;
// TODO: Hook recent Popup changes.
IPopupPositioner IPopupImpl.PopupPositioner => null;
IPopupPositioner IPopupImpl.PopupPositioner => null!;
public Size MaxAutoSizeHint => new Size(1920, 1080);
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public Action GotInputWhenDisabled { get; set; }
public Action? GotInputWhenDisabled { get; set; }
public bool IsClientAreaExtendedToDecorations => false;
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
public Action<bool>? ExtendClientAreaToDecorationsChanged { get; set; }
public bool NeedsManagedDecorations => false;
@ -243,17 +252,12 @@ namespace Avalonia.Headless
public Thickness OffScreenMargin => new Thickness();
public Action LostFocus { get; set; }
public Action? LostFocus { get; set; }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
public object TryGetFeature(Type featureType)
public object? TryGetFeature(Type featureType)
{
if (featureType == typeof(IStorageProvider))
{
return new NoopStorageProvider();
}
if(featureType == typeof(IClipboard))
if(featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService<IClipboard>();
}
@ -263,46 +267,58 @@ namespace Avalonia.Headless
void IHeadlessWindow.KeyPress(Key key, RawInputModifiers modifiers)
{
Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot, RawKeyEventType.KeyDown, key, modifiers));
Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot!, RawKeyEventType.KeyDown, key, modifiers));
}
void IHeadlessWindow.KeyRelease(Key key, RawInputModifiers modifiers)
{
Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot, RawKeyEventType.KeyUp, key, modifiers));
Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot!, RawKeyEventType.KeyUp, key, modifiers));
}
void IHeadlessWindow.MouseDown(Point point, int button, RawInputModifiers modifiers)
void IHeadlessWindow.MouseDown(Point point, MouseButton button, RawInputModifiers modifiers)
{
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot,
button == 0 ? RawPointerEventType.LeftButtonDown :
button == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.RightButtonDown,
point, modifiers));
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot!,
button switch
{
MouseButton.Left => RawPointerEventType.LeftButtonDown,
MouseButton.Right => RawPointerEventType.RightButtonDown,
MouseButton.Middle => RawPointerEventType.MiddleButtonDown,
MouseButton.XButton1 => RawPointerEventType.XButton1Down,
MouseButton.XButton2 => RawPointerEventType.XButton2Down,
_ => RawPointerEventType.Move,
}, point, modifiers));
}
void IHeadlessWindow.MouseMove(Point point, RawInputModifiers modifiers)
{
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot,
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot!,
RawPointerEventType.Move, point, modifiers));
}
void IHeadlessWindow.MouseUp(Point point, int button, RawInputModifiers modifiers)
void IHeadlessWindow.MouseUp(Point point, MouseButton button, RawInputModifiers modifiers)
{
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot,
button == 0 ? RawPointerEventType.LeftButtonUp :
button == 1 ? RawPointerEventType.MiddleButtonUp : RawPointerEventType.RightButtonUp,
point, modifiers));
Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot!,
button switch
{
MouseButton.Left => RawPointerEventType.LeftButtonUp,
MouseButton.Right => RawPointerEventType.RightButtonUp,
MouseButton.Middle => RawPointerEventType.MiddleButtonUp,
MouseButton.XButton1 => RawPointerEventType.XButton1Up,
MouseButton.XButton2 => RawPointerEventType.XButton2Up,
_ => RawPointerEventType.Move,
}, point, modifiers));
}
void IHeadlessWindow.MouseWheel(Point point, Vector delta, RawInputModifiers modifiers)
{
Input?.Invoke(new RawMouseWheelEventArgs(MouseDevice, Timestamp, InputRoot,
Input?.Invoke(new RawMouseWheelEventArgs(MouseDevice, Timestamp, InputRoot!,
point, delta, modifiers));
}
void IHeadlessWindow.DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers)
{
var device = AvaloniaLocator.Current.GetRequiredService<IDragDropDevice>();
Input?.Invoke(new RawDragEvent(device, type, InputRoot, point, data, effects, modifiers));
Input?.Invoke(new RawDragEvent(device, type, InputRoot!, point, data, effects, modifiers));
}
void IWindowImpl.Move(PixelPoint point)
@ -310,7 +326,7 @@ namespace Avalonia.Headless
}
public IPopupImpl CreatePopup()
public IPopupImpl? CreatePopup()
{
// TODO: Hook recent Popup changes.
return null;

10
src/Avalonia.Headless/IHeadlessWindow.cs → src/Headless/Avalonia.Headless/IHeadlessWindow.cs

@ -6,15 +6,15 @@ using Avalonia.Utilities;
namespace Avalonia.Headless
{
public interface IHeadlessWindow
internal interface IHeadlessWindow
{
IRef<IWriteableBitmapImpl> GetLastRenderedFrame();
Bitmap? GetLastRenderedFrame();
void KeyPress(Key key, RawInputModifiers modifiers);
void KeyRelease(Key key, RawInputModifiers modifiers);
void MouseDown(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseDown(Point point, MouseButton button, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseMove(Point point, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseUp(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseUp(Point point, MouseButton button, RawInputModifiers modifiers = RawInputModifiers.None);
void MouseWheel(Point point, Vector delta, RawInputModifiers modifiers = RawInputModifiers.None);
void DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers);
void DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None);
}
}

3
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -58,7 +58,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
new AvaloniaXamlIlThemeVariantProviderTransformer()
);
InsertBefore<ConvertPropertyValuesToAssignmentsTransformer>(
new AvaloniaXamlIlOptionMarkupExtensionTransformer());

14
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs

@ -337,6 +337,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var separators = new[] { "," };
var splitOptions = StringSplitOptions.RemoveEmptyEntries | trimOption;
var attribute = type.GetAllCustomAttributes().FirstOrDefault(a => a.Type == types.AvaloniaListAttribute);
if (attribute is not null)
{
if (attribute.Properties.TryGetValue("Separators", out var separatorsArray))
{
separators = ((Array)separatorsArray)?.OfType<string>().ToArray();
}
if (attribute.Properties.TryGetValue("SplitOptions", out var splitOptionsObj))
{
splitOptions = (StringSplitOptions)splitOptionsObj;
}
}
items = text.Split(separators, splitOptions ^ trimOption);
// Compiler targets netstandard, so we need to emulate StringSplitOptions.TrimEntries, if it was requested.
if (splitOptions.HasFlag(trimOption))

14
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs

@ -24,7 +24,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude;
var mergeSourceNodes = new List<XamlPropertyAssignmentNode>();
var hasAnyNonMergedResource = false;
var mergedResourceWasAdded = false;
foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray())
{
void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode)
@ -38,7 +38,8 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
&& objectInitialization.Manipulation is XamlPropertyAssignmentNode sourceAssignmentNode)
{
parent.Children.Remove(assignmentNode);
mergeSourceNodes.Add(sourceAssignmentNode);
mergeSourceNodes.Add(sourceAssignmentNode);
mergedResourceWasAdded = true;
}
else
{
@ -47,15 +48,10 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
valueNode);
}
}
else
{
hasAnyNonMergedResource = true;
}
if (hasAnyNonMergedResource && mergeSourceNodes.Any())
else if (mergeSourceNodes.Any())
{
throw new XamlDocumentParseException(context.CurrentDocument,
"Mix of MergeResourceInclude and other dictionaries inside of the ResourceDictionary.MergedDictionaries is not allowed",
"MergeResourceInclude should always be included last when mixing with other dictionaries inside of the ResourceDictionary.MergedDictionaries.",
valueNode);
}
}

31
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlThemeVariantProviderTransformer.cs

@ -0,0 +1,31 @@
using System.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class AvaloniaXamlIlThemeVariantProviderTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
var type = context.GetAvaloniaTypes().IThemeVariantProvider;
if (!(node is XamlAstObjectNode on
&& type.IsAssignableFrom(on.Type.GetClrType())))
return node;
var keyDirective = on.Children.FirstOrDefault(n => n is XamlAstXmlDirective d
&& d.Namespace == XamlNamespaces.Xaml2006 &&
d.Name == "Key") as XamlAstXmlDirective;
if (keyDirective is null)
return node;
var keyProp = type.Properties.First(p => p.Name == "Key");
on.Children.Add(new XamlAstXamlPropertyValueNode(keyDirective,
new XamlAstClrProperty(keyDirective, keyProp, context.Configuration),
keyDirective.Values, true));
return node;
}
}

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -33,6 +33,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType InheritDataTypeFromItemsAttribute { get; }
public IXamlType MarkupExtensionOptionAttribute { get; }
public IXamlType MarkupExtensionDefaultOptionAttribute { get; }
public IXamlType AvaloniaListAttribute { get; }
public IXamlType AvaloniaList { get; }
public IXamlType OnExtensionType { get; }
public IXamlType UnsetValueType { get; }
@ -110,6 +111,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
public IXamlType IThemeVariantProvider { get; }
public IXamlType UriKind { get; }
public IXamlConstructor UriConstructor { get; }
public IXamlType Style { get; }
@ -142,6 +144,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
InheritDataTypeFromItemsAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.InheritDataTypeFromItemsAttribute");
MarkupExtensionOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionOptionAttribute");
MarkupExtensionDefaultOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionDefaultOptionAttribute");
AvaloniaListAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.AvaloniaListAttribute");
AvaloniaList = cfg.TypeSystem.GetType("Avalonia.Collections.AvaloniaList`1");
OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On");
AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, AvaloniaObject,
@ -250,6 +253,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
IThemeVariantProvider = cfg.TypeSystem.GetType("Avalonia.Controls.IThemeVariantProvider");
UriKind = cfg.TypeSystem.GetType("System.UriKind");
UriConstructor = Uri.GetConstructor(new List<IXamlType>() { cfg.WellKnownTypes.String, UriKind });
Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style");

2
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@ -1 +1 @@
Subproject commit 5d1025f30d0ed6d8f419d82959c148276301f393
Subproject commit e5254eb1b2017f78a92acd466c8fa1e47401056b

6
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Media;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
@ -10,6 +11,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
private object? _anchor;
private BindingPriority _priority;
private ThemeVariant? _currentThemeVariant;
public DynamicResourceExtension()
{
@ -36,6 +38,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
(object?)serviceProvider.GetFirstParent<IResourceHost>();
}
_currentThemeVariant = StaticResourceExtension.GetDictionaryVariant(serviceProvider);
return this;
}
@ -59,7 +63,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
}
else if (_anchor is IResourceProvider resourceProvider)
{
var source = resourceProvider.GetResourceObservable(ResourceKey, GetConverter(targetProperty));
var source = resourceProvider.GetResourceObservable(ResourceKey, _currentThemeVariant, GetConverter(targetProperty));
return InstancedBinding.OneWay(source, _priority);
}

22
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -33,7 +33,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
var targetObject = provideTarget?.TargetObject;
var targetProperty = provideTarget?.TargetProperty;
var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant;
var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant
?? GetDictionaryVariant(serviceProvider);
var targetType = targetProperty switch
{
@ -78,6 +79,25 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
return ColorToBrushConverter.Convert(control.FindResource(ResourceKey!), targetType);
}
internal static ThemeVariant? GetDictionaryVariant(IServiceProvider serviceProvider)
{
var parents = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents;
if (parents is null)
{
return null;
}
foreach (var parent in parents)
{
if (parent is IThemeVariantProvider { Key: { } setKey })
{
return setKey;
}
}
return null;
}
}
}

4
src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs

@ -13,7 +13,7 @@ namespace Avalonia.Markup.Xaml.Styling
/// When used in runtime, this type might be unsafe with trimming and AOT.
/// </remarks>
[RequiresUnreferencedCode(TrimmingMessages.StyleResourceIncludeRequiresUnreferenceCodeMessage)]
public class ResourceInclude : IResourceProvider
public class ResourceInclude : IResourceProvider, IThemeVariantProvider
{
private readonly IServiceProvider? _serviceProvider;
private readonly Uri? _baseUri;
@ -65,6 +65,8 @@ namespace Avalonia.Markup.Xaml.Styling
/// </summary>
public Uri? Source { get; set; }
ThemeVariant? IThemeVariantProvider.Key { get; set; }
bool IResourceNode.HasResources => Loaded.HasResources;
public event EventHandler? OwnerChanged

22
tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs

@ -1,7 +1,9 @@
using System;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.UnitTests;
using BenchmarkDotNet.Attributes;
using Moq;
@ -30,27 +32,23 @@ namespace Avalonia.Benchmarks.Themes
_app.Dispose();
}
[Benchmark]
public void RepeatButton()
[Benchmark()]
[MethodImpl(MethodImplOptions.NoInlining)]
public void CreateButton()
{
var button = new RepeatButton();
var button = new Button();
_root.Child = button;
_root.LayoutManager.ExecuteLayoutPass();
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
}
private static IDisposable CreateApp()
{
var services = new TestServices(
assetLoader: new AssetLoader(),
globalClock: new MockGlobalClock(),
platform: new AppBuilder().RuntimePlatform,
renderInterface: new MockPlatformRenderInterface(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
theme: () => LoadFluentTheme(),
renderInterface: new NullRenderingPlatform(),
dispatcherImpl: new NullThreadingPlatform(),
fontManagerImpl: new MockFontManagerImpl(),
textShaperImpl: new MockTextShaperImpl(),
windowingPlatform: new MockWindowingPlatform());
standardCursorFactory: new NullCursorFactory(),
theme: () => LoadFluentTheme());
return UnitTestApplication.Start(services);
}

4
tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs

@ -1,5 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Platform;
@ -29,6 +29,7 @@ namespace Avalonia.Benchmarks.Themes
}
[Benchmark]
[MethodImpl(MethodImplOptions.NoInlining)]
public bool InitFluentTheme()
{
UnitTestApplication.Current.Styles[0] = new FluentTheme();
@ -36,6 +37,7 @@ namespace Avalonia.Benchmarks.Themes
}
[Benchmark]
[MethodImpl(MethodImplOptions.NoInlining)]
public bool InitSimpleTheme()
{
UnitTestApplication.Current.Styles[0] = new SimpleTheme();

19
tests/Avalonia.Headless.UnitTests/Avalonia.Headless.UnitTests.csproj

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\UnitTests.NetFX.props" />
<Import Project="..\..\build\Moq.props" />
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\SharedVersion.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
<ProjectReference Include="..\..\src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj" />
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>
</Project>

36
tests/Avalonia.Headless.UnitTests/InputTests.cs

@ -0,0 +1,36 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Threading;
using Xunit;
namespace Avalonia.Headless.UnitTests;
public class InputTests
{
[Fact]
public void Should_Click_Button_On_Window()
{
var buttonClicked = false;
var button = new Button
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
button.Click += (_, _) => buttonClicked = true;
var window = new Window
{
Width = 100,
Height = 100,
Content = button
};
window.Show();
window.MouseDown(new Point(50, 50), MouseButton.Left);
window.MouseUp(new Point(50, 50), MouseButton.Left);
Assert.True(buttonClicked);
}
}

33
tests/Avalonia.Headless.UnitTests/RenderingTests.cs

@ -0,0 +1,33 @@
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Threading;
using Xunit;
namespace Avalonia.Headless.UnitTests;
public class RenderingTests
{
[Fact]
public void Should_Render_Last_Frame_To_Bitmap()
{
var window = new Window
{
Content = new ContentControl
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Padding = new Thickness(4),
Content = new PathIcon
{
Data = StreamGeometry.Parse("M0,9 L10,0 20,9 19,10 10,2 1,10 z")
}
},
SizeToContent = SizeToContent.WidthAndHeight
};
window.Show();
var frame = window.CaptureRenderedFrame();
Assert.NotNull(frame);
}
}

24
tests/Avalonia.Headless.UnitTests/TestApplication.cs

@ -0,0 +1,24 @@
using Avalonia.Headless.UnitTests;
using Avalonia.Headless.XUnit;
using Avalonia.Themes.Simple;
using Xunit;
[assembly: AvaloniaTestFramework(typeof(TestApplication))]
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace Avalonia.Headless.UnitTests;
public class TestApplication : Application
{
public TestApplication()
{
Styles.Add(new SimpleTheme());
}
public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<TestApplication>()
.UseSkia()
.UseHeadless(new AvaloniaHeadlessPlatformOptions
{
UseHeadlessDrawing = false
});
}

32
tests/Avalonia.Headless.UnitTests/ThreadingTests.cs

@ -0,0 +1,32 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
using Xunit;
namespace Avalonia.Headless.UnitTests;
public class ThreadingTests
{
[Fact]
public void Should_Be_On_Dispatcher_Thread()
{
Dispatcher.UIThread.VerifyAccess();
}
[Fact]
public async Task DispatcherTimer_Works_On_The_Same_Thread()
{
var currentThread = Thread.CurrentThread;
var tcs = new TaskCompletionSource();
DispatcherTimer.RunOnce(() =>
{
Assert.Equal(currentThread, Thread.CurrentThread);
tcs.SetResult();
}, TimeSpan.FromTicks(1));
await tcs.Task;
}
}

31
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -261,6 +261,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var grid = AvaloniaRuntimeXamlLoader.Parse<Grid>(xaml);
Assert.Equal(4, grid.ColumnDefinitions.Count);
Assert.Equal(4, grid.RowDefinitions.Count);
var expected1 = new GridLength(100);
var expected2 = GridLength.Auto;
var expected3 = new GridLength(1, GridUnitType.Star);
var expected4 = new GridLength(100, GridUnitType.Star);
Assert.Equal(expected1, grid.ColumnDefinitions[0].Width);
Assert.Equal(expected2, grid.ColumnDefinitions[1].Width);
Assert.Equal(expected3, grid.ColumnDefinitions[2].Width);
Assert.Equal(expected4, grid.ColumnDefinitions[3].Width);
Assert.Equal(expected1, grid.RowDefinitions[0].Height);
Assert.Equal(expected2, grid.RowDefinitions[1].Height);
Assert.Equal(expected3, grid.RowDefinitions[2].Height);
Assert.Equal(expected4, grid.RowDefinitions[3].Height);
}
[Fact]
public void Grid_Row_Col_Definitions_Are_Parsed_Space_Delimiter()
{
var xaml = @"
<Grid xmlns='https://github.com/avaloniaui'
ColumnDefinitions='100 Auto * 100*'
RowDefinitions='100 Auto * 100*'>
</Grid>";
var grid = AvaloniaRuntimeXamlLoader.Parse<Grid>(xaml);
Assert.Equal(4, grid.ColumnDefinitions.Count);
Assert.Equal(4, grid.RowDefinitions.Count);

28
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs

@ -83,6 +83,34 @@ public class MergeResourceIncludeTests
Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.LoadGroup(documents));
}
[Fact]
public void MergeResourceInclude_Is_Allowed_After_ResourceInclude()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<SolidColorBrush x:Key='brush1'>Red</SolidColorBrush>
</ResourceDictionary>"),
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<SolidColorBrush x:Key='brush2'>Blue</SolidColorBrush>
</ResourceDictionary>"),
new RuntimeXamlLoaderDocument(@"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='avares://Tests/Resources2.xaml'/>
<MergeResourceInclude Source='avares://Tests/Resources1.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>")
};
AvaloniaRuntimeXamlLoader.LoadGroup(documents);
}
[Fact]
public void MergeResourceInclude_Works_With_Multiple_Resources()
{

136
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs

@ -1,9 +1,12 @@
using System.Linq;
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.UnitTests;
@ -140,7 +143,7 @@ public class ThemeDictionariesTests : XamlTestBase
Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color);
}
[Fact(Skip = "Not implemented")]
[Fact]
public void StaticResource_Inside_Of_ThemeDictionaries_Should_Use_Same_Theme_Key()
{
var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@"
@ -183,6 +186,135 @@ public class ThemeDictionariesTests : XamlTestBase
Assert.Equal(Colors.Black, ((ISolidColorBrush)border.Background)!.Color);
}
[Fact]
public void StaticResource_Inside_Of_ThemeDictionaries_Should_Use_Same_Theme_Key_From_Inner_File()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Inner.xaml"), @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<StaticResource x:Key='InnerKey' ResourceKey='OuterKey' />
</ResourceDictionary>"),
new RuntimeXamlLoaderDocument(@"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key='Default'>
<Color x:Key='OuterKey'>Green</Color>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='avares://Tests/Inner.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<ResourceDictionary x:Key='Dark'>
<Color x:Key='OuterKey'>White</Color>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='avares://Tests/Inner.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>")
};
var parsed = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var dictionary = (ResourceDictionary)parsed[1]!;
dictionary.TryGetResource("InnerKey", ThemeVariant.Dark, out var resource);
var colorResource = Assert.IsType<Color>(resource);
Assert.Equal(Colors.White, colorResource);
dictionary.TryGetResource("InnerKey", ThemeVariant.Light, out resource);
colorResource = Assert.IsType<Color>(resource);
Assert.Equal(Colors.Green, colorResource);
}
[Fact]
public void DynamicResource_Inside_Of_ThemeDictionaries_Should_Use_Same_Theme_Key_From_Inner_File()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Inner.xaml"), @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<SolidColorBrush x:Key='InnerKey' Color='{DynamicResource OuterKey}' />
</ResourceDictionary>"),
new RuntimeXamlLoaderDocument(@"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key='Default'>
<Color x:Key='OuterKey'>Green</Color>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='avares://Tests/Inner.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<ResourceDictionary x:Key='Dark'>
<Color x:Key='OuterKey'>White</Color>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='avares://Tests/Inner.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>")
};
var parsed = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var dictionary1 = (ResourceDictionary)parsed[0]!;
var dictionary2 = (ResourceDictionary)parsed[1]!;
var ownerApp = new Application(); // DynamicResource needs an owner to work
ownerApp.RequestedThemeVariant = new ThemeVariant("FakeOne", null);
ownerApp.Resources.MergedDictionaries.Add(dictionary1);
ownerApp.Resources.MergedDictionaries.Add(dictionary2);
dictionary2.TryGetResource("InnerKey", ThemeVariant.Dark, out var resource);
var colorResource = Assert.IsAssignableFrom<ISolidColorBrush>(resource);
Assert.Equal(Colors.White, colorResource.Color);
dictionary2.TryGetResource("InnerKey", ThemeVariant.Light, out resource);
colorResource = Assert.IsAssignableFrom<ISolidColorBrush>(resource);
Assert.Equal(Colors.Green, colorResource.Color);
}
[Fact]
public void DynamicResource_Inside_Control_Inside_Of_ThemeDictionaries_Should_Use_Control_Theme_Variant()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(@"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key='Light'>
<Color x:Key='ResourceKey'>Green</Color>
<Template x:Key='Template'>
<ThemeVariantScope RequestedThemeVariant='Dark' TextElement.Foreground='{DynamicResource ResourceKey}' />
</Template>
</ResourceDictionary>
<ResourceDictionary x:Key='Dark'>
<Color x:Key='ResourceKey'>White</Color>
<Template x:Key='Template'>
<ThemeVariantScope RequestedThemeVariant='Light' TextElement.Foreground='{DynamicResource ResourceKey}' />
</Template>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>")
};
var parsed = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var dictionary = (ResourceDictionary)parsed[0]!;
dictionary.TryGetResource("Template", ThemeVariant.Dark, out var resource);
var control = Assert.IsType<ThemeVariantScope>((resource as Template)?.Build());
control.Resources.MergedDictionaries.Add(dictionary);
Assert.Equal(Colors.Green, ((ISolidColorBrush)control[TextElement.ForegroundProperty]!).Color);
control.Resources.MergedDictionaries.Remove(dictionary);
dictionary.TryGetResource("Template", ThemeVariant.Light, out resource);
control = Assert.IsType<ThemeVariantScope>((resource as Template)?.Build());
control.Resources.MergedDictionaries.Add(dictionary);
Assert.Equal(Colors.White, ((ISolidColorBrush)control[TextElement.ForegroundProperty]!).Color);
}
[Fact]
public void StaticResource_Outside_Of_Dictionaries_Should_Use_Control_ThemeVariant()

Loading…
Cancel
Save