From 454edbeaba809c7c91c64e7225269a528fae2648 Mon Sep 17 00:00:00 2001 From: Zhe Fang Date: Tue, 24 Jun 2025 16:18:47 -0400 Subject: [PATCH] change opacity effect rendering method --- .../BetterLyrics.WinUI3.csproj | 12 +- .../Enums/LineRenderingType.cs | 21 + .../Enums/LyricsGlowEffectScope.cs | 35 - .../Enums/LyricsHighlightType.cs | 24 - .../BetterLyrics.WinUI3/Models/LyricsLine.cs | 34 +- .../Renderer/LyricsRenderer.xaml | 1 - .../Renderer/LyricsRenderer.xaml.cs | 12 +- .../Services/ISettingsService.cs | 4 +- .../Services/MusicSearchService.cs | 19 +- .../Services/SettingsService.cs | 134 +++- .../ViewModels/LyricsRendererViewModel.cs | 599 ++++++++++-------- .../LyricsSettingsControlViewModel.cs | 6 +- .../Views/SettingsPage.xaml | 1 - 13 files changed, 472 insertions(+), 430 deletions(-) create mode 100644 BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LineRenderingType.cs delete mode 100644 BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsGlowEffectScope.cs delete mode 100644 BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsHighlightType.cs diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj index 37e72f5..724bf66 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj @@ -18,18 +18,12 @@ - + - + @@ -46,7 +40,7 @@ - + diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LineRenderingType.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LineRenderingType.cs new file mode 100644 index 0000000..32dd0a9 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LineRenderingType.cs @@ -0,0 +1,21 @@ +// 2025/6/23 by Zhe Fang + +namespace BetterLyrics.WinUI3.Enums +{ + #region Enums + + /// + /// Defines the LineMaskType + /// + public enum LineRenderingType + { + UntilCurrentChar, + + /// + /// Current char only + /// + CurrentCharOnly, + } + + #endregion +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsGlowEffectScope.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsGlowEffectScope.cs deleted file mode 100644 index d51a436..0000000 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsGlowEffectScope.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 2025/6/23 by Zhe Fang - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BetterLyrics.WinUI3.Enums -{ - #region Enums - - /// - /// Defines the LyricsGlowEffectScope - /// - public enum LyricsGlowEffectScope - { - /// - /// Defines the WholeLyrics - /// - WholeLyrics, - - /// - /// Defines the CurrentLine - /// - CurrentLine, - - /// - /// Defines the CurrentChar - /// - CurrentChar, - } - - #endregion -} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsHighlightType.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsHighlightType.cs deleted file mode 100644 index be61717..0000000 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsHighlightType.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 2025/6/23 by Zhe Fang - -namespace BetterLyrics.WinUI3.Enums -{ - #region Enums - - /// - /// Defines the LyricsHighlightType - /// - public enum LyricsHighlightType - { - /// - /// Defines the LineByLine - /// - LineByLine, - - /// - /// Defines the CharByChar - /// - CharByChar, - } - - #endregion -} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs index 5ff1c32..030c341 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsLine.cs @@ -1,9 +1,10 @@ // 2025/6/23 by Zhe Fang -using BetterLyrics.WinUI3.Enums; -using BetterLyrics.WinUI3.Helper; using System.Collections.Generic; using System.Numerics; +using BetterLyrics.WinUI3.Enums; +using BetterLyrics.WinUI3.Helper; +using Microsoft.Graphics.Canvas.Text; namespace BetterLyrics.WinUI3.Models { @@ -14,6 +15,8 @@ namespace BetterLyrics.WinUI3.Models { #region Properties + public CanvasTextLayout? CanvasTextLayout { get; set; } + /// /// Gets or sets the CenterPosition /// @@ -80,32 +83,5 @@ namespace BetterLyrics.WinUI3.Models public string Text { get; set; } = ""; #endregion - - #region Methods - - /// - /// The Clone - /// - /// The - public LyricsLine Clone() - { - return new LyricsLine - { - Text = this.Text, - CharTimings = this.CharTimings, - StartMs = this.StartMs, - EndMs = this.EndMs, - PlayingState = this.PlayingState, - EnteringProgress = this.EnteringProgress, - ExitingProgress = this.ExitingProgress, - PlayingProgress = this.PlayingProgress, - Position = this.Position, - CenterPosition = this.CenterPosition, - Scale = this.Scale, - Opacity = this.Opacity, - }; - } - - #endregion } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml index d99aaa8..21aa722 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml @@ -13,7 +13,6 @@ diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs index a5a15f3..98c30d1 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/LyricsRenderer.xaml.cs @@ -1,10 +1,10 @@ // 2025/6/23 by Zhe Fang +using System.Diagnostics; using BetterLyrics.WinUI3.ViewModels; using CommunityToolkit.Mvvm.DependencyInjection; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using System.Diagnostics; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -56,16 +56,6 @@ namespace BetterLyrics.WinUI3.Renderer ViewModel.Draw(sender, args.DrawingSession); } - /// - /// The LyricsCanvas_Loaded - /// - /// The sender - /// The e - private void LyricsCanvas_Loaded(object sender, RoutedEventArgs e) - { - ViewModel.RequestRelayout(); - } - /// /// The LyricsCanvas_Update /// diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ISettingsService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ISettingsService.cs index 1ccb667..2af2d14 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ISettingsService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/ISettingsService.cs @@ -1,10 +1,10 @@ // 2025/6/23 by Zhe Fang +using System.Collections.Generic; using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Models; using Microsoft.UI.Text; using Microsoft.UI.Xaml; -using System.Collections.Generic; using Windows.UI.Text; namespace BetterLyrics.WinUI3.Services @@ -111,7 +111,7 @@ namespace BetterLyrics.WinUI3.Services /// /// Gets or sets the LyricsGlowEffectScope /// - LyricsGlowEffectScope LyricsGlowEffectScope { get; set; } + LineRenderingType LyricsGlowEffectScope { get; set; } /// /// Gets or sets the LyricsLineSpacingFactor diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MusicSearchService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MusicSearchService.cs index b8b98c5..d1afa4d 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MusicSearchService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MusicSearchService.cs @@ -1,8 +1,5 @@ // 2025/6/23 by Zhe Fang -using ATL; -using BetterLyrics.WinUI3.Enums; -using BetterLyrics.WinUI3.Helper; using System; using System.IO; using System.Linq; @@ -10,6 +7,9 @@ using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; +using ATL; +using BetterLyrics.WinUI3.Enums; +using BetterLyrics.WinUI3.Helper; using Windows.Storage; using Windows.Storage.FileProperties; @@ -232,11 +232,11 @@ namespace BetterLyrics.WinUI3.Services for (int j = 0; j <= b.Length; j++) d[0, j] = j; for (int i = 1; i <= a.Length; i++) - for (int j = 1; j <= b.Length; j++) - d[i, j] = Math.Min( - Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), - d[i - 1, j - 1] + (a[i - 1] == b[j - 1] ? 0 : 1) - ); + for (int j = 1; j <= b.Length; j++) + d[i, j] = Math.Min( + Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), + d[i - 1, j - 1] + (a[i - 1] == b[j - 1] ? 0 : 1) + ); return d[a.Length, b.Length]; } @@ -340,7 +340,8 @@ namespace BetterLyrics.WinUI3.Services if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist)) { //Track track = new(file); - //var plain = track.Lyrics.UnsynchronizedLyrics; + //var test1 = track.Lyrics.SynchronizedLyrics; + //var test2 = track.Lyrics.UnsynchronizedLyrics; try { diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SettingsService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SettingsService.cs index 849a885..3b62b21 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SettingsService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SettingsService.cs @@ -1,12 +1,12 @@ // 2025/6/23 by Zhe Fang +using System; +using System.Collections.Generic; +using System.Linq; using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Models; using BetterLyrics.WinUI3.Serialization; using Microsoft.UI.Xaml; -using System; -using System.Collections.Generic; -using System.Linq; using Windows.Storage; namespace BetterLyrics.WinUI3.Services @@ -202,7 +202,7 @@ namespace BetterLyrics.WinUI3.Services SetDefault(LyricsLineSpacingFactorKey, 0.5f); SetDefault(LyricsVerticalEdgeOpacityKey, 0); SetDefault(IsLyricsGlowEffectEnabledKey, true); - SetDefault(LyricsGlowEffectScopeKey, (int)LyricsGlowEffectScope.CurrentChar); + SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.CurrentCharOnly); } #endregion @@ -212,52 +212,92 @@ namespace BetterLyrics.WinUI3.Services /// /// Gets or sets the AutoStartWindowType /// - public AutoStartWindowType AutoStartWindowType { get => (AutoStartWindowType)GetValue(AutoStartWindowTypeKey); set => SetValue(AutoStartWindowTypeKey, (int)value); } + public AutoStartWindowType AutoStartWindowType + { + get => (AutoStartWindowType)GetValue(AutoStartWindowTypeKey); + set => SetValue(AutoStartWindowTypeKey, (int)value); + } /// /// Gets or sets the BackdropType /// - public BackdropType BackdropType { get => (BackdropType)GetValue(BackdropTypeKey); set => SetValue(BackdropTypeKey, (int)value); } + public BackdropType BackdropType + { + get => (BackdropType)GetValue(BackdropTypeKey); + set => SetValue(BackdropTypeKey, (int)value); + } /// /// Gets or sets the CoverImageRadius /// - public int CoverImageRadius { get => GetValue(CoverImageRadiusKey); set => SetValue(CoverImageRadiusKey, value); } + public int CoverImageRadius + { + get => GetValue(CoverImageRadiusKey); + set => SetValue(CoverImageRadiusKey, value); + } /// /// Gets or sets the CoverOverlayBlurAmount /// - public int CoverOverlayBlurAmount { get => GetValue(CoverOverlayBlurAmountKey); set => SetValue(CoverOverlayBlurAmountKey, value); } + public int CoverOverlayBlurAmount + { + get => GetValue(CoverOverlayBlurAmountKey); + set => SetValue(CoverOverlayBlurAmountKey, value); + } /// /// Gets or sets the CoverOverlayOpacity /// - public int CoverOverlayOpacity { get => GetValue(CoverOverlayOpacityKey); set => SetValue(CoverOverlayOpacityKey, value); } + public int CoverOverlayOpacity + { + get => GetValue(CoverOverlayOpacityKey); + set => SetValue(CoverOverlayOpacityKey, value); + } /// /// Gets or sets a value indicating whether IsCoverOverlayEnabled /// - public bool IsCoverOverlayEnabled { get => GetValue(IsCoverOverlayEnabledKey); set => SetValue(IsCoverOverlayEnabledKey, value); } + public bool IsCoverOverlayEnabled + { + get => GetValue(IsCoverOverlayEnabledKey); + set => SetValue(IsCoverOverlayEnabledKey, value); + } /// /// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled /// - public bool IsDynamicCoverOverlayEnabled { get => GetValue(IsDynamicCoverOverlayEnabledKey); set => SetValue(IsDynamicCoverOverlayEnabledKey, value); } + public bool IsDynamicCoverOverlayEnabled + { + get => GetValue(IsDynamicCoverOverlayEnabledKey); + set => SetValue(IsDynamicCoverOverlayEnabledKey, value); + } /// /// Gets or sets a value indicating whether IsFirstRun /// - public bool IsFirstRun { get => GetValue(IsFirstRunKey); set => SetValue(IsFirstRunKey, value); } + public bool IsFirstRun + { + get => GetValue(IsFirstRunKey); + set => SetValue(IsFirstRunKey, value); + } /// /// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled /// - public bool IsLyricsGlowEffectEnabled { get => GetValue(IsLyricsGlowEffectEnabledKey); set => SetValue(IsLyricsGlowEffectEnabledKey, value); } + public bool IsLyricsGlowEffectEnabled + { + get => GetValue(IsLyricsGlowEffectEnabledKey); + set => SetValue(IsLyricsGlowEffectEnabledKey, value); + } /// /// Gets or sets the Language /// - public Language Language { get => (Language)GetValue(LanguageKey); set => SetValue(LanguageKey, (int)value); } + public Language Language + { + get => (Language)GetValue(LanguageKey); + set => SetValue(LanguageKey, (int)value); + } /// /// Gets or sets the LocalLyricsFolders @@ -268,7 +308,8 @@ namespace BetterLyrics.WinUI3.Services System.Text.Json.JsonSerializer.Deserialize( GetValue(LocalLyricsFoldersKey) ?? "[]", SourceGenerationContext.Default.ListLocalLyricsFolder - )!; set => + )!; + set => SetValue( LocalLyricsFoldersKey, System.Text.Json.JsonSerializer.Serialize( @@ -281,37 +322,65 @@ namespace BetterLyrics.WinUI3.Services /// /// Gets or sets the LyricsAlignmentType /// - public LyricsAlignmentType LyricsAlignmentType { get => (LyricsAlignmentType)GetValue(LyricsAlignmentTypeKey); set => SetValue(LyricsAlignmentTypeKey, (int)value); } + public LyricsAlignmentType LyricsAlignmentType + { + get => (LyricsAlignmentType)GetValue(LyricsAlignmentTypeKey); + set => SetValue(LyricsAlignmentTypeKey, (int)value); + } /// /// Gets or sets the LyricsBlurAmount /// - public int LyricsBlurAmount { get => GetValue(LyricsBlurAmountKey); set => SetValue(LyricsBlurAmountKey, value); } + public int LyricsBlurAmount + { + get => GetValue(LyricsBlurAmountKey); + set => SetValue(LyricsBlurAmountKey, value); + } /// /// Gets or sets the LyricsFontColorType /// - public LyricsFontColorType LyricsFontColorType { get => (LyricsFontColorType)GetValue(LyricsFontColorTypeKey); set => SetValue(LyricsFontColorTypeKey, (int)value); } + public LyricsFontColorType LyricsFontColorType + { + get => (LyricsFontColorType)GetValue(LyricsFontColorTypeKey); + set => SetValue(LyricsFontColorTypeKey, (int)value); + } /// /// Gets or sets the LyricsFontSize /// - public int LyricsFontSize { get => GetValue(LyricsFontSizeKey); set => SetValue(LyricsFontSizeKey, value); } + public int LyricsFontSize + { + get => GetValue(LyricsFontSizeKey); + set => SetValue(LyricsFontSizeKey, value); + } /// /// Gets or sets the LyricsFontWeight /// - public LyricsFontWeight LyricsFontWeight { get => (LyricsFontWeight)GetValue(LyricsFontWeightKey); set => SetValue(LyricsFontWeightKey, (int)value); } + public LyricsFontWeight LyricsFontWeight + { + get => (LyricsFontWeight)GetValue(LyricsFontWeightKey); + set => SetValue(LyricsFontWeightKey, (int)value); + } /// /// Gets or sets the LyricsGlowEffectScope /// - public LyricsGlowEffectScope LyricsGlowEffectScope { get => (LyricsGlowEffectScope)GetValue(LyricsGlowEffectScopeKey); set => SetValue(LyricsGlowEffectScopeKey, (int)value); } + public LineRenderingType LyricsGlowEffectScope + { + get => (LineRenderingType)GetValue(LyricsGlowEffectScopeKey); + set => SetValue(LyricsGlowEffectScopeKey, (int)value); + } /// /// Gets or sets the LyricsLineSpacingFactor /// - public float LyricsLineSpacingFactor { get => GetValue(LyricsLineSpacingFactorKey); set => SetValue(LyricsLineSpacingFactorKey, value); } + public float LyricsLineSpacingFactor + { + get => GetValue(LyricsLineSpacingFactorKey); + set => SetValue(LyricsLineSpacingFactorKey, value); + } /// /// Gets or sets the LyricsSearchProvidersInfo @@ -322,7 +391,8 @@ namespace BetterLyrics.WinUI3.Services System.Text.Json.JsonSerializer.Deserialize( GetValue(LyricsSearchProvidersInfoKey) ?? "[]", SourceGenerationContext.Default.ListLyricsSearchProviderInfo - )!; set => + )!; + set => SetValue( LyricsSearchProvidersInfoKey, System.Text.Json.JsonSerializer.Serialize( @@ -335,17 +405,29 @@ namespace BetterLyrics.WinUI3.Services /// /// Gets or sets the LyricsVerticalEdgeOpacity /// - public int LyricsVerticalEdgeOpacity { get => GetValue(LyricsVerticalEdgeOpacityKey); set => SetValue(LyricsVerticalEdgeOpacityKey, value); } + public int LyricsVerticalEdgeOpacity + { + get => GetValue(LyricsVerticalEdgeOpacityKey); + set => SetValue(LyricsVerticalEdgeOpacityKey, value); + } /// /// Gets or sets the ThemeType /// - public ElementTheme ThemeType { get => (ElementTheme)GetValue(ThemeTypeKey); set => SetValue(ThemeTypeKey, (int)value); } + public ElementTheme ThemeType + { + get => (ElementTheme)GetValue(ThemeTypeKey); + set => SetValue(ThemeTypeKey, (int)value); + } /// /// Gets or sets the TitleBarType /// - public TitleBarType TitleBarType { get => (TitleBarType)GetValue(TitleBarTypeKey); set => SetValue(TitleBarTypeKey, (int)value); } + public TitleBarType TitleBarType + { + get => (TitleBarType)GetValue(TitleBarTypeKey); + set => SetValue(TitleBarTypeKey, (int)value); + } #endregion diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs index 9f5ac53..d75f224 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs @@ -1,5 +1,11 @@ // 2025/6/23 by Zhe Fang +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; using BetterInAppLyrics.WinUI3.ViewModels; using BetterLyrics.WinUI3.Enums; using BetterLyrics.WinUI3.Events; @@ -16,12 +22,7 @@ using Microsoft.Graphics.Canvas.Text; using Microsoft.Graphics.Canvas.UI.Xaml; using Microsoft.UI; using Microsoft.UI.Xaml; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; +using Microsoft.UI.Xaml.Shapes; using Windows.Foundation; using Windows.Graphics.Imaging; using Windows.UI; @@ -43,7 +44,7 @@ namespace BetterLyrics.WinUI3.ViewModels IRecipient>, IRecipient>, IRecipient>, - IRecipient>, + IRecipient>, IRecipient>>, IRecipient>> { @@ -130,7 +131,7 @@ namespace BetterLyrics.WinUI3.ViewModels /// /// Defines the _lyricsGlowEffectAmount /// - private readonly float _lyricsGlowEffectAmount = 6f; + private readonly float _lyricsGlowEffectAmount = 8f; /// /// Defines the _musicSearchService @@ -145,7 +146,7 @@ namespace BetterLyrics.WinUI3.ViewModels /// /// Defines the _rightMargin /// - private readonly double _rightMargin = 36; + private readonly float _rightMargin = 36f; /// /// Defines the _topMargin @@ -202,11 +203,6 @@ namespace BetterLyrics.WinUI3.ViewModels /// private Color _lightFontColor = Colors.White; - /// - /// Defines the _lyricsForGlowEffect - /// - private List? _lyricsForGlowEffect = []; - /// /// Defines the _multiLangLyrics /// @@ -277,7 +273,10 @@ namespace BetterLyrics.WinUI3.ViewModels _playbackService.SongInfoChanged += PlaybackService_SongInfoChanged; _playbackService.PositionChanged += PlaybackService_PositionChanged; - RefreshPlaybackInfo(); + _isPlaying = _playbackService.IsPlaying; + SongInfo = _playbackService.SongInfo; + TotalTime = _playbackService.Position; + UpdateFontColor(); } @@ -356,7 +355,7 @@ namespace BetterLyrics.WinUI3.ViewModels /// /// Gets or sets the LyricsGlowEffectScope /// - public LyricsGlowEffectScope LyricsGlowEffectScope { get; set; } + public LineRenderingType LyricsGlowEffectScope { get; set; } /// /// Gets or sets the LyricsLineSpacingFactor @@ -430,86 +429,19 @@ namespace BetterLyrics.WinUI3.ViewModels break; case LyricsDisplayType.LyricsOnly: case LyricsDisplayType.SplitView: - DrawLyrics( - control, - lyricsDs, - _multiLangLyrics.SafeGet(_langIndex), - _defaultOpacity, - LyricsHighlightType.LineByLine - ); + DrawLyrics(control, lyricsDs, LineRenderingType.UntilCurrentChar); break; default: break; } } - // Lyrics layer with opacity modification (used for glow effect) - using var modifiedLyrics = new CanvasCommandList(control); - using (var modifiedLyricsDs = modifiedLyrics.CreateDrawingSession()) - { - if (IsLyricsGlowEffectEnabled) - { - switch (DisplayType) - { - case LyricsDisplayType.AlbumArtOnly: - case LyricsDisplayType.PlaceholderOnly: - break; - case LyricsDisplayType.LyricsOnly: - case LyricsDisplayType.SplitView: - switch (LyricsGlowEffectScope) - { - case LyricsGlowEffectScope.WholeLyrics: - modifiedLyricsDs.DrawImage(lyrics); - break; - case LyricsGlowEffectScope.CurrentLine: - DrawLyrics( - control, - modifiedLyricsDs, - _lyricsForGlowEffect, - 0, - LyricsHighlightType.LineByLine - ); - break; - case LyricsGlowEffectScope.CurrentChar: - DrawLyrics( - control, - modifiedLyricsDs, - _lyricsForGlowEffect, - 0, - LyricsHighlightType.CharByChar - ); - break; - default: - break; - } - break; - default: - break; - } - } - } - - using var glowedLyrics = new CanvasCommandList(control); - using (var glowedLyricsDs = glowedLyrics.CreateDrawingSession()) - { - glowedLyricsDs.DrawImage( - new ShadowEffect - { - Source = modifiedLyrics, - BlurAmount = _lyricsGlowEffectAmount, - ShadowColor = _fontColor, - Optimization = EffectOptimization.Quality, - } - ); - glowedLyricsDs.DrawImage(lyrics); - } - // Mock gradient blurred lyrics layer using var blurredLyrics = new CanvasCommandList(control); using var blurredLyricsDs = blurredLyrics.CreateDrawingSession(); if (LyricsBlurAmount == 0) { - blurredLyricsDs.DrawImage(glowedLyrics); + blurredLyricsDs.DrawImage(lyrics); } else { @@ -519,7 +451,7 @@ namespace BetterLyrics.WinUI3.ViewModels { using var halfBlurredLyrics = new GaussianBlurEffect { - Source = glowedLyrics, + Source = lyrics, BlurAmount = (float)(LyricsBlurAmount * (1 - i / (0.5 - step))), Optimization = EffectOptimization.Quality, BorderMode = EffectBorderMode.Soft, @@ -800,11 +732,14 @@ namespace BetterLyrics.WinUI3.ViewModels /// The Receive /// /// The message - public void Receive(PropertyChangedMessage message) + public void Receive(PropertyChangedMessage message) { if (message.Sender is LyricsSettingsControlViewModel) { - if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsGlowEffectScope)) + if ( + message.PropertyName + == nameof(LyricsSettingsControlViewModel.LyricsGlowEffectScope) + ) { LyricsGlowEffectScope = message.NewValue; } @@ -827,24 +762,6 @@ namespace BetterLyrics.WinUI3.ViewModels } } - /// - /// The RefreshPlaybackInfo - /// - public void RefreshPlaybackInfo() - { - _isPlaying = _playbackService.IsPlaying; - SongInfo = _playbackService.SongInfo; - TotalTime = _playbackService.Position; - } - - /// - /// The RequestRelayout - /// - public void RequestRelayout() - { - _isRelayoutNeeded = true; - } - /// /// The Update /// @@ -887,30 +804,8 @@ namespace BetterLyrics.WinUI3.ViewModels _isRelayoutNeeded = false; } - UpdateLinesProps(_multiLangLyrics.SafeGet(_langIndex), _defaultOpacity); + UpdateLinesProps(); UpdateCanvasYScrollOffset(control); - - if (IsLyricsGlowEffectEnabled) - { - // Deep copy lyrics lines for glow effect - _lyricsForGlowEffect = _multiLangLyrics - .SafeGet(_langIndex) - ?.Select(line => line.Clone()) - .ToList(); - switch (LyricsGlowEffectScope) - { - case LyricsGlowEffectScope.WholeLyrics: - break; - case LyricsGlowEffectScope.CurrentLine: - UpdateLinesProps(_lyricsForGlowEffect, 0); - break; - case LyricsGlowEffectScope.CurrentChar: - UpdateLinesProps(_lyricsForGlowEffect, 0); - break; - default: - break; - } - } } /// @@ -1078,42 +973,49 @@ namespace BetterLyrics.WinUI3.ViewModels /// /// The control /// The ds - /// The source - /// The defaultOpacity /// The currentLineHighlightType private void DrawLyrics( ICanvasAnimatedControl control, CanvasDrawingSession ds, - List? source, - float defaultOpacity, - LyricsHighlightType currentLineHighlightType + LineRenderingType currentLineHighlightType ) { var (displayStartLineIndex, displayEndLineIndex) = GetVisibleLyricsLineIndexBoundaries(); - for ( - int i = displayStartLineIndex; - source?.Count > 0 && i >= 0 && i < source?.Count && i <= displayEndLineIndex; - i++ - ) + var currentPlayingLineIndex = GetCurrentPlayingLineIndex(); + + for (int i = displayStartLineIndex; i <= displayEndLineIndex; i++) { - var line = source?[i]; + var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i); - using var textLayout = new CanvasTextLayout( - control, - line?.Text, - _textFormat, - (float)_limitedLineWidthTransition.Value, - (float)control.Size.Height - ); + if (line == null) + { + continue; + } - float progressPerChar = 1f / line.Text.Length; + var textLayout = line.CanvasTextLayout; + + if (textLayout == null) + { + continue; + } var position = new Vector2(line.Position.X, line.Position.Y); + float layoutWidth = (float)textLayout.LayoutBounds.Width; + float layoutHeight = (float)textLayout.LayoutBounds.Height; + + if (layoutWidth <= 0 || layoutHeight <= 0) + { + continue; + } + float centerX = position.X; - float centerY = position.Y + (float)textLayout.LayoutBounds.Height / 2; + float centerY = position.Y + layoutHeight / 2; + + // X offset for alignment, used for rectangle mask + float maskXOffset = 0f; switch (LyricsAlignmentType) { @@ -1123,103 +1025,265 @@ namespace BetterLyrics.WinUI3.ViewModels case LyricsAlignmentType.Center: textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center; centerX += (float)_limitedLineWidthTransition.Value / 2; + maskXOffset = (_limitedLineWidthTransition.Value - layoutWidth) / 2; break; case LyricsAlignmentType.Right: textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right; centerX += (float)_limitedLineWidthTransition.Value; + maskXOffset = _limitedLineWidthTransition.Value - layoutWidth; break; default: break; } - int startIndex = 0; - - // Set brush - for (int j = 0; j < textLayout.LineCount; j++) - { - int count = textLayout.LineMetrics[j].CharacterCount; - var regions = textLayout.GetCharacterRegions(startIndex, count); - float subLinePlayingProgress = Math.Clamp( - (line.PlayingProgress * line.Text.Length - startIndex) / count, - 0, - 1 - ); - - float startX = (float)(regions[0].LayoutBounds.Left + position.X); - float endX = (float)(regions[^1].LayoutBounds.Right + position.X); - - if (currentLineHighlightType == LyricsHighlightType.LineByLine) - { - float[] pos = - [ - 0, - subLinePlayingProgress * (1 + progressPerChar) - progressPerChar, - subLinePlayingProgress * (1 + progressPerChar), - 1.5f, - ]; - float[] opacity = - [ - line.Opacity, - line.Opacity, - defaultOpacity, - defaultOpacity, - ]; - - using var brush = GetHorizontalFillBrush( - control, - pos, - opacity, - startX, - endX - ); - textLayout.SetBrush(startIndex, count, brush); - } - else if (currentLineHighlightType == LyricsHighlightType.CharByChar) - { - float[] pos = - [ - subLinePlayingProgress * (1 + progressPerChar) - 3 * progressPerChar, - subLinePlayingProgress * (1 + progressPerChar) - progressPerChar, - subLinePlayingProgress * (1 + progressPerChar), - 1.5f, - ]; - float[] opacity = - [ - defaultOpacity, - line.Opacity, - defaultOpacity, - defaultOpacity, - ]; - - using var brush = GetHorizontalFillBrush( - control, - pos, - opacity, - startX, - endX - ); - textLayout.SetBrush(startIndex, count, brush); - } - - startIndex += count; - } + float offsetToLeft = + (float)control.Size.Width - _rightMargin - _limitedLineWidthTransition.Value; // Scale ds.Transform = Matrix3x2.CreateScale(line.Scale, new Vector2(centerX, centerY)) * Matrix3x2.CreateTranslation( - (float)( - control.Size.Width - _rightMargin - _limitedLineWidthTransition.Value - ), + offsetToLeft, _canvasYScrollTransition.Value + (float)(control.Size.Height / 2) ); - ds.DrawTextLayout(textLayout, position, Colors.Transparent); + // Create the original lyrics line + using var pureLyricsLine = new CanvasCommandList(control); + using (var pureLyricsLineDs = pureLyricsLine.CreateDrawingSession()) + { + pureLyricsLineDs.DrawTextLayout(textLayout, position, _fontColor); + } + + // Create and draw glow (shadow) effect + if (IsLyricsGlowEffectEnabled) + { + ds.DrawImage( + new ShadowEffect + { + Source = new AlphaMaskEffect + { + Source = pureLyricsLine, + AlphaMask = CreateLineMask( + control, + line, + LyricsGlowEffectScope, + false + ), + }, + BlurAmount = _lyricsGlowEffectAmount, + ShadowColor = _fontColor, + Optimization = EffectOptimization.Quality, + } + ); + } + + // Create and draw highlight (opacity changed) effect + ds.DrawImage( + new AlphaMaskEffect + { + Source = pureLyricsLine, + AlphaMask = CreateLineMask( + control, + line, + LineRenderingType.UntilCurrentChar, + true + ), + } + ); + // Reset scale ds.Transform = Matrix3x2.Identity; } } + private CanvasCommandList CreateLineMask( + ICanvasAnimatedControl control, + LyricsLine line, + LineRenderingType lineMaskType, + bool isUnhighlightedAreaVisible + ) + { + var alphaMask = new CanvasCommandList(control); + + var textLayout = line.CanvasTextLayout; + if (textLayout == null) + { + return alphaMask; + } + + using (var ds = alphaMask.CreateDrawingSession()) + { + // Current playing char index + int charIndex = (int)(line.PlayingProgress * line.Text.Length); + int totalCharCountBefore = 0; + foreach (var lineMatrix in textLayout.LineMetrics) + { + int lineCharCount = lineMatrix.CharacterCount; + + var region = textLayout + .GetCharacterRegions(totalCharCountBefore, lineCharCount) + .FirstOrDefault(); + + var lineWidth = region.LayoutBounds.Width; + var lineHeight = region.LayoutBounds.Height; + + var lineLeft = (float)region.LayoutBounds.X; + var lineTop = (float)region.LayoutBounds.Y + line.Position.Y; + var lineRight = lineLeft + lineWidth; + + if ( + totalCharCountBefore <= charIndex + && charIndex < totalCharCountBefore + lineCharCount + ) + { + var currentRegion = textLayout + .GetCharacterRegions(charIndex, 1) + .FirstOrDefault(); + var charPlayingProgress = + line.PlayingProgress * line.Text.Length - charIndex; + + // 确保小于右边距 + float fadeSpacing = 18f; + + float currentCharWidth = (float)currentRegion.LayoutBounds.Width; + + float currentPlayingX = 0f; + + // 行首行尾增加 fadeSpacing 以完成完整的淡入淡出效果 + if (region.LayoutBounds.Left == currentRegion.LayoutBounds.Left) + { + currentCharWidth += fadeSpacing; + currentPlayingX = + lineLeft - fadeSpacing + currentCharWidth * charPlayingProgress; + } + else if (region.LayoutBounds.Right == currentRegion.LayoutBounds.Right) + { + currentCharWidth += fadeSpacing; + currentPlayingX = + (float)currentRegion.LayoutBounds.Left + + currentCharWidth * charPlayingProgress; + } + else + { + currentPlayingX = + (float)currentRegion.LayoutBounds.Left + + currentCharWidth * charPlayingProgress; + } + + float beforeFadeInX = lineLeft - fadeSpacing * 2; + + float beforePlayingOpacity = lineMaskType switch + { + LineRenderingType.UntilCurrentChar => line.Opacity, + LineRenderingType.CurrentCharOnly => isUnhighlightedAreaVisible + ? _defaultOpacity + : 0, + _ => line.Opacity, + }; + + // 画当前字符淡入之前部分(已播放) + ds?.FillRectangle( + new Rect( + beforeFadeInX, + lineTop, + currentPlayingX - fadeSpacing - beforeFadeInX, + lineHeight + ), + Color.FromArgb((byte)(255 * beforePlayingOpacity), 200, 0, 0) + ); + + float fadeInStartX = currentPlayingX - fadeSpacing; + float fadeInEndX = currentPlayingX; + + // 画正处在高亮字符之前 fadeSpaing 距离的渐变部分(淡入) + ds?.FillRectangle( + new Rect(fadeInStartX, lineTop, fadeSpacing, lineHeight), + GetHorizontalFillBrush( + control, + [(0f, beforePlayingOpacity), (1f, line.Opacity)], + fadeInStartX, + fadeInEndX + ) + ); + + float afterPlayingOpacity = isUnhighlightedAreaVisible + ? _defaultOpacity + : 0; + + float fadeOutStartX = currentPlayingX; + float fadeOutEndX = fadeOutStartX + fadeSpacing; + + // 画正处在高亮之后 fadeSpaing 距离的渐变部分(淡出) + ds?.FillRectangle( + new Rect(fadeOutStartX, lineTop, fadeSpacing, lineHeight), + GetHorizontalFillBrush( + control, + [(0f, line.Opacity), (1f, afterPlayingOpacity)], + fadeOutStartX, + fadeOutEndX + ) + ); + + // 画渐变之后透明度为 _defaultOpacity 的部分(未播放) + ds?.FillRectangle( + new Rect( + fadeOutEndX, + lineTop, + lineRight + fadeSpacing * 2 - fadeOutEndX, + lineHeight + ), + Color.FromArgb((byte)(255 * afterPlayingOpacity), 0, 200, 0) + ); + } + else + { + if (charIndex < totalCharCountBefore) + { + // 当前子行未播放 + float opacity = isUnhighlightedAreaVisible ? _defaultOpacity : 0; + ds?.FillRectangle( + new Rect(lineLeft, lineTop, lineWidth, lineHeight), + Color.FromArgb((byte)(255 * opacity), 0, 200, 0) + ); + } + else + { + // 当前子行已完全播放 + float opacity = lineMaskType switch + { + LineRenderingType.UntilCurrentChar => line.Opacity, + LineRenderingType.CurrentCharOnly => _defaultOpacity, + _ => line.Opacity, + }; + if (!isUnhighlightedAreaVisible) + { + switch (lineMaskType) + { + case LineRenderingType.UntilCurrentChar: + opacity *= + (line.Opacity - _defaultOpacity) + / (_highlightedOpacity - _defaultOpacity); + break; + case LineRenderingType.CurrentCharOnly: + opacity = 0; + break; + default: + break; + } + } + ds?.FillRectangle( + new Rect(lineLeft, lineTop, lineWidth, lineHeight), + Color.FromArgb((byte)(255 * opacity), 200, 0, 0) + ); + } + } + totalCharCountBefore += lineCharCount; + } + } + + return alphaMask; + } + /// /// The GetCurrentPlayingLineIndex /// @@ -1250,8 +1314,7 @@ namespace BetterLyrics.WinUI3.ViewModels /// The private CanvasLinearGradientBrush GetHorizontalFillBrush( ICanvasAnimatedControl control, - float[] stopPosition, - float[] stopOpacity, + List<(float position, float opacity)> stops, float startX, float endX ) @@ -1262,28 +1325,13 @@ namespace BetterLyrics.WinUI3.ViewModels return new CanvasLinearGradientBrush( control, - [ - new() + stops + .Select(stops => new CanvasGradientStop { - Position = stopPosition[0], - Color = Color.FromArgb((byte)(255 * stopOpacity[0]), r, g, b), - }, - new() - { - Position = stopPosition[1], - Color = Color.FromArgb((byte)(255 * stopOpacity[1]), r, g, b), - }, - new() - { - Position = stopPosition[2], - Color = Color.FromArgb((byte)(255 * stopOpacity[2]), r, g, b), - }, - new() - { - Position = stopPosition[3], - Color = Color.FromArgb((byte)(255 * stopOpacity[3]), r, g, b), - }, - ] + Position = stops.position, + Color = Color.FromArgb((byte)(stops.opacity * 255), r, g, b), + }) + .ToArray() ) { StartPoint = new Vector2(startX, 0), @@ -1488,20 +1536,27 @@ namespace BetterLyrics.WinUI3.ViewModels continue; } + if (line.CanvasTextLayout != null) + { + line.CanvasTextLayout.Dispose(); + line.CanvasTextLayout = null; + } + // Calculate layout bounds - using var textLayout = new CanvasTextLayout( + line.CanvasTextLayout = new CanvasTextLayout( control, line.Text, _textFormat, (float)_limitedLineWidthTransition.Value, (float)control.Size.Height ); + line.Position = new Vector2(0, y); y += - (float)textLayout.LayoutBounds.Height - / textLayout.LineCount - * (textLayout.LineCount + LyricsLineSpacingFactor); + (float)line.CanvasTextLayout.LayoutBounds.Height + / line.CanvasTextLayout.LineCount + * (line.CanvasTextLayout.LineCount + LyricsLineSpacingFactor); } } @@ -1512,10 +1567,6 @@ namespace BetterLyrics.WinUI3.ViewModels private void UpdateCanvasYScrollOffset(ICanvasAnimatedControl control) { var currentPlayingLineIndex = GetCurrentPlayingLineIndex(); - if (currentPlayingLineIndex < 0) - { - return; - } var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries(); @@ -1527,21 +1578,15 @@ namespace BetterLyrics.WinUI3.ViewModels // Set _scrollOffsetY LyricsLine? currentPlayingLine = _multiLangLyrics .SafeGet(_langIndex) - ?[currentPlayingLineIndex]; + ?.SafeGet(currentPlayingLineIndex); - if (currentPlayingLine == null) + var playingTextLayout = currentPlayingLine?.CanvasTextLayout; + + if (currentPlayingLine == null || playingTextLayout == null) { return; } - using var playingTextLayout = new CanvasTextLayout( - control, - currentPlayingLine.Text, - _textFormat, - (float)_limitedLineWidthTransition.Value, - (float)control.Size.Height - ); - float targetYScrollOffset = (float?)( -currentPlayingLine.Position.Y @@ -1566,18 +1611,12 @@ namespace BetterLyrics.WinUI3.ViewModels { var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i); - if (line == null) + if (line == null || line.CanvasTextLayout == null) { continue; } - using var textLayout = new CanvasTextLayout( - control, - line?.Text, - _textFormat, - (float)_limitedLineWidthTransition.Value, - (float)control.Size.Height - ); + var textLayout = line.CanvasTextLayout; if ( _canvasYScrollTransition.Value @@ -1662,7 +1701,7 @@ namespace BetterLyrics.WinUI3.ViewModels /// /// The source /// The defaultOpacity - private void UpdateLinesProps(List? source, float defaultOpacity) + private void UpdateLinesProps() { var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries(); @@ -1670,7 +1709,7 @@ namespace BetterLyrics.WinUI3.ViewModels for (int i = startLineIndex; i <= endLineIndex; i++) { - var line = source?.SafeGet(i); + var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i); if (line == null) { @@ -1684,7 +1723,7 @@ namespace BetterLyrics.WinUI3.ViewModels if (i + 1 <= endLineIndex) { lineExitingDurationMs = Math.Min( - source?[i + 1].DurationMs ?? 0, + _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i + 1)?.DurationMs ?? 0, lineExitingDurationMs ); } @@ -1696,7 +1735,7 @@ namespace BetterLyrics.WinUI3.ViewModels bool lineExiting = false; float scale = _defaultScale; - float opacity = defaultOpacity; + float opacity = _defaultOpacity; float playProgress = 0; @@ -1718,8 +1757,8 @@ namespace BetterLyrics.WinUI3.ViewModels _defaultScale + (_highlightedScale - _defaultScale) * (float)lineEnteringProgress; opacity = - defaultOpacity - + (_highlightedOpacity - defaultOpacity) * (float)lineEnteringProgress; + _defaultOpacity + + (_highlightedOpacity - _defaultOpacity) * (float)lineEnteringProgress; } } else @@ -1739,7 +1778,7 @@ namespace BetterLyrics.WinUI3.ViewModels - (_highlightedScale - _defaultScale) * (float)lineExitingProgress; opacity = _highlightedOpacity - - (_highlightedOpacity - defaultOpacity) + - (_highlightedOpacity - _defaultOpacity) * (float)lineExitingProgress; } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs index b672df1..38e2099 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSettingsControlViewModel.cs @@ -85,7 +85,7 @@ namespace BetterInAppLyrics.WinUI3.ViewModels /// [ObservableProperty] [NotifyPropertyChangedRecipients] - public partial LyricsGlowEffectScope LyricsGlowEffectScope { get; set; } + public partial LineRenderingType LyricsGlowEffectScope { get; set; } /// /// Gets or sets the LyricsLineSpacingFactor @@ -162,8 +162,8 @@ namespace BetterInAppLyrics.WinUI3.ViewModels /// /// The OnLyricsGlowEffectScopeChanged /// - /// The value - partial void OnLyricsGlowEffectScopeChanged(LyricsGlowEffectScope value) + /// The value + partial void OnLyricsGlowEffectScopeChanged(LineRenderingType value) { _settingsService?.LyricsGlowEffectScope = value; } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml index e02477f..26c46e7 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/SettingsPage.xaml @@ -450,7 +450,6 @@ -