Browse Source
Resolve StyleInclude and ResourceInclude at compile time + revisit StyleInclude usage from the codebehindpull/9479/head
committed by
GitHub
48 changed files with 820 additions and 766 deletions
@ -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 |
|||
{ |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
@ -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") |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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") |
|||
} |
|||
}; |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
|
|||
} |
|||
} |
|||
Loading…
Reference in new issue