diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 01bf303a20..4f9c4d7720 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -99,6 +99,7 @@
AssemblyFile="@(IntermediateAssembly)"
ReferencesFilePath="$(AvaloniaXamlReferencesTemporaryFilePath)"
OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
+ RefAssemblyFile="@(IntermediateRefAssembly)"
ProjectDirectory="$(MSBuildProjectDirectory)"
VerifyIl="$(AvaloniaXamlIlVerifyIl)"
ReportImportance="$(AvaloniaXamlReportImportance)"
diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml
index 5570ada27b..8f32fa01dd 100644
--- a/samples/ControlCatalog/App.xaml
+++ b/samples/ControlCatalog/App.xaml
@@ -9,6 +9,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index 750c1082a6..6c99eb5289 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -3,85 +3,46 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
-using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
using Avalonia.Themes.Simple;
using Avalonia.Themes.Fluent;
+using ControlCatalog.Models;
using ControlCatalog.ViewModels;
namespace ControlCatalog
{
public class App : Application
{
+ private readonly Styles _themeStylesContainer = new();
+ private FluentTheme? _fluentTheme;
+ private SimpleTheme? _simpleTheme;
+ private IResourceDictionary? _fluentBaseLightColors, _fluentBaseDarkColors;
+ private IStyle? _colorPickerFluent, _colorPickerSimple;
+ private IStyle? _dataGridFluent, _dataGridSimple;
+
public App()
{
DataContext = new ApplicationViewModel();
}
- public static readonly StyleInclude ColorPickerFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
- {
- Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml")
- };
-
- public static readonly StyleInclude ColorPickerSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
- {
- Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml")
- };
-
- public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
- {
- Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
- };
-
- public static readonly StyleInclude DataGridSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
- {
- Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml")
- };
-
- public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles"));
-
- public static SimpleTheme Simple = new SimpleTheme(new Uri("avares://ControlCatalog/Styles"));
-
- public static Styles SimpleLight = new Styles
- {
- new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
- },
- new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
- },
- new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml")
- },
- Simple
- };
-
- public static Styles SimpleDark = new Styles
- {
- new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
- },
- new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
- },
- new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml")
- },
- Simple
- };
-
public override void Initialize()
{
- Styles.Insert(0, Fluent);
- Styles.Insert(1, ColorPickerFluent);
- Styles.Insert(2, DataGridFluent);
+ Styles.Add(_themeStylesContainer);
+
AvaloniaXamlLoader.Load(this);
+
+ _fluentTheme = new FluentTheme();
+ _simpleTheme = new SimpleTheme();
+ _simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentAccentColors"]!);
+ _simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentBaseColors"]!);
+ _colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!;
+ _colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!;
+ _dataGridFluent = (IStyle)Resources["DataGridFluent"]!;
+ _dataGridSimple = (IStyle)Resources["DataGridSimple"]!;
+ _fluentBaseLightColors = (IResourceDictionary)Resources["FluentBaseLightColors"]!;
+ _fluentBaseDarkColors = (IResourceDictionary)Resources["FluentBaseDarkColors"]!;
+
+ SetThemeVariant(CatalogTheme.FluentLight);
}
public override void OnFrameworkInitializationCompleted()
@@ -97,5 +58,78 @@ namespace ControlCatalog
base.OnFrameworkInitializationCompleted();
}
+
+ private CatalogTheme _prevTheme;
+ public static CatalogTheme CurrentTheme => ((App)Current!)._prevTheme;
+ public static void SetThemeVariant(CatalogTheme theme)
+ {
+ var app = (App)Current!;
+ var prevTheme = app._prevTheme;
+ app._prevTheme = theme;
+ var shouldReopenWindow = theme switch
+ {
+ CatalogTheme.FluentLight => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
+ CatalogTheme.FluentDark => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
+ CatalogTheme.SimpleLight => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
+ CatalogTheme.SimpleDark => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
+ _ => throw new ArgumentOutOfRangeException(nameof(theme), theme, null)
+ };
+
+ if (app._themeStylesContainer.Count == 0)
+ {
+ app._themeStylesContainer.Add(new Style());
+ app._themeStylesContainer.Add(new Style());
+ app._themeStylesContainer.Add(new Style());
+ }
+
+ if (theme == CatalogTheme.FluentLight)
+ {
+ app._fluentTheme!.Mode = FluentThemeMode.Light;
+ app._themeStylesContainer[0] = app._fluentTheme;
+ app._themeStylesContainer[1] = app._colorPickerFluent!;
+ app._themeStylesContainer[2] = app._dataGridFluent!;
+ }
+ else if (theme == CatalogTheme.FluentDark)
+ {
+ app._fluentTheme!.Mode = FluentThemeMode.Dark;
+ app._themeStylesContainer[0] = app._fluentTheme;
+ app._themeStylesContainer[1] = app._colorPickerFluent!;
+ app._themeStylesContainer[2] = app._dataGridFluent!;
+ }
+ else if (theme == CatalogTheme.SimpleLight)
+ {
+ app._simpleTheme!.Mode = SimpleThemeMode.Light;
+ app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseDarkColors!);
+ app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseLightColors!);
+ app._themeStylesContainer[0] = app._simpleTheme;
+ app._themeStylesContainer[1] = app._colorPickerSimple!;
+ app._themeStylesContainer[2] = app._dataGridSimple!;
+ }
+ else if (theme == CatalogTheme.SimpleDark)
+ {
+ app._simpleTheme!.Mode = SimpleThemeMode.Dark;
+ app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseLightColors!);
+ app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseDarkColors!);
+ app._themeStylesContainer[0] = app._simpleTheme;
+ app._themeStylesContainer[1] = app._colorPickerSimple!;
+ app._themeStylesContainer[2] = app._dataGridSimple!;
+ }
+
+ if (shouldReopenWindow)
+ {
+ if (app.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
+ {
+ var oldWindow = desktopLifetime.MainWindow;
+ var newWindow = new MainWindow();
+ desktopLifetime.MainWindow = newWindow;
+ newWindow.Show();
+ oldWindow?.Close();
+ }
+ else if (app.ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
+ {
+ singleViewLifetime.MainView = new MainView();
+ }
+ }
+ }
}
}
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index 7001eb41ea..15e666ae7b 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -6,7 +6,6 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Immutable;
-using Avalonia.Themes.Fluent;
using ControlCatalog.Models;
using ControlCatalog.Pages;
@@ -31,46 +30,12 @@ namespace ControlCatalog
}
var themes = this.Get("Themes");
+ themes.SelectedItem = App.CurrentTheme;
themes.SelectionChanged += (sender, e) =>
{
if (themes.SelectedItem is CatalogTheme theme)
{
- var themeStyle = Application.Current!.Styles[0];
- if (theme == CatalogTheme.FluentLight)
- {
- if (App.Fluent.Mode != FluentThemeMode.Light)
- {
- App.Fluent.Mode = FluentThemeMode.Light;
- }
- Application.Current.Styles[0] = App.Fluent;
- Application.Current.Styles[1] = App.ColorPickerFluent;
- Application.Current.Styles[2] = App.DataGridFluent;
- }
- else if (theme == CatalogTheme.FluentDark)
- {
-
- if (App.Fluent.Mode != FluentThemeMode.Dark)
- {
- App.Fluent.Mode = FluentThemeMode.Dark;
- }
- Application.Current.Styles[0] = App.Fluent;
- Application.Current.Styles[1] = App.ColorPickerFluent;
- Application.Current.Styles[2] = App.DataGridFluent;
- }
- else if (theme == CatalogTheme.SimpleLight)
- {
- App.Simple.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Light;
- Application.Current.Styles[0] = App.SimpleLight;
- Application.Current.Styles[1] = App.ColorPickerSimple;
- Application.Current.Styles[2] = App.DataGridSimple;
- }
- else if (theme == CatalogTheme.SimpleDark)
- {
- App.Simple.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Dark;
- Application.Current.Styles[0] = App.SimpleDark;
- Application.Current.Styles[1] = App.ColorPickerSimple;
- Application.Current.Styles[2] = App.DataGridSimple;
- }
+ App.SetThemeVariant(theme);
}
};
diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
index e501fc650d..fd07e0a143 100644
--- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
+++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
@@ -12,12 +12,16 @@ namespace Avalonia.Build.Tasks
Enum.TryParse(ReportImportance, true, out MessageImportance outputImportance);
OutputPath = OutputPath ?? AssemblyFile;
+ RefOutputPath = RefOutputPath ?? RefAssemblyFile;
var outputPdb = GetPdbPath(OutputPath);
var input = AssemblyFile;
+ var refInput = RefOutputPath;
var inputPdb = GetPdbPath(input);
- // Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK
+ // Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK
if (OriginalCopyPath != null)
{
+ var originalCopyPathRef = Path.ChangeExtension(OriginalCopyPath, ".ref.dll");
+
File.Copy(AssemblyFile, OriginalCopyPath, true);
input = OriginalCopyPath;
File.Delete(AssemblyFile);
@@ -29,14 +33,24 @@ namespace Avalonia.Build.Tasks
File.Delete(inputPdb);
inputPdb = copyPdb;
}
+
+ if (!string.IsNullOrWhiteSpace(RefAssemblyFile) && File.Exists(RefAssemblyFile))
+ {
+ // We also copy ref assembly just for case if needed later for testing.
+ // But do not remove the original one, as MSBuild actually complains about it with multi-thread compiling.
+ File.Copy(RefAssemblyFile, originalCopyPathRef, true);
+ refInput = originalCopyPathRef;
+ }
}
var msg = $"CompileAvaloniaXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}";
BuildEngine.LogMessage(msg, outputImportance < MessageImportance.Low ? MessageImportance.High : outputImportance);
- var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input,
+ var res = XamlCompilerTaskExecutor.Compile(BuildEngine,
+ input, OutputPath,
+ refInput, RefOutputPath,
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
- ProjectDirectory, OutputPath, VerifyIl, DefaultCompileBindings, outputImportance,
+ ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch);
if (!res.Success)
return false;
@@ -68,6 +82,9 @@ namespace Avalonia.Build.Tasks
[Required]
public string ProjectDirectory { get; set; }
+ public string RefAssemblyFile { get; set; }
+ public string RefOutputPath { get; set; }
+
public string OutputPath { get; set; }
public bool VerifyIl { get; set; }
diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs
index f42fab5964..8ddea2d142 100644
--- a/src/Avalonia.Build.Tasks/Program.cs
+++ b/src/Avalonia.Build.Tasks/Program.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Build.Tasks
static int Main(string[] args)
{
- if (args.Length != 3)
+ if (args.Length < 3)
{
if (args.Length == 1)
{
@@ -27,11 +27,12 @@ namespace Avalonia.Build.Tasks
Console.WriteLine(@$"Usage:
1) dotnet ./Avalonia.Build.Tasks.dll
, where likes {referencesOutputPath}
- 2) dotnet ./Avalonia.Build.Tasks.dll
+ 2) dotnet ./Avalonia.Build.Tasks.dll
, where:
- likes {referencesOutputPath}/{OriginalDll}
- likes {referencesOutputPath}/{References}
- - likes {referencesOutputPath}/{OutDll}");
+ - likes {referencesOutputPath}/{OutDll}
+ - Likes {referencesOutputPath}/original.ref.dll");
return 1;
}
@@ -42,6 +43,7 @@ namespace Avalonia.Build.Tasks
AssemblyFile = args[0],
ReferencesFilePath = args[1],
OutputPath = args[2],
+ RefAssemblyFile = args.Length > 3 ? args[3] : null,
BuildEngine = new ConsoleBuildEngine(),
ProjectDirectory = Directory.GetCurrentDirectory(),
VerifyIl = true
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
index 3493fc06ed..974f8485c0 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -1,7 +1,7 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Reflection;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Microsoft.Build.Framework;
using Mono.Cecil;
@@ -21,6 +21,8 @@ namespace Avalonia.Build.Tasks
{
public static partial class XamlCompilerTaskExecutor
{
+ private const string CompiledAvaloniaXamlNamespace = "CompiledAvaloniaXaml";
+
static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
|| r.Name.ToLowerInvariant().EndsWith(".paml")
|| r.Name.ToLowerInvariant().EndsWith(".axaml");
@@ -37,42 +39,57 @@ namespace Avalonia.Build.Tasks
}
}
- public static CompileResult Compile(IBuildEngine engine, string input, string[] references,
- string projectDirectory,
- string output, bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey,
+ public static CompileResult Compile(IBuildEngine engine,
+ string input, string output,
+ string refInput, string refOutput,
+ string[] references, string projectDirectory,
+ bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey,
bool skipXamlCompilation)
{
- return Compile(engine, input, references, projectDirectory, output, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false);
+ return Compile(engine, input, output, refInput, refOutput, references, projectDirectory, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false);
}
- internal static CompileResult Compile(IBuildEngine engine, string input, string[] references,
- string projectDirectory,
- string output, bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch)
+ internal static CompileResult Compile(IBuildEngine engine,
+ string input, string output,
+ string refInput, string refOutput,
+ string[] references, string projectDirectory,
+ bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch)
{
try
{
- var typeSystem = new CecilTypeSystem(
- references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")),
- input);
+ references = references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")).ToArray();
+ var typeSystem = new CecilTypeSystem(references, input);
+ var refTypeSystem = !string.IsNullOrWhiteSpace(refInput) && File.Exists(refInput) ? new CecilTypeSystem(references, refInput) : null;
var asm = typeSystem.TargetAssemblyDefinition;
-
+ var refAsm = refTypeSystem?.TargetAssemblyDefinition;
if (!skipXamlCompilation)
{
- var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings,
- logImportance, debuggerLaunch);
- if (compileRes == null)
- return new CompileResult(true);
- if (compileRes == false)
- return new CompileResult(false);
+ var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, logImportance, debuggerLaunch);
+ if (compileRes == null)
+ return new CompileResult(true);
+ if (compileRes == false)
+ return new CompileResult(false);
+
+ if (refTypeSystem is not null)
+ {
+ var refCompileRes = CompileCoreForRefAssembly(engine, typeSystem, refTypeSystem);
+ if (refCompileRes == false)
+ return new CompileResult(false);
+ }
}
var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
if (!string.IsNullOrWhiteSpace(strongNameKey))
- writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
+ writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
asm.Write(output, writerParameters);
+ var refWriterParameters = new WriterParameters { WriteSymbols = false };
+ if (!string.IsNullOrWhiteSpace(strongNameKey))
+ writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
+ refAsm?.Write(refOutput, refWriterParameters);
+
return new CompileResult(true, true);
}
catch (Exception ex)
@@ -122,6 +139,7 @@ namespace Avalonia.Build.Tasks
if (avares.Resources.Count(CheckXamlName) == 0)
// Nothing to do
return null;
+
if (typeSystem.FindType("System.Reflection.AssemblyMetadataAttribute") is {} asmMetadata)
{
var ctor = asm.MainModule.ImportReference(typeSystem.GetTypeReference(asmMetadata).Resolve()
@@ -131,14 +149,14 @@ namespace Avalonia.Build.Tasks
var arg2 = new CustomAttributeArgument(strType, defaultCompileBindings.ToString());
asm.CustomAttributes.Add(new CustomAttribute(ctor) { ConstructorArguments = { arg1, arg2 } });
}
-
- var clrPropertiesDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlHelpers",
+
+ var clrPropertiesDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlHelpers",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(clrPropertiesDef);
- var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure",
+ var indexerAccessorClosure = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!IndexerAccessorFactoryClosure",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(indexerAccessorClosure);
- var trampolineBuilder = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlTrampolines",
+ var trampolineBuilder = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlTrampolines",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(trampolineBuilder);
@@ -154,7 +172,7 @@ namespace Avalonia.Build.Tasks
new DeterministicIdGenerator());
- var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext",
+ var contextDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlContext",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(contextDef);
@@ -175,8 +193,8 @@ namespace Avalonia.Build.Tasks
typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods
.First(x => x.Name == "CreateRootServiceProviderV2"));
- var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader",
- TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+ var loaderDispatcherDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!XamlLoader",
+ TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
loaderDispatcherDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
@@ -204,8 +222,8 @@ namespace Avalonia.Build.Tasks
bool CompileGroup(IResourceGroup group)
{
- var typeDef = new TypeDefinition("CompiledAvaloniaXaml", "!"+ group.Name,
- TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+ var typeDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!"+ group.Name,
+ TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
{
@@ -213,7 +231,9 @@ namespace Avalonia.Build.Tasks
});
asm.MainModule.Types.Add(typeDef);
var builder = typeSystem.CreateTypeBuilder(typeDef);
-
+
+ var populateMethodsToTransform = new List<(MethodDefinition populateMethod, string resourceFilePath)>();
+
foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x=>x.FilePath.ToLowerInvariant()))
{
try
@@ -279,13 +299,24 @@ namespace Avalonia.Build.Tasks
populateBuilder.DefineDelegateSubType(closureName, false, returnType, parameterTypes),
res.Uri, res
);
-
-
+
+ var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
+ .Methods.First(m => m.Name == populateName);
+
+ // Include populate method and all nested methods/closures used in the populate method,
+ // So we can replace style/resource includes in all of them.
+ populateMethodsToTransform.Add((compiledPopulateMethod, res.FilePath));
+ populateMethodsToTransform.AddRange(compiledPopulateMethod.Body.Instructions
+ .Where(b => b.OpCode == OpCodes.Call || b.OpCode == OpCodes.Callvirt || b.OpCode == OpCodes.Ldftn)
+ .Select(b => b.Operand)
+ .OfType()
+ .Where(m => compiledPopulateMethod.DeclaringType.NestedTypes.Contains(m.DeclaringType))
+ .Select(m => m.Resolve())
+ .Where(m => m.HasBody)
+ .Select(m => (m, res.FilePath)));
+
if (classTypeDefinition != null)
{
- var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
- .Methods.First(m => m.Name == populateName);
-
var designLoaderFieldType = typeSystem
.GetType("System.Action`1")
.MakeGenericType(typeSystem.GetType("System.Object"));
@@ -435,8 +466,15 @@ namespace Avalonia.Build.Tasks
}
res.Remove();
}
-
-
+
+ foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform)
+ {
+ if (!TransformXamlIncludes(engine, typeSystem, populateMethod, resourceFilePath, createRootServiceProviderMethod))
+ {
+ return false;
+ }
+ }
+
// Technically that's a hack, but it fixes corert incompatibility caused by deterministic builds
int dupeCounter = 1;
foreach (var grp in typeDef.NestedTypes.GroupBy(x => x.Name))
@@ -463,6 +501,166 @@ namespace Avalonia.Build.Tasks
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
return true;
}
+
+ static bool? CompileCoreForRefAssembly(
+ IBuildEngine engine, CecilTypeSystem sourceTypeSystem, CecilTypeSystem refTypeSystem)
+ {
+ var asm = refTypeSystem.TargetAssemblyDefinition;
+
+ var compiledTypes = sourceTypeSystem.TargetAssemblyDefinition.MainModule.Types
+ .Where(t => t.Namespace.StartsWith(CompiledAvaloniaXamlNamespace) && t.IsPublic).ToArray();
+ if (compiledTypes.Length == 0)
+ {
+ return null;
+ }
+
+ try
+ {
+ foreach (var ogType in compiledTypes)
+ {
+ var wrappedOgType = sourceTypeSystem.TargetAssembly.FindType(ogType.FullName);
+
+ var clrPropertiesDef = new TypeDefinition(ogType.Namespace, ogType.Name,
+ TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
+ asm.MainModule.Types.Add(clrPropertiesDef);
+ foreach (var attribute in ogType.CustomAttributes)
+ {
+ var method = asm.MainModule.ImportReference(attribute.Constructor);
+ clrPropertiesDef.CustomAttributes.Add(new CustomAttribute(method, attribute.GetBlob()));
+ }
+
+ var typeBuilder = refTypeSystem.CreateTypeBuilder(clrPropertiesDef);
+ foreach (var ogMethod in wrappedOgType.Methods.Where(m => m.IsPublic && m.IsStatic))
+ {
+ var method = typeBuilder.DefineMethod(ogMethod.ReturnType, ogMethod.Parameters, ogMethod.Name,
+ ogMethod.IsPublic, ogMethod.IsStatic, false);
+ method.Generator.Ldnull();
+ method.Generator.Throw();
+ }
+
+ typeBuilder.CreateType();
+ }
+ }
+ catch (Exception e)
+ {
+ engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", "",
+ 0, 0, 0, 0,
+ e.Message, "", "Avalonia"));
+ return false;
+ }
+
+ return true;
+ }
+ private static bool TransformXamlIncludes(
+ IBuildEngine engine, CecilTypeSystem typeSystem,
+ MethodDefinition populateMethod, string resourceFilePath,
+ MethodReference createRootServiceProviderMethod)
+ {
+ var asm = typeSystem.TargetAssemblyDefinition;
+ foreach (var instruction in populateMethod.Body.Instructions.ToArray())
+ {
+ const string resolveStyleIncludeName = "ResolveStyleInclude";
+ const string resolveResourceInclude = "ResolveResourceInclude";
+ if (instruction.OpCode == OpCodes.Call
+ && instruction.Operand is MethodReference
+ {
+ Name: resolveStyleIncludeName or resolveResourceInclude,
+ DeclaringType: { Name: "XamlIlRuntimeHelpers" }
+ })
+ {
+ int lineNumber = 0, linePosition = 0;
+ bool instructionsModified = false;
+ try
+ {
+ var assetSource = (string)instruction.Previous.Previous.Previous.Operand;
+ lineNumber = GetConstValue(instruction.Previous.Previous);
+ linePosition = GetConstValue(instruction.Previous);
+
+ var index = populateMethod.Body.Instructions.IndexOf(instruction);
+
+ assetSource = assetSource.Replace("avares://", "");
+
+ var assemblyNameSeparator = assetSource.IndexOf('/');
+ var fileNameSeparator = assetSource.LastIndexOf('/');
+ if (assemblyNameSeparator < 0 || fileNameSeparator < 0)
+ {
+ throw new InvalidProgramException(
+ $"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path.");
+ }
+
+ var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator);
+ var assetAssembly = typeSystem.FindAssembly(assetAssemblyName)
+ ?? throw new InvalidProgramException(
+ $"Unable to resolve assembly \"{assetAssemblyName}\"");
+
+ var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.'));
+ if (assetAssembly.FindType(fileName) is { } type
+ && type.FindConstructor() is { } ctor)
+ {
+ var ctorMethod =
+ asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor));
+ instructionsModified = true;
+ populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
+ populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
+ populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop);
+ populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod);
+ }
+ else
+ {
+ var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources")
+ ?? throw new InvalidOperationException(
+ $"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly");
+
+ var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator);
+ var buildMethod = resources.FindMethod(m => m.Name == relativeName)
+ ?? throw new InvalidOperationException(
+ $"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly");
+
+ var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod));
+ instructionsModified = true;
+ populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
+ populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
+ populateMethod.Body.Instructions[index - 1] =
+ Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
+ populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference);
+ }
+ }
+ catch (Exception e)
+ {
+ if (instructionsModified)
+ {
+ engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath,
+ lineNumber, linePosition, lineNumber, linePosition,
+ e.Message, "", "Avalonia"));
+ return false;
+ }
+ else
+ {
+ engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL",
+ resourceFilePath,
+ lineNumber, linePosition, lineNumber, linePosition,
+ e.Message, "", "Avalonia"));
+ }
+ }
+
+ static int GetConstValue(Instruction instruction)
+ {
+ if (instruction.OpCode is { Code : >= Code.Ldc_I4_0 and <= Code.Ldc_I4_8 })
+ {
+ return instruction.OpCode.Code - Code.Ldc_I4_0;
+ }
+ if (instruction.Operand is not null)
+ {
+ return Convert.ToInt32(instruction.Operand);
+ }
+
+ return 0;
+ }
+ }
+ }
+
+ return true;
+ }
}
}
diff --git a/src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml b/src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml
index 1cf2f7e43d..0fb3ab73c2 100644
--- a/src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml
@@ -1,7 +1,5 @@
-
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/Base.xaml
index 1e2acf736d..259d107b5c 100644
--- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/Base.xaml
@@ -1,8 +1,7 @@
-
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml
index 915be08e53..89841c92c0 100644
--- a/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml
@@ -1,7 +1,5 @@
-
+ 12,0,12,0
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml
index e5c0babb80..89b646fb52 100644
--- a/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml
@@ -1,7 +1,5 @@
-
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
index 7e3c8673f5..71a8bc3a3c 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
@@ -1,7 +1,5 @@
-
+
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
index 7917315e19..d764e1616c 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
@@ -1,7 +1,5 @@
-
+
diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
index 35603fe216..597fab22f8 100644
--- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
+++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
@@ -10,6 +10,7 @@
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
index 5383aa3180..a029be6b8d 100644
--- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
@@ -1,5 +1,4 @@
diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs
deleted file mode 100644
index 37822f5c8c..0000000000
--- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Avalonia.Markup.Xaml;
-using Avalonia.Styling;
-
-namespace Avalonia.Themes.Fluent.Controls
-{
- ///
- /// The default Avalonia theme.
- ///
- public class FluentControls : Styles
- {
- }
-}
diff --git a/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
index 1eb0493b08..5c48c297b8 100644
--- a/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
@@ -1,13 +1,12 @@
-
diff --git a/src/Avalonia.Themes.Fluent/FluentDark.xaml b/src/Avalonia.Themes.Fluent/FluentDark.xaml
deleted file mode 100644
index aad71b18fa..0000000000
--- a/src/Avalonia.Themes.Fluent/FluentDark.xaml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Themes.Fluent/FluentLight.xaml b/src/Avalonia.Themes.Fluent/FluentLight.xaml
deleted file mode 100644
index 907efe7ee6..0000000000
--- a/src/Avalonia.Themes.Fluent/FluentLight.xaml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs
deleted file mode 100644
index 79dd81a068..0000000000
--- a/src/Avalonia.Themes.Fluent/FluentTheme.cs
+++ /dev/null
@@ -1,244 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
-using Avalonia.Markup.Xaml.Styling;
-using Avalonia.Styling;
-
-#nullable enable
-
-namespace Avalonia.Themes.Fluent
-{
- public enum FluentThemeMode
- {
- Light,
- Dark,
- }
-
- public enum DensityStyle
- {
- Normal,
- Compact
- }
-
- ///
- /// Includes the fluent theme in an application.
- ///
- public class FluentTheme : AvaloniaObject, IStyle, IResourceProvider
- {
- private readonly Uri _baseUri;
- private Styles _fluentDark = new();
- private Styles _fluentLight = new();
- private Styles _sharedStyles = new();
- private Styles _densityStyles = new();
- private bool _isLoading;
- private IStyle? _loaded;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The base URL for the XAML context.
- public FluentTheme(Uri baseUri)
- {
- _baseUri = baseUri;
- InitStyles(baseUri);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The XAML service provider.
- public FluentTheme(IServiceProvider serviceProvider)
- {
- var ctx = serviceProvider.GetService(typeof(IUriContext)) as IUriContext
- ?? throw new NullReferenceException("Unable retrive UriContext");
- _baseUri = ctx.BaseUri;
- InitStyles(_baseUri);
- }
-
- public static readonly StyledProperty ModeProperty =
- AvaloniaProperty.Register(nameof(Mode));
-
- public static readonly StyledProperty DensityStyleProperty =
- AvaloniaProperty.Register(nameof(DensityStyle));
-
- ///
- /// Gets or sets the mode of the fluent theme (light, dark).
- ///
- public FluentThemeMode Mode
- {
- get => GetValue(ModeProperty);
- set => SetValue(ModeProperty, value);
- }
-
- ///
- /// Gets or sets the density style of the fluent theme (normal, compact).
- ///
- public DensityStyle DensityStyle
- {
- get => GetValue(DensityStyleProperty);
- set => SetValue(DensityStyleProperty, value);
- }
-
- protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
- {
- base.OnPropertyChanged(change);
-
- if (_loaded is null)
- {
- // If style wasn't yet loaded, no need to change children styles,
- // it will be applied later in Loaded getter.
- return;
- }
-
- if (change.Property == ModeProperty)
- {
- if (Mode == FluentThemeMode.Dark)
- {
- (Loaded as Styles)![1] = _fluentDark[0];
- (Loaded as Styles)![2] = _fluentDark[1];
- }
- else
- {
- (Loaded as Styles)![1] = _fluentLight[0];
- (Loaded as Styles)![2] = _fluentLight[1];
- }
- }
-
- if (change.Property == DensityStyleProperty)
- {
- if (DensityStyle == DensityStyle.Compact)
- {
- (Loaded as Styles)!.Add(_densityStyles[0]);
- }
- else if (DensityStyle == DensityStyle.Normal)
- {
- (Loaded as Styles)!.Remove(_densityStyles[0]);
- }
- }
- }
-
- public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner;
-
- ///
- /// Gets the loaded style.
- ///
- public IStyle Loaded
- {
- get
- {
- if (_loaded == null)
- {
- _isLoading = true;
-
- if (Mode == FluentThemeMode.Light)
- {
- _loaded = new Styles() { _sharedStyles , _fluentLight[0], _fluentLight[1] };
- }
- else if (Mode == FluentThemeMode.Dark)
- {
- _loaded = new Styles() { _sharedStyles, _fluentDark[0], _fluentDark[1] };
- }
-
- if (DensityStyle == DensityStyle.Compact)
- {
- (_loaded as Styles)!.Add(_densityStyles[0]);
- }
-
- _isLoading = false;
- }
-
- return _loaded!;
- }
- }
-
- bool IResourceNode.HasResources => (Loaded as IResourceProvider)?.HasResources ?? false;
-
- IReadOnlyList IStyle.Children => _loaded?.Children ?? Array.Empty();
-
- public event EventHandler? OwnerChanged
- {
- add
- {
- if (Loaded is IResourceProvider rp)
- {
- rp.OwnerChanged += value;
- }
- }
- remove
- {
- if (Loaded is IResourceProvider rp)
- {
- rp.OwnerChanged -= value;
- }
- }
- }
-
- public SelectorMatchResult TryAttach(IStyleable target, object? host) => Loaded.TryAttach(target, host);
-
- public bool TryGetResource(object key, out object? value)
- {
- if (!_isLoading && Loaded is IResourceProvider p)
- {
- return p.TryGetResource(key, out value);
- }
-
- value = null;
- return false;
- }
-
- void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner);
- void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner);
-
- private void InitStyles(Uri baseUri)
- {
- _sharedStyles = new Styles
- {
- new StyleInclude(baseUri)
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
- },
- new StyleInclude(baseUri)
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
- },
- new StyleInclude(baseUri)
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Controls/FluentControls.xaml")
- }
- };
-
- _fluentLight = new Styles
- {
- new StyleInclude(baseUri)
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml")
- },
- new StyleInclude(baseUri)
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml")
- }
- };
-
- _fluentDark = new Styles
- {
- new StyleInclude(baseUri)
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml")
- },
- new StyleInclude(baseUri)
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml")
- }
- };
-
- _densityStyles = new Styles
- {
- new StyleInclude(baseUri)
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/DensityStyles/Compact.xaml")
- }
- };
- }
- }
-}
diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml
new file mode 100644
index 0000000000..d8f8267fe5
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
new file mode 100644
index 0000000000..728e81b198
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
@@ -0,0 +1,124 @@
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Styling;
+
+namespace Avalonia.Themes.Fluent
+{
+ public enum FluentThemeMode
+ {
+ Light,
+ Dark,
+ }
+
+ public enum DensityStyle
+ {
+ Normal,
+ Compact
+ }
+
+ ///
+ /// Includes the fluent theme in an application.
+ ///
+ public class FluentTheme : Styles
+ {
+ private readonly IResourceDictionary _baseDark;
+ private readonly IResourceDictionary _fluentDark;
+ private readonly IResourceDictionary _baseLight;
+ private readonly IResourceDictionary _fluentLight;
+ private readonly Styles _compactStyles;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public FluentTheme()
+ {
+ AvaloniaXamlLoader.Load(this);
+
+ _baseDark = (IResourceDictionary)GetAndRemove("BaseDark");
+ _fluentDark = (IResourceDictionary)GetAndRemove("FluentDark");
+ _baseLight = (IResourceDictionary)GetAndRemove("BaseLight");
+ _fluentLight = (IResourceDictionary)GetAndRemove("FluentLight");
+ _compactStyles = (Styles)GetAndRemove("CompactStyles");
+
+ EnsureThemeVariants();
+ EnsureCompactStyles();
+
+ object GetAndRemove(string key)
+ {
+ var val = Resources[key]
+ ?? throw new KeyNotFoundException($"Key {key} was not found in the resources");
+ Resources.Remove(key);
+ return val;
+ }
+ }
+
+ public static readonly StyledProperty ModeProperty =
+ AvaloniaProperty.Register(nameof(Mode));
+
+ public static readonly StyledProperty DensityStyleProperty =
+ AvaloniaProperty.Register(nameof(DensityStyle));
+
+ ///
+ /// Gets or sets the mode of the fluent theme (light, dark).
+ ///
+ public FluentThemeMode Mode
+ {
+ get => GetValue(ModeProperty);
+ set => SetValue(ModeProperty, value);
+ }
+
+ ///
+ /// Gets or sets the density style of the fluent theme (normal, compact).
+ ///
+ public DensityStyle DensityStyle
+ {
+ get => GetValue(DensityStyleProperty);
+ set => SetValue(DensityStyleProperty, value);
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == ModeProperty)
+ {
+ EnsureThemeVariants();
+ }
+
+ if (change.Property == DensityStyleProperty)
+ {
+ EnsureCompactStyles();
+ }
+ }
+
+ private void EnsureThemeVariants()
+ {
+ var themeVariantResource1 = Mode == FluentThemeMode.Dark ? _baseDark : _baseLight;
+ var themeVariantResource2 = Mode == FluentThemeMode.Dark ? _fluentDark : _fluentLight;
+ var dict = Resources.MergedDictionaries;
+ if (dict.Count == 2)
+ {
+ dict.Insert(1, themeVariantResource1);
+ dict.Add(themeVariantResource2);
+ }
+ else
+ {
+ dict[1] = themeVariantResource1;
+ dict[3] = themeVariantResource2;
+ }
+ }
+
+ private void EnsureCompactStyles()
+ {
+ if (DensityStyle == DensityStyle.Compact)
+ {
+ Add(_compactStyles);
+ }
+ else
+ {
+ Remove(_compactStyles);
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Themes.Fluent/IBitmapToImageConverter.cs b/src/Avalonia.Themes.Fluent/IBitmapToImageConverter.cs
index 34670882f8..e6c46122fc 100644
--- a/src/Avalonia.Themes.Fluent/IBitmapToImageConverter.cs
+++ b/src/Avalonia.Themes.Fluent/IBitmapToImageConverter.cs
@@ -1,9 +1,5 @@
using System;
-using System.Collections.Generic;
using System.Globalization;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Media.Imaging;
@@ -12,7 +8,7 @@ namespace Avalonia.Themes.Fluent
{
internal class IBitmapToImageConverter : IValueConverter
{
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value != null && value is IBitmap bm)
return new Image { Source=bm };
@@ -20,7 +16,7 @@ namespace Avalonia.Themes.Fluent
return null;
}
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
diff --git a/src/Avalonia.Themes.Fluent/InverseBooleanValueConverter.cs b/src/Avalonia.Themes.Fluent/InverseBooleanValueConverter.cs
deleted file mode 100644
index 20bade2a67..0000000000
--- a/src/Avalonia.Themes.Fluent/InverseBooleanValueConverter.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-using System.Globalization;
-using Avalonia.Data.Converters;
-
-namespace Avalonia.Themes.Fluent
-{
- class InverseBooleanValueConverter : IValueConverter
- {
- public bool Default { get; set; }
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- return value is bool b ? !b : Default;
- }
-
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- return value is bool b ? !b : !Default;
- }
- }
-}
diff --git a/src/Avalonia.Themes.Simple/Accents/Base.xaml b/src/Avalonia.Themes.Simple/Accents/Base.xaml
index 1f7d703b54..bffdbd8a27 100644
--- a/src/Avalonia.Themes.Simple/Accents/Base.xaml
+++ b/src/Avalonia.Themes.Simple/Accents/Base.xaml
@@ -1,8 +1,7 @@
-
+
diff --git a/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml b/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml
index 1843abebfd..9ad9f70c98 100644
--- a/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml
+++ b/src/Avalonia.Themes.Simple/Accents/BaseDark.xaml
@@ -1,8 +1,6 @@
-
+
diff --git a/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml b/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml
index 6247815303..f96425cf06 100644
--- a/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml
+++ b/src/Avalonia.Themes.Simple/Accents/BaseLight.xaml
@@ -1,8 +1,6 @@
-
+
diff --git a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj
index 40ed4a0f87..e614dad4d9 100644
--- a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj
+++ b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml
index 160acd5872..ba1b35e2ee 100644
--- a/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml
@@ -1,14 +1,12 @@
-
-