Browse Source

Make all themes AOT friendly by moving StyleInclude to the XAML

pull/9413/head
Max Katz 4 years ago
parent
commit
07d53775ba
  1. 9
      samples/ControlCatalog/App.xaml
  2. 158
      samples/ControlCatalog/App.xaml.cs
  3. 39
      samples/ControlCatalog/MainView.xaml.cs
  4. 9
      src/Avalonia.Themes.Fluent/Accents/AccentColors.xaml
  5. 12
      src/Avalonia.Themes.Fluent/Accents/Base.xaml
  6. 11
      src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml
  7. 9
      src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml
  8. 11
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  9. 9
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  10. 1
      src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
  11. 1
      src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
  12. 12
      src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs
  13. 3
      src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
  14. 32
      src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml
  15. 9
      src/Avalonia.Themes.Fluent/FluentDark.xaml
  16. 9
      src/Avalonia.Themes.Fluent/FluentLight.xaml
  17. 244
      src/Avalonia.Themes.Fluent/FluentTheme.cs
  18. 21
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  19. 124
      src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs
  20. 8
      src/Avalonia.Themes.Fluent/IBitmapToImageConverter.cs
  21. 20
      src/Avalonia.Themes.Fluent/InverseBooleanValueConverter.cs
  22. 8
      src/Avalonia.Themes.Simple/Accents/Base.xaml
  23. 9
      src/Avalonia.Themes.Simple/Accents/BaseDark.xaml
  24. 9
      src/Avalonia.Themes.Simple/Accents/BaseLight.xaml
  25. 1
      src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj
  26. 4
      src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml
  27. 4
      src/Avalonia.Themes.Simple/IBitmapToImageConverter.cs
  28. 20
      src/Avalonia.Themes.Simple/InverseBooleanValueConverter.cs
  29. 166
      src/Avalonia.Themes.Simple/SimpleTheme.cs
  30. 17
      src/Avalonia.Themes.Simple/SimpleTheme.xaml
  31. 69
      src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs
  32. 2
      tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs
  33. 4
      tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs

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

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>
<!-- Add these as resources and not part MergedDictionaries so we can add or remove 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>
<!-- Add these as resources and not part MergedDictionaries so we can add or remove 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;
}
}
}
}

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

Loading…
Cancel
Save