Browse Source

Merge pull request #9413 from AvaloniaUI/assetinclude-intrinsic

Resolve StyleInclude and ResourceInclude at compile time + revisit StyleInclude usage from the codebehind
pull/9479/head
Jumar Macato 3 years ago
committed by GitHub
parent
commit
ed2a4a3d00
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/Avalonia/AvaloniaBuildTasks.targets
  2. 9
      samples/ControlCatalog/App.xaml
  3. 158
      samples/ControlCatalog/App.xaml.cs
  4. 39
      samples/ControlCatalog/MainView.xaml.cs
  5. 23
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  6. 8
      src/Avalonia.Build.Tasks/Program.cs
  7. 270
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  8. 9
      src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml
  9. 12
      src/Avalonia.Themes.Fluent/Accents/Base.xaml
  10. 11
      src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml
  11. 9
      src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml
  12. 11
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  13. 9
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  14. 1
      src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
  15. 1
      src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
  16. 12
      src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs
  17. 3
      src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
  18. 32
      src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml
  19. 9
      src/Avalonia.Themes.Fluent/FluentDark.xaml
  20. 9
      src/Avalonia.Themes.Fluent/FluentLight.xaml
  21. 244
      src/Avalonia.Themes.Fluent/FluentTheme.cs
  22. 21
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  23. 124
      src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
  24. 8
      src/Avalonia.Themes.Fluent/IBitmapToImageConverter.cs
  25. 20
      src/Avalonia.Themes.Fluent/InverseBooleanValueConverter.cs
  26. 8
      src/Avalonia.Themes.Simple/Accents/Base.xaml
  27. 9
      src/Avalonia.Themes.Simple/Accents/BaseDark.xaml
  28. 9
      src/Avalonia.Themes.Simple/Accents/BaseLight.xaml
  29. 1
      src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj
  30. 4
      src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml
  31. 4
      src/Avalonia.Themes.Simple/IBitmapToImageConverter.cs
  32. 20
      src/Avalonia.Themes.Simple/InverseBooleanValueConverter.cs
  33. 166
      src/Avalonia.Themes.Simple/SimpleTheme.cs
  34. 17
      src/Avalonia.Themes.Simple/SimpleTheme.xaml
  35. 69
      src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs
  36. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  37. 93
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs
  38. 6
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  39. 3
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  40. 22
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleIncludeExtension.cs
  41. 30
      src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
  42. 4
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  43. 13
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  44. 2
      tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs
  45. 4
      tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs
  46. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  47. 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  48. 32
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

1
packages/Avalonia/AvaloniaBuildTasks.targets

@ -99,6 +99,7 @@
AssemblyFile="@(IntermediateAssembly)"
ReferencesFilePath="$(AvaloniaXamlReferencesTemporaryFilePath)"
OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
RefAssemblyFile="@(IntermediateRefAssembly)"
ProjectDirectory="$(MSBuildProjectDirectory)"
VerifyIl="$(AvaloniaXamlIlVerifyIl)"
ReportImportance="$(AvaloniaXamlReportImportance)"

9
samples/ControlCatalog/App.xaml

@ -9,6 +9,15 @@
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</ResourceDictionary.MergedDictionaries>
<StyleInclude x:Key="DataGridFluent" Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
<StyleInclude x:Key="DataGridSimple" Source="avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml" />
<StyleInclude x:Key="ColorPickerFluent" Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />
<StyleInclude x:Key="ColorPickerSimple" Source="avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml" />
<ResourceInclude x:Key="FluentAccentColors" Source="avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml" />
<ResourceInclude x:Key="FluentBaseLightColors" Source="avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml" />
<ResourceInclude x:Key="FluentBaseDarkColors" Source="avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml" />
<ResourceInclude x:Key="FluentBaseColors" Source="avares://Avalonia.Themes.Fluent/Accents/Base.xaml" />
</ResourceDictionary>
</Application.Resources>
<Application.Styles>

158
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();
}
}
}
}
}

39
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<ComboBox>("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);
}
};

23
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; }

8
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 <ReferencesOutputPath>
, where <ReferencesOutputPath> likes {referencesOutputPath}
2) dotnet ./Avalonia.Build.Tasks.dll <AssemblyFilePath> <ReferencesFilePath> <OutputPath>
2) dotnet ./Avalonia.Build.Tasks.dll <AssemblyFilePath> <ReferencesFilePath> <OutputPath> <RefAssemblyFile>
, where:
- <AssemblyFilePath> likes {referencesOutputPath}/{OriginalDll}
- <ReferencesFilePath> likes {referencesOutputPath}/{References}
- <OutputPath> likes {referencesOutputPath}/{OutDll}");
- <OutputPath> likes {referencesOutputPath}/{OutDll}
- <RefAssemblyFile> 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

270
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<MethodReference>()
.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;
}
}
}

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

@ -1,7 +1,5 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
<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>
@ -11,5 +9,4 @@
<Color x:Key="SystemAccentColorLight1">#FF429CE3</Color>
<Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color>
<Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color>
</Style.Resources>
</Style>
</ResourceDictionary>

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

@ -1,8 +1,7 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System"
xmlns:converters="using:Avalonia.Controls.Converters">
<Style.Resources>
<ResourceDictionary xmlns="https://github.com/avaloniaui"
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 -->
<!-- SystemColor Color Resources (Reflect OS High Contrast Settings) -->
<Color x:Key="SystemColorButtonFaceColor">#FFF0F0F0</Color>
@ -36,5 +35,4 @@
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/>
<converters:CornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" Filter="BottomLeft, BottomRight"/>
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/>
</Style.Resources>
</Style>
</ResourceDictionary>

11
src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml

@ -1,7 +1,5 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- System Control Colors -->
<Color x:Key="SystemAltHighColor">#FF000000</Color>
<Color x:Key="SystemAltLowColor">#33000000</Color>
@ -175,6 +173,5 @@
<Thickness x:Key="MenuFlyoutScrollerMargin">0,4,0,4</Thickness>
<!-- Moved from Menu.xaml -->
<Thickness x:Key="MenuBarItemPadding">12,0,12,0</Thickness>
</Style.Resources>
</Style>
<Thickness x:Key="MenuBarItemPadding">12,0,12,0</Thickness>
</ResourceDictionary>

9
src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml

@ -1,7 +1,5 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- System Control Colors -->
<Color x:Key="SystemAltHighColor">#FFFFFFFF</Color>
<Color x:Key="SystemAltLowColor">#33FFFFFF</Color>
@ -179,5 +177,4 @@
<!-- Moved from Menu.xaml -->
<Thickness x:Key="MenuBarItemPadding">12,0,12,0</Thickness>
</Style.Resources>
</Style>
</ResourceDictionary>

11
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -1,7 +1,5 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Resources for Button.xaml -->
<StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlBackgroundAccentBrush" />
<StaticResource x:Key="AccentButtonBackgroundPointerOver" ResourceKey="SystemAccentColorLight1" />
@ -637,6 +635,5 @@
<StaticResource x:Key="FlyoutBorderThemeBrush" ResourceKey="SystemControlTransientBorderBrush" />
<!-- BaseResources for ScrollViewer.xaml -->
<SolidColorBrush x:Key="ScrollViewerScrollBarsSeparatorBackground" Color="{StaticResource SystemChromeMediumColor}" Opacity="0.9" />
</Style.Resources>
</Style>
<SolidColorBrush x:Key="ScrollViewerScrollBarsSeparatorBackground" Color="{StaticResource SystemChromeMediumColor}" Opacity="0.9" />
</ResourceDictionary>

9
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@ -1,7 +1,5 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Resources for Button.xaml -->
<StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlBackgroundAccentBrush" />
<StaticResource x:Key="AccentButtonBackgroundPointerOver" ResourceKey="SystemAccentColorLight1" />
@ -633,5 +631,4 @@
<!-- Resources for ScrollViewer.xaml -->
<SolidColorBrush x:Key="ScrollViewerScrollBarsSeparatorBackground" Color="{StaticResource SystemChromeMediumColor}" Opacity="0.9" />
</Style.Resources>
</Style>
</ResourceDictionary>

1
src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj

@ -10,6 +10,7 @@
<AvaloniaResource Include="**/*.xaml" />
<AvaloniaResource Include="Assets\*" />
</ItemGroup>
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ApiDiff.props" />

1
src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml

@ -1,5 +1,4 @@
<Styles
x:Class="Avalonia.Themes.Fluent.Controls.FluentControls"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>

12
src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs

@ -1,12 +0,0 @@
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
namespace Avalonia.Themes.Fluent.Controls
{
/// <summary>
/// The default Avalonia theme.
/// </summary>
public class FluentControls : Styles
{
}
}

3
src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml

@ -1,13 +1,12 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Avalonia.Themes.Fluent">
<local:InverseBooleanValueConverter x:Key="AvaloniaThemesFluentNativeMenuBarInverseBooleanValueConverter" Default="True"/>
<local:IBitmapToImageConverter x:Key="AvaloniaThemesFluentNativeMenuBarIBitmapToImageConverter"/>
<ControlTheme x:Key="{x:Type NativeMenuBar}" TargetType="NativeMenuBar">
<Setter Property="Template">
<ControlTemplate>
<Menu
IsVisible="{Binding $parent[TopLevel].(NativeMenu.IsNativeMenuExported), Converter={StaticResource AvaloniaThemesFluentNativeMenuBarInverseBooleanValueConverter}}"
IsVisible="{Binding !$parent[TopLevel].(NativeMenu.IsNativeMenuExported)}"
Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
<Menu.Styles>
<!-- Don't use x:DataType and compiled bindings here, as it might crash https://github.com/AvaloniaUI/Avalonia/pull/7954 -->

32
src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml

@ -3,21 +3,19 @@
<Style Selector="TextBlock" >
<Setter Property="FontSize" Value="14" />
</Style>
<Style>
<Style.Resources>
<x:Double x:Key="ControlContentThemeFontSize">14</x:Double>
<x:Double x:Key="ContentControlFontSize">14</x:Double>
<x:Double x:Key="TextControlThemeMinHeight">24</x:Double>
<Thickness x:Key="TextControlThemePadding">2,2,6,1</Thickness>
<x:Double x:Key="ListViewItemMinHeight">32</x:Double>
<x:Double x:Key="TreeViewItemMinHeight">24</x:Double>
<Thickness x:Key="TimePickerHostPadding">0,1,0,2</Thickness>
<Thickness x:Key="DatePickerHostPadding">0,1,0,2</Thickness>
<Thickness x:Key="DatePickerHostMonthPadding">9,0,0,1</Thickness>
<Thickness x:Key="ComboBoxEditableTextPadding">10,0,30,0</Thickness>
<x:Double x:Key="ComboBoxMinHeight">24</x:Double>
<Thickness x:Key="ComboBoxPadding">12,1,0,3</Thickness>
<x:Double x:Key="NavigationViewItemOnLeftMinHeight">32</x:Double>
</Style.Resources>
</Style>
<Styles.Resources>
<x:Double x:Key="ControlContentThemeFontSize">14</x:Double>
<x:Double x:Key="ContentControlFontSize">14</x:Double>
<x:Double x:Key="TextControlThemeMinHeight">24</x:Double>
<Thickness x:Key="TextControlThemePadding">2,2,6,1</Thickness>
<x:Double x:Key="ListViewItemMinHeight">32</x:Double>
<x:Double x:Key="TreeViewItemMinHeight">24</x:Double>
<Thickness x:Key="TimePickerHostPadding">0,1,0,2</Thickness>
<Thickness x:Key="DatePickerHostPadding">0,1,0,2</Thickness>
<Thickness x:Key="DatePickerHostMonthPadding">9,0,0,1</Thickness>
<Thickness x:Key="ComboBoxEditableTextPadding">10,0,30,0</Thickness>
<x:Double x:Key="ComboBoxMinHeight">24</x:Double>
<Thickness x:Key="ComboBoxPadding">12,1,0,3</Thickness>
<x:Double x:Key="NavigationViewItemOnLeftMinHeight">32</x:Double>
</Styles.Resources>
</Styles>

9
src/Avalonia.Themes.Fluent/FluentDark.xaml

@ -1,9 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System">
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/Base.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/FluentControls.xaml" />
</Styles>

9
src/Avalonia.Themes.Fluent/FluentLight.xaml

@ -1,9 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System">
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/Base.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/FluentControls.xaml" />
</Styles>

244
src/Avalonia.Themes.Fluent/FluentTheme.cs

@ -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
}
/// <summary>
/// Includes the fluent theme in an application.
/// </summary>
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;
/// <summary>
/// Initializes a new instance of the <see cref="FluentTheme"/> class.
/// </summary>
/// <param name="baseUri">The base URL for the XAML context.</param>
public FluentTheme(Uri baseUri)
{
_baseUri = baseUri;
InitStyles(baseUri);
}
/// <summary>
/// Initializes a new instance of the <see cref="FluentTheme"/> class.
/// </summary>
/// <param name="serviceProvider">The XAML service provider.</param>
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<FluentThemeMode> ModeProperty =
AvaloniaProperty.Register<FluentTheme, FluentThemeMode>(nameof(Mode));
public static readonly StyledProperty<DensityStyle> DensityStyleProperty =
AvaloniaProperty.Register<FluentTheme, DensityStyle>(nameof(DensityStyle));
/// <summary>
/// Gets or sets the mode of the fluent theme (light, dark).
/// </summary>
public FluentThemeMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}
/// <summary>
/// Gets or sets the density style of the fluent theme (normal, compact).
/// </summary>
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;
/// <summary>
/// Gets the loaded style.
/// </summary>
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> IStyle.Children => _loaded?.Children ?? Array.Empty<IStyle>();
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")
}
};
}
}
}

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

@ -0,0 +1,21 @@
<Styles x:Class="Avalonia.Themes.Fluent.FluentTheme"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="/Accents/AccentColors.xaml" />
<ResourceInclude Source="/Accents/Base.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- These are not part of MergedDictionaries so we can add or remove them easier -->
<ResourceInclude x:Key="BaseDark" Source="/Accents/BaseDark.xaml" />
<ResourceInclude x:Key="FluentDark" Source="/Accents/FluentControlResourcesDark.xaml" />
<ResourceInclude x:Key="BaseLight" Source="/Accents/BaseLight.xaml" />
<ResourceInclude x:Key="FluentLight" Source="/Accents/FluentControlResourcesLight.xaml" />
<StyleInclude x:Key="CompactStyles" Source="/DensityStyles/Compact.xaml" />
</ResourceDictionary>
</Styles.Resources>
<StyleInclude Source="/Controls/FluentControls.xaml" />
</Styles>

124
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
}
/// <summary>
/// Includes the fluent theme in an application.
/// </summary>
public class FluentTheme : Styles
{
private readonly IResourceDictionary _baseDark;
private readonly IResourceDictionary _fluentDark;
private readonly IResourceDictionary _baseLight;
private readonly IResourceDictionary _fluentLight;
private readonly Styles _compactStyles;
/// <summary>
/// Initializes a new instance of the <see cref="FluentTheme"/> class.
/// </summary>
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<FluentThemeMode> ModeProperty =
AvaloniaProperty.Register<FluentTheme, FluentThemeMode>(nameof(Mode));
public static readonly StyledProperty<DensityStyle> DensityStyleProperty =
AvaloniaProperty.Register<FluentTheme, DensityStyle>(nameof(DensityStyle));
/// <summary>
/// Gets or sets the mode of the fluent theme (light, dark).
/// </summary>
public FluentThemeMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}
/// <summary>
/// Gets or sets the density style of the fluent theme (normal, compact).
/// </summary>
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);
}
}
}
}

8
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();
}

20
src/Avalonia.Themes.Fluent/InverseBooleanValueConverter.cs

@ -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;
}
}
}

8
src/Avalonia.Themes.Simple/Accents/Base.xaml

@ -1,8 +1,7 @@
<Style
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
xmlns:sys="using:System">
<Color x:Key="ThemeAccentColor">#CC119EDA</Color>
<Color x:Key="ThemeAccentColor2">#99119EDA</Color>
<Color x:Key="ThemeAccentColor3">#66119EDA</Color>
@ -60,5 +59,4 @@
<sys:Double x:Key="IconElementThemeHeight">20</sys:Double>
<sys:Double x:Key="IconElementThemeWidth">20</sys:Double>
</Style.Resources>
</Style>
</ResourceDictionary>

9
src/Avalonia.Themes.Simple/Accents/BaseDark.xaml

@ -1,8 +1,6 @@
<Style
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="ThemeBackgroundColor">#FF282828</Color>
<Color x:Key="ThemeBorderLowColor">#FF505050</Color>
@ -34,5 +32,4 @@
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{StaticResource ThemeForegroundColor}" />
<SolidColorBrush x:Key="HighlightBrush" Color="{StaticResource HighlightColor}" />
</Style.Resources>
</Style>
</ResourceDictionary>

9
src/Avalonia.Themes.Simple/Accents/BaseLight.xaml

@ -1,8 +1,6 @@
<Style
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Color x:Key="ThemeBackgroundColor">#FFFFFFFF</Color>
<Color x:Key="ThemeBorderLowColor">#FFAAAAAA</Color>
@ -34,5 +32,4 @@
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{StaticResource ThemeForegroundColor}" />
<SolidColorBrush x:Key="HighlightBrush" Color="{StaticResource HighlightColor}" />
</Style.Resources>
</Style>
</ResourceDictionary>

1
src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj

@ -9,6 +9,7 @@
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<AvaloniaResource Include="**/*.xaml" />
</ItemGroup>
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ApiDiff.props" />

4
src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml

@ -1,14 +1,12 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:default="using:Avalonia.Themes.Simple">
<default:InverseBooleanValueConverter x:Key="AvaloniaThemesSimpleNativeMenuBarInverseBooleanValueConverter"
Default="True" />
<default:IBitmapToImageConverter x:Key="AvaloniaThemesSimpleNativeMenuBarIBitmapToImageConverter" />
<ControlTheme x:Key="{x:Type NativeMenuBar}"
TargetType="NativeMenuBar">
<Setter Property="Template">
<ControlTemplate>
<Menu IsVisible="{Binding $parent[TopLevel].(NativeMenu.IsNativeMenuExported), Converter={StaticResource AvaloniaThemesSimpleNativeMenuBarInverseBooleanValueConverter}}"
<Menu IsVisible="{Binding !$parent[TopLevel].(NativeMenu.IsNativeMenuExported)}"
Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
<Menu.Styles>
<!-- Don't use x:DataType and compiled bindings here, as it might crash https://github.com/AvaloniaUI/Avalonia/pull/7954 -->

4
src/Avalonia.Themes.Simple/IBitmapToImageConverter.cs

@ -12,7 +12,7 @@ namespace Avalonia.Themes.Simple
{
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 +20,7 @@ namespace Avalonia.Themes.Simple
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();
}

20
src/Avalonia.Themes.Simple/InverseBooleanValueConverter.cs

@ -1,20 +0,0 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace Avalonia.Themes.Simple
{
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;
}
}
}

166
src/Avalonia.Themes.Simple/SimpleTheme.cs

@ -1,166 +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.Simple
{
public class SimpleTheme : AvaloniaObject, IStyle, IResourceProvider
{
public static readonly StyledProperty<SimpleThemeMode> ModeProperty =
AvaloniaProperty.Register<SimpleTheme, SimpleThemeMode>(nameof(Mode));
private readonly Uri _baseUri;
private bool _isLoading;
private IStyle? _loaded;
private Styles _sharedStyles = new();
private Styles _simpleDark = new();
private Styles _simpleLight = new();
/// <summary>
/// Initializes a new instance of the <see cref="SimpleTheme"/> class.
/// </summary>
/// <param name="baseUri">The base URL for the XAML context.</param>
public SimpleTheme(Uri? baseUri = null)
{
_baseUri = baseUri ?? new Uri("avares://Avalonia.Themes.Simple/");
InitStyles(_baseUri);
}
/// <summary>
/// Initializes a new instance of the <see cref="SimpleTheme"/> class.
/// </summary>
/// <param name="serviceProvider">The XAML service provider.</param>
public SimpleTheme(IServiceProvider serviceProvider)
{
var service = serviceProvider.GetService(typeof(IUriContext));
if (service == null)
{
throw new Exception("There is no service object of type IUriContext!");
}
_baseUri = ((IUriContext)service).BaseUri;
InitStyles(_baseUri);
}
public event EventHandler? OwnerChanged
{
add
{
if (Loaded is IResourceProvider rp)
{
rp.OwnerChanged += value;
}
}
remove
{
if (Loaded is IResourceProvider rp)
{
rp.OwnerChanged -= value;
}
}
}
IReadOnlyList<IStyle> IStyle.Children => _loaded?.Children ?? Array.Empty<IStyle>();
bool IResourceNode.HasResources => (Loaded as IResourceProvider)?.HasResources ?? false;
public IStyle Loaded
{
get
{
if (_loaded == null)
{
_isLoading = true;
if (Mode == SimpleThemeMode.Light)
{
_loaded = new Styles { _sharedStyles, _simpleLight };
}
else if (Mode == SimpleThemeMode.Dark)
{
_loaded = new Styles { _sharedStyles, _simpleDark };
}
_isLoading = false;
}
return _loaded!;
}
}
/// <summary>
/// Gets or sets the mode of the fluent theme (light, dark).
/// </summary>
public SimpleThemeMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}
public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner;
void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner);
void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner);
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;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ModeProperty)
{
if (Mode == SimpleThemeMode.Dark)
{
(Loaded as Styles)![1] = _simpleDark[0];
}
else
{
(Loaded as Styles)![1] = _simpleLight[0];
}
}
}
private void InitStyles(Uri baseUri)
{
_sharedStyles = new Styles
{
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Simple/Controls/SimpleControls.xaml")
},
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Simple/Accents/Base.xaml")
}
};
_simpleLight = new Styles
{
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml")
}
};
_simpleDark = new Styles
{
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Simple/Accents/BaseDark.xaml")
}
};
}
}
}

17
src/Avalonia.Themes.Simple/SimpleTheme.xaml

@ -0,0 +1,17 @@
<Styles x:Class="Avalonia.Themes.Simple.SimpleTheme"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="/Accents/Base.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- These are not part of MergedDictionaries so we can add or remove them easier -->
<ResourceInclude x:Key="BaseDark" Source="/Accents/BaseDark.xaml" />
<ResourceInclude x:Key="BaseLight" Source="/Accents/BaseLight.xaml" />
</ResourceDictionary>
</Styles.Resources>
<StyleInclude Source="/Controls/SimpleControls.xaml" />
</Styles>

69
src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs

@ -0,0 +1,69 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
namespace Avalonia.Themes.Simple
{
public class SimpleTheme : Styles
{
public static readonly StyledProperty<SimpleThemeMode> ModeProperty =
AvaloniaProperty.Register<SimpleTheme, SimpleThemeMode>(nameof(Mode));
private readonly IResourceDictionary _simpleDark;
private readonly IResourceDictionary _simpleLight;
/// <summary>
/// Initializes a new instance of the <see cref="SimpleTheme"/> class.
/// </summary>
public SimpleTheme()
{
AvaloniaXamlLoader.Load(this);
_simpleDark = (IResourceDictionary)GetAndRemove("BaseDark");
_simpleLight = (IResourceDictionary)GetAndRemove("BaseLight");
EnsureThemeVariant();
object GetAndRemove(string key)
{
var val = Resources[key]
?? throw new KeyNotFoundException($"Key {key} was not found in the resources");
Resources.Remove(key);
return val;
}
}
/// <summary>
/// Gets or sets the mode of the fluent theme (light, dark).
/// </summary>
public SimpleThemeMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ModeProperty)
{
EnsureThemeVariant();
}
}
private void EnsureThemeVariant()
{
var themeVariantResource = Mode == SimpleThemeMode.Dark ? _simpleDark : _simpleLight;
var dict = Resources.MergedDictionaries;
if (dict.Count == 1)
{
dict.Add(themeVariantResource);
}
else
{
dict[1] = themeVariantResource;
}
}
}
}

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

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

93
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs

@ -0,0 +1,93 @@
using System.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is not XamlAstObjectNode objectNode
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
{
return node;
}
var nodeTypeName = objectNode.Type.GetClrType().Name;
var sourceProperty = objectNode.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source");
var directives = objectNode.Children.OfType<XamlAstXmlDirective>().ToList();
if (sourceProperty is null
|| objectNode.Children.Count != (directives.Count + 1))
{
throw new XamlParseException($"Unexpected property on the {nodeTypeName} node", node);
}
if (sourceProperty.Values.OfType<XamlAstTextNode>().FirstOrDefault() is not { } sourceTextNode)
{
// TODO: make it a compiler warning
// Source value can be set with markup extension instead of a text node, we don't support it here yet.
return node;
}
var originalAssetPath = sourceTextNode.Text;
if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/")))
{
return node;
}
var runtimeHelpers = context.GetAvaloniaTypes().RuntimeHelpers;
var markerMethodName = "Resolve" + nodeTypeName;
var markerMethod = runtimeHelpers.FindMethod(m => m.Name == markerMethodName && m.Parameters.Count == 3);
if (markerMethod is null)
{
throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{nodeTypeName}\" node", node);
}
return new XamlValueWithManipulationNode(
node,
new AssetIncludeMethodNode(node, markerMethod, originalAssetPath),
new XamlManipulationGroupNode(node, directives));
}
private class AssetIncludeMethodNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
{
private readonly IXamlMethod _method;
private readonly string _originalAssetPath;
public AssetIncludeMethodNode(
IXamlAstNode original, IXamlMethod method, string originalAssetPath)
: base(original)
{
_method = method;
_originalAssetPath = originalAssetPath;
}
public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, _method.ReturnType, false);
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var absoluteSource = _originalAssetPath;
if (absoluteSource.StartsWith("/"))
{
// Avoid Uri class here to avoid potential problems with escaping.
// Keeping string as close to the original as possible.
var absoluteBaseUrl = context.RuntimeContext.BaseUrl;
absoluteSource = absoluteBaseUrl.Substring(0, absoluteBaseUrl.LastIndexOf('/')) + absoluteSource;
}
codeGen.Ldstr(absoluteSource);
codeGen.Ldc_I4(Line);
codeGen.Ldc_I4(Position);
codeGen.EmitCall(_method);
return XamlILNodeEmitResult.Type(0, _method.ReturnType);
}
}
}

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

@ -102,6 +102,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType TextDecorations { get; }
public IXamlType TextTrimming { get; }
public IXamlType ISetter { get; }
public IXamlType IStyle { get; }
public IXamlType StyleInclude { get; }
public IXamlType ResourceInclude { get; }
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
@ -232,6 +235,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations");
TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter");
IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle");
StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude");
ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude");
IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary");
ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,

3
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -33,9 +33,7 @@
<Compile Include="MarkupExtensions\OnFormFactorExtension.cs" />
<Compile Include="MarkupExtensions\OnPlatformExtension.cs" />
<Compile Include="MarkupExtensions\ResolveByNameExtension.cs" />
<Compile Include="MarkupExtensions\ResourceInclude.cs" />
<Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
<Compile Include="MarkupExtensions\StyleIncludeExtension.cs" />
<Compile Include="Parsers\PropertyParser.cs" />
<Compile Include="Converters\BitmapTypeConverter.cs" />
<Compile Include="Converters\IconTypeConverter.cs" />
@ -45,6 +43,7 @@
<Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RuntimeXamlLoaderConfiguration.cs" />
<Compile Include="Styling\ResourceInclude.cs" />
<Compile Include="Styling\StyleInclude.cs" />
<Compile Include="Templates\ControlTemplate.cs" />
<Compile Include="Templates\DataTemplate.cs" />

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

@ -1,22 +0,0 @@
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
using System.ComponentModel;
using System;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class StyleIncludeExtension
{
public StyleIncludeExtension()
{
}
public IStyle ProvideValue(IServiceProvider serviceProvider)
{
return new StyleInclude(serviceProvider.GetContextBaseUri()) { Source = Source };
}
public Uri Source { get; set; }
}
}

30
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs → src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs

@ -1,20 +1,37 @@
using System;
using System.ComponentModel;
using Avalonia.Controls;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions
namespace Avalonia.Markup.Xaml.Styling
{
/// <summary>
/// Loads a resource dictionary from a specified URL.
/// </summary>
public class ResourceInclude : IResourceProvider
{
private Uri? _baseUri;
private readonly Uri? _baseUri;
private IResourceDictionary? _loaded;
private bool _isLoading;
/// <summary>
/// Initializes a new instance of the <see cref="ResourceInclude"/> class.
/// </summary>
/// <param name="baseUri">The base URL for the XAML context.</param>
public ResourceInclude(Uri? baseUri)
{
_baseUri = baseUri;
}
/// <summary>
/// Initializes a new instance of the <see cref="ResourceInclude"/> class.
/// </summary>
/// <param name="serviceProvider">The XAML service provider.</param>
public ResourceInclude(IServiceProvider serviceProvider)
{
_baseUri = serviceProvider.GetContextBaseUri();
}
/// <summary>
/// Gets the loaded resource dictionary.
/// </summary>
@ -61,12 +78,5 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
void IResourceProvider.AddOwner(IResourceHost owner) => Loaded.AddOwner(owner);
void IResourceProvider.RemoveOwner(IResourceHost owner) => Loaded.RemoveOwner(owner);
public ResourceInclude ProvideValue(IServiceProvider serviceProvider)
{
var tdc = (ITypeDescriptorContext)serviceProvider;
_baseUri = tdc?.GetContextBaseUri();
return this;
}
}
}

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

@ -12,7 +12,7 @@ namespace Avalonia.Markup.Xaml.Styling
/// </summary>
public class StyleInclude : IStyle, IResourceProvider
{
private readonly Uri _baseUri;
private readonly Uri? _baseUri;
private IStyle[]? _loaded;
private bool _isLoading;
@ -20,7 +20,7 @@ namespace Avalonia.Markup.Xaml.Styling
/// Initializes a new instance of the <see cref="StyleInclude"/> class.
/// </summary>
/// <param name="baseUri">The base URL for the XAML context.</param>
public StyleInclude(Uri baseUri)
public StyleInclude(Uri? baseUri)
{
_baseUri = baseUri;
}

13
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -5,7 +5,10 @@ using System.Reflection;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Platform;
using Avalonia.Styling;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Global
@ -14,6 +17,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
public static class XamlIlRuntimeHelpers
{
public static IStyle ResolveStyleInclude(string absoluteSource, int line, int position)
{
return new StyleInclude((Uri)null) { Source = new Uri(absoluteSource) }.Loaded;
}
public static IResourceDictionary ResolveResourceInclude(string absoluteSource, int line, int position)
{
return new ResourceInclude((Uri)null) { Source = new Uri(absoluteSource) }.Loaded;
}
public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{

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

@ -61,7 +61,7 @@ namespace Avalonia.Benchmarks.Themes
AssetLoader.RegisterResUriParsers();
return new Styles
{
new Avalonia.Themes.Fluent.FluentTheme(new Uri("avares://Avalonia.Benchmarks"))
new Avalonia.Themes.Fluent.FluentTheme()
{
}

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

@ -31,7 +31,7 @@ namespace Avalonia.Benchmarks.Themes
[Arguments(FluentThemeMode.Light)]
public bool InitFluentTheme(FluentThemeMode mode)
{
UnitTestApplication.Current.Styles[0] = new FluentTheme(new Uri("resm:Styles?assembly=Avalonia.Benchmarks"))
UnitTestApplication.Current.Styles[0] = new FluentTheme()
{
Mode = mode
};
@ -43,7 +43,7 @@ namespace Avalonia.Benchmarks.Themes
[Arguments(SimpleThemeMode.Light)]
public bool InitSimpleTheme(SimpleThemeMode mode)
{
UnitTestApplication.Current.Styles[0] = new SimpleTheme(new Uri("resm:Styles?assembly=Avalonia.Benchmarks"))
UnitTestApplication.Current.Styles[0] = new SimpleTheme()
{
Mode = mode
};

8
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -22,12 +22,12 @@
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Xaml\Style1.xaml">
<AvaloniaResource Include="Xaml\Style1.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Xaml\Style2.xaml">
</AvaloniaResource>
<AvaloniaResource Include="Xaml\Style2.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</AvaloniaResource>
<AvaloniaResource Include="Xaml\XamlIlClassWithPrecompiledXaml.xaml" />
<AvaloniaResource Include="Xaml\XamlIlClassWithCustomProperty.xaml" />
</ItemGroup>

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

@ -473,13 +473,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.True(styles.Count == 1);
var styleInclude = styles.First() as StyleInclude;
var styleInclude = styles.First() as IStyle;
Assert.NotNull(styleInclude);
var style = styleInclude.Loaded;
Assert.NotNull(style);
}
}

32
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -113,27 +113,41 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
public void StyleInclude_Is_Built()
{
using (UnitTestApplication.Start(TestServices.StyledWindow
.With(theme: () => new Styles())))
.With(theme: () => new Styles())))
{
var xaml = @"
<ContentControl xmlns='https://github.com/avaloniaui'>
<ContentControl.Styles>
<StyleInclude Source='resm:Avalonia.Markup.Xaml.UnitTests.Xaml.Style1.xaml?assembly=Avalonia.Markup.Xaml.UnitTests'/>
<StyleInclude Source='avares://Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml'/>
</ContentControl.Styles>
</ContentControl>";
var window = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
Assert.IsType<Style>(window.Styles[0]);
}
}
[Fact]
public void StyleInclude_Is_Built_Resources()
{
using (UnitTestApplication.Start(TestServices.StyledWindow
.With(theme: () => new Styles())))
{
var xaml = @"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Resources>
<StyleInclude x:Key='Include' Source='avares://Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml'/>
</ContentControl.Resources>
</ContentControl>";
Assert.Single(window.Styles);
var styleInclude = window.Styles[0] as StyleInclude;
var window = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
Assert.NotNull(styleInclude);
Assert.NotNull(styleInclude.Source);
Assert.NotNull(styleInclude.Loaded);
Assert.IsType<Style>(window.Resources["Include"]);
}
}
[Fact]
public void Setter_Can_Contain_Template()
{

Loading…
Cancel
Save