DependencyProperty generation

This commit is contained in:
Lightczx
2023-07-07 10:11:54 +08:00
parent b37d5331a2
commit 4d526bb363
18 changed files with 187 additions and 176 deletions

View File

@@ -167,6 +167,9 @@ csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_readonly_struct = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
# SA1600: Elements should be documented
dotnet_diagnostic.SA1600.severity = none
[*.vb]
#### 命名样式 ####

View File

@@ -99,4 +99,4 @@ internal sealed class CommandGenerator : IIncrementalGenerator
string normalizedClassName = classSymbol.ToDisplayString().Replace('<', '{').Replace('>', '}');
production.AddSource($"{normalizedClassName}.{commandName}.g.cs", code);
}
}
}

View File

@@ -0,0 +1,103 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Snap.Hutao.SourceGeneration.Primitive;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
namespace Snap.Hutao.SourceGeneration.Automation;
[Generator(LanguageNames.CSharp)]
internal sealed class DependencyPropertyGenerator : IIncrementalGenerator
{
private const string AttributeName = "Snap.Hutao.Core.Annotation.DependencyPropertyAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValueProvider<ImmutableArray<GeneratorSyntaxContext2>> commands =
context.SyntaxProvider.CreateSyntaxProvider(FilterAttributedClasses, CommandMethod)
.Where(GeneratorSyntaxContext2.NotNull)
.Collect();
context.RegisterImplementationSourceOutput(commands, GenerateDependencyPropertyImplementations);
}
private static bool FilterAttributedClasses(SyntaxNode node, CancellationToken token)
{
return node is ClassDeclarationSyntax classDeclarationSyntax
&& classDeclarationSyntax.Modifiers.Count > 1
&& classDeclarationSyntax.HasAttributeLists();
}
private static GeneratorSyntaxContext2 CommandMethod(GeneratorSyntaxContext context, CancellationToken token)
{
if (context.TryGetDeclaredSymbol(token, out INamedTypeSymbol? methodSymbol))
{
ImmutableArray<AttributeData> attributes = methodSymbol.GetAttributes();
if (attributes.Any(data => data.AttributeClass!.ToDisplayString() == AttributeName))
{
return new(context, methodSymbol, attributes);
}
}
return default;
}
private static void GenerateDependencyPropertyImplementations(SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> context2s)
{
foreach (GeneratorSyntaxContext2 context2 in context2s.DistinctBy(c => c.Symbol.ToDisplayString()))
{
GenerateDependencyPropertyImplementation(production, context2);
}
}
private static void GenerateDependencyPropertyImplementation(SourceProductionContext production, GeneratorSyntaxContext2 context2)
{
foreach (AttributeData propertyInfo in context2.Attributes.Where(attr => attr.AttributeClass!.ToDisplayString() == AttributeName))
{
ImmutableArray<TypedConstant> arguments = propertyInfo.ConstructorArguments;
string propertyName = (string)arguments[0].Value!;
string propertyType = arguments[0].Type!.ToDisplayString();
string type = arguments[1].Value!.ToString();
string defaultValue = arguments.Length > 2
? GetLiteralString(arguments[2])
: "default";
string className = context2.Symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
string code = $$"""
using Microsoft.UI.Xaml;
namespace {{context2.Symbol.ContainingNamespace}};
partial class {{className}}
{
private DependencyProperty {{propertyName}}Property =
DependencyProperty.Register(nameof({{propertyName}}), typeof({{type}}), typeof({{className}}), new PropertyMetadata(({{type}}){{defaultValue}}));
public {{type}} {{propertyName}}
{
get => ({{type}})GetValue({{propertyName}}Property);
set => SetValue({{propertyName}}Property, value);
}
}
""";
string normalizedClassName = context2.Symbol.ToDisplayString().Replace('<', '{').Replace('>', '}');
production.AddSource($"{normalizedClassName}.{propertyName}.g.cs", code);
}
}
private static string GetLiteralString(TypedConstant typedConstant)
{
if (typedConstant.Value is bool boolValue)
{
return boolValue ? "true" : "false";
}
return typedConstant.Value!.ToString();
}
}

View File

@@ -10,14 +10,6 @@
<Configurations>Debug;Release;Debug As Fake Elevated</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Optimize>True</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug As Fake Elevated|x64'">
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<None Remove="stylecop.json" />
</ItemGroup>

View File

@@ -10,29 +10,10 @@ namespace Snap.Hutao.Control.Behavior;
/// 按给定比例自动调整高度的行为
/// </summary>
[HighQuality]
internal sealed class AutoHeightBehavior : BehaviorBase<FrameworkElement>
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
internal sealed partial class AutoHeightBehavior : BehaviorBase<FrameworkElement>
{
private static readonly DependencyProperty TargetWidthProperty = Property<AutoHeightBehavior>.DependBoxed<double>(nameof(TargetWidth), BoxedValues.DoubleOne);
private static readonly DependencyProperty TargetHeightProperty = Property<AutoHeightBehavior>.DependBoxed<double>(nameof(TargetHeight), BoxedValues.DoubleOne);
/// <summary>
/// 目标宽度
/// </summary>
public double TargetWidth
{
get => (double)GetValue(TargetWidthProperty);
set => SetValue(TargetWidthProperty, value);
}
/// <summary>
/// 目标高度
/// </summary>
public double TargetHeight
{
get => (double)GetValue(TargetHeightProperty);
set => SetValue(TargetHeightProperty, value);
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{

View File

@@ -10,29 +10,10 @@ namespace Snap.Hutao.Control.Behavior;
/// 按给定比例自动调整高度的行为
/// </summary>
[HighQuality]
internal sealed class AutoWidthBehavior : BehaviorBase<FrameworkElement>
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
internal sealed partial class AutoWidthBehavior : BehaviorBase<FrameworkElement>
{
private static readonly DependencyProperty TargetWidthProperty = Property<AutoWidthBehavior>.DependBoxed<double>(nameof(TargetWidth), BoxedValues.DoubleOne);
private static readonly DependencyProperty TargetHeightProperty = Property<AutoWidthBehavior>.DependBoxed<double>(nameof(TargetHeight), BoxedValues.DoubleOne);
/// <summary>
/// 目标宽度
/// </summary>
public double TargetWidth
{
get => (double)GetValue(TargetWidthProperty);
set => SetValue(TargetWidthProperty, value);
}
/// <summary>
/// 目标高度
/// </summary>
public double TargetHeight
{
get => (double)GetValue(TargetHeightProperty);
set => SetValue(TargetHeightProperty, value);
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{

View File

@@ -10,30 +10,10 @@ namespace Snap.Hutao.Control.Behavior;
/// 在元素加载完成后执行命令的行为
/// </summary>
[HighQuality]
internal sealed class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
[DependencyProperty("Command", typeof(ICommand))]
[DependencyProperty("CommandParameter", typeof(object))]
internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
{
private static readonly DependencyProperty CommandProperty = Property<InvokeCommandOnLoadedBehavior>.Depend<ICommand>(nameof(Command));
private static readonly DependencyProperty CommandParameterProperty = Property<InvokeCommandOnLoadedBehavior>.Depend<object>(nameof(CommandParameter));
/// <summary>
/// 待执行的命令
/// </summary>
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
/// <summary>
/// 命令参数
/// </summary>
[MaybeNull]
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{

View File

@@ -12,16 +12,7 @@ namespace Snap.Hutao.Control;
/// when object is not used anymore.
/// </summary>
[HighQuality]
internal sealed class BindingProxy : DependencyObject
[DependencyProperty("DataContext", typeof(object))]
internal sealed partial class BindingProxy : DependencyObject
{
private static readonly DependencyProperty DataContextProperty = Property<BindingProxy>.Depend<object>(nameof(DataContext));
/// <summary>
/// 数据上下文
/// </summary>
public object? DataContext
{
get => GetValue(DataContextProperty);
set => SetValue(DataContextProperty, value);
}
}

View File

@@ -17,34 +17,13 @@ internal static class ContentDialogExtension
/// <param name="contentDialog">对话框</param>
/// <param name="taskContext">任务上下文</param>
/// <returns>用于恢复用户交互</returns>
public static async ValueTask<IDisposable> BlockAsync(this ContentDialog contentDialog, ITaskContext taskContext)
public static async ValueTask<ContentDialogHideToken> BlockAsync(this ContentDialog contentDialog, ITaskContext taskContext)
{
await taskContext.SwitchToMainThreadAsync();
contentDialog.ShowAsync().AsTask().SafeForget();
// E_ASYNC_OPERATION_NOT_STARTED 0x80000019
// Only a single ContentDialog can be open at any time.
return new ContentDialogHider(contentDialog, taskContext);
}
}
[SuppressMessage("", "SA1201")]
[SuppressMessage("", "SA1400")]
[SuppressMessage("", "SA1600")]
file readonly struct ContentDialogHider : IDisposable
{
private readonly ContentDialog contentDialog;
private readonly ITaskContext taskContext;
public ContentDialogHider(ContentDialog contentDialog, ITaskContext taskContext)
{
this.contentDialog = contentDialog;
this.taskContext = taskContext;
}
public void Dispose()
{
// Hide() must be called on main thread.
taskContext.InvokeOnMainThread(contentDialog.Hide);
return new ContentDialogHideToken(contentDialog, taskContext);
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Extension;
internal readonly struct ContentDialogHideToken : IDisposable
{
private readonly ContentDialog contentDialog;
private readonly ITaskContext taskContext;
public ContentDialogHideToken(ContentDialog contentDialog, ITaskContext taskContext)
{
this.contentDialog = contentDialog;
this.taskContext = taskContext;
}
public void Dispose()
{
// Hide() must be called on main thread.
taskContext.InvokeOnMainThread(contentDialog.Hide);
}
}

View File

@@ -20,10 +20,10 @@ namespace Snap.Hutao.Control.Image;
/// 为其他图像类控件提供基类
/// </summary>
[HighQuality]
internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
[DependencyProperty("EnableLazyLoading", typeof(bool), true)]
internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Control
{
private static readonly DependencyProperty SourceProperty = Property<CompositionImage>.Depend(nameof(Source), default(Uri), OnSourceChanged);
private static readonly DependencyProperty EnableLazyLoadingProperty = Property<CompositionImage>.DependBoxed<bool>(nameof(EnableLazyLoading), BoxedValues.True);
private static readonly ConcurrentCancellationTokenSource<CompositionImage> LoadingTokenSource = new();
private readonly IServiceProvider serviceProvider;
@@ -57,15 +57,6 @@ internal abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
set => SetValue(SourceProperty, value);
}
/// <summary>
/// 启用延迟加载
/// </summary>
public bool EnableLazyLoading
{
get => (bool)GetValue(EnableLazyLoadingProperty);
set => SetValue(EnableLazyLoadingProperty, value);
}
/// <summary>
/// 合成组合视觉
/// </summary>

View File

@@ -13,31 +13,12 @@ namespace Snap.Hutao.Control.Image;
/// 渐变图像
/// </summary>
[HighQuality]
internal sealed class Gradient : CompositionImage
[DependencyProperty("BackgroundDirection", typeof(GradientDirection), GradientDirection.TopToBottom)]
[DependencyProperty("ForegroundDirection", typeof(GradientDirection), GradientDirection.TopToBottom)]
internal sealed partial class Gradient : CompositionImage
{
private static readonly DependencyProperty BackgroundDirectionProperty = Property<Gradient>.Depend(nameof(BackgroundDirection), GradientDirection.TopToBottom);
private static readonly DependencyProperty ForegroundDirectionProperty = Property<Gradient>.Depend(nameof(ForegroundDirection), GradientDirection.TopToBottom);
private double imageAspectRatio;
/// <summary>
/// 背景方向
/// </summary>
public GradientDirection BackgroundDirection
{
get => (GradientDirection)GetValue(BackgroundDirectionProperty);
set => SetValue(BackgroundDirectionProperty, value);
}
/// <summary>
/// 前景方向
/// </summary>
public GradientDirection ForegroundDirection
{
get => (GradientDirection)GetValue(ForegroundDirectionProperty);
set => SetValue(ForegroundDirectionProperty, value);
}
/// <inheritdoc/>
protected override void OnUpdateVisual(SpriteVisual spriteVisual)
{

View File

@@ -10,31 +10,12 @@ namespace Snap.Hutao.Control.Panel;
/// 纵横比控件
/// </summary>
[HighQuality]
internal class AspectRatio : Microsoft.UI.Xaml.Controls.Control
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
internal sealed partial class AspectRatio : Microsoft.UI.Xaml.Controls.Control
{
private const double Epsilon = 2.2204460492503131e-016;
private static readonly DependencyProperty TargetWidthProperty = Property<AspectRatio>.DependBoxed<double>(nameof(TargetWidth), BoxedValues.DoubleOne);
private static readonly DependencyProperty TargetHeightProperty = Property<AspectRatio>.DependBoxed<double>(nameof(TargetHeight), BoxedValues.DoubleOne);
/// <summary>
/// 目标宽度
/// </summary>
public double TargetWidth
{
get => (double)GetValue(TargetWidthProperty);
set => SetValue(TargetWidthProperty, value);
}
/// <summary>
/// 目标高度
/// </summary>
public double TargetHeight
{
get => (double)GetValue(TargetHeightProperty);
set => SetValue(TargetHeightProperty, value);
}
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{

View File

@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Annotation;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
internal sealed class DependencyPropertyAttribute : Attribute
{
public DependencyPropertyAttribute(string name, Type type)
{
}
public DependencyPropertyAttribute(string name, Type type, object defaultValue)
{
}
}

View File

@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Service;
using System.Globalization;
using System.Runtime.CompilerServices;
using Windows.Globalization;
namespace Snap.Hutao.Core.DependencyInjection;
@@ -46,6 +47,7 @@ internal static class DependencyInjection
return serviceProvider;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IServiceProvider InitializeCulture(this IServiceProvider serviceProvider)
{
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();

View File

@@ -18,6 +18,7 @@ internal static class EnumerableServiceExtension
/// <param name="services">服务集合</param>
/// <param name="name">名称</param>
/// <returns>对应的服务</returns>
[Obsolete("该方法会导致不必要的服务实例化")]
public static TService Pick<TService>(this IEnumerable<TService> services, string name)
where TService : INamedService
{
@@ -31,6 +32,7 @@ internal static class EnumerableServiceExtension
/// <param name="services">服务集合</param>
/// <param name="isOversea">是否为海外服/Hoyolab</param>
/// <returns>对应的服务</returns>
[Obsolete("该方法会导致不必要的服务实例化")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TService Pick<TService>(this IEnumerable<TService> services, bool isOversea)
where TService : IOverseaSupport
@@ -45,6 +47,7 @@ internal static class EnumerableServiceExtension
/// <param name="serviceProvider">服务提供器</param>
/// <param name="isOversea">是否为海外服/Hoyolab</param>
/// <returns>对应的服务</returns>
[Obsolete("该方法会导致不必要的服务实例化")]
public static TService PickRequiredService<TService>(this IServiceProvider serviceProvider, bool isOversea)
where TService : IOverseaSupport
{

View File

@@ -87,6 +87,9 @@ internal static class AvatarIds
public static readonly AvatarId Mika = 10000080;
public static readonly AvatarId Kaveh = 10000081;
public static readonly AvatarId Baizhuer = 10000082;
public static readonly AvatarId Lynette = 10000083;
public static readonly AvatarId Lyney = 10000084;
public static readonly AvatarId Freminet = 10000085;
/// <summary>
/// 检查该角色是否为主角

View File

@@ -114,17 +114,6 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
}
}
private async Task RefreshUidCollectionAsync()
{
Response<List<string>> resp = await hutaoCloudService.GetUidsAsync().ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync();
if (Options.IsCloudServiceAllowed = resp.IsOk())
{
Uids = resp.Data!.ToObservableCollection();
}
}
[Command("NavigateToSpiralAbyssRecordCommand")]
private void NavigateToSpiralAbyssRecord()
{
@@ -138,4 +127,15 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
{
await Windows.System.Launcher.LaunchUriAsync("https://afdian.net/item/80d3b9decf9011edb5f452540025c377".ToUri());
}
private async Task RefreshUidCollectionAsync()
{
Response<List<string>> resp = await hutaoCloudService.GetUidsAsync().ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync();
if (Options.IsCloudServiceAllowed = resp.IsOk())
{
Uids = resp.Data!.ToObservableCollection();
}
}
}