mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Add experimental features
This commit is contained in:
@@ -29,6 +29,32 @@ dotnet_style_qualification_for_event = false:silent
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
dotnet_code_quality_unused_parameters = non_public:suggestion
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = false:silent
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = false:silent
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
dotnet_style_namespace_match_folder = true:suggestion
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||
dotnet_style_predefined_type_for_member_access = true:silent
|
||||
[*.cs]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -96,6 +122,31 @@ dotnet_diagnostic.SA1414.severity = none
|
||||
|
||||
# SA0001: XML comment analysis disabled
|
||||
dotnet_diagnostic.SA0001.severity = none
|
||||
csharp_style_prefer_parameter_null_checking = true:suggestion
|
||||
csharp_style_prefer_method_group_conversion = true:silent
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = false:silent
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:silent
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
csharp_style_prefer_pattern_matching = true:silent
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_prefer_not_pattern = true:suggestion
|
||||
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||
csharp_prefer_simple_default_expression = false:suggestion
|
||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||
csharp_style_prefer_tuple_swap = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:silent
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -11,7 +11,6 @@ using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using System.Diagnostics;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
|
||||
@@ -32,10 +31,12 @@ public partial class App : Application
|
||||
InitializeComponent();
|
||||
InitializeDependencyInjection();
|
||||
|
||||
// notice that we already call InitializeDependencyInjection() above
|
||||
// Notice that we already call InitializeDependencyInjection() above
|
||||
// so we can use Ioc here.
|
||||
logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
||||
|
||||
UnhandledException += AppUnhandledException;
|
||||
DebugSettings.BindingFailed += XamlBindingFailed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -44,17 +45,19 @@ public partial class App : Application
|
||||
public static Window? Window { get => window; set => window = value; }
|
||||
|
||||
/// <inheritdoc cref="Application"/>
|
||||
public static new App Current
|
||||
{
|
||||
get => (App)Application.Current;
|
||||
}
|
||||
public static new App Current => (App)Application.Current;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="Windows.Storage.ApplicationData.Current"/>
|
||||
/// </summary>
|
||||
public static Windows.Storage.ApplicationData AppData => Windows.Storage.ApplicationData.Current;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
[SuppressMessage("", "VSTHRD100")]
|
||||
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||
AppInstance mainInstance = AppInstance.FindOrRegisterForKey("main");
|
||||
@@ -67,28 +70,21 @@ public partial class App : Application
|
||||
}
|
||||
else
|
||||
{
|
||||
Uri? uri = null;
|
||||
if (activatedEventArgs.Kind == ExtendedActivationKind.Protocol)
|
||||
{
|
||||
IProtocolActivatedEventArgs protocolArgs = (activatedEventArgs.Data as IProtocolActivatedEventArgs)!;
|
||||
uri = protocolArgs.Uri;
|
||||
}
|
||||
|
||||
Window = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
Window.Activate();
|
||||
|
||||
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", Windows.Storage.ApplicationData.Current.TemporaryFolder.Path);
|
||||
if (activatedEventArgs.TryGetProtocolActivatedUri(out Uri? uri))
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IInfoBarService>().Information(uri.ToString());
|
||||
}
|
||||
|
||||
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", AppData.TemporaryFolder.Path);
|
||||
|
||||
Ioc.Default
|
||||
.GetRequiredService<IMetadataService>()
|
||||
.As<IMetadataInitializer>()?
|
||||
.InitializeInternalAsync()
|
||||
.SafeForget(logger: logger);
|
||||
|
||||
if (uri != null)
|
||||
{
|
||||
Ioc.Default.GetRequiredService<IInfoBarService>().Information(uri.ToString());
|
||||
}
|
||||
.SafeForget(logger);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,4 +116,9 @@ public partial class App : Application
|
||||
{
|
||||
logger.LogError(EventIds.UnhandledException, e.Exception, "未经处理的异常");
|
||||
}
|
||||
|
||||
private void XamlBindingFailed(object sender, BindingFailedEventArgs e)
|
||||
{
|
||||
logger.LogCritical(EventIds.XamlBindingError, "XAML绑定失败: {message}", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Context.FileSystem.Location;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace Snap.Hutao.Context.FileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存目录上下文
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class CacheContext : FileSystemContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的缓存目录上下文
|
||||
/// </summary>
|
||||
/// <param name="cache">缓存位置</param>
|
||||
public CacheContext(Cache cache)
|
||||
: base(cache)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取缓存文件夹
|
||||
/// </summary>
|
||||
/// <param name="folderName">文件夹名称</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>缓存文件夹</returns>
|
||||
public static Task<StorageFolder> GetFolderAsync(string folderName, CancellationToken token)
|
||||
{
|
||||
StorageFolder tempstate = ApplicationData.Current.TemporaryFolder;
|
||||
return tempstate.CreateFolderAsync(folderName, CreationCollisionOption.OpenIfExists).AsTask(token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取缓存文件的名称
|
||||
/// </summary>
|
||||
/// <param name="uri">uri</param>
|
||||
/// <returns>缓存文件的名称</returns>
|
||||
public static string GetCacheFileName(Uri uri)
|
||||
{
|
||||
return CreateHash64(uri.ToString()).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取缓存文件的名称
|
||||
/// </summary>
|
||||
/// <param name="url">url</param>
|
||||
/// <returns>缓存文件的名称</returns>
|
||||
public static string GetCacheFileName(string url)
|
||||
{
|
||||
return CreateHash64(url).ToString();
|
||||
}
|
||||
|
||||
private static ulong CreateHash64(string str)
|
||||
{
|
||||
byte[] utf8 = System.Text.Encoding.UTF8.GetBytes(str);
|
||||
|
||||
ulong value = (ulong)utf8.Length;
|
||||
for (int n = 0; n < utf8.Length; n++)
|
||||
{
|
||||
value += (ulong)utf8[n] << ((n * 5) % 56);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Context.FileSystem.Location;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存位置
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class Cache : IFileSystemLocation
|
||||
{
|
||||
private string? path;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetPath()
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
path = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
131
src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs
Normal file
131
src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Animations;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Snap.Hutao.Control.Image;
|
||||
|
||||
/// <summary>
|
||||
/// 合成图像控件
|
||||
/// 为其他图像类控件提供基类
|
||||
/// </summary>
|
||||
public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
private static readonly DependencyProperty SourceProperty = Property<CompositionImage>.Depend(nameof(Source), default(Uri), OnSourceChanged);
|
||||
private static readonly ConcurrentCancellationTokenSource<CompositionImage> LoadingTokenSource = new();
|
||||
|
||||
private SpriteVisual? spriteVisual;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的单色图像
|
||||
/// </summary>
|
||||
public CompositionImage()
|
||||
{
|
||||
IsTabStop = false;
|
||||
SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 源
|
||||
/// </summary>
|
||||
public Uri Source
|
||||
{
|
||||
get => (Uri)GetValue(SourceProperty);
|
||||
set => SetValue(SourceProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合成组合视觉
|
||||
/// </summary>
|
||||
/// <param name="compositor">合成器</param>
|
||||
/// <param name="imageSurface">图像表面</param>
|
||||
/// <returns>拼合视觉</returns>
|
||||
protected abstract SpriteVisual CompositeSpriteVisual(Compositor compositor, LoadedImageSurface imageSurface);
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载图像表面
|
||||
/// </summary>
|
||||
/// <param name="storageFile">文件</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>加载的图像表面</returns>
|
||||
protected virtual async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
|
||||
{
|
||||
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token))
|
||||
{
|
||||
return LoadedImageSurface.StartLoadFromStream(imageStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新视觉对象
|
||||
/// </summary>
|
||||
/// <param name="spriteVisual">拼合视觉</param>
|
||||
protected virtual void OnUpdateVisual(SpriteVisual spriteVisual)
|
||||
{
|
||||
spriteVisual.Size = ActualSize;
|
||||
}
|
||||
|
||||
private static Task<StorageFile> GetCachedFileAsync(Uri uri)
|
||||
{
|
||||
return Ioc.Default.GetRequiredService<IImageCache>().GetFileFromCacheAsync(uri);
|
||||
}
|
||||
|
||||
private static void OnApplyImageFailed(Exception exception)
|
||||
{
|
||||
Ioc.Default
|
||||
.GetRequiredService<IInfoBarService>()
|
||||
.Error(exception, "应用合成图像时发生异常");
|
||||
}
|
||||
|
||||
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
|
||||
{
|
||||
if (arg.NewValue is Uri uri && uri != (arg.OldValue as Uri) && !string.IsNullOrEmpty(uri.Host))
|
||||
{
|
||||
CompositionImage image = (CompositionImage)sender;
|
||||
ILogger<CompositionImage> logger = Ioc.Default.GetRequiredService<ILogger<CompositionImage>>();
|
||||
image.ApplyImageInternalAsync(uri, LoadingTokenSource.Register(image)).SafeForget(logger, OnApplyImageFailed);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyImageInternalAsync(Uri? uri, CancellationToken token)
|
||||
{
|
||||
await AnimationBuilder.Create().Opacity(0d).StartAsync(this, token);
|
||||
|
||||
if (uri is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StorageFile storageFile = await GetCachedFileAsync(uri);
|
||||
|
||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||
|
||||
LoadedImageSurface imageSurface = await LoadImageSurfaceAsync(storageFile, token);
|
||||
|
||||
spriteVisual = CompositeSpriteVisual(compositor, imageSurface);
|
||||
OnUpdateVisual(spriteVisual);
|
||||
|
||||
ElementCompositionPreview.SetElementChildVisual(this, spriteVisual);
|
||||
|
||||
await AnimationBuilder.Create().Opacity(1d).StartAsync(this, token);
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (e.NewSize != e.PreviousSize && spriteVisual is not null)
|
||||
{
|
||||
OnUpdateVisual(spriteVisual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Animations;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using System.Numerics;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage;
|
||||
@@ -22,61 +15,12 @@ namespace Snap.Hutao.Control.Image;
|
||||
/// <summary>
|
||||
/// 支持渐变的图像
|
||||
/// </summary>
|
||||
public class Gradient : Microsoft.UI.Xaml.Controls.Control
|
||||
public class Gradient : CompositionImage
|
||||
{
|
||||
private static readonly DependencyProperty SourceProperty = Property<Gradient>.Depend(nameof(Source), string.Empty, OnSourceChanged);
|
||||
private static readonly ConcurrentCancellationTokenSource<Gradient> LoadingTokenSource = new();
|
||||
|
||||
private SpriteVisual? spriteVisual;
|
||||
private double imageAspectRatio;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的渐变图像
|
||||
/// </summary>
|
||||
public Gradient()
|
||||
{
|
||||
SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 源
|
||||
/// </summary>
|
||||
public string Source
|
||||
{
|
||||
get => (string)GetValue(SourceProperty);
|
||||
set => SetValue(SourceProperty, value);
|
||||
}
|
||||
|
||||
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
|
||||
{
|
||||
Gradient gradient = (Gradient)sender;
|
||||
string url = (string)arg.NewValue;
|
||||
|
||||
ILogger<Gradient> logger = Ioc.Default.GetRequiredService<ILogger<Gradient>>();
|
||||
gradient.ApplyImageAsync(url, LoadingTokenSource.Register(gradient)).SafeForget(logger, OnApplyImageFailed);
|
||||
}
|
||||
|
||||
private static void OnApplyImageFailed(Exception exception)
|
||||
{
|
||||
Ioc.Default
|
||||
.GetRequiredService<IInfoBarService>()
|
||||
.Error(exception, "应用渐变图像时发生异常");
|
||||
}
|
||||
|
||||
private static Task<StorageFile?> GetCachedFileAsync(string url)
|
||||
{
|
||||
return Ioc.Default.GetRequiredService<IImageCache>().GetFileFromCacheAsync(new(url));
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (e.NewSize != e.PreviousSize && spriteVisual is not null)
|
||||
{
|
||||
UpdateVisual(spriteVisual);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisual(SpriteVisual spriteVisual)
|
||||
/// <inheritdoc/>
|
||||
protected override void OnUpdateVisual(SpriteVisual spriteVisual)
|
||||
{
|
||||
if (spriteVisual is not null)
|
||||
{
|
||||
@@ -85,34 +29,8 @@ public class Gradient : Microsoft.UI.Xaml.Controls.Control
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyImageAsync(string url, CancellationToken token)
|
||||
{
|
||||
await AnimationBuilder.Create().Opacity(0d).StartAsync(this, token);
|
||||
|
||||
StorageFile? storageFile = Must.NotNull((await GetCachedFileAsync(url))!);
|
||||
|
||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||
|
||||
LoadedImageSurface imageSurface = await LoadImageSurfaceAsync(storageFile, token);
|
||||
|
||||
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.UniformToFill, vRatio: 0f);
|
||||
|
||||
CompositionLinearGradientBrush backgroundBrush = compositor.CompositeLinearGradientBrush(new(1f, 0), Vector2.UnitY, new(0, Colors.White), new(1, Colors.Black));
|
||||
CompositionLinearGradientBrush foregroundBrush = compositor.CompositeLinearGradientBrush(Vector2.Zero, Vector2.UnitY, new(0, Colors.White), new(0.95f, Colors.Black));
|
||||
|
||||
CompositionEffectBrush gradientEffectBrush = compositor.CompositeBlendEffectBrush(backgroundBrush, foregroundBrush);
|
||||
CompositionEffectBrush opacityMaskEffectBrush = compositor.CompositeLuminanceToAlphaEffectBrush(gradientEffectBrush);
|
||||
CompositionEffectBrush alphaMaskEffectBrush = compositor.CompositeAlphaMaskEffectBrush(imageSurfaceBrush, opacityMaskEffectBrush);
|
||||
|
||||
spriteVisual = compositor.CompositeSpriteVisual(alphaMaskEffectBrush);
|
||||
UpdateVisual(spriteVisual);
|
||||
|
||||
ElementCompositionPreview.SetElementChildVisual(this, spriteVisual);
|
||||
|
||||
await AnimationBuilder.Create().Opacity(1d).StartAsync(this, token);
|
||||
}
|
||||
|
||||
private async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
|
||||
{
|
||||
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token))
|
||||
{
|
||||
@@ -122,4 +40,19 @@ public class Gradient : Microsoft.UI.Xaml.Controls.Control
|
||||
return LoadedImageSurface.StartLoadFromStream(imageStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override SpriteVisual CompositeSpriteVisual(Compositor compositor, LoadedImageSurface imageSurface)
|
||||
{
|
||||
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.UniformToFill, vRatio: 0f);
|
||||
|
||||
CompositionLinearGradientBrush backgroundBrush = compositor.CompositeLinearGradientBrush(new(1f, 0), Vector2.UnitY, new(0, Colors.White), new(1, Colors.Black));
|
||||
CompositionLinearGradientBrush foregroundBrush = compositor.CompositeLinearGradientBrush(Vector2.Zero, Vector2.UnitY, new(0, Colors.White), new(0.95f, Colors.Black));
|
||||
|
||||
CompositionEffectBrush gradientEffectBrush = compositor.CompositeBlendEffectBrush(backgroundBrush, foregroundBrush);
|
||||
CompositionEffectBrush opacityMaskEffectBrush = compositor.CompositeLuminanceToAlphaEffectBrush(gradientEffectBrush);
|
||||
CompositionEffectBrush alphaMaskEffectBrush = compositor.CompositeAlphaMaskEffectBrush(imageSurfaceBrush, opacityMaskEffectBrush);
|
||||
|
||||
return compositor.CompositeSpriteVisual(alphaMaskEffectBrush);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Animations;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Snap.Hutao.Control.Image;
|
||||
|
||||
/// <summary>
|
||||
/// 支持单色的图像
|
||||
/// </summary>
|
||||
public class MonoChrome : Microsoft.UI.Xaml.Controls.Control
|
||||
public class MonoChrome : CompositionImage
|
||||
{
|
||||
private static readonly DependencyProperty SourceProperty = Property<MonoChrome>.Depend(nameof(Source), string.Empty, OnSourceChanged);
|
||||
private static readonly ConcurrentCancellationTokenSource<MonoChrome> LoadingTokenSource = new();
|
||||
|
||||
private SpriteVisual? spriteVisual;
|
||||
private CompositionColorBrush? backgroundBrush;
|
||||
|
||||
/// <summary>
|
||||
@@ -35,55 +22,23 @@ public class MonoChrome : Microsoft.UI.Xaml.Controls.Control
|
||||
/// </summary>
|
||||
public MonoChrome()
|
||||
{
|
||||
SizeChanged += OnSizeChanged;
|
||||
ActualThemeChanged += OnActualThemeChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 源
|
||||
/// </summary>
|
||||
public string Source
|
||||
/// <inheritdoc/>
|
||||
protected override SpriteVisual CompositeSpriteVisual(Compositor compositor, LoadedImageSurface imageSurface)
|
||||
{
|
||||
get => (string)GetValue(SourceProperty);
|
||||
set => SetValue(SourceProperty, value);
|
||||
}
|
||||
CompositionColorBrush blackLayerBursh = compositor.CreateColorBrush(Colors.Black);
|
||||
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.Uniform, vRatio: 0f);
|
||||
|
||||
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
|
||||
{
|
||||
MonoChrome monoChrome = (MonoChrome)sender;
|
||||
string url = (string)arg.NewValue;
|
||||
CompositionEffectBrush overlayBrush = compositor.CompositeBlendEffectBrush(blackLayerBursh, imageSurfaceBrush, BlendEffectMode.Overlay);
|
||||
CompositionEffectBrush opacityBrush = compositor.CompositeLuminanceToAlphaEffectBrush(overlayBrush);
|
||||
|
||||
ILogger<MonoChrome> logger = Ioc.Default.GetRequiredService<ILogger<MonoChrome>>();
|
||||
monoChrome.ApplyImageAsync(url, LoadingTokenSource.Register(monoChrome)).SafeForget(logger, OnApplyImageFailed);
|
||||
}
|
||||
backgroundBrush = compositor.CreateColorBrush();
|
||||
SetBackgroundColor(backgroundBrush);
|
||||
CompositionEffectBrush alphaMaskEffectBrush = compositor.CompositeAlphaMaskEffectBrush(backgroundBrush, opacityBrush);
|
||||
|
||||
private static void OnApplyImageFailed(Exception exception)
|
||||
{
|
||||
Ioc.Default
|
||||
.GetRequiredService<IInfoBarService>()
|
||||
.Error(exception, "应用单色背景时发生异常");
|
||||
}
|
||||
|
||||
private static async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
|
||||
{
|
||||
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token))
|
||||
{
|
||||
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream).AsTask(token);
|
||||
return LoadedImageSurface.StartLoadFromStream(imageStream);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<StorageFile?> GetCachedFileAsync(string url)
|
||||
{
|
||||
return Ioc.Default.GetRequiredService<IImageCache>().GetFileFromCacheAsync(new(url));
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (e.NewSize != e.PreviousSize && spriteVisual is not null)
|
||||
{
|
||||
UpdateVisual(spriteVisual);
|
||||
}
|
||||
return compositor.CompositeSpriteVisual(alphaMaskEffectBrush);
|
||||
}
|
||||
|
||||
private void OnActualThemeChanged(FrameworkElement sender, object args)
|
||||
@@ -94,14 +49,6 @@ public class MonoChrome : Microsoft.UI.Xaml.Controls.Control
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisual(SpriteVisual spriteVisual)
|
||||
{
|
||||
if (spriteVisual is not null)
|
||||
{
|
||||
spriteVisual.Size = ActualSize;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetBackgroundColor(CompositionColorBrush backgroundBrush)
|
||||
{
|
||||
ApplicationTheme theme = ActualTheme switch
|
||||
@@ -118,32 +65,4 @@ public class MonoChrome : Microsoft.UI.Xaml.Controls.Control
|
||||
_ => throw Must.NeverHappen(),
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ApplyImageAsync(string url, CancellationToken token)
|
||||
{
|
||||
await AnimationBuilder.Create().Opacity(0d).StartAsync(this, token);
|
||||
|
||||
StorageFile? storageFile = Must.NotNull((await GetCachedFileAsync(url))!);
|
||||
|
||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||
|
||||
LoadedImageSurface imageSurface = await LoadImageSurfaceAsync(storageFile, token);
|
||||
|
||||
CompositionColorBrush blackLayerBursh = compositor.CreateColorBrush(Colors.Black);
|
||||
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.Uniform, vRatio: 0f);
|
||||
|
||||
CompositionEffectBrush overlayBrush = compositor.CompositeBlendEffectBrush(blackLayerBursh, imageSurfaceBrush, BlendEffectMode.Overlay);
|
||||
CompositionEffectBrush opacityBrush = compositor.CompositeLuminanceToAlphaEffectBrush(overlayBrush);
|
||||
|
||||
backgroundBrush = compositor.CreateColorBrush();
|
||||
SetBackgroundColor(backgroundBrush);
|
||||
CompositionEffectBrush alphaMaskEffectBrush = compositor.CompositeAlphaMaskEffectBrush(backgroundBrush, opacityBrush);
|
||||
|
||||
spriteVisual = compositor.CompositeSpriteVisual(alphaMaskEffectBrush);
|
||||
UpdateVisual(spriteVisual);
|
||||
|
||||
ElementCompositionPreview.SetElementChildVisual(this, spriteVisual);
|
||||
|
||||
await AnimationBuilder.Create().Opacity(1d).StartAsync(this, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,6 @@ public abstract class CacheBase<T>
|
||||
/// <returns>a StorageFile</returns>
|
||||
public async Task<StorageFile> GetFileFromCacheAsync(Uri uri)
|
||||
{
|
||||
logger.LogInformation(EventIds.FileCaching, "Begin caching for [{uri}]", uri);
|
||||
StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false);
|
||||
|
||||
string fileName = GetCacheFileName(uri);
|
||||
@@ -196,12 +195,16 @@ public abstract class CacheBase<T>
|
||||
|
||||
private async Task DownloadFileAsync(Uri uri, StorageFile baseFile)
|
||||
{
|
||||
logger.LogInformation(EventIds.FileCaching, "Begin downloading for {uri}", uri);
|
||||
|
||||
using (Stream httpStream = await httpClient.GetStreamAsync(uri))
|
||||
{
|
||||
using (Stream fs = await baseFile.OpenStreamForWriteAsync())
|
||||
using (Stream fileStream = await baseFile.OpenStreamForWriteAsync())
|
||||
{
|
||||
await httpStream.CopyToAsync(fs);
|
||||
await fs.FlushAsync();
|
||||
await httpStream.CopyToAsync(fileStream);
|
||||
|
||||
// Call this before dispose fileStream.
|
||||
await fileStream.FlushAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,11 @@ internal static class EventIds
|
||||
/// </summary>
|
||||
public static readonly EventId CacheException = 100004;
|
||||
|
||||
/// <summary>
|
||||
/// Xaml绑定错误
|
||||
/// </summary>
|
||||
public static readonly EventId XamlBindingError = 100005;
|
||||
|
||||
// 服务
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
|
||||
namespace Snap.Hutao.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="AppActivationArguments"/> 扩展
|
||||
/// </summary>
|
||||
public static class AppActivationArgumentsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 尝试获取协议启动的Uri
|
||||
/// </summary>
|
||||
/// <param name="activatedEventArgs">应用程序激活参数</param>
|
||||
/// <param name="uri">协议Uri</param>
|
||||
/// <returns>是否存在协议Uri</returns>
|
||||
public static bool TryGetProtocolActivatedUri(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out Uri? uri)
|
||||
{
|
||||
uri = null;
|
||||
if (activatedEventArgs.Kind == ExtendedActivationKind.Protocol)
|
||||
{
|
||||
IProtocolActivatedEventArgs protocolArgs = (activatedEventArgs.Data as IProtocolActivatedEventArgs)!;
|
||||
uri = protocolArgs.Uri;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -20,3 +20,4 @@ public static class PackageVersionExtensions
|
||||
return new Version(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Context.FileSystem;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Model.Metadata.Achievement;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
@@ -80,7 +81,7 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
|
||||
public async Task InitializeInternalAsync(CancellationToken token = default)
|
||||
{
|
||||
logger.LogInformation(EventIds.MetadataInitialization, "Metadata initializaion begin");
|
||||
|
||||
ValueStopwatch stopwatch = ValueStopwatch.StartNew();
|
||||
IsInitialized = await TryUpdateMetadataAsync(token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<AppxBundle>Never</AppxBundle>
|
||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||
<StartupObject>Snap.Hutao.Program</StartupObject>
|
||||
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
|
||||
<DefineConstants>DISABLE_XAML_GENERATED_MAIN;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shcc="using:Snap.Hutao.Control.Cancellable"
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Converter"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<mxi:Interaction.Behaviors>
|
||||
@@ -24,15 +23,14 @@
|
||||
</mxi:Interaction.Behaviors>
|
||||
<shcc:CancellablePage.Resources>
|
||||
<cwuconv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
|
||||
|
||||
|
||||
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
|
||||
</shcc:CancellablePage.Resources>
|
||||
<Grid>
|
||||
<ScrollViewer Padding="0,0,4,0">
|
||||
<ItemsControl
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding Announcement.List}"
|
||||
ItemsSource="{Binding Announcement.List}"
|
||||
Padding="0"
|
||||
Margin="12,12,0,-12">
|
||||
<ItemsControl.ItemTemplate>
|
||||
@@ -50,7 +48,7 @@
|
||||
ItemsSource="{Binding List}"
|
||||
Margin="0,0,2,0">
|
||||
<cwucont:AdaptiveGridView.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource DefaultGridViewItemStyle}" TargetType="GridViewItem">
|
||||
<Style TargetType="GridViewItem" BasedOn="{StaticResource DefaultGridViewItemStyle}">
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
</cwucont:AdaptiveGridView.ItemContainerStyle>
|
||||
@@ -66,8 +64,7 @@
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!--Image Layer-->
|
||||
<Border
|
||||
cwu:UIElementExtensions.ClipToBounds="True">
|
||||
<Border cwu:UIElementExtensions.ClipToBounds="True">
|
||||
<Border
|
||||
VerticalAlignment="Top"
|
||||
cwu:VisualExtensions.NormalizedCenterPoint="0.5">
|
||||
@@ -77,11 +74,6 @@
|
||||
<shci:CachedImage
|
||||
Stretch="UniformToFill"
|
||||
Source="{Binding Banner}"/>
|
||||
<!--<Border.Background>
|
||||
<ImageBrush
|
||||
ImageSource="{Binding Banner}"
|
||||
Stretch="UniformToFill"/>
|
||||
</Border.Background>-->
|
||||
<cwua:Explicit.Animations>
|
||||
<cwua:AnimationSet x:Name="ImageZoomInAnimation">
|
||||
<shca:ImageZoomInAnimation/>
|
||||
@@ -149,6 +141,7 @@
|
||||
Opacity="0.8"
|
||||
Margin="4,4,4,4"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Visibility="{Binding ShouldShowTimeDescription,Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Text="{Binding TimeDescription}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:sc="using:SettingsUI.Controls"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
d:DataContext="{d:DesignInstance shv:SettingViewModel}">
|
||||
<Page.Resources>
|
||||
<Style TargetType="Expander">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DefaultButtonStyle}">
|
||||
<Setter Property="MinWidth" Value="160"/>
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
<ScrollViewer>
|
||||
<StackPanel
|
||||
Margin="32,0,24,0">
|
||||
<StackPanel Margin="32,0,24,0">
|
||||
|
||||
<sc:SettingsGroup Header="关于 胡桃">
|
||||
<sc:SettingExpander>
|
||||
@@ -35,7 +35,25 @@
|
||||
CornerRadius="0,0,4,4"/>
|
||||
</sc:SettingExpander>
|
||||
</sc:SettingsGroup>
|
||||
|
||||
|
||||
<sc:SettingsGroup Header="测试功能">
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="打开 数据 文件夹">
|
||||
<sc:Setting.ActionContent>
|
||||
<Button Content="打开" Command="{Binding Experimental.OpenDataFolderCommand}"/>
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
|
||||
<sc:Setting
|
||||
Icon=""
|
||||
Header="打开 缓存 文件夹">
|
||||
<sc:Setting.ActionContent>
|
||||
<Button Content="打开" Command="{Binding Experimental.OpenCacheFolderCommand}"/>
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
</sc:SettingsGroup>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Page>
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
<shci:Gradient
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Selected,Converter={StaticResource AvatarNameCardPicConverter}}"/>
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="0,0,16,16">
|
||||
<!--简介-->
|
||||
@@ -394,6 +395,7 @@
|
||||
</ScrollViewer>
|
||||
|
||||
</Expander>
|
||||
|
||||
<TextBlock Text="天赋" Style="{StaticResource BaseTextBlockStyle}" Margin="16,16,0,0"/>
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding Selected.SkillDepot.Skills}"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Windows.Storage;
|
||||
using Windows.System;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// 实验性功能视图模型
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class ExperimentalFeaturesViewModel : ObservableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的实验性功能视图模型
|
||||
/// </summary>
|
||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||
public ExperimentalFeaturesViewModel(IAsyncRelayCommandFactory asyncRelayCommandFactory)
|
||||
{
|
||||
OpenCacheFolderCommand = asyncRelayCommandFactory.Create(OpenCacheFolderAsync);
|
||||
OpenDataFolderCommand = asyncRelayCommandFactory.Create(OpenDataFolderAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开临时文件夹命令
|
||||
/// </summary>
|
||||
public ICommand OpenCacheFolderCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 打开数据文件夹命令
|
||||
/// </summary>
|
||||
public ICommand OpenDataFolderCommand { get; }
|
||||
|
||||
private Task OpenCacheFolderAsync()
|
||||
{
|
||||
return Launcher.LaunchFolderAsync(App.AppData.TemporaryFolder).AsTask();
|
||||
}
|
||||
|
||||
private async Task OpenDataFolderAsync()
|
||||
{
|
||||
StorageFolder folder = await KnownFolders.DocumentsLibrary.GetFolderAsync("Hutao").AsTask().ConfigureAwait(false);
|
||||
await Launcher.LaunchFolderAsync(folder).AsTask().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,27 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// 测试视图模型
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class SettingViewModel
|
||||
internal class SettingViewModel : ObservableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的测试视图模型
|
||||
/// </summary>
|
||||
public SettingViewModel()
|
||||
/// <param name="experimental">实验性功能</param>
|
||||
public SettingViewModel(ExperimentalFeaturesViewModel experimental)
|
||||
{
|
||||
Experimental = experimental;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实验性功能
|
||||
/// </summary>
|
||||
public ExperimentalFeaturesViewModel Experimental { get; }
|
||||
}
|
||||
Reference in New Issue
Block a user