refactor controls

This commit is contained in:
DismissedLight
2023-07-17 17:14:15 +08:00
parent f2fc5e443c
commit e9ee31a604
25 changed files with 292 additions and 150 deletions

View File

@@ -14,17 +14,24 @@ namespace Snap.Hutao.Control.Behavior;
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
internal sealed partial class AutoHeightBehavior : BehaviorBase<FrameworkElement>
{
private readonly SizeChangedEventHandler sizeChangedEventHandler;
public AutoHeightBehavior()
{
sizeChangedEventHandler = OnSizeChanged;
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
UpdateElement();
AssociatedObject.SizeChanged += OnSizeChanged;
AssociatedObject.SizeChanged += sizeChangedEventHandler;
}
/// <inheritdoc/>
protected override void OnDetaching()
{
AssociatedObject.SizeChanged -= OnSizeChanged;
AssociatedObject.SizeChanged -= sizeChangedEventHandler;
base.OnDetaching();
}

View File

@@ -14,17 +14,24 @@ namespace Snap.Hutao.Control.Behavior;
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
internal sealed partial class AutoWidthBehavior : BehaviorBase<FrameworkElement>
{
private readonly SizeChangedEventHandler sizeChangedEventHandler;
public AutoWidthBehavior()
{
sizeChangedEventHandler = OnSizeChanged;
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
UpdateElement();
AssociatedObject.SizeChanged += OnSizeChanged;
AssociatedObject.SizeChanged += sizeChangedEventHandler;
}
/// <inheritdoc/>
protected override void OnDetaching()
{
AssociatedObject.SizeChanged -= OnSizeChanged;
AssociatedObject.SizeChanged -= sizeChangedEventHandler;
base.OnDetaching();
}

View File

@@ -14,6 +14,8 @@ namespace Snap.Hutao.Control.Behavior;
internal sealed class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBase<ComboBox>
{
private readonly IMessenger messenger;
private readonly EventHandler<object> dropDownOpenedHandler;
private readonly EventHandler<object> dropDownClosedHandler;
/// <summary>
/// AppTitleBar Workaround
@@ -21,31 +23,33 @@ internal sealed class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : Beh
public ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior()
{
messenger = Ioc.Default.GetRequiredService<IMessenger>();
dropDownOpenedHandler = OnDropDownOpened;
dropDownClosedHandler = OnDropDownClosed;
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
AssociatedObject.DropDownOpened += OnDropDownOpened;
AssociatedObject.DropDownClosed += OnDropDownClosed;
AssociatedObject.DropDownOpened += dropDownOpenedHandler;
AssociatedObject.DropDownClosed += dropDownClosedHandler;
}
/// <inheritdoc/>
protected override void OnDetaching()
{
AssociatedObject.DropDownOpened -= OnDropDownOpened;
AssociatedObject.DropDownClosed -= OnDropDownClosed;
AssociatedObject.DropDownOpened -= dropDownOpenedHandler;
AssociatedObject.DropDownClosed -= dropDownClosedHandler;
base.OnDetaching();
}
private void OnDropDownOpened(object? sender, object e)
{
messenger.Send(new Message.FlyoutOpenCloseMessage(true));
messenger.Send(Message.FlyoutStateChangedMessage.Open);
}
private void OnDropDownClosed(object? sender, object e)
{
messenger.Send(new Message.FlyoutOpenCloseMessage(false));
messenger.Send(Message.FlyoutStateChangedMessage.Close);
}
}

View File

@@ -17,6 +17,6 @@ internal sealed class OpenAttachedFlyoutAction : DependencyObject, IAction
public object Execute(object sender, object parameter)
{
FlyoutBase.ShowAttachedFlyout(sender as FrameworkElement);
return null!;
return default!;
}
}

View File

@@ -15,7 +15,7 @@ internal static class BoxedValues
public static readonly object DoubleZero = 0D;
/// <summary>
/// <see cref="double"/> 0
/// <see cref="double"/> 1
/// </summary>
public static readonly object DoubleOne = 1D;

View File

@@ -20,10 +20,10 @@ internal static class ContentDialogExtension
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.
contentDialog.ShowAsync().AsTask().SafeForget();
return new ContentDialogHideToken(contentDialog, taskContext);
}
}

View File

@@ -5,11 +5,14 @@ using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Extension;
internal readonly struct ContentDialogHideToken : IDisposable
internal struct ContentDialogHideToken : IDisposable, IAsyncDisposable
{
private readonly ContentDialog contentDialog;
private readonly ITaskContext taskContext;
private bool disposed = false;
private bool disposing = false;
public ContentDialogHideToken(ContentDialog contentDialog, ITaskContext taskContext)
{
this.contentDialog = contentDialog;
@@ -18,7 +21,24 @@ internal readonly struct ContentDialogHideToken : IDisposable
public void Dispose()
{
// Hide() must be called on main thread.
taskContext.InvokeOnMainThread(contentDialog.Hide);
if (!disposed && !disposing)
{
disposing = true;
taskContext.InvokeOnMainThread(contentDialog.Hide); // Hide() must be called on main thread.
disposing = false;
disposed = true;
}
}
public async ValueTask DisposeAsync()
{
if (!disposed && !disposing)
{
disposing = true;
await taskContext.SwitchToMainThreadAsync();
contentDialog.Hide();
disposing = false;
disposed = true;
}
}
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Extension;
internal static class DependencyObjectExtension
{
public static IServiceProvider ServiceProvider(this DependencyObject obj)
{
return Ioc.Default;
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
namespace Snap.Hutao.Control.Extension;
internal static class FrameworkElementExtension
{
/// <summary>
/// Make properties below false:
/// <code>
/// * AllowFocusOnInteraction
/// * IsDoubleTapEnabled
/// * IsHitTestVisible
/// * IsHoldingEnabled
/// * IsRightTapEnabled
/// * IsTabStop
/// </code>
/// </summary>
/// <param name="frameworkElement">元素</param>
public static void DisableInteraction(this FrameworkElement frameworkElement)
{
frameworkElement.AllowFocusOnInteraction = false;
frameworkElement.IsDoubleTapEnabled = false;
frameworkElement.IsHitTestVisible = false;
frameworkElement.IsHoldingEnabled = false;
frameworkElement.IsRightTapEnabled = false;
frameworkElement.IsTabStop = false;
}
}

View File

@@ -32,16 +32,10 @@ internal sealed class CachedImage : ImageEx
try
{
Verify.Operation(imageUri.Host != string.Empty, SH.ControlImageCachedImageInvalidResourceUri);
// BitmapImage need to be created by main thread.
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true);
// check token state to determine whether the operation should be canceled.
token.ThrowIfCancellationRequested();
// BitmapImage initialize with a uri will increase image quality and loading speed.
return new BitmapImage(file.ToUri());
Verify.Operation(!string.IsNullOrEmpty(imageUri.Host), SH.ControlImageCachedImageInvalidResourceUri);
string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread.
token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled.
return new BitmapImage(file.ToUri()); // BitmapImage initialize with a uri will increase image quality and loading speed.
}
catch (COMException)
{

View File

@@ -7,11 +7,13 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Animation;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Service.Notification;
using System.IO;
using System.Net.Http;
using System.Runtime.InteropServices;
using Windows.Foundation;
namespace Snap.Hutao.Control.Image;
@@ -28,6 +30,11 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
private readonly IServiceProvider serviceProvider;
private readonly RoutedEventHandler unloadEventHandler;
private readonly SizeChangedEventHandler sizeChangedEventHandler;
private readonly TypedEventHandler<LoadedImageSurface, LoadedImageSourceLoadCompletedEventArgs> loadedImageSourceLoadCompletedEventHandler;
private TaskCompletionSource? surfaceLoadTaskCompletionSource;
private SpriteVisual? spriteVisual;
private bool isShow = true;
@@ -36,16 +43,16 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
/// </summary>
protected CompositionImage()
{
serviceProvider = Ioc.Default;
serviceProvider = this.ServiceProvider();
this.DisableInteraction();
AllowFocusOnInteraction = false;
IsDoubleTapEnabled = false;
IsHitTestVisible = false;
IsHoldingEnabled = false;
IsRightTapEnabled = false;
IsTabStop = false;
unloadEventHandler = OnUnload;
Unloaded += unloadEventHandler;
SizeChanged += OnSizeChanged;
sizeChangedEventHandler = OnSizeChanged;
SizeChanged += sizeChangedEventHandler;
loadedImageSourceLoadCompletedEventHandler = OnLoadImageSurfaceLoadCompleted;
}
/// <summary>
@@ -65,26 +72,19 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
/// <returns>拼合视觉</returns>
protected abstract SpriteVisual CompositeSpriteVisual(Compositor compositor, LoadedImageSurface imageSurface);
/// <summary>
/// 异步加载图像表面
/// </summary>
/// <param name="file">文件</param>
/// <param name="token">取消令牌</param>
/// <returns>加载的图像表面</returns>
protected virtual async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
protected virtual void LoadImageSurfaceCompleted(LoadedImageSurface surface)
{
}
protected virtual void Unloading()
{
TaskCompletionSource loadCompleteTaskSource = new();
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri());
surface.LoadCompleted += (s, e) => loadCompleteTaskSource.TrySetResult();
await loadCompleteTaskSource.Task.ConfigureAwait(true);
return surface;
}
/// <summary>
/// 更新视觉对象
/// </summary>
/// <param name="spriteVisual">拼合视觉</param>
protected virtual void OnUpdateVisual(SpriteVisual spriteVisual)
protected virtual void UpdateVisual(SpriteVisual spriteVisual)
{
spriteVisual.Size = ActualSize;
}
@@ -158,15 +158,28 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
if (imageSurface != null)
{
spriteVisual = CompositeSpriteVisual(compositor, imageSurface);
OnUpdateVisual(spriteVisual);
using (imageSurface)
{
spriteVisual = CompositeSpriteVisual(compositor, imageSurface);
UpdateVisual(spriteVisual);
ElementCompositionPreview.SetElementChildVisual(this, spriteVisual);
await ShowAsync(token).ConfigureAwait(true);
ElementCompositionPreview.SetElementChildVisual(this, spriteVisual);
await ShowAsync(token).ConfigureAwait(true);
}
}
}
}
private async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
{
surfaceLoadTaskCompletionSource = new();
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri());
surface.LoadCompleted += loadedImageSourceLoadCompletedEventHandler;
await surfaceLoadTaskCompletionSource.Task.ConfigureAwait(true);
LoadImageSurfaceCompleted(surface);
return surface;
}
private async Task ShowAsync(CancellationToken token)
{
if (!isShow)
@@ -209,11 +222,27 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
}
}
private void OnLoadImageSurfaceLoadCompleted(LoadedImageSurface surface, LoadedImageSourceLoadCompletedEventArgs e)
{
surfaceLoadTaskCompletionSource?.TrySetResult();
surface.LoadCompleted -= loadedImageSourceLoadCompletedEventHandler;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize != e.PreviousSize && spriteVisual != null)
{
OnUpdateVisual(spriteVisual);
UpdateVisual(spriteVisual);
}
}
private void OnUnload(object sender, RoutedEventArgs e)
{
Unloading();
spriteVisual?.Dispose();
spriteVisual = null;
SizeChanged -= sizeChangedEventHandler;
Unloaded -= unloadEventHandler;
}
}

View File

@@ -20,7 +20,7 @@ internal sealed partial class Gradient : CompositionImage
private double imageAspectRatio;
/// <inheritdoc/>
protected override void OnUpdateVisual(SpriteVisual? spriteVisual)
protected override void UpdateVisual(SpriteVisual? spriteVisual)
{
if (spriteVisual is null)
{
@@ -31,15 +31,9 @@ internal sealed partial class Gradient : CompositionImage
spriteVisual.Size = ActualSize;
}
/// <inheritdoc/>
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
protected override void LoadImageSurfaceCompleted(LoadedImageSurface surface)
{
TaskCompletionSource loadCompleteTaskSource = new();
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri());
surface.LoadCompleted += (_, _) => loadCompleteTaskSource.TrySetResult();
await loadCompleteTaskSource.Task.ConfigureAwait(true);
imageAspectRatio = surface.NaturalSize.AspectRatio();
return surface;
}
/// <inheritdoc/>

View File

@@ -7,6 +7,7 @@ using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Theme;
using Windows.Foundation;
namespace Snap.Hutao.Control.Image;
@@ -16,6 +17,8 @@ namespace Snap.Hutao.Control.Image;
[HighQuality]
internal sealed class MonoChrome : CompositionImage
{
private readonly TypedEventHandler<FrameworkElement, object> actualThemeChangedEventHandler;
private CompositionColorBrush? backgroundBrush;
/// <summary>
@@ -23,7 +26,8 @@ internal sealed class MonoChrome : CompositionImage
/// </summary>
public MonoChrome()
{
ActualThemeChanged += OnActualThemeChanged;
actualThemeChangedEventHandler = OnActualThemeChanged;
ActualThemeChanged += actualThemeChangedEventHandler;
}
/// <inheritdoc/>
@@ -41,6 +45,16 @@ internal sealed class MonoChrome : CompositionImage
return compositor.CompositeSpriteVisual(alphaMaskEffectBrush);
}
protected override void Unloading()
{
ActualThemeChanged -= actualThemeChangedEventHandler;
backgroundBrush?.Dispose();
backgroundBrush = null;
base.Unloading();
}
private void OnActualThemeChanged(FrameworkElement sender, object args)
{
if (backgroundBrush != null)

View File

@@ -5,27 +5,26 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
Name="RootSplitButton"
Padding="0,6"
Click="SplitButtonClick"
Loaded="OnRootControlLoaded"
mc:Ignorable="d">
<SplitButton.Content>
<FontIcon Name="IconPresenter" Glyph="&#xE8FD;"/>
</SplitButton.Content>
<SplitButton.Flyout>
<MenuFlyout>
<RadioMenuFlyoutItem
Click="RadioMenuFlyoutItemClick"
Click="OnMenuItemClick"
Icon="{shcm:FontIcon Glyph=&#xE8FD;}"
Tag="List"
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/>
<RadioMenuFlyoutItem
Click="RadioMenuFlyoutItemClick"
Click="OnMenuItemClick"
Icon="{shcm:FontIcon Glyph=&#xF0E2;}"
Tag="Grid"
Text="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/>
</MenuFlyout>
</SplitButton.Flyout>
</SplitButton>

View File

@@ -3,6 +3,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation;
namespace Snap.Hutao.Control.Panel;
@@ -16,12 +17,28 @@ internal sealed partial class PanelSelector : SplitButton
private static readonly DependencyProperty CurrentProperty = Property<PanelSelector>.Depend(nameof(Current), List, OnCurrentChanged);
private readonly RoutedEventHandler loadedEventHandler;
private readonly RoutedEventHandler unloadedEventHandler;
private readonly TypedEventHandler<SplitButton, SplitButtonClickEventArgs> clickEventHandler;
private readonly RoutedEventHandler menuItemClickEventHandler;
/// <summary>
/// 构造一个新的面板选择器
/// </summary>
public PanelSelector()
{
InitializeComponent();
loadedEventHandler = OnRootLoaded;
Loaded += loadedEventHandler;
clickEventHandler = OnRootClick;
Click += clickEventHandler;
menuItemClickEventHandler = OnMenuItemClick;
unloadedEventHandler = OnRootUnload;
Unloaded += unloadedEventHandler;
}
/// <summary>
@@ -33,37 +50,54 @@ internal sealed partial class PanelSelector : SplitButton
set => SetValue(CurrentProperty, value);
}
private static void OnCurrentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
private static void InitializeItems(PanelSelector selector)
{
OnCurrentChanged((PanelSelector)obj, (string)args.NewValue);
MenuFlyout menuFlyout = (MenuFlyout)selector.Flyout;
int hash = selector.GetHashCode();
foreach (RadioMenuFlyoutItem item in menuFlyout.Items.Cast<RadioMenuFlyoutItem>())
{
item.GroupName = $"{nameof(PanelSelector)}GroupOf@{hash}";
item.Click += selector.menuItemClickEventHandler;
}
}
private static void OnCurrentChanged(PanelSelector sender, string current)
private static void OnCurrentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
MenuFlyout menuFlyout = (MenuFlyout)sender.RootSplitButton.Flyout;
RadioMenuFlyoutItem targetItem = menuFlyout.Items
.Cast<RadioMenuFlyoutItem>()
UpdateRootGlyphAndItemIsCheck((PanelSelector)obj, (string)args.NewValue);
}
private static void UpdateRootGlyphAndItemIsCheck(PanelSelector sender, string current)
{
RadioMenuFlyoutItem targetItem = (RadioMenuFlyoutItem)((MenuFlyout)sender.Flyout).Items
.Single(i => (string)i.Tag == current);
targetItem.IsChecked = true;
sender.IconPresenter.Glyph = ((FontIcon)targetItem.Icon).Glyph;
}
private void OnRootControlLoaded(object sender, RoutedEventArgs e)
private void OnRootLoaded(object sender, RoutedEventArgs e)
{
// because the GroupName shares in global
// we have to implement a control scoped GroupName.
PanelSelector selector = (PanelSelector)sender;
MenuFlyout menuFlyout = (MenuFlyout)selector.RootSplitButton.Flyout;
int hash = GetHashCode();
foreach (RadioMenuFlyoutItem item in menuFlyout.Items.Cast<RadioMenuFlyoutItem>())
{
item.GroupName = $"{nameof(PanelSelector)}GroupOf@{hash}";
}
OnCurrentChanged(selector, Current);
InitializeItems(selector);
UpdateRootGlyphAndItemIsCheck(selector, Current);
}
private void SplitButtonClick(SplitButton sender, SplitButtonClickEventArgs args)
private void OnRootUnload(object sender, RoutedEventArgs e)
{
Loaded -= loadedEventHandler;
Click -= clickEventHandler;
foreach (MenuFlyoutItemBase item in ((MenuFlyout)((PanelSelector)sender).Flyout).Items)
{
((RadioMenuFlyoutItem)item).Click -= menuItemClickEventHandler;
}
Unloaded -= unloadedEventHandler;
}
private void OnRootClick(SplitButton sender, SplitButtonClickEventArgs args)
{
MenuFlyout menuFlyout = (MenuFlyout)sender.Flyout;
@@ -84,9 +118,8 @@ internal sealed partial class PanelSelector : SplitButton
Current = (string)item.Tag;
}
private void RadioMenuFlyoutItemClick(object sender, RoutedEventArgs e)
private void OnMenuItemClick(object sender, RoutedEventArgs e)
{
RadioMenuFlyoutItem item = (RadioMenuFlyoutItem)sender;
Current = (string)item.Tag;
Current = (string)((FrameworkElement)sender).Tag;
}
}

View File

@@ -10,6 +10,7 @@ namespace Snap.Hutao.Control;
/// </summary>
/// <typeparam name="TOwner">所有者的类型</typeparam>
[HighQuality]
[Obsolete("Use DependencyPropertyAttribute whenever possible")]
internal static class Property<TOwner>
{
/// <summary>

View File

@@ -20,6 +20,7 @@ internal class ScopedPage : Page
// Allow GC to Collect the IServiceScope
private static readonly WeakReference<IServiceScope> PreviousScopeReference = new(null!);
private readonly RoutedEventHandler unloadEventHandler;
private readonly CancellationTokenSource viewCancellationTokenSource = new();
private readonly IServiceScope currentScope;
@@ -28,7 +29,8 @@ internal class ScopedPage : Page
/// </summary>
protected ScopedPage()
{
Unloaded += OnScopedPageUnloaded;
unloadEventHandler = OnUnloaded;
Unloaded += unloadEventHandler;
currentScope = Ioc.Default.CreateScope();
DisposePreviousScope();
@@ -47,19 +49,6 @@ internal class ScopedPage : Page
}
}
/// <summary>
/// 初始化
/// 应当在 InitializeComponent() 前调用
/// </summary>
/// <typeparam name="TViewModel">视图模型类型</typeparam>
protected void InitializeWith<TViewModel>()
where TViewModel : class, IViewModel
{
IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewCancellationTokenSource.Token;
DataContext = viewModel;
}
/// <summary>
/// 异步通知接收器
/// </summary>
@@ -75,6 +64,19 @@ internal class ScopedPage : Page
extra.NotifyNavigationCompleted();
}
/// <summary>
/// 初始化
/// 应当在 InitializeComponent() 前调用
/// </summary>
/// <typeparam name="TViewModel">视图模型类型</typeparam>
protected void InitializeWith<TViewModel>()
where TViewModel : class, IViewModel
{
IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService<TViewModel>();
viewModel.CancellationToken = viewCancellationTokenSource.Token;
DataContext = viewModel;
}
/// <inheritdoc/>
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
@@ -105,9 +107,9 @@ internal class ScopedPage : Page
}
}
private void OnScopedPageUnloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
private void OnUnloaded(object sender, RoutedEventArgs e)
{
DataContext = null;
Unloaded -= OnScopedPageUnloaded;
Unloaded -= unloadEventHandler;
}
}

View File

@@ -7,8 +7,10 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Control.Theme;
using Windows.Foundation;
using Windows.UI;
namespace Snap.Hutao.Control.Text;
@@ -29,19 +31,22 @@ internal sealed class DescriptionTextBlock : ContentControl
private static readonly int ItalicTagFullLength = "<i></i>".Length;
private static readonly int ItalicTagLeftLength = "<i>".Length;
private readonly TypedEventHandler<FrameworkElement, object> actualThemeChangedEventHandler;
/// <summary>
/// 构造一个新的呈现描述文本的文本块
/// </summary>
public DescriptionTextBlock()
{
IsTabStop = false;
this.DisableInteraction();
Content = new TextBlock()
{
TextWrapping = TextWrapping.Wrap,
};
ActualThemeChanged += OnActualThemeChanged;
actualThemeChangedEventHandler = OnActualThemeChanged;
ActualThemeChanged += actualThemeChangedEventHandler;
}
/// <summary>
@@ -55,47 +60,47 @@ internal sealed class DescriptionTextBlock : ContentControl
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock text = (TextBlock)((DescriptionTextBlock)d).Content;
TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
ReadOnlySpan<char> description = (string)e.NewValue;
UpdateDescription(text, description);
UpdateDescription(textBlock, description);
}
private static void UpdateDescription(TextBlock text, in ReadOnlySpan<char> description)
private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan<char> description)
{
text.Inlines.Clear();
textBlock.Inlines.Clear();
int last = 0;
for (int i = 0; i < description.Length;)
{
// newline
if (description[i..].IndexOf(@"\n") is 0)
if (description[i..].StartsWith(@"\n"))
{
AppendText(text, description[last..i]);
AppendLineBreak(text);
AppendText(textBlock, description[last..i]);
AppendLineBreak(textBlock);
i += 1;
last = i;
}
// color tag
else if (description[i..].IndexOf("<c") is 0)
else if (description[i..].StartsWith("<c"))
{
AppendText(text, description[last..i]);
AppendText(textBlock, description[last..i]);
Rgba32 color = new(description.Slice(i + 8, 8).ToString());
int length = description[(i + ColorTagLeftLength)..].IndexOf('<');
AppendColorText(text, description.Slice(i + ColorTagLeftLength, length), color);
AppendColorText(textBlock, description.Slice(i + ColorTagLeftLength, length), color);
i += length + ColorTagFullLength;
last = i;
}
// italic
else if (description[i..].IndexOf("<i") is 0)
else if (description[i..].StartsWith("<i"))
{
AppendText(text, description[last..i]);
AppendText(textBlock, description[last..i]);
int length = description[(i + ItalicTagLeftLength)..].IndexOf('<');
AppendItalicText(text, description.Slice(i + ItalicTagLeftLength, length));
AppendItalicText(textBlock, description.Slice(i + ItalicTagLeftLength, length));
i += length + ItalicTagFullLength;
last = i;
@@ -107,7 +112,7 @@ internal sealed class DescriptionTextBlock : ContentControl
if (i == description.Length - 1)
{
AppendText(text, description[last..(i + 1)]);
AppendText(textBlock, description[last..(i + 1)]);
}
}
}
@@ -126,6 +131,7 @@ internal sealed class DescriptionTextBlock : ContentControl
}
else
{
// Make lighter in light mode
HslColor hsl = color.ToHsl();
hsl.L *= 0.3;
targetColor = Rgba32.FromHsl(hsl);

View File

@@ -97,7 +97,7 @@
<Style BasedOn="{StaticResource DefaultScrollViewerStyle}" TargetType="ScrollViewer">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<!-- TODO: When will DefaultInfoBarStyle added -->
<Style TargetType="InfoBar">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>

View File

@@ -25,9 +25,9 @@ internal abstract class ValueConverter<TFrom, TTo> : IValueConverter
Ioc.Default
.GetRequiredService<ILogger<ValueConverter<TFrom, TTo>>>()
.LogError(ex, "值转换器异常");
}
return null;
throw;
}
#else
return Convert((TFrom)value);
#endif

View File

@@ -35,7 +35,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
};
private readonly ILogger logger;
private readonly HttpClient httpClient;
private readonly IHttpClientFactory httpClientFactory;
private readonly IServiceProvider serviceProvider;
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
@@ -50,7 +50,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
public ImageCache(IServiceProvider serviceProvider)
{
logger = serviceProvider.GetRequiredService<ILogger<ImageCache>>();
httpClient = serviceProvider.GetRequiredService<IHttpClientFactory>().CreateClient(nameof(ImageCache));
httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
this.serviceProvider = serviceProvider;
}
@@ -58,20 +58,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
/// <inheritdoc/>
public void RemoveInvalid()
{
string folder = GetCacheFolder();
string[] files = Directory.GetFiles(folder);
List<string> filesToDelete = new();
foreach (string file in files)
{
if (IsFileInvalid(file, false))
{
filesToDelete.Add(file);
}
}
RemoveInternal(filesToDelete);
RemoveInternal(Directory.GetFiles(GetCacheFolder()).Where(file => IsFileInvalid(file, false)));
}
/// <inheritdoc/>
@@ -92,11 +79,10 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
string[] files = Directory.GetFiles(folder);
List<string> filesToDelete = new();
foreach (Uri uri in uriForCachedItems)
foreach (ref readonly Uri uri in uriForCachedItems)
{
string filePath = Path.Combine(folder, GetCacheFileName(uri));
if (Array.IndexOf(files, filePath) >= 0)
if (files.Contains(filePath))
{
filesToDelete.Add(filePath);
}
@@ -184,6 +170,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
logger.LogInformation("Begin downloading for {Uri}", uri);
int retryCount = 0;
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
while (retryCount < 6)
{
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))

View File

@@ -46,6 +46,7 @@ internal sealed class ScopedDbCurrent<TEntity, TMessage>
return;
}
// TODO: Troubeshooting why the serviceProvider will NRE
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

View File

@@ -25,13 +25,7 @@ internal sealed class ExceptionFormat
foreach (DictionaryEntry entry in exception.Data)
{
builder
.Append(entry.Key)
.Append(":[")
.Append(TypeNameHelper.GetTypeDisplayName(entry.Value))
.Append("]:")
.Append(entry.Value)
.Append(StringLiterals.CRLF);
builder.AppendLine($"{entry.Key}:[{TypeNameHelper.GetTypeDisplayName(entry.Value)}]:entry.Value");
}
builder.AppendLine(SectionSeparator);

View File

@@ -24,7 +24,7 @@ namespace Snap.Hutao.Core.Windowing;
/// </summary>
/// <typeparam name="TWindow">窗体类型</typeparam>
[SuppressMessage("", "CA1001")]
internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessage>
internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutStateChangedMessage>
where TWindow : Window, IWindowOptionsSource
{
private readonly TWindow window;
@@ -53,7 +53,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
}
/// <inheritdoc/>
public void Receive(FlyoutOpenCloseMessage message)
public void Receive(FlyoutStateChangedMessage message)
{
UpdateDragRectangles(message.IsOpen);
}

View File

@@ -7,17 +7,22 @@ namespace Snap.Hutao.Message;
/// Flyout开启关闭消息
/// </summary>
[HighQuality]
internal sealed class FlyoutOpenCloseMessage
internal sealed class FlyoutStateChangedMessage
{
/// <summary>
/// 构造一个新的Flyout开启关闭消息
/// </summary>
/// <param name="isOpen">是否为开启状态</param>
public FlyoutOpenCloseMessage(bool isOpen)
public FlyoutStateChangedMessage(bool isOpen)
{
IsOpen = isOpen;
}
public static FlyoutStateChangedMessage Open { get; } = new(true);
public static FlyoutStateChangedMessage Close { get; } = new(false);
/// <summary>
/// 是否为开启状态
/// </summary>