实现控件主题源码生成初始化

实现控件主题源码生成初始化
This commit is contained in:
polarboy 2024-08-19 19:30:20 +08:00
parent 90ea34f6bc
commit f066e23856
10 changed files with 212 additions and 83 deletions

View File

@ -1,4 +1,5 @@
using AtomUI.Utils;
using AtomUI.Theme;
using AtomUI.Utils;
using Avalonia;
namespace AtomUI.Controls;
@ -8,5 +9,6 @@ public class BootstrapInitializer : IBootstrapInitializer
public void Init()
{
AvaloniaLocator.CurrentMutable.BindToSelf(new ToolTipService());
ControlThemeRegister.Register();
}
}

View File

@ -0,0 +1,71 @@
using System.Runtime.CompilerServices;
namespace AtomUI.Theme
{
internal class ControlThemeRegister
{
internal static void Register()
{
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.AddOnDecoratedBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.AddOnDecoratedInnerBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.AlertTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ArrowDecoratedBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ButtonSpinnerInnerBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ButtonSpinnerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.DefaultButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.LinkButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.PrimaryButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TextButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ToggleIconButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CheckBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CollapseItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CollapseTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.EmptyIndicatorTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ExpanderTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.MenuFlyoutPresenterTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.LineEditTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.SearchEditDecoratedBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.SearchEditTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TextBoxInnerBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TextBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.LoadingIndicatorAdornerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.LoadingIndicatorTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ContextMenuTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.MenuItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.MenuScrollViewerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.MenuSeparatorTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.MenuTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TopLevelMenuItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.NumericUpDownTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.OptionButtonGroupTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.OptionButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CircleProgressTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.DashboardProgressTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ProgressBarTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.StepsProgressBarTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.RadioButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.SegmentedItemBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.SegmentedItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.SegmentedTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.SeparatorTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.SliderTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.SliderThumbTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.Switch.ToggleSwitchTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.BaseOverflowMenuItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.BaseTabScrollViewerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CardTabControlTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CardTabItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TabControlTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TabItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CardTabStripItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CardTabStripTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TabStripItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TabStripTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TagTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ToolTipTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.NodeSwitcherButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TreeViewItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.TreeViewTheme());
}
}
}

View File

@ -13,12 +13,6 @@ public class TextBox : AvaloniaTextBox
public const string WarningPC = ":warning";
#region
public static readonly StyledProperty<object?> LeftAddOnProperty =
AvaloniaProperty.Register<TextBox, object?>(nameof(LeftAddOn));
public static readonly StyledProperty<object?> RightAddOnProperty =
AvaloniaProperty.Register<TextBox, object?>(nameof(RightAddOn));
public static readonly StyledProperty<SizeType> SizeTypeProperty =
AddOnDecoratedBox.SizeTypeProperty.AddOwner<TextBox>();
@ -35,18 +29,6 @@ public class TextBox : AvaloniaTextBox
public static readonly StyledProperty<bool> IsEnableRevealButtonProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(IsEnableRevealButton), false);
public object? LeftAddOn
{
get => GetValue(LeftAddOnProperty);
set => SetValue(LeftAddOnProperty, value);
}
public object? RightAddOn
{
get => GetValue(RightAddOnProperty);
set => SetValue(RightAddOnProperty, value);
}
public SizeType SizeType
{
get => GetValue(SizeTypeProperty);
@ -93,18 +75,6 @@ public class TextBox : AvaloniaTextBox
set => SetAndRaise(IsEffectiveShowClearButtonProperty, ref _isEffectiveShowClearButton, value);
}
internal static readonly DirectProperty<TextBox, bool> DisabledInnerBoxPaddingProperty =
AvaloniaProperty.RegisterDirect<TextBox, bool>(nameof(DisabledInnerBoxPadding),
o => o.DisabledInnerBoxPadding,
(o, v) => o.DisabledInnerBoxPadding = v);
private bool _disabledInnerBoxPadding = false;
internal bool DisabledInnerBoxPadding
{
get => _disabledInnerBoxPadding;
set => SetAndRaise(DisabledInnerBoxPaddingProperty, ref _disabledInnerBoxPadding, value);
}
internal static readonly DirectProperty<TextBox, bool> EmbedModeProperty =
AvaloniaProperty.RegisterDirect<TextBox, bool>(nameof(EmbedMode),
o => o.EmbedMode,
@ -147,9 +117,7 @@ public class TextBox : AvaloniaTextBox
// TODO 到底是否需要这样,这些控件的管辖区理论上不应该我们控制
if (change.Property == InnerLeftContentProperty ||
change.Property == InnerRightContentProperty ||
change.Property == LeftAddOnProperty ||
change.Property == RightAddOnProperty) {
change.Property == InnerRightContentProperty) {
if (change.OldValue is Control oldControl) {
UIStructureUtils.SetTemplateParent(oldControl, null);
}

View File

@ -1,9 +1,7 @@
using AtomUI.Theme.Styling;
using Avalonia;
using Avalonia;
namespace AtomUI.Controls;
[ControlThemeProvider]
public class TextBoxInnerBox : AddOnDecoratedInnerBox
{
#region
@ -29,18 +27,6 @@ public class TextBoxInnerBox : AddOnDecoratedInnerBox
#endregion
#region
internal static readonly DirectProperty<TextBoxInnerBox, bool> DisabledInnerBoxPaddingProperty =
AvaloniaProperty.RegisterDirect<TextBoxInnerBox, bool>(nameof(DisabledInnerBoxPadding),
o => o.DisabledInnerBoxPadding,
(o, v) => o.DisabledInnerBoxPadding = v);
private bool _disabledInnerBoxPadding;
internal bool DisabledInnerBoxPadding
{
get => _disabledInnerBoxPadding;
set => SetAndRaise(DisabledInnerBoxPaddingProperty, ref _disabledInnerBoxPadding, value);
}
internal static readonly DirectProperty<TextBoxInnerBox, bool> EmbedModeProperty =
AvaloniaProperty.RegisterDirect<TextBoxInnerBox, bool>(nameof(EmbedMode),
@ -72,7 +58,7 @@ public class TextBoxInnerBox : AddOnDecoratedInnerBox
protected override void BuildEffectiveInnerBoxPadding()
{
if (!_disabledInnerBoxPadding) {
if (!_embedMode) {
base.BuildEffectiveInnerBoxPadding();
}
}

View File

@ -4,14 +4,14 @@ using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class TextBoxInnerBoxTheme : AddOnDecoratedInnerBoxTheme
{
public TextBoxInnerBoxTheme() : base(typeof(TextBoxInnerBox)) {}
protected override void BuildDisabledStyle()
{
var embedModeStyle = new Style(selector => selector.PropertyEquals(TextBoxInnerBox.EmbedModeProperty, false));
var embedModeStyle = new Style(selector => selector.Nesting().PropertyEquals(TextBoxInnerBox.EmbedModeProperty, false));
var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled));
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(InnerBoxDecoratorPart));
decoratorStyle.Add(Border.BackgroundProperty, GlobalResourceKey.ColorBgContainerDisabled);

View File

@ -47,7 +47,6 @@ public class TextBoxTheme : BaseControlTheme
editInnerBox.RegisterInNameScope(scope);
CreateTemplateParentBinding(editInnerBox, TextBoxInnerBox.DisabledInnerBoxPaddingProperty, TextBox.DisabledInnerBoxPaddingProperty);
CreateTemplateParentBinding(editInnerBox, TextBoxInnerBox.LeftAddOnContentProperty, TextBox.InnerLeftContentProperty);
CreateTemplateParentBinding(editInnerBox, TextBoxInnerBox.RightAddOnContentProperty, TextBox.InnerRightContentProperty);
CreateTemplateParentBinding(editInnerBox, TextBoxInnerBox.SizeTypeProperty, TextBox.SizeTypeProperty);

View File

@ -57,8 +57,8 @@ internal class NumericUpDownTheme : BaseControlTheme
HorizontalAlignment = HorizontalAlignment.Stretch,
BorderThickness = new Thickness(0),
TextWrapping = TextWrapping.NoWrap,
DisabledInnerBoxPadding = true,
AcceptsReturn = false
AcceptsReturn = false,
EmbedMode = true
};
BindUtils.RelayBind(this, DataValidationErrors.ErrorsProperty, textBox, DataValidationErrors.ErrorsProperty);

View File

@ -0,0 +1,72 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace AtomUI.Generator.ControlTheme;
public class ControlThemeRegisterClassSourceWriter
{
private SourceProductionContext _context;
private ImmutableArray<string> _classes;
private List<string> _usingInfos;
public ControlThemeRegisterClassSourceWriter(SourceProductionContext context, ImmutableArray<string> classes)
{
_context = context;
_classes = classes;
_usingInfos = new List<string>();
SetupUsingInfos();
}
private void SetupUsingInfos()
{
_usingInfos.Add("System.Runtime.CompilerServices");
}
public void Write()
{
var compilationUnitSyntax = BuildCompilationUnitSyntax();
_context.AddSource($"ControlThemeRegister.g.cs", compilationUnitSyntax.NormalizeWhitespace().ToFullString());
}
private CompilationUnitSyntax BuildCompilationUnitSyntax()
{
var compilationUnit = SyntaxFactory.CompilationUnit();
var usingSyntaxList = new List<UsingDirectiveSyntax>();
foreach (var usingInfo in _usingInfos) {
var usingSyntax = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(usingInfo));
usingSyntaxList.Add(usingSyntax);
}
compilationUnit = compilationUnit.AddUsings(usingSyntaxList.ToArray());
// 添加命名空间
var namespaceSyntax = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName("AtomUI.Theme"));
var themeManagerClassDecl = SyntaxFactory.ClassDeclaration("ControlThemeRegister")
.AddModifiers(SyntaxFactory.Token(SyntaxKind.InternalKeyword))
.AddMembers(GenerateControlThemeCreateMethod());
namespaceSyntax = namespaceSyntax.AddMembers(themeManagerClassDecl);
compilationUnit = compilationUnit.AddMembers(namespaceSyntax);
return compilationUnit;
}
private MethodDeclarationSyntax GenerateControlThemeCreateMethod()
{
List<ExpressionStatementSyntax> objectCreateStmts = new();
foreach (var className in _classes) {
var registerExprStmt = SyntaxFactory.ParseExpression($"ThemeManager.Current.RegisterControlTheme(new {className}())");
var statement = SyntaxFactory.ExpressionStatement(registerExprStmt);
objectCreateStmts.Add(statement);
}
return SyntaxFactory.MethodDeclaration(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "Register")
.AddModifiers(SyntaxFactory.Token(SyntaxKind.InternalKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword))
.WithBody(SyntaxFactory.Block(objectCreateStmts));
}
}

View File

@ -0,0 +1,42 @@
using AtomUI.Generator.ControlTheme;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace AtomUI.Generator;
[Generator]
public class ControlThemeRegisterGenerator : IIncrementalGenerator
{
public const string ControlDesignTokenAttribute = "AtomUI.Theme.Styling.ControlThemeProviderAttribute";
public void Initialize(IncrementalGeneratorInitializationContext initContext)
{
var controlThemesProvider = initContext.SyntaxProvider.ForAttributeWithMetadataName(ControlDesignTokenAttribute,
((node, token) => true),
(context, token) =>
{
if (context.TargetNode is ClassDeclarationSyntax classDeclaration) {
string ns = string.Empty;
if (classDeclaration.Parent is FileScopedNamespaceDeclarationSyntax fileScopedNamespaceDecl) {
ns = fileScopedNamespaceDecl.Name.ToString();
} else if (classDeclaration.Parent is NamespaceDeclarationSyntax namespaceDecl) {
ns = namespaceDecl.Name.ToString();
}
if (!string.IsNullOrEmpty(ns)) {
return $"{ns}.{classDeclaration.Identifier.Text}";
}
return classDeclaration.Identifier.Text;
}
return string.Empty;
}).Collect();
initContext.RegisterImplementationSourceOutput(controlThemesProvider, (context, infos) =>
{
if (infos.Length > 0) {
var classWriter = new ControlThemeRegisterClassSourceWriter(context, infos);
classWriter.Write();
}
});
}
}

View File

@ -1,7 +1,6 @@
using System.Reflection;
using AtomUI.Controls.MotionScene;
using AtomUI.MotionScene;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
@ -13,7 +12,7 @@ namespace AtomUI.Theme;
/// <summary>
/// 当切换主题时候就是动态的换 ResourceDictionary 里面的东西
/// </summary>
public class ThemeManager : Styles, IThemeManager
public partial class ThemeManager : Styles, IThemeManager
{
public const string THEME_DIR = "Themes";
public const string DEFAULT_THEME_ID = "DaybreakBlueLight";
@ -23,11 +22,12 @@ public class ThemeManager : Styles, IThemeManager
private Dictionary<string, Theme> _themePool;
private List<string> _customThemeDirs;
private List<string> _builtInThemeDirs;
private string DefaultThemeId { get; set; }
private ResourceDictionary? _controlThemeResources;
public ITheme? ActivatedTheme => _activatedTheme;
public IReadOnlyList<string> CustomThemeDirs => _customThemeDirs;
public static ThemeManager Current { get; }
public string DefaultThemeId { get; set; }
public event EventHandler<ThemeOperateEventArgs>? ThemeCreatedEvent;
public event EventHandler<ThemeOperateEventArgs>? ThemeAboutToLoadEvent;
@ -52,6 +52,7 @@ public class ThemeManager : Styles, IThemeManager
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), THEME_DIR)
};
DefaultThemeId = DEFAULT_THEME_ID;
_controlThemeResources = new ResourceDictionary();
}
public IReadOnlyCollection<ITheme> AvailableThemes
@ -230,16 +231,9 @@ public class ThemeManager : Styles, IThemeManager
internal void InvokeBootstrapInitializers()
{
// 暂时就初始化自己的
var assemblies = Assembly.GetEntryAssembly()?.GetReferencedAssemblies().Where(assembly =>
{
if (assembly.Name is null) {
return false;
}
return assembly.Name.StartsWith("AtomUI.Controls");
}).Select(assemblyName => Assembly.Load(assemblyName));
var assemblies = Assembly.GetEntryAssembly()?.GetReferencedAssemblies().Select(assemblyName => Assembly.Load(assemblyName));
var initializers = assemblies?.SelectMany(assembly => assembly.GetTypes())
.Where(type => typeof(IBootstrapInitializer).IsAssignableFrom(type))
.Where(type => type.IsClass && typeof(IBootstrapInitializer).IsAssignableFrom(type))
.Select(type => (IBootstrapInitializer)Activator.CreateInstance(type)!);
if (initializers is not null) {
foreach (var initializer in initializers) {
@ -250,23 +244,18 @@ public class ThemeManager : Styles, IThemeManager
internal void RegisterControlThemes()
{
var controlThemeProviders = Assembly.GetEntryAssembly()?
.GetReferencedAssemblies().Select(assemblyName => Assembly.Load(assemblyName))
.SelectMany(assembly => assembly.GetTypes())
.Where(type => type.IsDefined(typeof(ControlThemeProviderAttribute)) && typeof(BaseControlTheme).IsAssignableFrom(type))
.Select(type => Activator.CreateInstance(type));
var resources = new ResourceDictionary();
if (controlThemeProviders is not null) {
foreach (var item in controlThemeProviders) {
if (item is BaseControlTheme controlTheme) {
controlTheme.Build();
object? resourceKey = controlTheme.ThemeResourceKey();
resourceKey ??= controlTheme.TargetType!;
resources.Add(resourceKey, controlTheme);
}
}
if (_controlThemeResources is not null) {
Resources.MergedDictionaries.Add(_controlThemeResources);
_controlThemeResources = null;
}
Resources.MergedDictionaries.Add(resources);
}
public void RegisterControlTheme(BaseControlTheme controlTheme)
{
controlTheme.Build();
object? resourceKey = controlTheme.ThemeResourceKey();
resourceKey ??= controlTheme.TargetType!;
_controlThemeResources?.Add(resourceKey, controlTheme);
}
private void RegisterServices()