diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContext.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContext.cs index 656de2d290..fab1dbcd52 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContext.cs @@ -2,24 +2,34 @@ // Licensed under the MIT license. using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; namespace Snap.Hutao.Core.Threading; [Injection(InjectAs.Singleton, typeof(ITaskContext))] internal sealed class TaskContext : ITaskContext, ITaskContextUnsafe { - private readonly DispatcherQueueSynchronizationContext synchronizationContext; private readonly DispatcherQueue dispatcherQueue; public TaskContext() { dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - synchronizationContext = new(dispatcherQueue); + DispatcherQueueSynchronizationContext synchronizationContext = new(dispatcherQueue); SynchronizationContext.SetSynchronizationContext(synchronizationContext); } public DispatcherQueue DispatcherQueue { get => dispatcherQueue; } + public static ITaskContext GetForDependencyObject(DependencyObject dependencyObject) + { + return GetForDispatcherQueue(dependencyObject.DispatcherQueue); + } + + public static ITaskContext GetForDispatcherQueue(DispatcherQueue dispatcherQueue) + { + return new TaskContextWrapperForDispatcherQueue(dispatcherQueue); + } + public ThreadPoolSwitchOperation SwitchToBackgroundAsync() { return new(dispatcherQueue); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContextWrapperForDispatcherQueue.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContextWrapperForDispatcherQueue.cs new file mode 100644 index 0000000000..3036d30775 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/TaskContextWrapperForDispatcherQueue.cs @@ -0,0 +1,41 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Dispatching; + +namespace Snap.Hutao.Core.Threading; + +internal sealed class TaskContextWrapperForDispatcherQueue : ITaskContext +{ + private readonly DispatcherQueue dispatcherQueue; + + public TaskContextWrapperForDispatcherQueue(DispatcherQueue dispatcherQueue) + { + this.dispatcherQueue = dispatcherQueue; + } + + public void BeginInvokeOnMainThread(Action action) + { + dispatcherQueue.TryEnqueue(() => action()); + } + + public void InvokeOnMainThread(Action action) + { + dispatcherQueue.Invoke(action); + } + + public T InvokeOnMainThread(Func action) + { + return dispatcherQueue.Invoke(action); + } + + public ThreadPoolSwitchOperation SwitchToBackgroundAsync() + { + return new(dispatcherQueue); + } + + public DispatcherQueueSwitchOperation SwitchToMainThreadAsync() + { + return new(dispatcherQueue); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Behavior/PeriodicInvokeCommandOrOnActualThemeChangedBehavior.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Behavior/PeriodicInvokeCommandOrOnActualThemeChangedBehavior.cs index 62c544cc3b..4778bf2c3d 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Behavior/PeriodicInvokeCommandOrOnActualThemeChangedBehavior.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/Behavior/PeriodicInvokeCommandOrOnActualThemeChangedBehavior.cs @@ -42,10 +42,7 @@ protected override void OnAssociatedObjectLoaded() private void OnActualThemeChanged(FrameworkElement sender, object args) { - if (shouldReactToActualThemeChange) - { - acutalThemeChangedCts.Cancel(); - } + acutalThemeChangedCts.Cancel(); } private void TryExecuteCommand() @@ -70,17 +67,14 @@ private async Task RunCoreAsync() break; } - // TODO: Reconsider approach to get the ServiceProvider - ITaskContext taskContext = Ioc.Default.GetRequiredService(); - await taskContext.SwitchToMainThreadAsync(); - TryExecuteCommand(); - + ITaskContext taskContext = TaskContext.GetForDispatcherQueue(AssociatedObject.DispatcherQueue); await taskContext.SwitchToBackgroundAsync(); try { using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(acutalThemeChangedCts.Token, periodicTimerStopCts.Token)) { await timer.WaitForNextTickAsync(linkedCts.Token).ConfigureAwait(false); + taskContext.BeginInvokeOnMainThread(TryExecuteCommand); } } catch (OperationCanceledException) @@ -91,8 +85,6 @@ private async Task RunCoreAsync() } } - shouldReactToActualThemeChange = true; - acutalThemeChangedCts.Dispose(); acutalThemeChangedCts = new(); periodicTimerStopCts.Dispose(); diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/MainView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/MainView.xaml.cs index 8a59935a30..224f734c0d 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/MainView.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/MainView.xaml.cs @@ -9,10 +9,6 @@ namespace Snap.Hutao.UI.Xaml.View; -/// -/// 主视图 -/// -[HighQuality] internal sealed partial class MainView : UserControl { private readonly INavigationService navigationService; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/MainViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/MainViewModel.cs index 1874d4ff94..704d735b6d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/MainViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/MainViewModel.cs @@ -16,6 +16,8 @@ namespace Snap.Hutao.ViewModel; [Injection(InjectAs.Singleton)] internal sealed partial class MainViewModel : Abstraction.ViewModel, IMainViewModelInitialization { + private readonly AsyncLock backgroundImageLock = new(); + private readonly IBackgroundImageService backgroundImageService; private readonly ILogger logger; private readonly ITaskContext taskContext; @@ -65,45 +67,48 @@ private async Task UpdateBackgroundCoreAsync(bool forceRefresh) return; } - (bool shouldRefresh, BackgroundImage? backgroundImage) = await backgroundImageService.GetNextBackgroundImageAsync(forceRefresh ? default : previousBackgroundImage).ConfigureAwait(false); - - if (shouldRefresh) + using (await backgroundImageLock.LockAsync().ConfigureAwait(false)) { - previousBackgroundImage = backgroundImage; - await taskContext.SwitchToMainThreadAsync(); - - await AnimationBuilder - .Create() - .Opacity( - to: 0D, - duration: Constants.ImageOpacityFadeInOut, - easingType: EasingType.Quartic, - easingMode: EasingMode.EaseInOut) - .StartAsync(backgroundImagePresenter) - .ConfigureAwait(true); - - backgroundImagePresenter.Source = backgroundImage?.ImageSource; - double targetOpacity = backgroundImage is not null - ? ThemeHelper.IsDarkMode(backgroundImagePresenter.ActualTheme) - ? 1 - backgroundImage.Luminance - : backgroundImage.Luminance - : 0; - - logger.LogInformation( - "Background image: [Accent color: {AccentColor}] [Luminance: {Luminance}] [Opacity: {TargetOpacity}]", - backgroundImage?.AccentColor.ToString(CultureInfo.CurrentCulture), - backgroundImage?.Luminance, - targetOpacity); - - await AnimationBuilder - .Create() - .Opacity( - to: targetOpacity, - duration: Constants.ImageOpacityFadeInOut, - easingType: EasingType.Quartic, - easingMode: EasingMode.EaseInOut) - .StartAsync(backgroundImagePresenter) - .ConfigureAwait(true); + (bool shouldRefresh, BackgroundImage? backgroundImage) = await backgroundImageService.GetNextBackgroundImageAsync(forceRefresh ? default : previousBackgroundImage).ConfigureAwait(false); + + if (shouldRefresh) + { + previousBackgroundImage = backgroundImage; + await taskContext.SwitchToMainThreadAsync(); + + await AnimationBuilder + .Create() + .Opacity( + to: 0D, + duration: Constants.ImageOpacityFadeInOut, + easingType: EasingType.Quartic, + easingMode: EasingMode.EaseInOut) + .StartAsync(backgroundImagePresenter) + .ConfigureAwait(true); + + backgroundImagePresenter.Source = backgroundImage?.ImageSource; + double targetOpacity = backgroundImage is not null + ? ThemeHelper.IsDarkMode(backgroundImagePresenter.ActualTheme) + ? 1 - backgroundImage.Luminance + : backgroundImage.Luminance + : 0; + + logger.LogInformation( + "Background image: [Accent color: {AccentColor}] [Luminance: {Luminance}] [Opacity: {TargetOpacity}]", + backgroundImage?.AccentColor, + backgroundImage?.Luminance, + targetOpacity); + + await AnimationBuilder + .Create() + .Opacity( + to: targetOpacity, + duration: Constants.ImageOpacityFadeInOut, + easingType: EasingType.Quartic, + easingMode: EasingMode.EaseInOut) + .StartAsync(backgroundImagePresenter) + .ConfigureAwait(true); + } } } } \ No newline at end of file