From 3185de7023021c976e86caa0632b17b6fcd57d6c Mon Sep 17 00:00:00 2001 From: LanZhan-Harmony Date: Mon, 23 Mar 2026 20:33:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A8=E7=94=BB=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UntamedMusicPlayer/Views/LyricPage.xaml.cs | 148 +++++++++++++----- UntamedMusicPlayer/Views/RootPlayBarView.xaml | 52 ++---- .../Views/RootPlayBarView.xaml.cs | 123 --------------- 3 files changed, 123 insertions(+), 200 deletions(-) diff --git a/UntamedMusicPlayer/Views/LyricPage.xaml.cs b/UntamedMusicPlayer/Views/LyricPage.xaml.cs index 19cf42d..bccc739 100644 --- a/UntamedMusicPlayer/Views/LyricPage.xaml.cs +++ b/UntamedMusicPlayer/Views/LyricPage.xaml.cs @@ -1,8 +1,11 @@ using System.ComponentModel; +using System.Numerics; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Hosting; using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media.Imaging; using UntamedMusicPlayer.Controls; using UntamedMusicPlayer.Models; using UntamedMusicPlayer.Playback; @@ -15,6 +18,8 @@ public sealed partial class LyricPage : Page, IDisposable { public LyricViewModel ViewModel { get; } + private bool isFirstLoad = true; + private readonly Timer _autoScrollDelayTimer; private bool _isManualScrolling; private bool _isProgrammaticScroll; @@ -27,13 +32,7 @@ public sealed partial class LyricPage : Page, IDisposable private DateTimeOffset _contentGridMarginAnimationStart; private double _contentGridMarginFrom; private double _contentGridMarginTo; - - private DispatcherQueueTimer? _coverSizeAnimationTimer; - private DateTimeOffset _coverSizeAnimationStart; - private double _coverSizeFromWidth; - private double _coverSizeFromHeight; - private double _coverSizeToWidth; - private double _coverSizeToHeight; + private CancellationTokenSource? _coverLoadWaitCts; public LyricPage() { @@ -57,13 +56,89 @@ public sealed partial class LyricPage : Page, IDisposable { if (ReferenceGrid.ActualWidth > 0 && ReferenceGrid.ActualHeight > 0) { - ChangeCoverSize(ReferenceGrid.ActualWidth, ReferenceGrid.ActualHeight); + RestartWaitForCoverAndRecalculate(); } _isManualScrolling = false; _autoScrollDelayTimer.Change(Timeout.Infinite, Timeout.Infinite); } } + private void RestartWaitForCoverAndRecalculate() + { + _coverLoadWaitCts?.Cancel(); + _coverLoadWaitCts?.Dispose(); + _coverLoadWaitCts = new CancellationTokenSource(); + _ = RecalculateCoverSizeWhenCoverReadyAsync(_coverLoadWaitCts.Token); + } + + private async Task RecalculateCoverSizeWhenCoverReadyAsync(CancellationToken cancellationToken) + { + var cover = Data.PlayState.CurrentSong?.Cover; + if (cover is null) + { + return; + } + + var loaded = await WaitCoverLoadedAsync(cover, cancellationToken); + if (!loaded || cancellationToken.IsCancellationRequested) + { + return; + } + + if (ReferenceGrid.ActualWidth > 0 && ReferenceGrid.ActualHeight > 0) + { + ChangeCoverSize(ReferenceGrid.ActualWidth, ReferenceGrid.ActualHeight); + } + } + + private static async Task WaitCoverLoadedAsync( + BitmapImage cover, + CancellationToken cancellationToken + ) + { + if (cover.PixelWidth > 0 && cover.PixelHeight > 0) + { + return true; + } + + var tcs = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously + ); + + void OnImageOpened(object sender, RoutedEventArgs args) => tcs.TrySetResult(true); + void OnImageFailed(object sender, ExceptionRoutedEventArgs args) => tcs.TrySetResult(false); + + cover.ImageOpened += OnImageOpened; + cover.ImageFailed += OnImageFailed; + + using var cancellationRegistration = cancellationToken.Register(() => + tcs.TrySetCanceled(cancellationToken) + ); + + try + { + if (cover.PixelWidth > 0 && cover.PixelHeight > 0) + { + return true; + } + var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(1500, cancellationToken)); + if (completedTask != tcs.Task) + { + return false; + } + return await tcs.Task; + } + catch (OperationCanceledException) + { + return false; + } + finally + { + cover.ImageOpened -= OnImageOpened; + cover.ImageFailed -= OnImageFailed; + } + } + private void OnRootPlayBarChanged(object? sender, PropertyChangedEventArgs e) { if ( @@ -308,6 +383,14 @@ public sealed partial class LyricPage : Page, IDisposable return; } + if (isFirstLoad) + { + isFirstLoad = false; + CoverBorder.Width = targetWidth; + CoverBorder.Height = targetHeight; + return; + } + if ( Math.Abs(currentWidth - targetWidth) < 0.5 && Math.Abs(currentHeight - targetHeight) < 0.5 @@ -318,40 +401,23 @@ public sealed partial class LyricPage : Page, IDisposable return; } - _coverSizeAnimationTimer ??= DispatcherQueue.CreateTimer(); - _coverSizeAnimationTimer.Stop(); - _coverSizeAnimationTimer.Interval = TimeSpan.FromMilliseconds(16); - _coverSizeAnimationTimer.Tick -= CoverSizeAnimationTick; - _coverSizeAnimationTimer.Tick += CoverSizeAnimationTick; + CoverBorder.Width = targetWidth; + CoverBorder.Height = targetHeight; - _coverSizeFromWidth = currentWidth; - _coverSizeFromHeight = currentHeight; - _coverSizeToWidth = targetWidth; - _coverSizeToHeight = targetHeight; - _coverSizeAnimationStart = DateTimeOffset.Now; + var visual = ElementCompositionPreview.GetElementVisual(CoverBorder); + visual.StopAnimation("Scale"); + visual.CenterPoint = new Vector3((float)(targetWidth / 2), (float)(targetHeight / 2), 0f); - _coverSizeAnimationTimer.Start(); - } + var initialScaleX = (float)(currentWidth / targetWidth); + var initialScaleY = (float)(currentHeight / targetHeight); + visual.Scale = new Vector3(initialScaleX, initialScaleY, 1f); - private void CoverSizeAnimationTick(DispatcherQueueTimer sender, object args) - { - const double durationMs = 450; - var elapsedMs = (DateTimeOffset.Now - _coverSizeAnimationStart).TotalMilliseconds; - var progress = Math.Clamp(elapsedMs / durationMs, 0d, 1d); - var easedProgress = 1 - Math.Pow(1 - progress, 3); + var compositor = visual.Compositor; + var scaleAnimation = compositor.CreateVector3KeyFrameAnimation(); + scaleAnimation.InsertKeyFrame(1f, Vector3.One); + scaleAnimation.Duration = TimeSpan.FromMilliseconds(450); - CoverBorder.Width = - _coverSizeFromWidth + ((_coverSizeToWidth - _coverSizeFromWidth) * easedProgress); - CoverBorder.Height = - _coverSizeFromHeight + ((_coverSizeToHeight - _coverSizeFromHeight) * easedProgress); - - if (progress >= 1) - { - sender.Stop(); - sender.Tick -= CoverSizeAnimationTick; - CoverBorder.Width = _coverSizeToWidth; - CoverBorder.Height = _coverSizeToHeight; - } + visual.StartAnimation("Scale", scaleAnimation); } private void TextBlock_SizeChanged(object sender, SizeChangedEventArgs e) @@ -431,12 +497,12 @@ public sealed partial class LyricPage : Page, IDisposable { _autoScrollDelayTimer.Dispose(); _titleBarHideTimer?.Dispose(); + _coverLoadWaitCts?.Cancel(); + _coverLoadWaitCts?.Dispose(); + _coverLoadWaitCts = null; _contentGridMarginAnimationTimer?.Stop(); _contentGridMarginAnimationTimer?.Tick -= ContentGridMarginAnimationTick; _contentGridMarginAnimationTimer = null; - _coverSizeAnimationTimer?.Stop(); - _coverSizeAnimationTimer?.Tick -= CoverSizeAnimationTick; - _coverSizeAnimationTimer = null; Data.PlayState.PropertyChanged -= OnStateChanged; Data.RootPlayBarViewModel?.PropertyChanged -= OnRootPlayBarChanged; Data.LyricPage = null; diff --git a/UntamedMusicPlayer/Views/RootPlayBarView.xaml b/UntamedMusicPlayer/Views/RootPlayBarView.xaml index d827f5d..6f5f60e 100644 --- a/UntamedMusicPlayer/Views/RootPlayBarView.xaml +++ b/UntamedMusicPlayer/Views/RootPlayBarView.xaml @@ -7,7 +7,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:model="using:UntamedMusicPlayer.Models" xmlns:ui="using:CommunityToolkit.WinUI" - Unloaded="RootPlayBarView_Unloaded" mc:Ignorable="d"> @@ -215,41 +214,22 @@ - - - - - - - - - - - - - - - - + + + + diff --git a/UntamedMusicPlayer/Views/RootPlayBarView.xaml.cs b/UntamedMusicPlayer/Views/RootPlayBarView.xaml.cs index 905fc2a..cbf13b7 100644 --- a/UntamedMusicPlayer/Views/RootPlayBarView.xaml.cs +++ b/UntamedMusicPlayer/Views/RootPlayBarView.xaml.cs @@ -1,9 +1,6 @@ -using System.ComponentModel; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Media.Animation; using UntamedMusicPlayer.Contracts.Models; using UntamedMusicPlayer.Controls; using UntamedMusicPlayer.Helpers; @@ -18,7 +15,6 @@ namespace UntamedMusicPlayer.Views; public sealed partial class RootPlayBarView : UserControl { private bool _hasPointerPressed = false; - private Storyboard? _songInfoTransitionStoryboard; public RootPlayBarViewModel ViewModel { get; } @@ -28,119 +24,6 @@ public sealed partial class RootPlayBarView : UserControl InitializeComponent(); ViewModel = App.GetService(); Data.RootPlayBarView = this; - - Data.PlayState.PropertyChanged += OnStateChanged; - UpdateSongInfoWithoutAnimation(); - } - - private void OnStateChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName is nameof(SharedPlaybackState.CurrentSong)) - { - AnimateSongInfoToCurrentSong(); - } - } - - private void UpdateSongInfoWithoutAnimation() - { - var title = Data.PlayState.CurrentSong?.Title ?? ""; - var artistAndAlbum = Data.PlayState.CurrentSong?.ArtistAndAlbumStr ?? ""; - - SongTitleTextBlock.Text = title; - ArtistAndAlbumTextBlock.Text = artistAndAlbum; - ArtistAndAlbumTextBlock.Visibility = string.IsNullOrWhiteSpace(artistAndAlbum) - ? Visibility.Collapsed - : Visibility.Visible; - - CurrentSongInfoPanel.Opacity = 1; - IncomingSongInfoPanel.Visibility = Visibility.Collapsed; - IncomingSongInfoPanel.Opacity = 0; - } - - private void AnimateSongInfoToCurrentSong() - { - var title = Data.PlayState.CurrentSong?.Title ?? ""; - var artistAndAlbum = Data.PlayState.CurrentSong?.ArtistAndAlbumStr ?? ""; - if (SongTitleTextBlock.Text == title && ArtistAndAlbumTextBlock.Text == artistAndAlbum) - { - return; - } - - StopSongInfoTransition(); - - IncomingSongTitleTextBlock.Text = title; - IncomingArtistAndAlbumTextBlock.Text = artistAndAlbum; - IncomingArtistAndAlbumTextBlock.Visibility = string.IsNullOrWhiteSpace(artistAndAlbum) - ? Visibility.Collapsed - : Visibility.Visible; - - IncomingSongInfoPanel.Visibility = Visibility.Visible; - - var currentFadeOut = new DoubleAnimation - { - From = 1, - To = 0, - Duration = TimeSpan.FromMilliseconds(200), - }; - Storyboard.SetTarget(currentFadeOut, CurrentSongInfoPanel); - Storyboard.SetTargetProperty(currentFadeOut, nameof(Opacity)); - - var currentSlideUp = new DoubleAnimation - { - From = 0, - To = -70, - Duration = TimeSpan.FromMilliseconds(220), - EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }, - }; - Storyboard.SetTarget(currentSlideUp, CurrentSongInfoTransform); - Storyboard.SetTargetProperty(currentSlideUp, nameof(CompositeTransform.TranslateY)); - - var incomingFadeIn = new DoubleAnimation - { - From = 0, - To = 1, - Duration = TimeSpan.FromMilliseconds(400), - }; - Storyboard.SetTarget(incomingFadeIn, IncomingSongInfoPanel); - Storyboard.SetTargetProperty(incomingFadeIn, nameof(Opacity)); - - var incomingSlideIn = new DoubleAnimation - { - From = 70, - To = 0, - Duration = TimeSpan.FromMilliseconds(450), - EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }, - }; - Storyboard.SetTarget(incomingSlideIn, IncomingSongInfoTransform); - Storyboard.SetTargetProperty(incomingSlideIn, nameof(CompositeTransform.TranslateY)); - - _songInfoTransitionStoryboard = new Storyboard(); - _songInfoTransitionStoryboard.Children.Add(currentFadeOut); - _songInfoTransitionStoryboard.Children.Add(currentSlideUp); - _songInfoTransitionStoryboard.Children.Add(incomingFadeIn); - _songInfoTransitionStoryboard.Children.Add(incomingSlideIn); - _songInfoTransitionStoryboard.Completed += SongInfoTransitionStoryboard_Completed; - _songInfoTransitionStoryboard.Begin(); - } - - private void SongInfoTransitionStoryboard_Completed(object? sender, object e) - { - ArtistAndAlbumTextBlock.Visibility = IncomingArtistAndAlbumTextBlock.Visibility; - IncomingSongInfoPanel.Visibility = Visibility.Collapsed; - StopSongInfoTransition(); - } - - private void StopSongInfoTransition() - { - SongTitleTextBlock.Text = IncomingSongTitleTextBlock.Text; - ArtistAndAlbumTextBlock.Text = IncomingArtistAndAlbumTextBlock.Text; - if (_songInfoTransitionStoryboard is null) - { - return; - } - _songInfoTransitionStoryboard.Completed -= SongInfoTransitionStoryboard_Completed; - _songInfoTransitionStoryboard.Stop(); - _songInfoTransitionStoryboard = null; } public string GetCurrent(TimeSpan current) => @@ -390,10 +273,4 @@ public sealed partial class RootPlayBarView : UserControl var dialog = new PropertiesDialog(currentSong!) { XamlRoot = XamlRoot }; await dialog.ShowAsync(); } - - private void RootPlayBarView_Unloaded(object sender, RoutedEventArgs e) - { - Data.PlayState.PropertyChanged -= OnStateChanged; - StopSongInfoTransition(); - } }