mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
refactor
This commit is contained in:
@@ -13,7 +13,6 @@ using BetterLyrics.WinUI3.Services.ResourceService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.TranslateService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -116,7 +115,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<SettingsPageViewModel>()
|
||||
.AddSingleton<LyricsPageViewModel>()
|
||||
.AddSingleton<MusicGalleryViewModel>()
|
||||
.AddSingleton<LyricsRendererViewModel>()
|
||||
//.AddSingleton<LyricsRendererViewModel>()
|
||||
.AddSingleton<AboutControlViewModel>()
|
||||
.BuildServiceProvider()
|
||||
);
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<PackageReference Include="ComputeSharp.D2D1.WinUI" Version="3.2.0" />
|
||||
<PackageReference Include="csharp-kana" Version="1.0.2" />
|
||||
<PackageReference Include="csharp-pinyin" Version="1.0.1" />
|
||||
<PackageReference Include="DevWinUI.Controls" Version="9.5.0" />
|
||||
<PackageReference Include="DevWinUI.Controls" Version="9.6.0" />
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
|
||||
<PackageReference Include="F23.StringSimilarity" Version="7.0.0" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.2" />
|
||||
@@ -80,7 +80,7 @@
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
<PackageReference Include="NTextCat" Version="0.3.65" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.3-dev-02320" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.0" />
|
||||
@@ -93,7 +93,7 @@
|
||||
<PackageReference Include="Vanara.Windows.Shell" Version="4.2.1" />
|
||||
<PackageReference Include="VCollab.DiscordRichPresence" Version="1.7.0" />
|
||||
<PackageReference Include="WinUIEx" Version="2.9.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.8.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.9.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ColorThief.WinUI3\ColorThief.WinUI3.csproj" />
|
||||
|
||||
@@ -218,13 +218,6 @@
|
||||
Value="{x:Bind LyricsStyleSettings.LyricsLineSpacingFactor, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageLyricsTranslationSeparator" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBox AcceptsReturn="True" Text="{x:Bind LyricsStyleSettings.LyricsTranslationSeparator, Mode=TwoWay}" />
|
||||
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FontSize=12, Glyph=}" Style="{StaticResource GhostButtonStyle}" />
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Renderer.LyricsRenderer"
|
||||
x:Class="BetterLyrics.WinUI3.Controls.NowPlayingCanvas"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Renderer"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Unloaded="LyricsCanvas_Unloaded"
|
||||
Unloaded="Canvas_Unloaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<canvas:CanvasAnimatedControl
|
||||
x:Name="LyricsCanvas"
|
||||
CreateResources="LyricsCanvas_CreateResources"
|
||||
Draw="LyricsCanvas_Draw"
|
||||
Update="LyricsCanvas_Update" />
|
||||
x:Name="Canvas"
|
||||
CreateResources="Canvas_CreateResources"
|
||||
Draw="Canvas_Draw"
|
||||
Update="Canvas_Update" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,607 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Logic;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Renderer;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.LiveStatesService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
public sealed partial class NowPlayingCanvas : UserControl,
|
||||
IRecipient<PropertyChangedMessage<Color>>,
|
||||
IRecipient<PropertyChangedMessage<BitmapImage?>>,
|
||||
IRecipient<PropertyChangedMessage<TimeSpan>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsData?>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>
|
||||
{
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
|
||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||
private readonly ILastFMService _lastFMService = Ioc.Default.GetRequiredService<ILastFMService>();
|
||||
|
||||
private readonly LyricsRenderer _lyricsRenderer = new();
|
||||
private readonly FluidBackgroundRenderer _fluidRenderer = new();
|
||||
private readonly PureColorBackgroundRenderer _pureColorRenderer = new();
|
||||
private readonly SnowRenderer _snowRenderer = new();
|
||||
private readonly FogRenderer _fogRenderer = new();
|
||||
private readonly SpectrumRenderer _spectrumRenderer = new();
|
||||
|
||||
private readonly LyricsSynchronizer _synchronizer = new();
|
||||
private readonly LyricsLayoutManager _layoutManager = new();
|
||||
private readonly LyricsAnimator _animator = new();
|
||||
private readonly LyricsThemeManager _themeManager;
|
||||
|
||||
private readonly SpectrumAnalyzer _spectrumAnalyzer = new();
|
||||
|
||||
private Color _environmentalColor = Colors.Transparent;
|
||||
private readonly ValueTransition<Color> _immersiveBgColorTransition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
private readonly ValueTransition<double> _immersiveBgOpacityTransition = new(
|
||||
initialValue: 1f,
|
||||
durationSeconds: 0.3f
|
||||
);
|
||||
private readonly ValueTransition<Color> _accentColor1Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
private readonly ValueTransition<Color> _accentColor2Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
private readonly ValueTransition<Color> _accentColor3Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
private readonly ValueTransition<Color> _accentColor4Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
private readonly ValueTransition<double> _canvasYScrollTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutQuad
|
||||
);
|
||||
|
||||
private TimeSpan _songPosition; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1>
|
||||
private TimeSpan _totalPlayedTime; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ػ<EFBFBD><D8BB>ظ<EFBFBD><D8B8><EFBFBD><EFBFBD>ŵ<EFBFBD>ʱ<EFBFBD>䣩
|
||||
private bool _isLastFMTracked = false;
|
||||
|
||||
private double _renderLyricsStartX = 0;
|
||||
private double _renderLyricsStartY = 0;
|
||||
private double _renderLyricsWidth = 0;
|
||||
private double _renderLyricsHeight = 0;
|
||||
private double _renderLyricsOpacity = 0;
|
||||
|
||||
private bool _isLayoutChanged = true;
|
||||
private int _playingLineIndex;
|
||||
private (int Start, int End) _visibleRange;
|
||||
private double _canvasTargetScrollOffset;
|
||||
private LyricsThemeColors _currentThemeColors;
|
||||
|
||||
public TimeSpan SongPosition => _songPosition;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><CABC> X <20><><EFBFBD><EFBFBD>
|
||||
public double LyricsStartX
|
||||
{
|
||||
get { return (double)GetValue(LyricsStartXProperty); }
|
||||
set { SetValue(LyricsStartXProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LyricsStartXProperty =
|
||||
DependencyProperty.Register(nameof(LyricsStartX), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ Y <20><><EFBFBD><EFBFBD>
|
||||
public double LyricsStartY
|
||||
{
|
||||
get { return (double)GetValue(LyricsStartYProperty); }
|
||||
set { SetValue(LyricsStartYProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LyricsStartYProperty =
|
||||
DependencyProperty.Register(nameof(LyricsStartY), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
public double LyricsWidth
|
||||
{
|
||||
get { return (double)GetValue(LyricsWidthProperty); }
|
||||
set { SetValue(LyricsWidthProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LyricsWidthProperty =
|
||||
DependencyProperty.Register(nameof(LyricsWidth), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߶<EFBFBD>
|
||||
public double LyricsHeight
|
||||
{
|
||||
get { return (double)GetValue(LyricsHeightProperty); }
|
||||
set { SetValue(LyricsHeightProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LyricsHeightProperty =
|
||||
DependencyProperty.Register(nameof(LyricsHeight), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><CDB8><EFBFBD><EFBFBD>
|
||||
public double LyricsOpacity
|
||||
{
|
||||
get { return (double)GetValue(LyricsOpacityProperty); }
|
||||
set { SetValue(LyricsOpacityProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LyricsOpacityProperty =
|
||||
DependencyProperty.Register(nameof(LyricsOpacity), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
|
||||
|
||||
public NowPlayingCanvas()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<Color>>(this);
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<BitmapImage?>>(this);
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<TimeSpan>>(this);
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsData?>>(this);
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsWindowStatus>>(this);
|
||||
|
||||
_themeManager = new(_mediaSessionsService);
|
||||
}
|
||||
|
||||
private static void OnLayoutPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is NowPlayingCanvas canvas)
|
||||
{
|
||||
if (e.Property == LyricsStartXProperty)
|
||||
{
|
||||
canvas._renderLyricsStartX = Convert.ToDouble(e.NewValue);
|
||||
}
|
||||
else if (e.Property == LyricsStartYProperty)
|
||||
{
|
||||
canvas._renderLyricsStartY = Convert.ToDouble(e.NewValue);
|
||||
}
|
||||
else if (e.Property == LyricsWidthProperty)
|
||||
{
|
||||
canvas._renderLyricsWidth = Convert.ToDouble(e.NewValue);
|
||||
}
|
||||
else if (e.Property == LyricsHeightProperty)
|
||||
{
|
||||
canvas._renderLyricsHeight = Convert.ToDouble(e.NewValue);
|
||||
}
|
||||
else if (e.Property == LyricsOpacityProperty)
|
||||
{
|
||||
canvas._renderLyricsOpacity = Convert.ToDouble(e.NewValue);
|
||||
}
|
||||
|
||||
canvas.TriggerRelayout();
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
|
||||
private void Canvas_Draw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
|
||||
{
|
||||
var bounds = new Rect(0, 0, sender.Size.Width, sender.Size.Height);
|
||||
|
||||
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
|
||||
var albumArtLayout = status.AlbumArtLayoutSettings;
|
||||
var lyricsBg = status.LyricsBackgroundSettings;
|
||||
var lyricsStyle = status.LyricsStyleSettings;
|
||||
var lyricsEffect = status.LyricsEffectSettings;
|
||||
|
||||
var lyricsData = _mediaSessionsService.CurrentLyricsData;
|
||||
double songDuration = _mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0;
|
||||
bool isForceWordByWord = _settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect;
|
||||
|
||||
double fixedSongPositionMs = _songPosition.TotalMilliseconds + (_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
|
||||
|
||||
Color overlayColor;
|
||||
double finalOpacity;
|
||||
|
||||
if (status.IsAdaptToEnvironment)
|
||||
{
|
||||
// <20><><EFBFBD><EFBFBD>Ӧɫ
|
||||
overlayColor = _immersiveBgColorTransition.Value;
|
||||
finalOpacity = _immersiveBgOpacityTransition.Value * lyricsBg.PureColorOverlayOpacity / 100.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ר<><D7A8>ɫ
|
||||
overlayColor = _accentColor1Transition.Value;
|
||||
finalOpacity = lyricsBg.PureColorOverlayOpacity / 100.0;
|
||||
}
|
||||
|
||||
_pureColorRenderer.Draw(
|
||||
args.DrawingSession,
|
||||
bounds,
|
||||
overlayColor,
|
||||
finalOpacity,
|
||||
lyricsBg.IsPureColorOverlayEnabled
|
||||
);
|
||||
|
||||
_fluidRenderer.Opacity = lyricsBg.FluidOverlayOpacity;
|
||||
_fluidRenderer.IsEnabled = lyricsBg.IsFluidOverlayEnabled;
|
||||
_fluidRenderer.Draw(sender, args.DrawingSession);
|
||||
|
||||
_snowRenderer.Draw(sender, args.DrawingSession);
|
||||
|
||||
_fogRenderer.Draw(sender, args.DrawingSession);
|
||||
|
||||
_lyricsRenderer.Draw(
|
||||
control: sender,
|
||||
ds: args.DrawingSession,
|
||||
lyricsData: _mediaSessionsService.CurrentLyricsData,
|
||||
playingLineIndex: _playingLineIndex,
|
||||
startVisibleIndex: _visibleRange.Start,
|
||||
endVisibleIndex: _visibleRange.End,
|
||||
canvasHeight: sender.Size.Height,
|
||||
lyricsX: _renderLyricsStartX,
|
||||
lyricsWidth: _renderLyricsWidth,
|
||||
windowStatus: status,
|
||||
strokeColor: _currentThemeColors.StrokeFontColor,
|
||||
bgColor: _currentThemeColors.BgFontColor,
|
||||
getPlaybackState: (lineIndex) =>
|
||||
{
|
||||
if (lyricsData == null) return new LinePlaybackState();
|
||||
|
||||
var line = lyricsData.LyricsLines.ElementAtOrDefault(lineIndex);
|
||||
if (line == null) return new LinePlaybackState();
|
||||
|
||||
var nextLine = lyricsData.LyricsLines.ElementAtOrDefault(lineIndex + 1);
|
||||
|
||||
return _synchronizer.GetLinePlayingProgress(
|
||||
fixedSongPositionMs,
|
||||
line,
|
||||
nextLine,
|
||||
songDuration,
|
||||
isForceWordByWord
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (_spectrumAnalyzer.IsCapturing)
|
||||
{
|
||||
_spectrumRenderer.Draw(
|
||||
resourceCreator: sender,
|
||||
ds: args.DrawingSession,
|
||||
spectrumData: _spectrumAnalyzer?.SmoothSpectrum,
|
||||
barCount: _spectrumAnalyzer?.BarCount ?? 0,
|
||||
isEnabled: lyricsBg.IsSpectrumOverlayEnabled,
|
||||
placement: lyricsBg.SpectrumPlacement,
|
||||
canvasWidth: sender.Size.Width,
|
||||
canvasHeight: sender.Size.Height,
|
||||
fillColor: _currentThemeColors.BgFontColor
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void Canvas_Update(ICanvasAnimatedControl sender, CanvasAnimatedUpdateEventArgs args)
|
||||
{
|
||||
var lyricsBg = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings;
|
||||
var lyricsEffect = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings;
|
||||
|
||||
TimeSpan elapsedTime = args.Timing.ElapsedTime;
|
||||
|
||||
_accentColor1Transition.Update(elapsedTime);
|
||||
_accentColor2Transition.Update(elapsedTime);
|
||||
_accentColor3Transition.Update(elapsedTime);
|
||||
_accentColor4Transition.Update(elapsedTime);
|
||||
|
||||
_immersiveBgOpacityTransition.Update(elapsedTime);
|
||||
_immersiveBgColorTransition.Update(elapsedTime);
|
||||
|
||||
UpdatePlaybackState(elapsedTime);
|
||||
|
||||
#region UpdatePlayingLineIndex
|
||||
|
||||
int newPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPosition.TotalMilliseconds, _mediaSessionsService.CurrentLyricsData);
|
||||
bool isPlayingLineChanged = newPlayingIndex != _playingLineIndex;
|
||||
_playingLineIndex = newPlayingIndex;
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateTargetScrollOffset
|
||||
|
||||
var targetScroll = _layoutManager.CalculateTargetScrollOffset(_mediaSessionsService.CurrentLyricsData, _playingLineIndex);
|
||||
if (targetScroll.HasValue) _canvasTargetScrollOffset = targetScroll.Value;
|
||||
|
||||
_canvasYScrollTransition.StartTransition(_canvasTargetScrollOffset);
|
||||
_canvasYScrollTransition.Update(elapsedTime);
|
||||
|
||||
#endregion
|
||||
|
||||
_visibleRange = _layoutManager.CalculateVisibleRange(
|
||||
_mediaSessionsService.CurrentLyricsData?.LyricsLines,
|
||||
_canvasYScrollTransition.Value, // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>λ<EFBFBD><CEBB>
|
||||
sender.Size.Height
|
||||
);
|
||||
|
||||
_animator.UpdateVisibleLines(
|
||||
_mediaSessionsService.CurrentLyricsData,
|
||||
_visibleRange.Start,
|
||||
_visibleRange.End,
|
||||
_playingLineIndex,
|
||||
sender.Size.Height,
|
||||
_canvasTargetScrollOffset,
|
||||
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings,
|
||||
_canvasYScrollTransition,
|
||||
elapsedTime,
|
||||
isPlayingLineChanged || _isLayoutChanged
|
||||
);
|
||||
|
||||
_lyricsRenderer.CalculateLyrics3DMatrix(
|
||||
lyricsEffect: lyricsEffect,
|
||||
lyricsX: _renderLyricsStartX,
|
||||
lyricsY: _renderLyricsStartY,
|
||||
lyricsWidth: _renderLyricsWidth,
|
||||
canvasHeight: sender.Size.Height
|
||||
);
|
||||
|
||||
_isLayoutChanged = false;
|
||||
|
||||
if (_fluidRenderer.IsEnabled)
|
||||
{
|
||||
_fluidRenderer.UpdateColors(
|
||||
_accentColor1Transition.Value,
|
||||
_accentColor2Transition.Value,
|
||||
_accentColor3Transition.Value,
|
||||
_accentColor4Transition.Value
|
||||
);
|
||||
_fluidRenderer.Update(elapsedTime);
|
||||
}
|
||||
|
||||
_snowRenderer.IsEnabled = lyricsBg.IsSnowFlakeOverlayEnabled;
|
||||
_snowRenderer.Amount = lyricsBg.SnowFlakeOverlayAmount / 100f;
|
||||
_snowRenderer.Speed = lyricsBg.SnowFlakeOverlaySpeed;
|
||||
_snowRenderer.Update(elapsedTime.TotalSeconds);
|
||||
|
||||
_fogRenderer.IsEnabled = lyricsBg.IsFogOverlayEnabled;
|
||||
_fogRenderer.Update(elapsedTime.TotalSeconds);
|
||||
|
||||
if (lyricsBg.IsSpectrumOverlayEnabled && !_spectrumAnalyzer.IsCapturing)
|
||||
{
|
||||
_spectrumAnalyzer.BarCount = 64;
|
||||
_spectrumAnalyzer.StartCapture();
|
||||
}
|
||||
else if (!lyricsBg.IsSpectrumOverlayEnabled && _spectrumAnalyzer.IsCapturing)
|
||||
{
|
||||
_spectrumAnalyzer.StopCapture();
|
||||
}
|
||||
if (_spectrumAnalyzer.IsCapturing)
|
||||
{
|
||||
_spectrumAnalyzer.UpdateSmoothSpectrum();
|
||||
}
|
||||
}
|
||||
|
||||
private void Canvas_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Canvas.RemoveFromVisualTree();
|
||||
Canvas = null;
|
||||
|
||||
_fluidRenderer.Dispose();
|
||||
_snowRenderer.Dispose();
|
||||
_fogRenderer.Dispose();
|
||||
_spectrumRenderer.Dispose();
|
||||
DisposeAnalyzer();
|
||||
}
|
||||
|
||||
private async void Canvas_CreateResources(CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
|
||||
{
|
||||
args.TrackAsyncAction(_fluidRenderer.LoadResourcesAsync().AsAsyncAction());
|
||||
_snowRenderer.LoadResources();
|
||||
_fogRenderer.LoadResources();
|
||||
|
||||
TriggerRelayout();
|
||||
}
|
||||
|
||||
// ====
|
||||
|
||||
private void UpdateColorConfig()
|
||||
{
|
||||
var themeColors = _themeManager.UpdateColors(
|
||||
_liveStatesService.LiveStates.LyricsWindowStatus,
|
||||
_environmentalColor,
|
||||
_accentColor1Transition,
|
||||
_accentColor2Transition,
|
||||
_accentColor3Transition,
|
||||
_accentColor4Transition
|
||||
);
|
||||
|
||||
_currentThemeColors = themeColors;
|
||||
|
||||
_accentColor1Transition.StartTransition(_currentThemeColors.AccentColor1);
|
||||
_accentColor2Transition.StartTransition(_currentThemeColors.AccentColor2);
|
||||
_accentColor3Transition.StartTransition(_currentThemeColors.AccentColor3);
|
||||
_accentColor4Transition.StartTransition(_currentThemeColors.AccentColor4);
|
||||
}
|
||||
|
||||
private void DisposeAnalyzer()
|
||||
{
|
||||
if (_spectrumAnalyzer.IsCapturing)
|
||||
{
|
||||
_spectrumAnalyzer.StopCapture();
|
||||
}
|
||||
_spectrumAnalyzer.Dispose();
|
||||
}
|
||||
|
||||
private void TriggerRelayout()
|
||||
{
|
||||
if (_layoutManager == null || _mediaSessionsService.CurrentLyricsData == null) return;
|
||||
|
||||
_layoutManager.MeasureAndArrange(
|
||||
resourceCreator: Canvas,
|
||||
lyricsData: _mediaSessionsService.CurrentLyricsData,
|
||||
status: _liveStatesService.LiveStates.LyricsWindowStatus,
|
||||
appSettings: _settingsService.AppSettings,
|
||||
canvasWidth: Canvas.Size.Width,
|
||||
canvasHeight: Canvas.Size.Height,
|
||||
lyricsWidth: _renderLyricsWidth
|
||||
);
|
||||
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
|
||||
private void UpdatePlaybackState(TimeSpan elapsedTime)
|
||||
{
|
||||
if (_mediaSessionsService.CurrentIsPlaying)
|
||||
{
|
||||
_songPosition += elapsedTime;
|
||||
_totalPlayedTime += elapsedTime;
|
||||
CheckAndScrobbleLastFM();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckAndScrobbleLastFM()
|
||||
{
|
||||
bool isEnabled = _mediaSessionsService.CurrentMediaSourceProviderInfo?.IsLastFMTrackEnabled ?? false;
|
||||
if (!isEnabled || _isLastFMTracked) return;
|
||||
|
||||
var songInfo = _mediaSessionsService.CurrentSongInfo;
|
||||
if (songInfo == null || songInfo.Duration <= 0) return;
|
||||
|
||||
if (_totalPlayedTime.TotalSeconds >= songInfo.Duration * 0.5)
|
||||
{
|
||||
_isLastFMTracked = true;
|
||||
_lastFMService.TrackAsync(songInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPlaybackState()
|
||||
{
|
||||
_totalPlayedTime = TimeSpan.Zero;
|
||||
_totalPlayedTime = TimeSpan.Zero;
|
||||
_isLastFMTracked = false;
|
||||
}
|
||||
|
||||
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
|
||||
{
|
||||
if (_mediaSessionsService.CurrentSongInfo == null
|
||||
|| _mediaSessionsService.CurrentLyricsData == null
|
||||
|| _mediaSessionsService.CurrentLyricsData.LyricsLines.Count == 0)
|
||||
{
|
||||
return new Tuple<int, int>(-1, -1);
|
||||
}
|
||||
|
||||
return new Tuple<int, int>(0, _mediaSessionsService.CurrentLyricsData.LyricsLines.Count - 1);
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<Color> message)
|
||||
{
|
||||
if (message.Sender is LyricsWindowViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.BackdropAccentColor))
|
||||
{
|
||||
var newColor = message.NewValue;
|
||||
|
||||
_immersiveBgColorTransition.StartTransition(newColor);
|
||||
_environmentalColor = newColor;
|
||||
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<BitmapImage?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapImage))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<TimeSpan> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
|
||||
{
|
||||
var realPosition = message.NewValue;
|
||||
|
||||
var diff = Math.Abs(_songPosition.TotalMilliseconds - realPosition.TotalMilliseconds);
|
||||
var timelineSyncThreshold = _mediaSessionsService.CurrentMediaSourceProviderInfo?.TimelineSyncThreshold ?? 0;
|
||||
|
||||
// ƫ<><C6AB> or seek
|
||||
if (diff >= timelineSyncThreshold)
|
||||
{
|
||||
_songPosition = realPosition;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˿<EFBFBD>ͷ<EFBFBD><CDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD> LastFM ͳ<><CDB3>״̬
|
||||
if (_songPosition.TotalSeconds <= 1)
|
||||
{
|
||||
_totalPlayedTime = TimeSpan.Zero;
|
||||
_isLastFMTracked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// <20>϶<EFBFBD><CFB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȴ<EFBFBD><C8B4><EFBFBD><EFBFBD><EFBFBD>
|
||||
if (diff >= timelineSyncThreshold + 5000)
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsData?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentLyricsData))
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (IsLoaded)
|
||||
{
|
||||
TriggerRelayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsWindowStatus> message)
|
||||
{
|
||||
if (message.Sender is LiveStates)
|
||||
{
|
||||
if (message.PropertyName == nameof(LiveStates.LyricsWindowStatus))
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (IsLoaded)
|
||||
{
|
||||
TriggerRelayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public partial class SpectrumAnalyzer : IDisposable
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private WasapiLoopbackCapture? _capture;
|
||||
|
||||
private int _sampleRate = 48000;
|
||||
@@ -121,12 +122,15 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
Array.Copy(_spectrumRightData, 0, _spectrumData, _spectrumLeftData.Length, _spectrumRightData.Length);
|
||||
}
|
||||
|
||||
for (int i = 0; i < BarCount; i++)
|
||||
lock (_lock)
|
||||
{
|
||||
int index = (int)((float)i / BarCount * _spectrumData.Length);
|
||||
if (index < _spectrumData.Length)
|
||||
for (int i = 0; i < BarCount; i++)
|
||||
{
|
||||
_currentSpectrum[i] = _spectrumData[index] * 250f * Sensitivity;
|
||||
int index = (int)((float)i / BarCount * _spectrumData.Length);
|
||||
if (index < _spectrumData.Length)
|
||||
{
|
||||
_currentSpectrum[i] = _spectrumData[index] * 250f * Sensitivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,10 +143,13 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < BarCount; i++)
|
||||
lock (_lock)
|
||||
{
|
||||
SmoothSpectrum[i] = SmoothSpectrum[i] * SmoothingFactor +
|
||||
_currentSpectrum[i] * (1 - SmoothingFactor);
|
||||
for (int i = 0; i < BarCount; i++)
|
||||
{
|
||||
SmoothSpectrum[i] = SmoothSpectrum[i] * SmoothingFactor +
|
||||
_currentSpectrum[i] * (1 - SmoothingFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
105
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs
Normal file
105
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Logic
|
||||
{
|
||||
public class LyricsAnimator
|
||||
{
|
||||
private readonly double _defaultScale = 0.75f;
|
||||
private readonly double _highlightedScale = 1.0f;
|
||||
|
||||
public void UpdateVisibleLines(
|
||||
LyricsData? lyricsData,
|
||||
int startVisibleIndex,
|
||||
int endVisibleIndex,
|
||||
int playingLineIndex,
|
||||
double canvasHeight,
|
||||
double targetYScrollOffset,
|
||||
LyricsEffectSettings lyricsEffect,
|
||||
ValueTransition<double> canvasYScrollTransition,
|
||||
TimeSpan elapsedTime,
|
||||
bool isForceUpdate) // 对应 _isLayoutChanged || _isPlayingLineChanged
|
||||
{
|
||||
if (lyricsData == null) return;
|
||||
|
||||
var currentPlayingLine = lyricsData.LyricsLines.ElementAtOrDefault(playingLineIndex);
|
||||
if (currentPlayingLine == null) return;
|
||||
|
||||
// 循环更新可见行
|
||||
for (int i = startVisibleIndex; i <= endVisibleIndex + 1; i++)
|
||||
{
|
||||
var line = lyricsData.LyricsLines.ElementAtOrDefault(i);
|
||||
if (line == null) continue;
|
||||
|
||||
if (isForceUpdate)
|
||||
{
|
||||
int lineCountDelta = i - playingLineIndex;
|
||||
int absLineCountDelta = Math.Abs(lineCountDelta);
|
||||
double distanceFromPlayingLine = Math.Abs(line.OriginalPosition.Y - currentPlayingLine.OriginalPosition.Y);
|
||||
double distanceFactor = Math.Clamp(distanceFromPlayingLine / (canvasHeight / 2), 0, 1);
|
||||
|
||||
line.AngleTransition.StartTransition(lyricsEffect.IsFanLyricsEnabled
|
||||
? Math.PI
|
||||
* (lyricsEffect.FanLyricsAngle / 180.0)
|
||||
* distanceFactor
|
||||
* (i > playingLineIndex ? 1 : -1)
|
||||
: 0
|
||||
);
|
||||
|
||||
line.BlurAmountTransition.StartTransition(5 * distanceFactor);
|
||||
line.ScaleTransition.StartTransition(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale));
|
||||
line.PhoneticTextOpacityTransition.StartTransition(absLineCountDelta == 0 ? 0.3 : (1 - distanceFactor) * 0.3);
|
||||
line.OriginalTextOpacityTransition.StartTransition(absLineCountDelta == 0 ? 1 : (1 - distanceFactor) * 0.3);
|
||||
line.TranslatedTextOpacityTransition.StartTransition(absLineCountDelta == 0 ? 0.3 : (1 - distanceFactor) * 0.3);
|
||||
|
||||
double yScrollDuration;
|
||||
double yScrollDelay;
|
||||
|
||||
if (lineCountDelta < 0)
|
||||
{
|
||||
yScrollDuration =
|
||||
canvasYScrollTransition.DurationSeconds +
|
||||
distanceFactor * (lyricsEffect.LyricsScrollTopDuration / 1000.0 - canvasYScrollTransition.DurationSeconds);
|
||||
yScrollDelay = distanceFactor * lyricsEffect.LyricsScrollTopDelay / 1000.0;
|
||||
}
|
||||
else if (lineCountDelta == 0)
|
||||
{
|
||||
yScrollDuration = canvasYScrollTransition.DurationSeconds;
|
||||
yScrollDelay = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
yScrollDuration =
|
||||
canvasYScrollTransition.DurationSeconds +
|
||||
distanceFactor * (lyricsEffect.LyricsScrollBottomDuration / 1000.0 - canvasYScrollTransition.DurationSeconds);
|
||||
yScrollDelay = distanceFactor * lyricsEffect.LyricsScrollBottomDelay / 1000.0;
|
||||
}
|
||||
|
||||
line.AngleTransition.SetEasingType(canvasYScrollTransition.EasingType);
|
||||
line.AngleTransition.SetDuration(yScrollDuration);
|
||||
line.AngleTransition.SetDelay(yScrollDelay);
|
||||
|
||||
line.YOffsetTransition.SetEasingType(canvasYScrollTransition.EasingType);
|
||||
line.YOffsetTransition.SetDuration(yScrollDuration);
|
||||
line.YOffsetTransition.SetDelay(yScrollDelay);
|
||||
//line.YOffsetTransition.StartTransition(_canvasTargetYScrollOffset, _isLayoutChanged);
|
||||
line.YOffsetTransition.StartTransition(targetYScrollOffset);
|
||||
}
|
||||
|
||||
line.AngleTransition.Update(elapsedTime);
|
||||
line.ScaleTransition.Update(elapsedTime);
|
||||
line.BlurAmountTransition.Update(elapsedTime);
|
||||
line.PhoneticTextOpacityTransition.Update(elapsedTime);
|
||||
line.OriginalTextOpacityTransition.Update(elapsedTime);
|
||||
line.TranslatedTextOpacityTransition.Update(elapsedTime);
|
||||
line.YOffsetTransition.Update(elapsedTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Logic
|
||||
{
|
||||
public class LyricsLayoutManager
|
||||
{
|
||||
public void MeasureAndArrange(
|
||||
ICanvasAnimatedControl resourceCreator,
|
||||
LyricsData? lyricsData,
|
||||
LyricsWindowStatus status,
|
||||
AppSettings appSettings,
|
||||
double canvasWidth,
|
||||
double canvasHeight,
|
||||
double lyricsWidth)
|
||||
{
|
||||
if (lyricsData == null || resourceCreator == null) return;
|
||||
|
||||
// 计算字体大小
|
||||
int originalFontSize, phoneticFontSize, translatedFontSize;
|
||||
var style = status.LyricsStyleSettings;
|
||||
|
||||
if (style.IsDynamicLyricsFontSize)
|
||||
{
|
||||
originalFontSize = (int)Math.Clamp(Math.Min(canvasHeight, canvasWidth) / 15, 18, 96);
|
||||
translatedFontSize = phoneticFontSize = (int)(originalFontSize * 2.0 / 3.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
originalFontSize = style.OriginalLyricsFontSize;
|
||||
phoneticFontSize = style.PhoneticLyricsFontSize;
|
||||
translatedFontSize = style.TranslatedLyricsFontSize;
|
||||
}
|
||||
|
||||
var fontWeight = style.LyricsFontWeight;
|
||||
|
||||
// 排版
|
||||
double currentY = 0;
|
||||
|
||||
foreach (var line in lyricsData.LyricsLines)
|
||||
{
|
||||
if (line == null) continue;
|
||||
|
||||
line.RecreateTextLayout(
|
||||
resourceCreator,
|
||||
appSettings.TranslationSettings.IsChineseRomanizationEnabled || appSettings.TranslationSettings.IsJapaneseRomanizationEnabled,
|
||||
appSettings.TranslationSettings.IsTranslationEnabled,
|
||||
phoneticFontSize, originalFontSize, translatedFontSize,
|
||||
fontWeight,
|
||||
style.LyricsCJKFontFamily, style.LyricsWesternFontFamily,
|
||||
lyricsWidth, canvasHeight, style.LyricsAlignmentType
|
||||
);
|
||||
|
||||
line.RecreateTextGeometry();
|
||||
|
||||
// 注音层
|
||||
line.PhoneticPosition = new Vector2(0, (float)currentY);
|
||||
if (line.PhoneticCanvasTextLayout != null)
|
||||
{
|
||||
currentY += line.PhoneticCanvasTextLayout.LayoutBounds.Height;
|
||||
// 间距
|
||||
currentY += (line.PhoneticCanvasTextLayout.LayoutBounds.Height / line.PhoneticCanvasTextLayout.LineCount) * 0.1;
|
||||
}
|
||||
|
||||
// 原文层
|
||||
line.OriginalPosition = new Vector2(0, (float)currentY);
|
||||
if (line.OriginalCanvasTextLayout != null)
|
||||
{
|
||||
currentY += line.OriginalCanvasTextLayout.LayoutBounds.Height;
|
||||
}
|
||||
|
||||
// 翻译层
|
||||
if (line.TranslatedCanvasTextLayout != null)
|
||||
{
|
||||
// 间距
|
||||
currentY += (line.TranslatedCanvasTextLayout.LayoutBounds.Height / line.TranslatedCanvasTextLayout.LineCount) * 0.1;
|
||||
}
|
||||
line.TranslatedPosition = new Vector2(0, (float)currentY);
|
||||
if (line.TranslatedCanvasTextLayout != null)
|
||||
{
|
||||
currentY += line.TranslatedCanvasTextLayout.LayoutBounds.Height;
|
||||
}
|
||||
|
||||
// 行间距
|
||||
if (line.OriginalCanvasTextLayout != null)
|
||||
{
|
||||
currentY += (line.OriginalCanvasTextLayout.LayoutBounds.Height / line.OriginalCanvasTextLayout.LineCount) * style.LyricsLineSpacingFactor;
|
||||
}
|
||||
|
||||
// 更新中心点
|
||||
line.UpdateCenterPosition(lyricsWidth, style.LyricsAlignmentType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算当前应该滚动到的目标 Y 轴偏移量
|
||||
/// </summary>
|
||||
public double? CalculateTargetScrollOffset(
|
||||
LyricsData? lyricsData,
|
||||
int playingLineIndex)
|
||||
{
|
||||
var lines = lyricsData?.LyricsLines;
|
||||
if (lines == null || lines.Count == 0) return null;
|
||||
|
||||
var currentLine = lines.ElementAtOrDefault(playingLineIndex);
|
||||
var firstLine = lines.FirstOrDefault();
|
||||
|
||||
if (currentLine?.OriginalCanvasTextLayout == null || firstLine == null) return null;
|
||||
|
||||
return -currentLine.OriginalPosition.Y
|
||||
+ firstLine.OriginalPosition.Y
|
||||
- (currentLine.TranslatedPosition.Y
|
||||
+ (currentLine.TranslatedCanvasTextLayout?.LayoutBounds.Height ?? 0)
|
||||
- currentLine.PhoneticPosition.Y) / 2.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算当前屏幕可见的行范围
|
||||
/// 返回值: (StartVisibleIndex, EndVisibleIndex)
|
||||
/// </summary>
|
||||
public (int Start, int End) CalculateVisibleRange(
|
||||
IList<LyricsLine>? lines,
|
||||
double currentScrollOffset,
|
||||
double canvasHeight)
|
||||
{
|
||||
if (lines == null || lines.Count == 0) return (-1, -1);
|
||||
|
||||
double offset = currentScrollOffset + canvasHeight / 2;
|
||||
|
||||
int start = FindFirstVisibleLine(lines, offset);
|
||||
int end = FindLastVisibleLine(lines, offset, canvasHeight);
|
||||
|
||||
// 修正边界情况
|
||||
if (start != -1 && end == -1)
|
||||
{
|
||||
end = lines.Count - 1;
|
||||
}
|
||||
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
private int FindFirstVisibleLine(IList<LyricsLine> lines, double offset)
|
||||
{
|
||||
int left = 0, right = lines.Count - 1, result = -1;
|
||||
while (left <= right)
|
||||
{
|
||||
int mid = (left + right) / 2;
|
||||
var line = lines[mid];
|
||||
if (line.OriginalCanvasTextLayout == null) break;
|
||||
double value = offset + line.OriginalPosition.Y + (double)line.OriginalCanvasTextLayout.LayoutBounds.Height;
|
||||
if (value >= 0) { result = mid; right = mid - 1; }
|
||||
else { left = mid + 1; }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int FindLastVisibleLine(IList<LyricsLine> lines, double offset, double canvasHeight)
|
||||
{
|
||||
int left = 0, right = lines.Count - 1, result = -1;
|
||||
while (left <= right)
|
||||
{
|
||||
int mid = (left + right) / 2;
|
||||
var line = lines[mid];
|
||||
if (line.OriginalCanvasTextLayout == null) break;
|
||||
double value = offset + line.OriginalPosition.Y + (double)line.OriginalCanvasTextLayout.LayoutBounds.Height;
|
||||
if (value >= canvasHeight) { result = mid; right = mid - 1; }
|
||||
else { left = mid + 1; }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Logic
|
||||
{
|
||||
public class LyricsSynchronizer
|
||||
{
|
||||
private int _lastFoundIndex = 0;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_lastFoundIndex = 0;
|
||||
}
|
||||
|
||||
public int GetCurrentLineIndex(double currentTimeMs, LyricsData? lyricsData)
|
||||
{
|
||||
if (lyricsData == null || lyricsData.LyricsLines.Count == 0) return 0;
|
||||
var lines = lyricsData.LyricsLines;
|
||||
|
||||
//// Cache hit
|
||||
//if (IsTimeInLine(currentTimeMs, lines, _lastFoundIndex)) return _lastFoundIndex;
|
||||
//if (_lastFoundIndex + 1 < lines.Count && IsTimeInLine(currentTimeMs, lines, _lastFoundIndex + 1))
|
||||
//{
|
||||
// _lastFoundIndex++;
|
||||
// return _lastFoundIndex;
|
||||
//}
|
||||
|
||||
// Cache miss
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
if (IsTimeInLine(currentTimeMs, lines, i))
|
||||
{
|
||||
_lastFoundIndex = i;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// Default
|
||||
return Math.Min(_lastFoundIndex, lines.Count - 1);
|
||||
}
|
||||
|
||||
public LinePlaybackState GetLinePlayingProgress(
|
||||
double currentTimeMs,
|
||||
LyricsLine line,
|
||||
LyricsLine? nextLine,
|
||||
double songDurationMs,
|
||||
bool isForceWordByWord)
|
||||
{
|
||||
var state = new LinePlaybackState { SyllableStartIndex = 0, SyllableLength = 0, SyllableProgress = 0 };
|
||||
|
||||
if (line == null) return state;
|
||||
|
||||
double lineEndMs;
|
||||
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
|
||||
else if (nextLine != null) lineEndMs = nextLine.StartMs;
|
||||
else lineEndMs = songDurationMs;
|
||||
|
||||
// 还没到
|
||||
if (currentTimeMs < line.StartMs) return state;
|
||||
|
||||
// 过了
|
||||
if (currentTimeMs > lineEndMs)
|
||||
{
|
||||
state.SyllableProgress = 1f;
|
||||
state.SyllableStartIndex = Math.Max(0, line.OriginalText.Length - 1);
|
||||
state.SyllableLength = 1;
|
||||
return state;
|
||||
}
|
||||
|
||||
// 逐字
|
||||
if (line.LyricsSyllables != null && line.LyricsSyllables.Count > 1)
|
||||
{
|
||||
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
|
||||
}
|
||||
|
||||
// 强制逐字
|
||||
if (isForceWordByWord && line.OriginalText.Length > 0)
|
||||
{
|
||||
return CalculateSimulatedProgress(currentTimeMs, line, lineEndMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 普通行
|
||||
state.SyllableStartIndex = line.OriginalText.Length;
|
||||
state.SyllableProgress = 1f;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
private LinePlaybackState CalculateSyllableProgress(double time, LyricsLine line, double lineEndMs)
|
||||
{
|
||||
var state = new LinePlaybackState();
|
||||
int count = line.LyricsSyllables.Count;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var timing = line.LyricsSyllables[i];
|
||||
var nextTiming = (i + 1 < count) ? line.LyricsSyllables[i + 1] : null;
|
||||
|
||||
double timingEndMs = timing.EndMs ?? nextTiming?.StartMs ?? lineEndMs;
|
||||
|
||||
// 在当前字范围内
|
||||
if (time >= timing.StartMs && time <= timingEndMs)
|
||||
{
|
||||
state.SyllableStartIndex = timing.StartIndex;
|
||||
state.SyllableLength = timing.Text.Length;
|
||||
state.SyllableProgress = (timingEndMs > timing.StartMs)
|
||||
? (time - timing.StartMs) / (timingEndMs - timing.StartMs)
|
||||
: 0;
|
||||
return state;
|
||||
}
|
||||
// 在空隙中 (已过当前字,未到下个字)
|
||||
else if (time > timingEndMs && (nextTiming == null || time < nextTiming.StartMs))
|
||||
{
|
||||
state.SyllableProgress = 1f; // 保持上个字满进度
|
||||
state.SyllableStartIndex = timing.StartIndex;
|
||||
state.SyllableLength = timing.Text.Length;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private LinePlaybackState CalculateSimulatedProgress(double time, LyricsLine line, double lineEndMs)
|
||||
{
|
||||
var state = new LinePlaybackState();
|
||||
int textLength = line.OriginalText.Length;
|
||||
|
||||
double progress = (time - line.StartMs) / (lineEndMs - line.StartMs);
|
||||
progress = Math.Clamp(progress, 0, 1);
|
||||
|
||||
double charFloatIndex = progress * textLength;
|
||||
int charIndex = (int)charFloatIndex;
|
||||
|
||||
state.SyllableStartIndex = Math.Clamp(charIndex, 0, textLength - 1);
|
||||
state.SyllableLength = 1;
|
||||
state.SyllableProgress = charFloatIndex - charIndex;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private bool IsTimeInLine(double time, IList<LyricsLine> lines, int index)
|
||||
{
|
||||
if (index < 0 || index >= lines.Count) return false;
|
||||
var line = lines[index];
|
||||
var nextLine = (index + 1 < lines.Count) ? lines[index + 1] : null;
|
||||
if (time < line.StartMs) return false;
|
||||
if (nextLine != null && time >= nextLine.StartMs) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Logic
|
||||
{
|
||||
public class LyricsThemeManager
|
||||
{
|
||||
private readonly IMediaSessionsService _mediaSessionsService;
|
||||
|
||||
public LyricsThemeManager(IMediaSessionsService mediaSessionsService)
|
||||
{
|
||||
_mediaSessionsService = mediaSessionsService;
|
||||
}
|
||||
|
||||
public LyricsThemeColors UpdateColors(
|
||||
LyricsWindowStatus status,
|
||||
Color environmentalColor,
|
||||
ValueTransition<Color> accentColor1Transition,
|
||||
ValueTransition<Color> accentColor2Transition,
|
||||
ValueTransition<Color> accentColor3Transition,
|
||||
ValueTransition<Color> accentColor4Transition
|
||||
)
|
||||
{
|
||||
var result = new LyricsThemeColors();
|
||||
|
||||
ElementTheme themeTypeSent;
|
||||
if (status.IsAdaptToEnvironment)
|
||||
{
|
||||
themeTypeSent = Helper.ColorHelper.GetElementThemeFromBackgroundColor(environmentalColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
themeTypeSent = status.LyricsBackgroundSettings.LyricsBackgroundTheme;
|
||||
}
|
||||
|
||||
bool isLight = themeTypeSent switch
|
||||
{
|
||||
ElementTheme.Default => Application.Current.RequestedTheme == ApplicationTheme.Light,
|
||||
ElementTheme.Light => true,
|
||||
ElementTheme.Dark => false,
|
||||
_ => false
|
||||
};
|
||||
|
||||
Color adaptiveGrayedFontColor;
|
||||
Color grayedEnvironmentalColor;
|
||||
Color? adaptiveColoredFontColor;
|
||||
|
||||
Color darkColor = Colors.Black;
|
||||
Color lightColor = Colors.White;
|
||||
|
||||
if (isLight)
|
||||
{
|
||||
adaptiveGrayedFontColor = darkColor;
|
||||
// brightness = 0.7f;
|
||||
grayedEnvironmentalColor = lightColor;
|
||||
|
||||
result.AccentColor1 = _mediaSessionsService.LightAccentColors.ElementAtOrDefault(0);
|
||||
result.AccentColor2 = _mediaSessionsService.LightAccentColors.ElementAtOrDefault(1);
|
||||
result.AccentColor3 = _mediaSessionsService.LightAccentColors.ElementAtOrDefault(2);
|
||||
result.AccentColor4 = _mediaSessionsService.LightAccentColors.ElementAtOrDefault(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
adaptiveGrayedFontColor = lightColor;
|
||||
// brightness = 0.3f;
|
||||
grayedEnvironmentalColor = darkColor;
|
||||
|
||||
result.AccentColor1 = _mediaSessionsService.DarkAccentColors.ElementAtOrDefault(0);
|
||||
result.AccentColor2 = _mediaSessionsService.DarkAccentColors.ElementAtOrDefault(1);
|
||||
result.AccentColor3 = _mediaSessionsService.DarkAccentColors.ElementAtOrDefault(2);
|
||||
result.AccentColor4 = _mediaSessionsService.DarkAccentColors.ElementAtOrDefault(3);
|
||||
}
|
||||
|
||||
if (status.IsAdaptToEnvironment)
|
||||
{
|
||||
adaptiveColoredFontColor = Helper.ColorHelper.GetForegroundColor(environmentalColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isLight)
|
||||
adaptiveColoredFontColor = _mediaSessionsService.DarkAccentColors.ElementAtOrDefault(0);
|
||||
else
|
||||
adaptiveColoredFontColor = _mediaSessionsService.LightAccentColors.ElementAtOrDefault(0);
|
||||
}
|
||||
|
||||
result.ThemeType = themeTypeSent;
|
||||
|
||||
// 背景字色
|
||||
switch (status.LyricsStyleSettings.LyricsBgFontColorType)
|
||||
{
|
||||
case LyricsFontColorType.AdaptiveGrayed:
|
||||
result.BgFontColor = adaptiveGrayedFontColor;
|
||||
break;
|
||||
case LyricsFontColorType.AdaptiveColored:
|
||||
result.BgFontColor = adaptiveColoredFontColor ?? adaptiveGrayedFontColor;
|
||||
break;
|
||||
case LyricsFontColorType.Custom:
|
||||
result.BgFontColor = status.LyricsStyleSettings.LyricsCustomBgFontColor;
|
||||
break;
|
||||
default:
|
||||
result.BgFontColor = adaptiveGrayedFontColor;
|
||||
break;
|
||||
}
|
||||
|
||||
// 前景字色
|
||||
switch (status.LyricsStyleSettings.LyricsFgFontColorType)
|
||||
{
|
||||
case LyricsFontColorType.AdaptiveGrayed:
|
||||
result.FgFontColor = adaptiveGrayedFontColor;
|
||||
break;
|
||||
case LyricsFontColorType.AdaptiveColored:
|
||||
result.FgFontColor = adaptiveColoredFontColor ?? adaptiveGrayedFontColor;
|
||||
break;
|
||||
case LyricsFontColorType.Custom:
|
||||
result.FgFontColor = status.LyricsStyleSettings.LyricsCustomFgFontColor;
|
||||
break;
|
||||
default:
|
||||
result.FgFontColor = adaptiveGrayedFontColor;
|
||||
break;
|
||||
}
|
||||
|
||||
// 描边颜色
|
||||
switch (status.LyricsStyleSettings.LyricsStrokeFontColorType)
|
||||
{
|
||||
case LyricsFontColorType.AdaptiveGrayed:
|
||||
result.StrokeFontColor = grayedEnvironmentalColor.WithBrightness(0.7);
|
||||
break;
|
||||
case LyricsFontColorType.AdaptiveColored:
|
||||
result.StrokeFontColor = environmentalColor.WithBrightness(0.7);
|
||||
break;
|
||||
case LyricsFontColorType.Custom:
|
||||
result.StrokeFontColor = status.LyricsStyleSettings.LyricsCustomStrokeFontColor;
|
||||
break;
|
||||
default:
|
||||
result.StrokeFontColor = Colors.Transparent;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public struct LinePlaybackState
|
||||
{
|
||||
public int SyllableStartIndex;
|
||||
public int SyllableLength;
|
||||
public double SyllableProgress;
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTranslatedText(LyricsData translationData, string separator, int toleranceMs = 0)
|
||||
public void SetTranslatedText(LyricsData translationData, int toleranceMs = 0)
|
||||
{
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
@@ -68,7 +68,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPhoneticText(LyricsData phoneticData, string separator, int toleranceMs = 0)
|
||||
public void SetPhoneticText(LyricsData phoneticData, int toleranceMs = 0)
|
||||
{
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
@@ -89,7 +89,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTranslation(string translation, string separator)
|
||||
public void SetTranslation(string translation)
|
||||
{
|
||||
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
|
||||
int i = 0;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public struct LyricsThemeColors
|
||||
{
|
||||
public Color BgFontColor;
|
||||
public Color FgFontColor;
|
||||
public Color StrokeFontColor;
|
||||
|
||||
public Color AccentColor1;
|
||||
public Color AccentColor2;
|
||||
public Color AccentColor3;
|
||||
public Color AccentColor4;
|
||||
|
||||
public ElementTheme ThemeType;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsFontColorType LyricsStrokeFontColorType { get; set; } = LyricsFontColorType.AdaptiveGrayed;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsFontWeight LyricsFontWeight { get; set; } = LyricsFontWeight.Bold;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double LyricsLineSpacingFactor { get; set; } = 0.5;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsTranslationSeparator { get; set; } = StringHelper.NewLine;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsCJKFontFamily { get; set; } = FontHelper.SystemFontFamilies.FirstOrDefault() ?? "";
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsWesternFontFamily { get; set; } = FontHelper.SystemFontFamilies.FirstOrDefault() ?? "";
|
||||
|
||||
@@ -48,7 +47,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
LyricsStrokeFontColorType = this.LyricsStrokeFontColorType,
|
||||
LyricsFontWeight = this.LyricsFontWeight,
|
||||
LyricsLineSpacingFactor = this.LyricsLineSpacingFactor,
|
||||
LyricsTranslationSeparator = this.LyricsTranslationSeparator,
|
||||
LyricsCJKFontFamily = this.LyricsCJKFontFamily,
|
||||
LyricsWesternFontFamily = this.LyricsWesternFontFamily,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
public class FluidBackgroundRenderer : IDisposable
|
||||
{
|
||||
private PixelShaderEffect? _fluidEffect;
|
||||
private float _timeAccumulator = 0f;
|
||||
private Vector3 _c1, _c2, _c3, _c4;
|
||||
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
public double Opacity { get; set; } = 1.0;
|
||||
|
||||
public bool EnableLightWave { get; set; } = false;
|
||||
|
||||
public async Task LoadResourcesAsync()
|
||||
{
|
||||
Dispose();
|
||||
|
||||
try
|
||||
{
|
||||
var uri = new Uri("ms-appx:///Assets/FluidEffect.bin");
|
||||
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(uri);
|
||||
|
||||
using (var stream = await file.OpenReadAsync())
|
||||
{
|
||||
var buffer = new Windows.Storage.Streams.Buffer((uint)stream.Size);
|
||||
await stream.ReadAsync(buffer, (uint)stream.Size, Windows.Storage.Streams.InputStreamOptions.None);
|
||||
byte[] bytes = buffer.ToArray();
|
||||
|
||||
_fluidEffect = new PixelShaderEffect(bytes);
|
||||
|
||||
_fluidEffect.Properties["EnableLightWave"] = EnableLightWave;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[FluidRenderer] Load Failed: {ex.Message}");
|
||||
_fluidEffect = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateColors(Color c1, Color c2, Color c3, Color c4)
|
||||
{
|
||||
_c1 = c1.ToVector3RGB();
|
||||
_c2 = c2.ToVector3RGB();
|
||||
_c3 = c3.ToVector3RGB();
|
||||
_c4 = c4.ToVector3RGB();
|
||||
}
|
||||
|
||||
public void Update(TimeSpan deltaTime)
|
||||
{
|
||||
if (_fluidEffect == null || !IsEnabled) return;
|
||||
|
||||
_timeAccumulator += (float)deltaTime.TotalSeconds;
|
||||
|
||||
_fluidEffect.Properties["iTime"] = _timeAccumulator;
|
||||
|
||||
_fluidEffect.Properties["color1"] = _c1;
|
||||
_fluidEffect.Properties["color2"] = _c2;
|
||||
_fluidEffect.Properties["color3"] = _c3;
|
||||
_fluidEffect.Properties["color4"] = _c4;
|
||||
|
||||
_fluidEffect.Properties["EnableLightWave"] = EnableLightWave;
|
||||
}
|
||||
|
||||
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
if (_fluidEffect == null || !IsEnabled || Opacity <= 0) return;
|
||||
|
||||
float pixelWidth = control.ConvertDipsToPixels((float)control.Size.Width, CanvasDpiRounding.Round);
|
||||
float pixelHeight = control.ConvertDipsToPixels((float)control.Size.Height, CanvasDpiRounding.Round);
|
||||
|
||||
_fluidEffect.Properties["Width"] = pixelWidth;
|
||||
_fluidEffect.Properties["Height"] = pixelHeight;
|
||||
|
||||
if (Opacity >= 1.0)
|
||||
{
|
||||
ds.DrawImage(_fluidEffect);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var opacityEffect = new OpacityEffect
|
||||
{
|
||||
Source = _fluidEffect,
|
||||
Opacity = (float)Opacity
|
||||
})
|
||||
{
|
||||
ds.DrawImage(opacityEffect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_fluidEffect?.Dispose();
|
||||
_fluidEffect = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using BetterLyrics.WinUI3.Shaders;
|
||||
using ComputeSharp.D2D1.WinUI;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
public class FogRenderer : IDisposable
|
||||
{
|
||||
private PixelShaderEffect<FogEffect>? _fogEffect;
|
||||
private float _timeAccumulator = 0f;
|
||||
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
|
||||
public void LoadResources()
|
||||
{
|
||||
Dispose();
|
||||
_fogEffect = new PixelShaderEffect<FogEffect>();
|
||||
}
|
||||
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
if (_fogEffect == null || !IsEnabled) return;
|
||||
_timeAccumulator += (float)deltaTime;
|
||||
}
|
||||
|
||||
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
if (_fogEffect == null || !IsEnabled) return;
|
||||
|
||||
float width = control.ConvertDipsToPixels((float)control.Size.Width, CanvasDpiRounding.Round);
|
||||
float height = control.ConvertDipsToPixels((float)control.Size.Height, CanvasDpiRounding.Round);
|
||||
|
||||
_fogEffect.ConstantBuffer = new FogEffect(
|
||||
_timeAccumulator,
|
||||
new float2(width, height)
|
||||
);
|
||||
|
||||
ds.DrawImage(_fogEffect);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_fogEffect?.Dispose();
|
||||
_fogEffect = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using Lyricify.Lyrics.Providers.Web.Netease;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
public class LyricsRenderer
|
||||
{
|
||||
private readonly PlayingLineRenderer _playingRenderer = new();
|
||||
private readonly UnplayingLineRenderer _unplayingRenderer = new();
|
||||
|
||||
private Matrix4x4 _threeDimMatrix = Matrix4x4.Identity;
|
||||
|
||||
public void Draw(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
LyricsData? lyricsData,
|
||||
int playingLineIndex,
|
||||
int startVisibleIndex,
|
||||
int endVisibleIndex,
|
||||
double canvasHeight,
|
||||
double lyricsX,
|
||||
double lyricsWidth,
|
||||
LyricsWindowStatus windowStatus,
|
||||
Color strokeColor,
|
||||
Color bgColor,
|
||||
Func<int, LinePlaybackState> getPlaybackState)
|
||||
{
|
||||
if (windowStatus.LyricsEffectSettings.Is3DLyricsEnabled)
|
||||
{
|
||||
using (var layer = new CanvasCommandList(control))
|
||||
{
|
||||
using (var layerDs = layer.CreateDrawingSession())
|
||||
{
|
||||
DrawLyrics(
|
||||
control,
|
||||
layerDs,
|
||||
lyricsData,
|
||||
playingLineIndex,
|
||||
startVisibleIndex,
|
||||
endVisibleIndex,
|
||||
canvasHeight,
|
||||
lyricsX,
|
||||
lyricsWidth,
|
||||
windowStatus,
|
||||
strokeColor,
|
||||
bgColor,
|
||||
getPlaybackState);
|
||||
}
|
||||
|
||||
ds.DrawImage(new Transform3DEffect
|
||||
{
|
||||
Source = layer,
|
||||
TransformMatrix = _threeDimMatrix
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawLyrics(
|
||||
control,
|
||||
ds,
|
||||
lyricsData,
|
||||
playingLineIndex,
|
||||
startVisibleIndex,
|
||||
endVisibleIndex,
|
||||
canvasHeight,
|
||||
lyricsX,
|
||||
lyricsWidth,
|
||||
windowStatus,
|
||||
strokeColor,
|
||||
bgColor,
|
||||
getPlaybackState);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLyrics(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
LyricsData? lyricsData,
|
||||
int playingLineIndex,
|
||||
int startVisibleIndex,
|
||||
int endVisibleIndex,
|
||||
double canvasHeight,
|
||||
double lyricsX,
|
||||
double lyricsWidth,
|
||||
LyricsWindowStatus windowStatus,
|
||||
Color strokeColor,
|
||||
Color bgColor,
|
||||
Func<int, LinePlaybackState> getPlaybackState)
|
||||
{
|
||||
if (lyricsData == null) return;
|
||||
|
||||
var currentPlayingLine = lyricsData.LyricsLines.ElementAtOrDefault(playingLineIndex);
|
||||
if (currentPlayingLine == null) return;
|
||||
|
||||
var effectSettings = windowStatus.LyricsEffectSettings;
|
||||
var styleSettings = windowStatus.LyricsStyleSettings;
|
||||
|
||||
var rotationY = currentPlayingLine.OriginalPosition.WithX(effectSettings.FanLyricsAngle < 0 ? (float)lyricsWidth : 0);
|
||||
|
||||
for (int i = startVisibleIndex; i <= endVisibleIndex; i++)
|
||||
{
|
||||
var line = lyricsData.LyricsLines.ElementAtOrDefault(i);
|
||||
if (line == null) continue;
|
||||
|
||||
if (line.OriginalCanvasTextLayout == null) continue;
|
||||
if (line.OriginalCanvasTextLayout.LayoutBounds.Width <= 0) continue;
|
||||
|
||||
double yOffset = line.YOffsetTransition.Value + canvasHeight / 2;
|
||||
|
||||
var transform =
|
||||
Matrix3x2.CreateScale((float)line.ScaleTransition.Value, line.CenterPosition) *
|
||||
Matrix3x2.CreateRotation((float)line.AngleTransition.Value, rotationY) *
|
||||
Matrix3x2.CreateTranslation((float)lyricsX, (float)yOffset);
|
||||
|
||||
ds.Transform = transform;
|
||||
|
||||
using (var textOnlyLayer = RenderBaseTextLayer(control, line, styleSettings.LyricsFontStrokeWidth, strokeColor, bgColor))
|
||||
{
|
||||
if (i == playingLineIndex)
|
||||
{
|
||||
var state = getPlaybackState(i);
|
||||
|
||||
_playingRenderer.Draw(control, ds, textOnlyLayer, line, state, effectSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
_unplayingRenderer.Draw(ds, textOnlyLayer, line);
|
||||
}
|
||||
}
|
||||
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
}
|
||||
|
||||
private CanvasCommandList RenderBaseTextLayer(
|
||||
ICanvasResourceCreator resourceCreator,
|
||||
LyricsLine line,
|
||||
double strokeWidth,
|
||||
Color strokeColor,
|
||||
Color fillColor)
|
||||
{
|
||||
var commandList = new CanvasCommandList(resourceCreator);
|
||||
using (var clds = commandList.CreateDrawingSession())
|
||||
{
|
||||
if (strokeWidth > 0)
|
||||
{
|
||||
DrawGeometrySafely(clds, line.PhoneticCanvasGeometry, line.PhoneticPosition, strokeColor, strokeWidth);
|
||||
DrawGeometrySafely(clds, line.OriginalCanvasGeometry, line.OriginalPosition, strokeColor, strokeWidth);
|
||||
DrawGeometrySafely(clds, line.TranslatedCanvasGeometry, line.TranslatedPosition, strokeColor, strokeWidth);
|
||||
}
|
||||
|
||||
DrawTextLayoutSafely(clds, line.PhoneticCanvasTextLayout, line.PhoneticPosition, fillColor);
|
||||
DrawTextLayoutSafely(clds, line.OriginalCanvasTextLayout, line.OriginalPosition, fillColor);
|
||||
DrawTextLayoutSafely(clds, line.TranslatedCanvasTextLayout, line.TranslatedPosition, fillColor);
|
||||
}
|
||||
return commandList;
|
||||
}
|
||||
|
||||
private void DrawGeometrySafely(CanvasDrawingSession ds, CanvasGeometry? geo, Vector2 pos, Color color, double width)
|
||||
{
|
||||
if (geo == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
ds.DrawGeometry(geo, pos, color, (float)width);
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
private void DrawTextLayoutSafely(CanvasDrawingSession ds, CanvasTextLayout? layout, Vector2 pos, Color color)
|
||||
{
|
||||
if (layout == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
ds.DrawTextLayout(layout, pos, color);
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
public void CalculateLyrics3DMatrix(LyricsEffectSettings lyricsEffect, double lyricsX, double lyricsY, double lyricsWidth, double canvasHeight)
|
||||
{
|
||||
if (!lyricsEffect.Is3DLyricsEnabled) return;
|
||||
|
||||
Vector3 center = new(
|
||||
(float)(lyricsX + lyricsWidth / 2),
|
||||
(float)(lyricsY + canvasHeight / 2),
|
||||
0);
|
||||
|
||||
float rotationX = (float)(Math.PI * lyricsEffect.Lyrics3DXAngle / 180.0);
|
||||
float rotationY = (float)(Math.PI * lyricsEffect.Lyrics3DYAngle / 180.0);
|
||||
float rotationZ = (float)(Math.PI * lyricsEffect.Lyrics3DZAngle / 180.0);
|
||||
|
||||
Matrix4x4 rotation =
|
||||
Matrix4x4.CreateRotationX(rotationX) *
|
||||
Matrix4x4.CreateRotationY(rotationY) *
|
||||
Matrix4x4.CreateRotationZ(rotationZ);
|
||||
Matrix4x4 perspective = Matrix4x4.Identity;
|
||||
perspective.M34 = 1.0f / lyricsEffect.Lyrics3DDepth;
|
||||
|
||||
// 组合变换:
|
||||
// 1. 将中心移到原点
|
||||
// 2. 旋转
|
||||
// 3. 应用透视
|
||||
// 4. 将中心移回原位
|
||||
_threeDimMatrix =
|
||||
Matrix4x4.CreateTranslation(-center) *
|
||||
rotation *
|
||||
perspective *
|
||||
Matrix4x4.CreateTranslation(center);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
public sealed partial class LyricsRenderer : UserControl
|
||||
{
|
||||
public LyricsRendererViewModel ViewModel { get; set; }
|
||||
|
||||
public LyricsRenderer()
|
||||
{
|
||||
InitializeComponent();
|
||||
ViewModel = Ioc.Default.GetRequiredService<LyricsRendererViewModel>();
|
||||
}
|
||||
|
||||
private void LyricsCanvas_Draw(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
|
||||
{
|
||||
using var ds = args.DrawingSession;
|
||||
ViewModel.Draw(sender, ds);
|
||||
}
|
||||
|
||||
private void LyricsCanvas_Update(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args)
|
||||
{
|
||||
ViewModel.Update(sender, args);
|
||||
}
|
||||
|
||||
private void LyricsCanvas_Unloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
LyricsCanvas.RemoveFromVisualTree();
|
||||
LyricsCanvas = null;
|
||||
}
|
||||
|
||||
private void LyricsCanvas_CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
|
||||
{
|
||||
ViewModel.CreateResources(sender, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Brushes;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
public class PlayingLineRenderer
|
||||
{
|
||||
public void Draw(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
ICanvasImage textOnlyLayer,
|
||||
LyricsLine line,
|
||||
LinePlaybackState playbackState,
|
||||
LyricsEffectSettings settings)
|
||||
{
|
||||
DrawPhonetic(ds, textOnlyLayer, line);
|
||||
DrawOriginalText(control, ds, textOnlyLayer, line, playbackState, settings);
|
||||
DrawTranslated(ds, textOnlyLayer, line);
|
||||
}
|
||||
|
||||
private void DrawPhonetic(CanvasDrawingSession ds, ICanvasImage source, LyricsLine line)
|
||||
{
|
||||
if (line.PhoneticCanvasTextLayout == null) return;
|
||||
|
||||
var opacity = line.PhoneticTextOpacityTransition.Value;
|
||||
var blur = line.BlurAmountTransition.Value;
|
||||
var bounds = line.PhoneticCanvasTextLayout.LayoutBounds;
|
||||
|
||||
var destRect = new Rect(
|
||||
bounds.X + line.PhoneticPosition.X,
|
||||
bounds.Y + line.PhoneticPosition.Y,
|
||||
bounds.Width,
|
||||
bounds.Height
|
||||
);
|
||||
|
||||
using (var blurEffect = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = (float)blur,
|
||||
Source = source,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
})
|
||||
{
|
||||
ds.DrawImage(blurEffect, destRect, destRect, (float)opacity);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTranslated(CanvasDrawingSession ds, ICanvasImage source, LyricsLine line)
|
||||
{
|
||||
if (line.TranslatedCanvasTextLayout == null) return;
|
||||
|
||||
var opacity = line.TranslatedTextOpacityTransition.Value;
|
||||
var blur = line.BlurAmountTransition.Value;
|
||||
var bounds = line.TranslatedCanvasTextLayout.LayoutBounds;
|
||||
|
||||
var destRect = new Rect(
|
||||
bounds.X + line.TranslatedPosition.X,
|
||||
bounds.Y + line.TranslatedPosition.Y,
|
||||
bounds.Width,
|
||||
bounds.Height
|
||||
);
|
||||
|
||||
using (var blurEffect = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = (float)blur,
|
||||
Source = source,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
})
|
||||
{
|
||||
ds.DrawImage(blurEffect, destRect, destRect, (float)opacity);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawOriginalText(
|
||||
ICanvasResourceCreator resourceCreator,
|
||||
CanvasDrawingSession ds,
|
||||
ICanvasImage source,
|
||||
LyricsLine line,
|
||||
LinePlaybackState state,
|
||||
LyricsEffectSettings settings)
|
||||
{
|
||||
if (line.OriginalCanvasTextLayout == null) return;
|
||||
|
||||
var originalTextOpacity = line.OriginalTextOpacityTransition.Value;
|
||||
|
||||
var curCharIndex = state.SyllableStartIndex + state.SyllableLength * state.SyllableProgress;
|
||||
float fadeWidth = (1f / Math.Max(1, line.OriginalText.Length)) * 0.5f;
|
||||
|
||||
var lineRegions = line.OriginalCanvasTextLayout.GetCharacterRegions(0, line.OriginalText.Length);
|
||||
|
||||
foreach (var subLineRegion in lineRegions)
|
||||
{
|
||||
DrawSubLineRegion(resourceCreator, ds, source, line, subLineRegion, curCharIndex, fadeWidth, originalTextOpacity, state, settings);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSubLineRegion(
|
||||
ICanvasResourceCreator resourceCreator,
|
||||
CanvasDrawingSession ds,
|
||||
ICanvasImage source,
|
||||
LyricsLine line,
|
||||
CanvasTextLayoutRegion subLineRegion,
|
||||
double curCharIndex,
|
||||
float fadeWidth,
|
||||
double originalTextOpacity,
|
||||
LinePlaybackState state,
|
||||
LyricsEffectSettings settings)
|
||||
{
|
||||
var subLineLayoutBounds = subLineRegion.LayoutBounds;
|
||||
Rect subLineRect = new(
|
||||
subLineLayoutBounds.X + line.OriginalPosition.X,
|
||||
subLineLayoutBounds.Y + line.OriginalPosition.Y,
|
||||
subLineLayoutBounds.Width,
|
||||
subLineLayoutBounds.Height
|
||||
);
|
||||
|
||||
using (var maskLayer = new CanvasCommandList(resourceCreator))
|
||||
{
|
||||
using (var maskLayerDs = maskLayer.CreateDrawingSession())
|
||||
{
|
||||
float progressInRegion = (float)((curCharIndex - subLineRegion.CharacterIndex) / subLineRegion.CharacterCount);
|
||||
progressInRegion = Math.Clamp(progressInRegion, 0, 1 + fadeWidth);
|
||||
|
||||
var stop1 = Colors.White.WithAlpha((byte)(255 * originalTextOpacity));
|
||||
var stop2 = Color.FromArgb((byte)(255 * Math.Min(0.3, originalTextOpacity)), 255, 255, 255);
|
||||
|
||||
using (var maskBrush = new CanvasLinearGradientBrush(resourceCreator,
|
||||
[
|
||||
new CanvasGradientStop { Position = 0, Color = stop1 },
|
||||
new CanvasGradientStop { Position = progressInRegion, Color = stop1 },
|
||||
new CanvasGradientStop { Position = progressInRegion + fadeWidth, Color = stop2 },
|
||||
new CanvasGradientStop { Position = 1 + fadeWidth, Color = stop2 }
|
||||
]))
|
||||
{
|
||||
maskBrush.StartPoint = new Vector2((float)subLineRect.X, (float)subLineRect.Y);
|
||||
maskBrush.EndPoint = new Vector2((float)(subLineRect.X + subLineRect.Width), (float)subLineRect.Y);
|
||||
maskLayerDs.FillRectangle(subLineRect, maskBrush);
|
||||
}
|
||||
}
|
||||
|
||||
using (var cropEffect = new CropEffect
|
||||
{
|
||||
Source = source,
|
||||
SourceRectangle = subLineRect,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
})
|
||||
using (var textWithOpacityLayer = new AlphaMaskEffect
|
||||
{
|
||||
Source = cropEffect,
|
||||
AlphaMask = maskLayer
|
||||
})
|
||||
{
|
||||
int endCharIndex = subLineRegion.CharacterIndex + subLineRegion.CharacterCount;
|
||||
for (int i = subLineRegion.CharacterIndex; i < endCharIndex; i++)
|
||||
{
|
||||
DrawSingleCharacter(ds, line, i, curCharIndex, textWithOpacityLayer, state, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSingleCharacter(
|
||||
CanvasDrawingSession ds,
|
||||
LyricsLine line,
|
||||
int charIndex,
|
||||
double exactProgressIndex,
|
||||
ICanvasImage maskedSource,
|
||||
LinePlaybackState state,
|
||||
LyricsEffectSettings settings)
|
||||
{
|
||||
var curCharIndexInt = (int)Math.Floor(exactProgressIndex);
|
||||
if (line.OriginalCanvasTextLayout == null) return;
|
||||
|
||||
var charRegions = line.OriginalCanvasTextLayout.GetCharacterRegions(charIndex, 1);
|
||||
if (charRegions.Length == 0) return;
|
||||
var charRegion = charRegions[0];
|
||||
var charLayoutBounds = charRegion.LayoutBounds;
|
||||
|
||||
var sourceCharRect = new Rect(
|
||||
charLayoutBounds.X + line.OriginalPosition.X,
|
||||
charLayoutBounds.Y + line.OriginalPosition.Y,
|
||||
charLayoutBounds.Width,
|
||||
charLayoutBounds.Height
|
||||
);
|
||||
|
||||
double floatOffset = 0;
|
||||
double scale = 1;
|
||||
double glow = 0;
|
||||
|
||||
if (settings.IsLyricsFloatAnimationEnabled)
|
||||
{
|
||||
double targetFloatOffset = sourceCharRect.Height * 0.05;
|
||||
if (charIndex < curCharIndexInt) floatOffset = 0;
|
||||
else if (charIndex == curCharIndexInt)
|
||||
{
|
||||
var p = exactProgressIndex - curCharIndexInt;
|
||||
floatOffset = -targetFloatOffset + p * targetFloatOffset;
|
||||
}
|
||||
else floatOffset = -targetFloatOffset;
|
||||
}
|
||||
|
||||
var parentSyllable = line.LyricsSyllables.FirstOrDefault(x => x.StartIndex <= charIndex && charIndex < x.StartIndex + x.Text.Length);
|
||||
|
||||
if (parentSyllable != null && parentSyllable.IsLongDuration && parentSyllable.StartIndex == state.SyllableStartIndex)
|
||||
{
|
||||
if (settings.IsLyricsScaleEffectEnabled)
|
||||
{
|
||||
scale += Math.Sin(state.SyllableProgress * Math.PI) * 0.15;
|
||||
}
|
||||
if (settings.IsLyricsGlowEffectEnabled)
|
||||
{
|
||||
glow = Math.Sin(state.SyllableProgress * Math.PI) * sourceCharRect.Height * 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
var destCharRect = sourceCharRect.Scale(scale).AddY(-floatOffset);
|
||||
|
||||
using (var singleCharCrop = new CropEffect
|
||||
{
|
||||
Source = maskedSource,
|
||||
SourceRectangle = sourceCharRect,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
})
|
||||
{
|
||||
// 这里不做 glow > 0 的判断而总是加入辉光效果是为了平滑不断层
|
||||
using (var glowEffect = new GaussianBlurEffect
|
||||
{
|
||||
Source = singleCharCrop,
|
||||
BlurAmount = (float)glow,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
})
|
||||
{
|
||||
ds.DrawImage(glowEffect, destCharRect.Extend(sourceCharRect.Height), sourceCharRect.Extend(sourceCharRect.Height));
|
||||
}
|
||||
|
||||
ds.DrawImage(singleCharCrop, destCharRect, sourceCharRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
public class PureColorBackgroundRenderer
|
||||
{
|
||||
public void Draw(
|
||||
CanvasDrawingSession ds,
|
||||
Rect bounds,
|
||||
Color color,
|
||||
double opacity,
|
||||
bool isEnabled)
|
||||
{
|
||||
if (!isEnabled || opacity <= 0) return;
|
||||
|
||||
ds.FillRectangle(
|
||||
bounds,
|
||||
color.WithAlpha((byte)(opacity * 255))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using BetterLyrics.WinUI3.Shaders;
|
||||
using ComputeSharp.D2D1.WinUI;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
public class SnowRenderer : IDisposable
|
||||
{
|
||||
private PixelShaderEffect<SnowEffect>? _snowEffect;
|
||||
private float _timeAccumulator = 0f;
|
||||
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
public float Amount { get; set; } = 0.5f;
|
||||
public float Speed { get; set; } = 1.0f;
|
||||
|
||||
public void LoadResources()
|
||||
{
|
||||
Dispose();
|
||||
_snowEffect = new PixelShaderEffect<SnowEffect>();
|
||||
}
|
||||
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
if (_snowEffect == null || !IsEnabled) return;
|
||||
_timeAccumulator += (float)deltaTime;
|
||||
}
|
||||
|
||||
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
if (_snowEffect == null || !IsEnabled) return;
|
||||
|
||||
float width = control.ConvertDipsToPixels((float)control.Size.Width, CanvasDpiRounding.Round);
|
||||
float height = control.ConvertDipsToPixels((float)control.Size.Height, CanvasDpiRounding.Round);
|
||||
|
||||
_snowEffect.ConstantBuffer = new SnowEffect(
|
||||
_timeAccumulator,
|
||||
new float2(width, height),
|
||||
Amount, // 0.0 ~ 1.0
|
||||
Speed
|
||||
);
|
||||
|
||||
ds.DrawImage(_snowEffect);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_snowEffect?.Dispose();
|
||||
_snowEffect = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Brushes;
|
||||
using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.UI;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
public class SpectrumRenderer : IDisposable
|
||||
{
|
||||
private CanvasGeometry? _spectrumGeometry;
|
||||
|
||||
public void Draw(
|
||||
ICanvasResourceCreator resourceCreator,
|
||||
CanvasDrawingSession ds,
|
||||
float[]? spectrumData,
|
||||
int barCount,
|
||||
bool isEnabled,
|
||||
SpectrumPlacement placement,
|
||||
double canvasWidth,
|
||||
double canvasHeight,
|
||||
Color fillColor
|
||||
)
|
||||
{
|
||||
_spectrumGeometry?.Dispose();
|
||||
_spectrumGeometry = null;
|
||||
|
||||
if (!isEnabled || spectrumData == null || spectrumData.Length == 0) return;
|
||||
|
||||
_spectrumGeometry = CreateGeometry(resourceCreator, spectrumData, barCount, placement, canvasWidth, canvasHeight);
|
||||
|
||||
if (_spectrumGeometry != null)
|
||||
{
|
||||
DrawGeometry(ds, _spectrumGeometry, fillColor, placement, canvasHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private CanvasGeometry? CreateGeometry(
|
||||
ICanvasResourceCreator creator,
|
||||
float[] data,
|
||||
int barCount,
|
||||
SpectrumPlacement placement,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
if (barCount < 2) return null;
|
||||
|
||||
var points = new Vector2[barCount];
|
||||
float pointSpacing = (float)width / (barCount - 1);
|
||||
|
||||
for (int i = 0; i < barCount; i++)
|
||||
{
|
||||
float val = i < data.Length ? data[i] : 0;
|
||||
points[i] = new Vector2(i * pointSpacing, val);
|
||||
}
|
||||
|
||||
// 限制高度
|
||||
float maxY = 0;
|
||||
foreach (var p in points) if (p.Y > maxY) maxY = p.Y;
|
||||
|
||||
float limitY = (float)height * 0.2f;
|
||||
if (maxY > limitY)
|
||||
{
|
||||
float ratio = limitY / maxY;
|
||||
for (int i = 0; i < points.Length; i++) points[i].Y *= ratio;
|
||||
}
|
||||
|
||||
// 翻转 Y 轴
|
||||
if (placement == SpectrumPlacement.Bottom)
|
||||
{
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
{
|
||||
points[i].Y = (float)height - points[i].Y;
|
||||
}
|
||||
}
|
||||
|
||||
using var pathBuilder = new CanvasPathBuilder(creator);
|
||||
pathBuilder.BeginFigure(points[0]);
|
||||
|
||||
for (int i = 0; i < barCount - 1; i++)
|
||||
{
|
||||
Vector2 p0 = points[Math.Max(i - 1, 0)];
|
||||
Vector2 p1 = points[i];
|
||||
Vector2 p2 = points[i + 1];
|
||||
Vector2 p3 = points[Math.Min(i + 2, barCount - 1)];
|
||||
|
||||
Vector2 cp1 = p1 + (p2 - p0) / 6.0f;
|
||||
Vector2 cp2 = p2 - (p3 - p1) / 6.0f;
|
||||
|
||||
pathBuilder.AddCubicBezier(cp1, cp2, p2);
|
||||
}
|
||||
|
||||
// 封口
|
||||
if (placement == SpectrumPlacement.Top)
|
||||
{
|
||||
pathBuilder.AddLine(new Vector2(points[barCount - 1].X, 0));
|
||||
pathBuilder.AddLine(new Vector2(points[0].X, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
pathBuilder.AddLine(new Vector2(points[barCount - 1].X, (float)height));
|
||||
pathBuilder.AddLine(new Vector2(points[0].X, (float)height));
|
||||
}
|
||||
|
||||
pathBuilder.EndFigure(CanvasFigureLoop.Closed);
|
||||
return CanvasGeometry.CreatePath(pathBuilder);
|
||||
}
|
||||
|
||||
private void DrawGeometry(
|
||||
CanvasDrawingSession ds,
|
||||
CanvasGeometry geometry,
|
||||
Color color,
|
||||
SpectrumPlacement placement,
|
||||
double height)
|
||||
{
|
||||
var stops = new CanvasGradientStop[]
|
||||
{
|
||||
new() { Position = 0.0f, Color = Colors.Transparent },
|
||||
new() { Position = 0.7f, Color = Colors.Transparent },
|
||||
new() { Position = 1.0f, Color = color }
|
||||
};
|
||||
|
||||
using var brush = new CanvasLinearGradientBrush(ds, stops);
|
||||
|
||||
if (placement == SpectrumPlacement.Top)
|
||||
{
|
||||
brush.StartPoint = new Vector2(0, (float)height);
|
||||
brush.EndPoint = new Vector2(0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
brush.StartPoint = new Vector2(0, 0);
|
||||
brush.EndPoint = new Vector2(0, (float)height);
|
||||
}
|
||||
|
||||
ds.FillGeometry(geometry, brush);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_spectrumGeometry?.Dispose();
|
||||
_spectrumGeometry = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using System.Numerics;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
public class UnplayingLineRenderer
|
||||
{
|
||||
public void Draw(
|
||||
CanvasDrawingSession ds,
|
||||
ICanvasImage textOnlyLayer,
|
||||
LyricsLine line)
|
||||
{
|
||||
var blurAmount = (float)line.BlurAmountTransition.Value;
|
||||
|
||||
if (line.PhoneticCanvasTextLayout != null)
|
||||
{
|
||||
DrawPart(ds, textOnlyLayer,
|
||||
line.PhoneticCanvasTextLayout,
|
||||
line.PhoneticPosition,
|
||||
blurAmount,
|
||||
(float)line.PhoneticTextOpacityTransition.Value);
|
||||
}
|
||||
|
||||
if (line.OriginalCanvasTextLayout != null)
|
||||
{
|
||||
DrawPart(ds, textOnlyLayer,
|
||||
line.OriginalCanvasTextLayout,
|
||||
line.OriginalPosition,
|
||||
blurAmount,
|
||||
(float)line.OriginalTextOpacityTransition.Value);
|
||||
}
|
||||
|
||||
if (line.TranslatedCanvasTextLayout != null)
|
||||
{
|
||||
DrawPart(ds, textOnlyLayer,
|
||||
line.TranslatedCanvasTextLayout,
|
||||
line.TranslatedPosition,
|
||||
blurAmount,
|
||||
(float)line.TranslatedTextOpacityTransition.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPart(
|
||||
CanvasDrawingSession ds,
|
||||
ICanvasImage source,
|
||||
CanvasTextLayout layout,
|
||||
Vector2 position,
|
||||
float blur,
|
||||
float opacity)
|
||||
{
|
||||
if (opacity <= 0) return;
|
||||
|
||||
var bounds = layout.LayoutBounds;
|
||||
var destRect = new Rect(
|
||||
bounds.X + position.X,
|
||||
bounds.Y + position.Y,
|
||||
bounds.Width,
|
||||
bounds.Height
|
||||
);
|
||||
|
||||
using (var blurEffect = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = blur,
|
||||
Source = source,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
})
|
||||
{
|
||||
ds.DrawImage(blurEffect, destRect, destRect, opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,9 +63,10 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
await bitmapImage.SetSourceAsync(ImageHelper.ToIRandomAccessStream(buffer));
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
AlbumArtBitmapImage = bitmapImage;
|
||||
LightAccentColors = lightPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
|
||||
DarkAccentColors = darkPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
|
||||
|
||||
AlbumArtBitmapImage = bitmapImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
private int _langIndex = 0;
|
||||
private List<LyricsData> _lyricsDataArr = [];
|
||||
|
||||
public LyricsData? CurrentLyricsData => _lyricsDataArr.ElementAtOrDefault(_langIndex);
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsData? CurrentLyricsData { get; private set; }
|
||||
|
||||
public event EventHandler<LyricsChangedEventArgs>? LyricsChanged;
|
||||
|
||||
@@ -35,15 +35,17 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
[ObservableProperty] public partial bool IsTranslating { get; set; } = false;
|
||||
|
||||
private void SetCurrentLyricsData()
|
||||
{
|
||||
CurrentLyricsData = _lyricsDataArr.ElementAtOrDefault(_langIndex);
|
||||
}
|
||||
|
||||
private async Task RefreshTranslationAsync(CancellationToken token)
|
||||
{
|
||||
TranslationSearchProvider = null;
|
||||
_lyricsDataArr.ElementAtOrDefault(0)?.ClearTranslatedText();
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
|
||||
});
|
||||
SetCurrentLyricsData();
|
||||
|
||||
IsTranslating = true;
|
||||
|
||||
@@ -53,10 +55,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
IsTranslating = false;
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
|
||||
});
|
||||
SetCurrentLyricsData();
|
||||
|
||||
}
|
||||
|
||||
private async Task SetTranslatedTextAsync(CancellationToken token)
|
||||
@@ -86,7 +86,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
_logger.LogInformation("Found translated text in lyrics data at index {FoundIndex}", found);
|
||||
|
||||
_lyricsDataArr.FirstOrDefault()?.SetTranslatedText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
|
||||
_lyricsDataArr.FirstOrDefault()?.SetTranslatedText(_lyricsDataArr[found], 50);
|
||||
TranslationSearchProvider = CurrentLyricsSearchResult?.Provider.ToTranslationSearchProvider();
|
||||
}
|
||||
else if (_settingsService.AppSettings.TranslationSettings.IsLibreTranslateEnabled)
|
||||
@@ -99,7 +99,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
if (token.IsCancellationRequested) return;
|
||||
if (translated == string.Empty) return;
|
||||
|
||||
_lyricsDataArr.FirstOrDefault()?.SetTranslation(translated, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator);
|
||||
_lyricsDataArr.FirstOrDefault()?.SetTranslation(translated);
|
||||
|
||||
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
if (found >= 0)
|
||||
{
|
||||
_logger.LogInformation("Found phonetic text in lyrics data at index {FoundIndex}", found);
|
||||
_lyricsDataArr.FirstOrDefault()?.SetPhoneticText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
|
||||
_lyricsDataArr.FirstOrDefault()?.SetPhoneticText(_lyricsDataArr[found], 50);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -153,10 +153,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
CurrentLyricsSearchResult = null;
|
||||
_lyricsDataArr = [LyricsData.GetLoadingPlaceholder()];
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
|
||||
});
|
||||
SetCurrentLyricsData();
|
||||
|
||||
if (CurrentSongInfo != null)
|
||||
{
|
||||
|
||||
@@ -714,14 +714,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsTranslationSeparator))
|
||||
{
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is TranslationSettings)
|
||||
if (message.Sender is TranslationSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageCode))
|
||||
{
|
||||
|
||||
@@ -12,6 +12,8 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using DevWinUI;
|
||||
using Lyricify.Lyrics.Providers.Web.Netease;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
@@ -25,35 +27,27 @@ using Windows.Storage.Streams;
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsPageViewModel : BaseViewModel,
|
||||
IRecipient<PropertyChangedMessage<TimeSpan>>,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<double>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<SongInfo?>>,
|
||||
IRecipient<PropertyChangedMessage<BitmapImage?>>
|
||||
IRecipient<PropertyChangedMessage<BitmapImage?>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsLayoutOrientation>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsDisplayType>>
|
||||
{
|
||||
public IMediaSessionsService MediaSessionsService { get; private set; }
|
||||
private readonly ILiveStatesService _liveStatesService;
|
||||
|
||||
private readonly ThrottleHelper _timelineThrottle = new(TimeSpan.FromSeconds(1));
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Vector3 AlbumArtTranslation { get; set; } = new();
|
||||
[ObservableProperty] public partial double AlbumArtWithSongInfoStackPanelHeight { get; set; } = 0;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double LastAlbumArtOpacity { get; set; } = 1;
|
||||
[ObservableProperty] public partial double LastAlbumArtOpacity { get; set; } = 1;
|
||||
[ObservableProperty] public partial double AlbumArtOpacity { get; set; } = 1;
|
||||
[ObservableProperty] public partial BitmapImage? LastAlbumArtBitmapImage { get; set; }
|
||||
[ObservableProperty] public partial BitmapImage? AlbumArtBitmapImage { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double AlbumArtOpacity { get; set; } = 1;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial BitmapImage? LastAlbumArtBitmapImage { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial BitmapImage? AlbumArtBitmapImage { get; set; }
|
||||
|
||||
public LyricsRendererViewModel.LyricsRendererViewModel LyricsRendererViewModel { get; private set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double LyricsX { get; set; } = 0;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double LyricsY { get; set; } = 0;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double LyricsWidth { get; set; } = 0;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double LyricsOpacity { get; set; } = 0;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Matrix4x4 Lyrics3DMatrix { get; set; } = Matrix4x4.Identity;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LiveStates LiveStates { get; set; }
|
||||
@@ -79,24 +73,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial double TimelineSliderThumbSeconds { get; set; } = 0;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double RootWidth { get; set; } = 0;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double RootHeight { get; set; } = 0;
|
||||
|
||||
public TextBlock? TitleTextBlock { get; set; }
|
||||
public TextBlock? ArtistsTextBlock { get; set; }
|
||||
public TextBlock? AlbumTextBlock { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double SongInfoOpacity { get; set; } = 1;
|
||||
|
||||
public LyricsPageViewModel(IMediaSessionsService mediaSessionsService, ILiveStatesService liveStatesService, LyricsRendererViewModel.LyricsRendererViewModel lyricsRendererViewModel)
|
||||
public LyricsPageViewModel(IMediaSessionsService mediaSessionsService, ILiveStatesService liveStatesService)
|
||||
{
|
||||
_liveStatesService = liveStatesService;
|
||||
MediaSessionsService = mediaSessionsService;
|
||||
LyricsRendererViewModel = lyricsRendererViewModel;
|
||||
|
||||
LiveStates = _liveStatesService.LiveStates;
|
||||
|
||||
@@ -109,61 +92,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
Volume = e;
|
||||
}
|
||||
|
||||
private void RenderTextBlock(TextBlock? sender, string? text, int fontSize)
|
||||
{
|
||||
if (sender == null || string.IsNullOrEmpty(text)) return;
|
||||
|
||||
var lyricsStyleSettings = LiveStates.LyricsWindowStatus.LyricsStyleSettings;
|
||||
sender.Inlines.Clear();
|
||||
foreach (char c in text)
|
||||
{
|
||||
var fontFamilyName = LanguageHelper.IsCJK(c) ? lyricsStyleSettings.LyricsCJKFontFamily : lyricsStyleSettings.LyricsWesternFontFamily;
|
||||
var run = new Run
|
||||
{
|
||||
Text = c.ToString(),
|
||||
FontFamily = new Microsoft.UI.Xaml.Media.FontFamily(fontFamilyName),
|
||||
FontSize = fontSize,
|
||||
};
|
||||
|
||||
sender.Inlines.Add(run);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetTitleFontSize()
|
||||
{
|
||||
var albumArtLayoutSettings = LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings;
|
||||
if (albumArtLayoutSettings.IsAutoSongInfoFontSize)
|
||||
{
|
||||
return (int)Math.Clamp(Math.Min(RootHeight, RootWidth) / 20, 8, 72);
|
||||
}
|
||||
else
|
||||
{
|
||||
return albumArtLayoutSettings.SongInfoFontSize;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetArtistsAlbumFontSize()
|
||||
{
|
||||
return (int)(GetTitleFontSize() * 0.8);
|
||||
}
|
||||
|
||||
private void RenderSongInfo()
|
||||
{
|
||||
RenderTextBlock(TitleTextBlock, MediaSessionsService.CurrentSongInfo?.Title, GetTitleFontSize());
|
||||
RenderTextBlock(ArtistsTextBlock, MediaSessionsService.CurrentSongInfo?.DisplayArtists, GetArtistsAlbumFontSize());
|
||||
RenderTextBlock(AlbumTextBlock, MediaSessionsService.CurrentSongInfo?.Album, GetArtistsAlbumFontSize());
|
||||
}
|
||||
|
||||
partial void OnTimelineSliderThumbSecondsChanged(double value)
|
||||
{
|
||||
TimelineSliderThumbLyricsLine = MediaSessionsService.CurrentLyricsData?.GetLyricsLine(value);
|
||||
}
|
||||
|
||||
partial void OnRootHeightChanged(double value)
|
||||
{
|
||||
RenderSongInfo();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void OpenSettingsWindow()
|
||||
{
|
||||
@@ -194,89 +127,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
await MediaSessionsService.NextAsync();
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<TimeSpan> message)
|
||||
{
|
||||
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.TotalTime))
|
||||
{
|
||||
if (_timelineThrottle.CanTrigger())
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
TimelinePositionSeconds = message.NewValue.TotalSeconds;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<double> message)
|
||||
{
|
||||
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.AlbumArtX))
|
||||
{
|
||||
AlbumArtTranslation = AlbumArtTranslation.WithElement(0, (float)message.NewValue - 32);
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsRendererViewModel.AlbumArtY))
|
||||
{
|
||||
AlbumArtTranslation = AlbumArtTranslation.WithElement(1, (float)message.NewValue - 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is AlbumArtLayoutSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(AlbumArtLayoutSettings.SongInfoFontSize))
|
||||
{
|
||||
RenderSongInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is AlbumArtLayoutSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(AlbumArtLayoutSettings.IsAutoSongInfoFontSize))
|
||||
{
|
||||
RenderSongInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCJKFontFamily))
|
||||
{
|
||||
RenderSongInfo();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsWesternFontFamily))
|
||||
{
|
||||
RenderSongInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
|
||||
{
|
||||
SongInfoOpacity = 0;
|
||||
await Task.Delay(Constants.Time.AnimationDuration);
|
||||
RenderSongInfo();
|
||||
SongInfoOpacity = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void Receive(PropertyChangedMessage<BitmapImage?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
@@ -296,5 +146,28 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsLayoutOrientation> message)
|
||||
{
|
||||
if (message.Sender is LyricsWindowStatus)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowStatus.LyricsLayoutOrientation))
|
||||
{
|
||||
//OnLayoutChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
|
||||
{
|
||||
if (message.Sender is LyricsWindowStatus)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowStatus.LyricsDisplayType))
|
||||
{
|
||||
//OnLayoutChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
{
|
||||
public void CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
|
||||
{
|
||||
_logger.LogInformation("Creating resources... Reason: {Reason}", args.Reason);
|
||||
switch (args.Reason)
|
||||
{
|
||||
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.FirstTime:
|
||||
_isDeviceChanged = true;
|
||||
break;
|
||||
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.NewDevice:
|
||||
_isDeviceChanged = true;
|
||||
break;
|
||||
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.DpiChanged:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,540 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Brushes;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Windows.Media.Media3D;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Effects;
|
||||
using Windows.UI;
|
||||
using static Vanara.PInvoke.Kernel32;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
{
|
||||
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsPureColorOverlayEnabled)
|
||||
{
|
||||
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment)
|
||||
{
|
||||
FillBackground(ds, _immersiveBgColorTransition.Value, 0f,
|
||||
_immersiveBgOpacityTransition.Value * _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PureColorOverlayOpacity / 100f);
|
||||
}
|
||||
else
|
||||
{
|
||||
FillBackground(ds, _albumArtAccentColor1Transition.Value, 0f,
|
||||
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PureColorOverlayOpacity / 100.0);
|
||||
}
|
||||
}
|
||||
DrawFluidBackground(control, ds);
|
||||
DrawSpectrum(control, ds);
|
||||
|
||||
DrawSnowEffect(ds);
|
||||
|
||||
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.Is3DLyricsEnabled)
|
||||
{
|
||||
// Blurred lyrics layer
|
||||
using var blurredLyrics = new CanvasCommandList(control);
|
||||
using (var blurredLyricsDs = blurredLyrics.CreateDrawingSession())
|
||||
{
|
||||
DrawLyrics(control, blurredLyricsDs);
|
||||
}
|
||||
ds.DrawImage(new Transform3DEffect
|
||||
{
|
||||
Source = blurredLyrics,
|
||||
TransformMatrix = _lyrics3DMatrix
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawLyrics(control, ds);
|
||||
}
|
||||
|
||||
DrawFogEffect(ds);
|
||||
//DrawRaindropEffect(ds, combined);
|
||||
|
||||
if (_isDebugOverlayEnabled)
|
||||
{
|
||||
_drawFrameCount++;
|
||||
|
||||
var currentPlayingLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
|
||||
if (currentPlayingLine != null)
|
||||
{
|
||||
GetLinePlayingProgress(
|
||||
_playingLineIndex,
|
||||
out int syllableStartIndex,
|
||||
out int syllableLength,
|
||||
out double syllableProgress
|
||||
);
|
||||
|
||||
ds.DrawText(
|
||||
$"[DEBUG]\n" +
|
||||
$"Canvas size: {_canvasWidth}x{_canvasHeight}\n" +
|
||||
$"FPS (Draw): {_displayedDrawFrameCount}\n" +
|
||||
$"Playing line: {_playingLineIndex}\n" +
|
||||
$"Syllable start idx: {syllableStartIndex}\n" +
|
||||
$"Syllable len: {syllableLength}\n" +
|
||||
$"Syllable prog: {syllableProgress}\n" +
|
||||
$"Visible lines: [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
|
||||
$"Total line count: {GetMaxLyricsLineIndexBoundaries().Item2 + 1}\n" +
|
||||
$"Cur time: {TotalTime + TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0)}\n" +
|
||||
$"Song duration: {TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0)}\n" +
|
||||
$"Y offset: {_canvasYScrollTransition.Value}",
|
||||
new Vector2(10, 40),
|
||||
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White,
|
||||
_debugTextFormat
|
||||
);
|
||||
}
|
||||
|
||||
if (_drawFrameStopwatch?.Elapsed.TotalSeconds >= 1.0)
|
||||
{
|
||||
_displayedDrawFrameCount = _drawFrameCount;
|
||||
_drawFrameStopwatch?.Restart();
|
||||
_drawFrameCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawSpectrum(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
if (_spectrumGeometry != null)
|
||||
{
|
||||
var gradientStops = new CanvasGradientStop[]
|
||||
{
|
||||
new() { Position = 0.0f, Color = Colors.Transparent },
|
||||
new() { Position = 0.7f, Color = Colors.Transparent },
|
||||
new() { Position = 1.0f, Color = _adaptiveColoredFontColor ?? _albumArtAccentColor1Transition.Value }
|
||||
};
|
||||
|
||||
using var gradientBrush = new CanvasLinearGradientBrush(ds, gradientStops);
|
||||
|
||||
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.SpectrumPlacement)
|
||||
{
|
||||
case SpectrumPlacement.Top:
|
||||
gradientBrush.StartPoint = new Vector2(0, (float)_canvasHeight);
|
||||
gradientBrush.EndPoint = new Vector2(0, 0);
|
||||
break;
|
||||
case SpectrumPlacement.Bottom:
|
||||
gradientBrush.StartPoint = new Vector2(0, 0);
|
||||
gradientBrush.EndPoint = new Vector2(0, (float)_canvasHeight);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// 使用渐变画刷填充
|
||||
ds.FillGeometry(_spectrumGeometry, gradientBrush);
|
||||
|
||||
// 纯色
|
||||
//ds.FillGeometry(geometry, _adaptiveColoredFontColor ?? _albumArtAccentColor1Transition.Value);
|
||||
|
||||
// 绘制轮廓线
|
||||
//var lineColor = Colors.SkyBlue;
|
||||
//float strokeWidth = 2f;
|
||||
//ds.DrawGeometry(geometry, _albumArtAccentColor4Transition.Value, strokeWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFluidBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
if (_fluidEffect != null && _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsFluidOverlayEnabled)
|
||||
{
|
||||
ds.DrawImage(new OpacityEffect
|
||||
{
|
||||
Source = _fluidEffect,
|
||||
Opacity = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.FluidOverlayOpacity / 100f
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
var currentPlayingLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
|
||||
if (currentPlayingLine == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lyricsEffectSettings = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings;
|
||||
|
||||
var rotationY = currentPlayingLine.OriginalPosition.WithX(lyricsEffectSettings.FanLyricsAngle < 0 ? (float)_maxLyricsWidth : 0);
|
||||
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i);
|
||||
if (line == null) continue;
|
||||
|
||||
var textLayout = line.OriginalCanvasTextLayout;
|
||||
if (textLayout == null) continue;
|
||||
|
||||
double layoutWidth = (double)textLayout.LayoutBounds.Width;
|
||||
double layoutHeight = (double)textLayout.LayoutBounds.Height;
|
||||
|
||||
if (layoutWidth <= 0 || layoutHeight <= 0) continue;
|
||||
|
||||
double yOffset = line.YOffsetTransition.Value + _canvasHeight / 2 + _lyricsYTransition.Value;
|
||||
|
||||
//// 组合变换:缩放 -> 旋转 -> 平移
|
||||
ds.Transform =
|
||||
Matrix3x2.CreateScale((float)line.ScaleTransition.Value, line.CenterPosition) *
|
||||
Matrix3x2.CreateRotation((float)line.AngleTransition.Value, rotationY) *
|
||||
Matrix3x2.CreateTranslation((float)_lyricsX, (float)yOffset);
|
||||
|
||||
using var textOnlyLayer = new CanvasCommandList(control);
|
||||
using (var textOnlyLayerDs = textOnlyLayer.CreateDrawingSession())
|
||||
{
|
||||
var strokeWidth = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontStrokeWidth;
|
||||
|
||||
// 描边
|
||||
if (strokeWidth > 0)
|
||||
{
|
||||
if (line.PhoneticCanvasGeometry != null)
|
||||
{
|
||||
textOnlyLayerDs.DrawGeometry(line.PhoneticCanvasGeometry, line.PhoneticPosition, _strokeFontColor, strokeWidth);
|
||||
}
|
||||
if (line.OriginalCanvasGeometry != null)
|
||||
{
|
||||
textOnlyLayerDs.DrawGeometry(line.OriginalCanvasGeometry, line.OriginalPosition, _strokeFontColor, strokeWidth);
|
||||
}
|
||||
if (line.TranslatedCanvasGeometry != null)
|
||||
{
|
||||
textOnlyLayerDs.DrawGeometry(line.TranslatedCanvasGeometry, line.TranslatedPosition, _strokeFontColor, strokeWidth);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制文本(填充)
|
||||
if (line.PhoneticCanvasTextLayout != null)
|
||||
{
|
||||
textOnlyLayerDs.DrawTextLayout(line.PhoneticCanvasTextLayout, line.PhoneticPosition, _bgFontColor);
|
||||
}
|
||||
if (line.OriginalCanvasTextLayout != null)
|
||||
{
|
||||
textOnlyLayerDs.DrawTextLayout(line.OriginalCanvasTextLayout, line.OriginalPosition, _bgFontColor);
|
||||
}
|
||||
if (line.TranslatedCanvasTextLayout != null)
|
||||
{
|
||||
textOnlyLayerDs.DrawTextLayout(line.TranslatedCanvasTextLayout, line.TranslatedPosition, _bgFontColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (i == _playingLineIndex)
|
||||
{
|
||||
DrawPlayingLine(control, ds, textOnlyLayer, line, i);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawUnPlayingLine(ds, textOnlyLayer, line);
|
||||
}
|
||||
|
||||
// Reset scale
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPlayingLine(ICanvasAnimatedControl control, CanvasDrawingSession ds, ICanvasImage textOnlyLayer, LyricsLine line, int lineIndex)
|
||||
{
|
||||
var lyricsEffectSettings = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings;
|
||||
|
||||
var lineBlurAmount = line.BlurAmountTransition.Value;
|
||||
|
||||
var phoneticTextOpacity = line.PhoneticTextOpacityTransition.Value;
|
||||
var originalTextOpacity = line.OriginalTextOpacityTransition.Value;
|
||||
var translatedTextOpacity = line.TranslatedTextOpacityTransition.Value;
|
||||
|
||||
var phoneticTextLayout = line.PhoneticCanvasTextLayout;
|
||||
if (phoneticTextLayout != null)
|
||||
{
|
||||
Rect bounds = phoneticTextLayout.LayoutBounds;
|
||||
|
||||
var rect = new Rect(
|
||||
bounds.X + line.PhoneticPosition.X,
|
||||
bounds.Y + line.PhoneticPosition.Y,
|
||||
bounds.Width,
|
||||
bounds.Height
|
||||
);
|
||||
ds.DrawImage(new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = (float)lineBlurAmount,
|
||||
Source = textOnlyLayer,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
}, rect, rect, (float)phoneticTextOpacity);
|
||||
}
|
||||
|
||||
var originalTextLayout = line.OriginalCanvasTextLayout;
|
||||
if (originalTextLayout != null)
|
||||
{
|
||||
GetLinePlayingProgress(lineIndex, out int syllableStartIndex, out int syllableLength, out double syllableProgress);
|
||||
|
||||
var curCharIndex = syllableStartIndex + syllableLength * syllableProgress; // 当前唱的字符(相对于整行歌词,非子行)
|
||||
float fadeWidth = (1f / line.OriginalText.Length) * 0.5f; // 渐变边缘宽度:半个字
|
||||
|
||||
var lineRegions = originalTextLayout.GetCharacterRegions(0, line.OriginalText.Length);
|
||||
var charCountUpToCurRegion = 0;
|
||||
foreach (var subLineRegion in lineRegions)
|
||||
{
|
||||
var subLineLayoutBounds = subLineRegion.LayoutBounds;
|
||||
charCountUpToCurRegion += subLineRegion.CharacterCount;
|
||||
|
||||
Rect subLineRect = new Rect(
|
||||
subLineLayoutBounds.X + line.OriginalPosition.X,
|
||||
subLineLayoutBounds.Y + line.OriginalPosition.Y,
|
||||
subLineLayoutBounds.Width,
|
||||
subLineLayoutBounds.Height
|
||||
);
|
||||
|
||||
using (var maskLayer = new CanvasCommandList(control))
|
||||
{
|
||||
using (var maskLayerDs = maskLayer.CreateDrawingSession())
|
||||
{
|
||||
float currentPos = (float)((curCharIndex - subLineRegion.CharacterIndex) / subLineRegion.CharacterCount);
|
||||
currentPos = Math.Clamp(currentPos, 0, 1 + fadeWidth);
|
||||
|
||||
using (var maskBrush = new CanvasLinearGradientBrush(ds,
|
||||
[
|
||||
new CanvasGradientStop { Position = 0, Color = Colors.White.WithAlpha((byte)(255 * originalTextOpacity)) }, // 左侧:亮
|
||||
new CanvasGradientStop { Position = currentPos, Color = Colors.White.WithAlpha((byte)(255 * originalTextOpacity)) },
|
||||
new CanvasGradientStop { Position = currentPos + fadeWidth, Color = Color.FromArgb((byte)(255 * Math.Min(0.3, originalTextOpacity)), 255, 255, 255) }, // 过渡到暗
|
||||
new CanvasGradientStop { Position = 1 + fadeWidth, Color = Color.FromArgb((byte)(255 * Math.Min(0.3, originalTextOpacity)), 255, 255, 255) } // 右侧:暗
|
||||
]))
|
||||
{
|
||||
if (maskBrush != null)
|
||||
{
|
||||
maskBrush.StartPoint = new Vector2((float)subLineRect.X, (float)subLineRect.Y);
|
||||
maskBrush.EndPoint = new Vector2((float)(subLineRect.X + subLineRect.Width), (float)subLineRect.Y);
|
||||
|
||||
maskLayerDs.FillRectangle(subLineRect, maskBrush);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using var textWithOpacityLayer = new AlphaMaskEffect
|
||||
{
|
||||
Source = new CropEffect
|
||||
{
|
||||
Source = textOnlyLayer,
|
||||
SourceRectangle = subLineRect,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
},
|
||||
AlphaMask = maskLayer,
|
||||
};
|
||||
|
||||
for (int i = subLineRegion.CharacterIndex; i < subLineRegion.CharacterIndex + subLineRegion.CharacterCount; i++)
|
||||
{
|
||||
int curCharIndexInt = (int)Math.Floor(curCharIndex);
|
||||
|
||||
var charRegions = originalTextLayout.GetCharacterRegions(i, 1);
|
||||
if (charRegions.Length > 0)
|
||||
{
|
||||
// START 处理浮动动画
|
||||
double floatOffset = 0;
|
||||
double targetFloatOffset = 2;
|
||||
if (lyricsEffectSettings.IsLyricsFloatAnimationEnabled)
|
||||
{
|
||||
if (i < curCharIndexInt)
|
||||
{
|
||||
floatOffset = 0;
|
||||
}
|
||||
else if (i == curCharIndexInt)
|
||||
{
|
||||
var charProgress = curCharIndex - curCharIndexInt;
|
||||
floatOffset = -targetFloatOffset + charProgress * targetFloatOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
floatOffset = -targetFloatOffset;
|
||||
}
|
||||
}
|
||||
// END 处理浮动动画
|
||||
|
||||
var charRegion = charRegions.FirstOrDefault();
|
||||
var charLayoutBounds = charRegion.LayoutBounds;
|
||||
|
||||
var sourceCharRect = new Rect(
|
||||
charLayoutBounds.X + line.OriginalPosition.X,
|
||||
charLayoutBounds.Y + line.OriginalPosition.Y,
|
||||
charLayoutBounds.Width,
|
||||
charLayoutBounds.Height
|
||||
);
|
||||
|
||||
// START 处理缩放、辉光动画
|
||||
double scale = 1;
|
||||
double glow = 0;
|
||||
var parentSyllable = line.LyricsSyllables.FirstOrDefault(x => x.StartIndex <= i && i < x.StartIndex + x.Text.Length);
|
||||
if (parentSyllable != null && parentSyllable.IsLongDuration && parentSyllable.StartIndex == syllableStartIndex)
|
||||
{
|
||||
if (lyricsEffectSettings.IsLyricsScaleEffectEnabled)
|
||||
{
|
||||
scale += Math.Sin(syllableProgress * Math.PI) * 0.15;
|
||||
}
|
||||
if (lyricsEffectSettings.IsLyricsGlowEffectEnabled)
|
||||
{
|
||||
glow = Math.Sin(syllableProgress * Math.PI) * 8;
|
||||
}
|
||||
}
|
||||
// END 处理缩放、辉光动画
|
||||
|
||||
using (var charWithOpacityLayer = new CropEffect
|
||||
{
|
||||
Source = textWithOpacityLayer,
|
||||
SourceRectangle = sourceCharRect,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
})
|
||||
{
|
||||
var destCharRect = sourceCharRect.Scale(scale).AddY(-floatOffset);
|
||||
|
||||
if (glow > 0)
|
||||
{
|
||||
ds.DrawImage(new GaussianBlurEffect
|
||||
{
|
||||
Source = charWithOpacityLayer,
|
||||
BlurAmount = (float)glow,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
}, destCharRect.Extend(16), sourceCharRect.Extend(16));
|
||||
}
|
||||
ds.DrawImage(charWithOpacityLayer, destCharRect, sourceCharRect);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var translatedTextLayout = line.TranslatedCanvasTextLayout;
|
||||
if (translatedTextLayout != null)
|
||||
{
|
||||
Rect bounds = translatedTextLayout.LayoutBounds;
|
||||
|
||||
var rect = new Rect(
|
||||
bounds.X + line.TranslatedPosition.X,
|
||||
bounds.Y + line.TranslatedPosition.Y,
|
||||
bounds.Width,
|
||||
bounds.Height
|
||||
);
|
||||
ds.DrawImage(new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = (float)lineBlurAmount,
|
||||
Source = textOnlyLayer,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
}, rect, rect, (float)translatedTextOpacity);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawUnPlayingLine(CanvasDrawingSession ds, ICanvasImage textOnlyLayer, LyricsLine line)
|
||||
{
|
||||
var lineBlurAmount = line.BlurAmountTransition.Value;
|
||||
|
||||
var phoneticTextOpacity = line.PhoneticTextOpacityTransition.Value;
|
||||
var originalTextOpacity = line.OriginalTextOpacityTransition.Value;
|
||||
var translatedTextOpacity = line.TranslatedTextOpacityTransition.Value;
|
||||
|
||||
var phoneticTextLayout = line.PhoneticCanvasTextLayout;
|
||||
if (phoneticTextLayout != null)
|
||||
{
|
||||
Rect bounds = phoneticTextLayout.LayoutBounds;
|
||||
|
||||
var rect = new Rect(
|
||||
bounds.X + line.PhoneticPosition.X,
|
||||
bounds.Y + line.PhoneticPosition.Y,
|
||||
bounds.Width,
|
||||
bounds.Height
|
||||
);
|
||||
ds.DrawImage(new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = (float)lineBlurAmount,
|
||||
Source = textOnlyLayer,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
}, rect, rect, (float)phoneticTextOpacity);
|
||||
}
|
||||
|
||||
var originalTextLayout = line.OriginalCanvasTextLayout;
|
||||
if (originalTextLayout != null)
|
||||
{
|
||||
Rect bounds = originalTextLayout.LayoutBounds;
|
||||
|
||||
var rect = new Rect(
|
||||
bounds.X + line.OriginalPosition.X,
|
||||
bounds.Y + line.OriginalPosition.Y,
|
||||
bounds.Width,
|
||||
bounds.Height
|
||||
);
|
||||
ds.DrawImage(new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = (float)lineBlurAmount,
|
||||
Source = textOnlyLayer,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
}, rect, rect, (float)originalTextOpacity);
|
||||
}
|
||||
|
||||
var translatedTextLayout = line.TranslatedCanvasTextLayout;
|
||||
if (translatedTextLayout != null)
|
||||
{
|
||||
Rect bounds = translatedTextLayout.LayoutBounds;
|
||||
|
||||
var rect = new Rect(
|
||||
bounds.X + line.TranslatedPosition.X,
|
||||
bounds.Y + line.TranslatedPosition.Y,
|
||||
bounds.Width,
|
||||
bounds.Height
|
||||
);
|
||||
ds.DrawImage(new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = (float)lineBlurAmount,
|
||||
Source = textOnlyLayer,
|
||||
BorderMode = EffectBorderMode.Soft
|
||||
}, rect, rect, (float)translatedTextOpacity);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSnowEffect(CanvasDrawingSession ds)
|
||||
{
|
||||
if (_snowEffect != null && _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsSnowFlakeOverlayEnabled)
|
||||
{
|
||||
ds.DrawImage(_snowEffect);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFogEffect(CanvasDrawingSession ds)
|
||||
{
|
||||
if (_fogEffect != null && _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsFogOverlayEnabled)
|
||||
{
|
||||
ds.DrawImage(_fogEffect);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRaindropEffect(CanvasDrawingSession ds, IGraphicsEffectSource source)
|
||||
{
|
||||
if (_raindropEffect != null)
|
||||
{
|
||||
_raindropEffect.Sources[0] = source;
|
||||
ds.DrawImage(_raindropEffect);
|
||||
}
|
||||
}
|
||||
|
||||
private void FillBackground(CanvasDrawingSession ds, Color color, double radius, double opacity)
|
||||
{
|
||||
ds.FillRoundedRectangle(
|
||||
new Rect(0, 0, _canvasWidth, _canvasHeight),
|
||||
(float)radius,
|
||||
(float)radius,
|
||||
color.WithAlpha((byte)(opacity * 255))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Shaders;
|
||||
using ComputeSharp.D2D1.WinUI;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
{
|
||||
private PixelShaderEffect? _fluidEffect;
|
||||
private PixelShaderEffect<SnowEffect>? _snowEffect;
|
||||
private PixelShaderEffect<FogEffect>? _fogEffect;
|
||||
private PixelShaderEffect<RaindropEffect>? _raindropEffect;
|
||||
|
||||
private void DisposeFluidEffect()
|
||||
{
|
||||
_fluidEffect?.Dispose();
|
||||
_fluidEffect = null;
|
||||
}
|
||||
|
||||
private async void RecreateFluidEffect(ICanvasAnimatedControl control)
|
||||
{
|
||||
DisposeFluidEffect();
|
||||
|
||||
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/FluidEffect.bin"));
|
||||
IBuffer buffer = await FileIO.ReadBufferAsync(file);
|
||||
var bytes = buffer.ToArray();
|
||||
_fluidEffect = new PixelShaderEffect(bytes);
|
||||
_fluidEffect.Properties["Width"] = (float)control.ConvertDipsToPixels((float)control.Size.Width, CanvasDpiRounding.Round);
|
||||
_fluidEffect.Properties["Height"] = (float)control.ConvertDipsToPixels((float)control.Size.Height, CanvasDpiRounding.Round);
|
||||
_fluidEffect.Properties["color1"] = _albumArtAccentColor1Transition.Value.ToVector3RGB();
|
||||
_fluidEffect.Properties["color2"] = _albumArtAccentColor2Transition.Value.ToVector3RGB();
|
||||
_fluidEffect.Properties["color3"] = _albumArtAccentColor3Transition.Value.ToVector3RGB();
|
||||
_fluidEffect.Properties["color4"] = _albumArtAccentColor4Transition.Value.ToVector3RGB();
|
||||
_fluidEffect.Properties["EnableLightWave"] = false;
|
||||
}
|
||||
|
||||
private void DisposeSnowEffect()
|
||||
{
|
||||
_snowEffect?.Dispose();
|
||||
_snowEffect = null;
|
||||
}
|
||||
|
||||
private void RecreateSnowEffect()
|
||||
{
|
||||
DisposeSnowEffect();
|
||||
_snowEffect = new();
|
||||
}
|
||||
|
||||
private void DisposeFogEffect()
|
||||
{
|
||||
_fogEffect?.Dispose();
|
||||
_fogEffect = null;
|
||||
}
|
||||
|
||||
private void RecreateFogEffect()
|
||||
{
|
||||
DisposeFogEffect();
|
||||
_fogEffect = new();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,414 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
: IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<double>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<Color>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsDisplayType>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsLayoutOrientation>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsFontColorType>>,
|
||||
IRecipient<PropertyChangedMessage<TextAlignmentType>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<EasingType>>,
|
||||
IRecipient<PropertyChangedMessage<TimeSpan>>,
|
||||
IRecipient<PropertyChangedMessage<AlbumArtLayoutSettings>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsBackgroundSettings>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>,
|
||||
IRecipient<PropertyChangedMessage<SongInfo?>>,
|
||||
IRecipient<PropertyChangedMessage<List<Color>>>
|
||||
{
|
||||
private void OnSongInfoChanged()
|
||||
{
|
||||
_songDurationMs = (int)(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? TimeSpan.FromMinutes(99).TotalMilliseconds);
|
||||
|
||||
_songInfoOpacityTransition.Reset(0f);
|
||||
_songInfoOpacityTransition.StartTransition(1f);
|
||||
|
||||
TotalTime = TimeSpan.Zero;
|
||||
|
||||
// 处理 Last.fm 追踪
|
||||
_totalPlayingTime = TimeSpan.Zero;
|
||||
_isLastFMTracked = false;
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is AboutControlViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(AboutControlViewModel.IsDebugOverlayEnabled))
|
||||
{
|
||||
_isDebugOverlayEnabled = message.NewValue;
|
||||
_isDebugOverlayEnabledChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsEffectSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsEffectSettings.IsLyricsGlowEffectEnabled))
|
||||
{
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.IsFanLyricsEnabled))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.IsLyricsFloatAnimationEnabled))
|
||||
{
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.Is3DLyricsEnabled))
|
||||
{
|
||||
_isLyrics3DMatrixChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.IsDynamicLyricsFontSize))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is AlbumArtLayoutSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(AlbumArtLayoutSettings.AutoAlbumArtSize))
|
||||
{
|
||||
_isAlbumArtSizeChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsBackgroundSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsBackgroundSettings.IsSpectrumOverlayEnabled))
|
||||
{
|
||||
_isSpectrumOverlayEnabledChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsBackgroundSettings.IsFluidOverlayEnabled))
|
||||
{
|
||||
_isFluidOverlayEnabledChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsBackgroundSettings.IsSnowFlakeOverlayEnabled))
|
||||
{
|
||||
_isSnowOverlayEnabledChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsBackgroundSettings.IsFogOverlayEnabled))
|
||||
{
|
||||
_isFogOverlayEnabledChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<Color> message)
|
||||
{
|
||||
if (message.Sender is LyricsWindowViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.BackdropAccentColor))
|
||||
{
|
||||
_immersiveBgColorTransition.StartTransition(message.NewValue);
|
||||
_environmentalColor = message.NewValue;
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomBgFontColor))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomFgFontColor))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomStrokeFontColor))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<double> message)
|
||||
{
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsLineSpacingFactor))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is AlbumArtLayoutSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(AlbumArtLayoutSettings.AlbumArtSize))
|
||||
{
|
||||
_isAlbumArtSizeChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsEffectSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollDuration))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollTopDuration))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollBottomDuration))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollTopDelay))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollBottomDelay))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.FanLyricsAngle))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.Lyrics3DXAngle))
|
||||
{
|
||||
_isLyrics3DMatrixChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.Lyrics3DYAngle))
|
||||
{
|
||||
_isLyrics3DMatrixChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.Lyrics3DZAngle))
|
||||
{
|
||||
_isLyrics3DMatrixChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.Lyrics3DDepth))
|
||||
{
|
||||
_isLyrics3DMatrixChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.PhoneticLyricsFontSize))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsStyleSettings.OriginalLyricsFontSize))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsStyleSettings.TranslatedLyricsFontSize))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFontStrokeWidth))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<TextAlignmentType> message)
|
||||
{
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsAlignmentType))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
|
||||
{
|
||||
if (message.Sender is LyricsWindowStatus)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowStatus.LyricsDisplayType))
|
||||
{
|
||||
_isDisplayTypeChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsLayoutOrientation> message)
|
||||
{
|
||||
if (message.Sender is LyricsWindowStatus)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowStatus.LyricsLayoutOrientation))
|
||||
{
|
||||
_isLyricsLayoutOrientationChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
|
||||
{
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsBgFontColorType))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFgFontColorType))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsStrokeFontColorType))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
|
||||
{
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFontWeight))
|
||||
{
|
||||
_isLyricsFontWeightChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ElementTheme> message)
|
||||
{
|
||||
if (message.Sender is LyricsBackgroundSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsBackgroundSettings.LyricsBackgroundTheme))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<EasingType> message)
|
||||
{
|
||||
if (message.Sender is LyricsEffectSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollEasingType))
|
||||
{
|
||||
_canvasYScrollTransition.SetEasingType(message.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCJKFontFamily))
|
||||
{
|
||||
_isLyricsFontFamilyChanged = true;
|
||||
}
|
||||
}
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsWesternFontFamily))
|
||||
{
|
||||
_isLyricsFontFamilyChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsWindowStatus> message)
|
||||
{
|
||||
if (message.Sender is LiveStates)
|
||||
{
|
||||
if (message.PropertyName == nameof(LiveStates.LyricsWindowStatus))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
|
||||
_isLayoutChanged = true;
|
||||
|
||||
_isLyricsWindowsStatusChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<AlbumArtLayoutSettings> message)
|
||||
{
|
||||
if (message.Sender is LyricsWindowStatus)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowStatus.AlbumArtLayoutSettings))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsBackgroundSettings> message)
|
||||
{
|
||||
if (message.Sender is LyricsWindowStatus)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowStatus.LyricsBackgroundSettings))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
|
||||
{
|
||||
OnSongInfoChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<TimeSpan> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
|
||||
{
|
||||
var diff = Math.Abs(TotalTime.TotalMilliseconds - _mediaSessionsService.CurrentPosition.TotalMilliseconds);
|
||||
var timelineSyncThreshold = _mediaSessionsService.CurrentMediaSourceProviderInfo?.TimelineSyncThreshold ?? 0;
|
||||
if (diff >= timelineSyncThreshold)
|
||||
{
|
||||
TotalTime = _mediaSessionsService.CurrentPosition;
|
||||
if (TotalTime.TotalSeconds <= 1)
|
||||
{
|
||||
_totalPlayingTime = TimeSpan.Zero;
|
||||
_isLastFMTracked = false;
|
||||
}
|
||||
}
|
||||
// 大跨度,刷新布局,避免歌词不显示
|
||||
if (diff >= timelineSyncThreshold + 5000)
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<List<Color>> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.LightAccentColors))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else if (message.PropertyName == nameof(IMediaSessionsService.DarkAccentColors))
|
||||
{
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.UI;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
{
|
||||
private readonly ValueTransition<double> _canvasYScrollTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutQuad
|
||||
);
|
||||
|
||||
private readonly ValueTransition<Color> _immersiveBgColorTransition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
|
||||
private readonly ValueTransition<Color> _albumArtAccentColor1Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
|
||||
private readonly ValueTransition<Color> _albumArtAccentColor2Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
|
||||
private readonly ValueTransition<Color> _albumArtAccentColor3Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
|
||||
private readonly ValueTransition<Color> _albumArtAccentColor4Transition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
|
||||
private readonly ValueTransition<double> _immersiveBgOpacityTransition = new(
|
||||
initialValue: 1f,
|
||||
durationSeconds: 0.3f
|
||||
);
|
||||
|
||||
private readonly ValueTransition<double> _lyricsYTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutQuad
|
||||
);
|
||||
|
||||
private readonly ValueTransition<double> _lyricsOpacityTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f
|
||||
);
|
||||
|
||||
private readonly ValueTransition<double> _albumArtOpacityTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 1f
|
||||
);
|
||||
|
||||
private readonly ValueTransition<double> _albumArtXTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutQuad
|
||||
);
|
||||
|
||||
private readonly ValueTransition<double> _albumArtYTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutQuad
|
||||
);
|
||||
|
||||
private readonly ValueTransition<double> _songInfoOpacityTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 1f
|
||||
);
|
||||
|
||||
private readonly ValueTransition<double> _lyricsBgBrightnessTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 1f
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,294 +0,0 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.LiveStatesService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
public partial class LyricsRendererViewModel : BaseViewModel
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial AppSettings AppSettings { get; set; }
|
||||
|
||||
private bool _isLastFMTracked = false;
|
||||
|
||||
/// <summary>
|
||||
/// This includes all the time the song has been played, even if the user seeks back.
|
||||
/// </summary>
|
||||
private TimeSpan _totalPlayingTime = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Refresh elapsed time for every frame by render.
|
||||
/// </summary>
|
||||
private TimeSpan _elapsedTime = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the current time position of the song.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
|
||||
|
||||
private int _songDurationMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
|
||||
|
||||
private Stopwatch? _drawFrameStopwatch;
|
||||
private int _drawFrameCount = 0;
|
||||
private int _displayedDrawFrameCount = 0;
|
||||
|
||||
private double _albumArtSize = 0f;
|
||||
|
||||
[ObservableProperty] public partial double AlbumArtSize { get; set; } = 0;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double AlbumArtX { get; set; } = 0;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double AlbumArtY { get; set; } = 0;
|
||||
|
||||
private double _canvasWidth = 0f;
|
||||
private double _canvasHeight = 0f;
|
||||
|
||||
private readonly double _defaultScale = 0.75f;
|
||||
private readonly double _highlightedScale = 1.0f;
|
||||
|
||||
private double _canvasTargetYScrollOffset = 0;
|
||||
|
||||
private double _lyricsX = 0f;
|
||||
private double _maxLyricsWidth = 0f;
|
||||
private double _maxSongInfoWidth = 0f;
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IMediaSessionsService _mediaSessionsService;
|
||||
private readonly ILastFMService _lastFMService;
|
||||
private readonly ILiveStatesService _liveStatesService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private double _leftMargin = 36f;
|
||||
private double _middleMargin = 36f;
|
||||
private double _rightMargin = 36f;
|
||||
private double _topMargin = 36f;
|
||||
private double _bottomMargin = 36f;
|
||||
|
||||
private Color _adaptiveGrayedFontColor = Colors.Transparent;
|
||||
private Color? _adaptiveColoredFontColor = null;
|
||||
|
||||
private Color _environmentalColor = Colors.Transparent;
|
||||
private Color _grayedEnvironmentalColor = Colors.Transparent;
|
||||
|
||||
private Color _lightColor = Colors.White;
|
||||
private Color _darkColor = Colors.Black;
|
||||
|
||||
private Color _bgFontColor;
|
||||
private Color _fgFontColor;
|
||||
private Color _strokeFontColor;
|
||||
|
||||
private int _playingLineIndex = -1;
|
||||
|
||||
private int _startVisibleLineIndex = -1;
|
||||
private int _endVisibleLineIndex = -1;
|
||||
|
||||
private LyricsData? _currentLyricsData;
|
||||
|
||||
private bool _isDebugOverlayEnabled = false;
|
||||
|
||||
private int _phoneticLyricsFontSize = 18;
|
||||
private int _originalLyricsFontSize = 36;
|
||||
private int _translatedLyricsFontSize = 18;
|
||||
|
||||
private LyricsFontWeight _originalLyricsFontWeight = LyricsFontWeight.Bold;
|
||||
|
||||
private CanvasTextFormat _debugTextFormat = new()
|
||||
{
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.ExtraBlack,
|
||||
};
|
||||
|
||||
private CanvasGeometry? _spectrumGeometry = null;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ElementTheme ThemeTypeSent { get; set; }
|
||||
|
||||
private SpectrumAnalyzer? _spectrumAnalyzer;
|
||||
|
||||
private Matrix4x4 _lyrics3DMatrix = Matrix4x4.Identity;
|
||||
|
||||
public LyricsRendererViewModel(
|
||||
ISettingsService settingsService,
|
||||
IMediaSessionsService mediaSessionsService,
|
||||
ILastFMService lastFMService,
|
||||
ILiveStatesService liveStatesService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_mediaSessionsService = mediaSessionsService;
|
||||
_liveStatesService = liveStatesService;
|
||||
_lastFMService = lastFMService;
|
||||
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
|
||||
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
|
||||
_mediaSessionsService.LyricsChanged += MediaSessionsService_LyricsChanged;
|
||||
|
||||
UpdateColorConfig();
|
||||
|
||||
_spectrumAnalyzer = new SpectrumAnalyzer();
|
||||
}
|
||||
|
||||
private void MediaSessionsService_LyricsChanged(object? sender, LyricsChangedEventArgs e)
|
||||
{
|
||||
_currentLyricsData = e.LyricsData;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
|
||||
private int GetCurrentPlayingLineIndex()
|
||||
{
|
||||
var totalMs = TotalTime.TotalMilliseconds + _mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0;
|
||||
if (totalMs < _currentLyricsData?.LyricsLines.FirstOrDefault()?.StartMs) return 0;
|
||||
|
||||
for (int i = 0; i < _currentLyricsData?.LyricsLines.Count; i++)
|
||||
{
|
||||
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i);
|
||||
if (line == null) continue;
|
||||
var nextLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i + 1);
|
||||
if (nextLine != null && line.StartMs <= totalMs && totalMs < nextLine.StartMs)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
else if (nextLine == null && line.StartMs <= totalMs)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return GetMaxLyricsLineIndexBoundaries().Item2;
|
||||
}
|
||||
|
||||
private void GetLinePlayingProgress(int lineIndex, out int syllableStartIndex, out int syllableLength, out double syllableProgress)
|
||||
{
|
||||
syllableStartIndex = 0;
|
||||
syllableLength = 0;
|
||||
syllableProgress = 0f;
|
||||
|
||||
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(lineIndex);
|
||||
if (line == null) return;
|
||||
var nextLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(lineIndex + 1);
|
||||
|
||||
int lineEndMs;
|
||||
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
|
||||
else if (nextLine != null) lineEndMs = nextLine.StartMs;
|
||||
else lineEndMs = _songDurationMs;
|
||||
|
||||
double now = (double)TotalTime.TotalMilliseconds + (double)(_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
|
||||
|
||||
// 1. 还没到本句
|
||||
if (now < line.StartMs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 已经超过本句
|
||||
if (now > lineEndMs)
|
||||
{
|
||||
syllableProgress = 1f;
|
||||
syllableStartIndex = line.OriginalText.Length - 1;
|
||||
syllableLength = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 有逐字时间轴
|
||||
if (line.LyricsSyllables != null && line.LyricsSyllables.Count > 1)
|
||||
{
|
||||
int charTimingsCount = line.LyricsSyllables.Count;
|
||||
for (int i = 0; i < charTimingsCount; i++)
|
||||
{
|
||||
var timing = line.LyricsSyllables[i];
|
||||
var nextTiming = line.LyricsSyllables.ElementAtOrDefault(i + 1);
|
||||
|
||||
int timingEndMs;
|
||||
if (timing.EndMs != null) timingEndMs = timing.EndMs.Value;
|
||||
else if (nextTiming != null) timingEndMs = nextTiming.StartMs;
|
||||
else timingEndMs = lineEndMs;
|
||||
|
||||
syllableStartIndex = timing.StartIndex;
|
||||
syllableLength = timing.Text.Length;
|
||||
|
||||
// 当前时间在某个字的高亮区间
|
||||
if (now >= timing.StartMs && now <= timingEndMs)
|
||||
{
|
||||
if (timingEndMs != timing.StartMs)
|
||||
{
|
||||
syllableProgress = (now - timing.StartMs) / (timingEndMs - timing.StartMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
syllableProgress = 0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (now > timingEndMs && (nextTiming == null || now < nextTiming?.StartMs))
|
||||
{
|
||||
syllableProgress = 1f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int textLength = line.OriginalText.Length;
|
||||
if (textLength == 0) return;
|
||||
|
||||
if (_settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect)
|
||||
{
|
||||
// 没有逐字时间轴,均匀分配每个字的高亮时间
|
||||
double lineProgress = (now - line.StartMs) / (lineEndMs - line.StartMs);
|
||||
lineProgress = Math.Clamp(lineProgress, 0f, 1f);
|
||||
|
||||
// 计算当前高亮到第几个字
|
||||
double charFloatIndex = lineProgress * textLength;
|
||||
int charIndex = (int)charFloatIndex;
|
||||
syllableStartIndex = Math.Clamp(charIndex, 0, textLength - 1);
|
||||
syllableLength = 1;
|
||||
|
||||
// 当前字的进度(0~1)
|
||||
syllableProgress = charFloatIndex - charIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
syllableStartIndex = textLength;
|
||||
syllableProgress = 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
|
||||
{
|
||||
if (_mediaSessionsService.CurrentSongInfo == null
|
||||
|| _currentLyricsData == null
|
||||
|| _currentLyricsData.LyricsLines.Count == 0)
|
||||
{
|
||||
return new Tuple<int, int>(-1, -1);
|
||||
}
|
||||
|
||||
return new Tuple<int, int>(0, _currentLyricsData.LyricsLines.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ using BetterLyrics.WinUI3.Services.LiveStatesService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -211,13 +210,13 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
public void Receive(PropertyChangedMessage<ElementTheme> message)
|
||||
{
|
||||
if (message.Sender is LyricsRendererViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.ThemeTypeSent))
|
||||
{
|
||||
ThemeType = message.NewValue;
|
||||
}
|
||||
}
|
||||
//if (message.Sender is LyricsRendererViewModel)
|
||||
//{
|
||||
// if (message.PropertyName == nameof(LyricsRendererViewModel.ThemeTypeSent))
|
||||
// {
|
||||
// ThemeType = message.NewValue;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
xmlns:renderer="using:BetterLyrics.WinUI3.Renderer"
|
||||
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
Loaded="Page_Loaded"
|
||||
Unloaded="Page_Unloaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
@@ -24,25 +26,50 @@
|
||||
RightTapped="RootGrid_RightTapped"
|
||||
SizeChanged="RootGrid_SizeChanged">
|
||||
|
||||
<!-- Lyrics area -->
|
||||
<renderer:LyricsRenderer />
|
||||
<!-- Win2D drawing area -->
|
||||
<uc:NowPlayingCanvas x:Name="NowPlayingCanvas" />
|
||||
|
||||
<!-- Album art and song info area -->
|
||||
<StackPanel
|
||||
x:Name="AlbumArtWithSongInfoStackPanel"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsLayoutOrientation, Converter={StaticResource LyricsLayoutOrientationNegationToOrientationConverter}, Mode=OneWay}"
|
||||
Translation="{x:Bind ViewModel.AlbumArtTranslation, Mode=OneWay}">
|
||||
SizeChanged="AlbumArtWithSongInfoStackPanel_SizeChanged"
|
||||
Translation="0,0,0">
|
||||
<StackPanel.TranslationTransition>
|
||||
<Vector3Transition />
|
||||
</StackPanel.TranslationTransition>
|
||||
|
||||
<!-- Album art -->
|
||||
<Grid Padding="32">
|
||||
<Grid x:Name="ShadowCastGrid" CornerRadius="32">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsDisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsDisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="1">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsDisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="2">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<Grid x:Name="ShadowCastGrid" CornerRadius="{x:Bind AlbumArtCornerRadius, Mode=OneWay}">
|
||||
<Image
|
||||
Width="{x:Bind ViewModel.LyricsRendererViewModel.AlbumArtSize, Mode=OneWay}"
|
||||
Height="{x:Bind ViewModel.LyricsRendererViewModel.AlbumArtSize, Mode=OneWay}"
|
||||
Width="{x:Bind AlbumArtSize, Mode=OneWay}"
|
||||
Height="{x:Bind AlbumArtSize, Mode=OneWay}"
|
||||
Opacity="{x:Bind ViewModel.LastAlbumArtOpacity, Mode=OneWay}"
|
||||
Source="{x:Bind ViewModel.LastAlbumArtBitmapImage, Mode=OneWay}">
|
||||
<Image.OpacityTransition>
|
||||
@@ -50,21 +77,22 @@
|
||||
</Image.OpacityTransition>
|
||||
</Image>
|
||||
<Image
|
||||
Width="{x:Bind ViewModel.LyricsRendererViewModel.AlbumArtSize, Mode=OneWay}"
|
||||
Height="{x:Bind ViewModel.LyricsRendererViewModel.AlbumArtSize, Mode=OneWay}"
|
||||
Width="{x:Bind AlbumArtSize, Mode=OneWay}"
|
||||
Height="{x:Bind AlbumArtSize, Mode=OneWay}"
|
||||
Opacity="{x:Bind ViewModel.AlbumArtOpacity, Mode=OneWay}"
|
||||
Source="{x:Bind ViewModel.AlbumArtBitmapImage, Mode=OneWay}">
|
||||
<Image.OpacityTransition>
|
||||
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
|
||||
</Image.OpacityTransition>
|
||||
</Image>
|
||||
<dev:AnimatedImage ImageUrl="" />
|
||||
</Grid>
|
||||
<Border
|
||||
x:Name="ShadowRect"
|
||||
Width="{x:Bind ViewModel.LyricsRendererViewModel.AlbumArtSize, Mode=OneWay}"
|
||||
Height="{x:Bind ViewModel.LyricsRendererViewModel.AlbumArtSize, Mode=OneWay}"
|
||||
Width="{x:Bind AlbumArtSize, Mode=OneWay}"
|
||||
Height="{x:Bind AlbumArtSize, Mode=OneWay}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="32"
|
||||
CornerRadius="{x:Bind AlbumArtCornerRadius, Mode=OneWay}"
|
||||
Loaded="ShadowRect_Loaded"
|
||||
Translation="0,0,64">
|
||||
<Border.Shadow>
|
||||
@@ -74,7 +102,7 @@
|
||||
</Grid>
|
||||
|
||||
<!-- Song info -->
|
||||
<StackPanel Width="{x:Bind ViewModel.LyricsRendererViewModel.AlbumArtSize, Mode=OneWay}" Opacity="{x:Bind ViewModel.SongInfoOpacity, Mode=OneWay}">
|
||||
<StackPanel x:Name="SongInfoStackPanel" Width="{x:Bind AlbumArtSize, Mode=OneWay}">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
|
||||
</StackPanel.OpacityTransition>
|
||||
@@ -92,32 +120,90 @@
|
||||
<interactivity:ChangePropertyAction PropertyName="Margin" Value="-16,32,0,0" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}, Mode=OneWay}"
|
||||
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
|
||||
IsTextSelectionEnabled="True"
|
||||
Loaded="TitleTextBlock_Loaded"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBlock
|
||||
x:Name="ArtistsTextBlock"
|
||||
HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}, Mode=OneWay}"
|
||||
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
|
||||
IsTextSelectionEnabled="True"
|
||||
Loaded="ArtistsTextBlock_Loaded"
|
||||
Opacity="0.5"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowArtists, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBlock
|
||||
x:Name="AlbumTextBlock"
|
||||
HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}, Mode=OneWay}"
|
||||
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
|
||||
IsTextSelectionEnabled="True"
|
||||
Loaded="AlbumTextBlock_Loaded"
|
||||
Opacity="0.5"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowAlbum, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<dev:OpacityMaskView>
|
||||
<dev:OpacityMaskView.OpacityMask>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Offset="0" Color="Transparent" />
|
||||
<GradientStop Offset="0.02" Color="#FFFFFFFF" />
|
||||
<GradientStop Offset="0.98" Color="#FFFFFFFF" />
|
||||
<GradientStop Offset="1" Color="Transparent" />
|
||||
</LinearGradientBrush>
|
||||
</dev:OpacityMaskView.OpacityMask>
|
||||
<dev:AutoScrollView
|
||||
x:Name="TitleAutoScrollHoverEffectView"
|
||||
Width="{x:Bind AlbumArtSize, Mode=OneWay}"
|
||||
Padding="5,0,0,0"
|
||||
IsPlaying="False"
|
||||
PointerCanceled="TitleAutoScrollHoverEffectView_PointerCanceled"
|
||||
PointerEntered="TitleAutoScrollHoverEffectView_PointerEntered"
|
||||
PointerExited="TitleAutoScrollHoverEffectView_PointerExited"
|
||||
ScrollingPixelsPreSecond="20">
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}, Mode=OneWay}"
|
||||
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
|
||||
TextTrimming="None"
|
||||
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</dev:AutoScrollView>
|
||||
</dev:OpacityMaskView>
|
||||
|
||||
<dev:OpacityMaskView>
|
||||
<dev:OpacityMaskView.OpacityMask>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Offset="0" Color="Transparent" />
|
||||
<GradientStop Offset="0.02" Color="#FFFFFFFF" />
|
||||
<GradientStop Offset="0.98" Color="#FFFFFFFF" />
|
||||
<GradientStop Offset="1" Color="Transparent" />
|
||||
</LinearGradientBrush>
|
||||
</dev:OpacityMaskView.OpacityMask>
|
||||
<dev:AutoScrollView
|
||||
x:Name="ArtistsAutoScrollHoverEffectView"
|
||||
Width="{x:Bind AlbumArtSize, Mode=OneWay}"
|
||||
Padding="5,0,0,0"
|
||||
IsPlaying="False"
|
||||
PointerCanceled="ArtistsAutoScrollHoverEffectView_PointerCanceled"
|
||||
PointerEntered="ArtistsAutoScrollHoverEffectView_PointerEntered"
|
||||
PointerExited="ArtistsAutoScrollHoverEffectView_PointerExited"
|
||||
ScrollingPixelsPreSecond="20">
|
||||
<TextBlock
|
||||
x:Name="ArtistsTextBlock"
|
||||
HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}, Mode=OneWay}"
|
||||
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
|
||||
Opacity="0.5"
|
||||
TextTrimming="None"
|
||||
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowArtists, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</dev:AutoScrollView>
|
||||
</dev:OpacityMaskView>
|
||||
|
||||
<dev:OpacityMaskView>
|
||||
<dev:OpacityMaskView.OpacityMask>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Offset="0" Color="Transparent" />
|
||||
<GradientStop Offset="0.02" Color="#FFFFFFFF" />
|
||||
<GradientStop Offset="0.98" Color="#FFFFFFFF" />
|
||||
<GradientStop Offset="1" Color="Transparent" />
|
||||
</LinearGradientBrush>
|
||||
</dev:OpacityMaskView.OpacityMask>
|
||||
<dev:AutoScrollView
|
||||
x:Name="AlbumAutoScrollHoverEffectView"
|
||||
Width="{x:Bind AlbumArtSize, Mode=OneWay}"
|
||||
Padding="5,0,0,0"
|
||||
IsPlaying="False"
|
||||
PointerCanceled="AlbumAutoScrollHoverEffectView_PointerCanceled"
|
||||
PointerEntered="AlbumAutoScrollHoverEffectView_PointerEntered"
|
||||
PointerExited="AlbumAutoScrollHoverEffectView_PointerExited"
|
||||
ScrollingPixelsPreSecond="20">
|
||||
<TextBlock
|
||||
x:Name="AlbumTextBlock"
|
||||
HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}, Mode=OneWay}"
|
||||
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
|
||||
Opacity="0.5"
|
||||
TextTrimming="None"
|
||||
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowAlbum, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</dev:AutoScrollView>
|
||||
</dev:OpacityMaskView>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
@@ -403,8 +489,7 @@
|
||||
Maximum="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DurationMs, Mode=OneWay, Converter={StaticResource MillisecondsToSecondsConverter}}"
|
||||
Minimum="0"
|
||||
Style="{StaticResource GhostSliderStyle}"
|
||||
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}"
|
||||
Value="{x:Bind ViewModel.TimelinePositionSeconds, Mode=OneWay}" />
|
||||
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}" />
|
||||
<Grid
|
||||
x:Name="TimelineSliderLyricsLineInfo"
|
||||
Margin="0,-48,0,0"
|
||||
|
||||
@@ -4,25 +4,63 @@ using BetterLyrics.WinUI3.Controls;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.LiveStatesService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using CommunityToolkit.WinUI;
|
||||
using DevWinUI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
public sealed partial class LyricsPage : Page
|
||||
public sealed partial class LyricsPage : Page,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<SongInfo?>>
|
||||
{
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
|
||||
|
||||
private readonly LyricsRendererViewModel _lyricsRendererViewModel = Ioc.Default.GetRequiredService<LyricsRendererViewModel>();
|
||||
private double _leftMargin = 36f;
|
||||
private double _middleMargin = 36f;
|
||||
private double _rightMargin = 36f;
|
||||
private double _topMargin = 36f;
|
||||
private double _bottomMargin = 36f;
|
||||
|
||||
private readonly DispatcherQueueTimer _rootGridSizeChangedTimer;
|
||||
|
||||
public double AlbumArtSize
|
||||
{
|
||||
get { return (double)GetValue(AlbumArtSizeProperty); }
|
||||
set { SetValue(AlbumArtSizeProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AlbumArtSizeProperty =
|
||||
DependencyProperty.Register(nameof(AlbumArtSize), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(0.0));
|
||||
|
||||
public CornerRadius AlbumArtCornerRadius
|
||||
{
|
||||
get { return (CornerRadius)GetValue(AlbumArtCornerRadiusProperty); }
|
||||
set { SetValue(AlbumArtCornerRadiusProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AlbumArtCornerRadiusProperty =
|
||||
DependencyProperty.Register(nameof(AlbumArtCornerRadius), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(new CornerRadius(0)));
|
||||
|
||||
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
|
||||
|
||||
@@ -31,6 +69,299 @@ namespace BetterLyrics.WinUI3.Views
|
||||
this.InitializeComponent();
|
||||
|
||||
DataContext = Ioc.Default.GetRequiredService<LyricsPageViewModel>();
|
||||
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<int>>(this);
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<bool>>(this);
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<string>>(this);
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<SongInfo?>>(this);
|
||||
|
||||
_rootGridSizeChangedTimer = App.Current.Resources.DispatcherQueue.CreateTimer();
|
||||
}
|
||||
|
||||
private void CompositionTarget_Rendering(object? sender, object e)
|
||||
{
|
||||
var currentTime = NowPlayingCanvas.SongPosition.TotalSeconds;
|
||||
TimelineSlider.Value = currentTime;
|
||||
}
|
||||
|
||||
private void RenderTextBlock(TextBlock? sender, string? text, int fontSize)
|
||||
{
|
||||
if (sender == null || string.IsNullOrEmpty(text) || fontSize == 0) return;
|
||||
|
||||
var lyricsStyleSettings = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings;
|
||||
|
||||
sender.Inlines.Clear();
|
||||
foreach (var ch in text)
|
||||
{
|
||||
var fontFamilyName = LanguageHelper.IsCJK(ch) ? lyricsStyleSettings.LyricsCJKFontFamily : lyricsStyleSettings.LyricsWesternFontFamily;
|
||||
sender.Inlines.Add(new Run { Text = $"{ch}", FontFamily = new Microsoft.UI.Xaml.Media.FontFamily(fontFamilyName) });
|
||||
}
|
||||
sender.FontSize = fontSize;
|
||||
}
|
||||
|
||||
private void RenderTextBlock(AnimatedTextBlock? sender, string? text, int fontSize)
|
||||
{
|
||||
if (sender == null || string.IsNullOrEmpty(text) || fontSize == 0) return;
|
||||
|
||||
var lyricsStyleSettings = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings;
|
||||
var fontFamilyName = LanguageHelper.IsCJK(text) ? lyricsStyleSettings.LyricsCJKFontFamily : lyricsStyleSettings.LyricsWesternFontFamily;
|
||||
|
||||
sender.FontFamily = new Microsoft.UI.Xaml.Media.FontFamily(fontFamilyName);
|
||||
sender.FontSize = fontSize;
|
||||
|
||||
sender.Text = text;
|
||||
}
|
||||
|
||||
private void RenderSongInfo()
|
||||
{
|
||||
RenderTextBlock(TitleTextBlock, _mediaSessionsService.CurrentSongInfo?.Title, GetTitleFontSize());
|
||||
RenderTextBlock(ArtistsTextBlock, _mediaSessionsService.CurrentSongInfo?.DisplayArtists, GetArtistsAlbumFontSize());
|
||||
RenderTextBlock(AlbumTextBlock, _mediaSessionsService.CurrentSongInfo?.Album, GetArtistsAlbumFontSize());
|
||||
}
|
||||
|
||||
private void UpdateAlbumArtSize()
|
||||
{
|
||||
var lyricsWindowStatus = _liveStatesService.LiveStates.LyricsWindowStatus;
|
||||
var albumArtLayoutSettings = lyricsWindowStatus.AlbumArtLayoutSettings;
|
||||
double temp = 0;
|
||||
switch (lyricsWindowStatus.LyricsLayoutOrientation)
|
||||
{
|
||||
case LyricsLayoutOrientation.Horizontal:
|
||||
if (albumArtLayoutSettings.AutoAlbumArtSize)
|
||||
{
|
||||
temp = Math.Min(
|
||||
(RootGrid.ActualHeight - _topMargin - _bottomMargin) * 8.5 / 16.0,
|
||||
(RootGrid.ActualWidth - _leftMargin - _middleMargin - _rightMargin) / 2.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
temp = albumArtLayoutSettings.AlbumArtSize;
|
||||
}
|
||||
break;
|
||||
case LyricsLayoutOrientation.Vertical:
|
||||
if (albumArtLayoutSettings.AutoAlbumArtSize)
|
||||
{
|
||||
temp = Math.Min(
|
||||
(RootGrid.ActualHeight - _topMargin - _bottomMargin) * 3.0 / 16.0,
|
||||
(RootGrid.ActualWidth - _leftMargin - _middleMargin - _rightMargin) * 4.0 / 16.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
temp = albumArtLayoutSettings.AlbumArtSize;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
AlbumArtSize = Math.Max(0, temp);
|
||||
}
|
||||
|
||||
private void UpdateAlbumArtCornerRadius()
|
||||
{
|
||||
var factor = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.CoverImageRadius / 100.0;
|
||||
AlbumArtCornerRadius = new((AlbumArtSize / 2) * factor);
|
||||
}
|
||||
|
||||
private double GetAlbumArtY()
|
||||
{
|
||||
double temp = 0;
|
||||
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsLayoutOrientation)
|
||||
{
|
||||
case LyricsLayoutOrientation.Horizontal:
|
||||
temp = (RootGrid.ActualHeight - AlbumArtWithSongInfoStackPanel.ActualHeight) / 2.0;
|
||||
break;
|
||||
case LyricsLayoutOrientation.Vertical:
|
||||
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsDisplayType)
|
||||
{
|
||||
case LyricsDisplayType.AlbumArtOnly:
|
||||
temp = (RootGrid.ActualHeight - AlbumArtSize) / 2.0;
|
||||
break;
|
||||
case LyricsDisplayType.SplitView:
|
||||
temp = _topMargin;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (float)temp - 32;
|
||||
}
|
||||
|
||||
private double GetAlbumArtX()
|
||||
{
|
||||
double temp = 0;
|
||||
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsLayoutOrientation)
|
||||
{
|
||||
case LyricsLayoutOrientation.Horizontal:
|
||||
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsDisplayType)
|
||||
{
|
||||
case LyricsDisplayType.AlbumArtOnly:
|
||||
temp = RootGrid.ActualWidth / 2.0 - AlbumArtSize / 2.0;
|
||||
break;
|
||||
case LyricsDisplayType.SplitView:
|
||||
temp = _leftMargin + ((RootGrid.ActualWidth - _leftMargin - _middleMargin - _rightMargin) / 2.0 - AlbumArtSize) / 2.0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case LyricsLayoutOrientation.Vertical:
|
||||
temp = _leftMargin;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (float)temp - 32;
|
||||
}
|
||||
|
||||
private void UpdateAlbumArtTranslation()
|
||||
{
|
||||
var x = GetAlbumArtX();
|
||||
var y = GetAlbumArtY();
|
||||
AlbumArtWithSongInfoStackPanel.Translation = new((float)x, (float)y, 0);
|
||||
}
|
||||
|
||||
private void UpdateMargin()
|
||||
{
|
||||
_topMargin = _bottomMargin = _leftMargin = _middleMargin = _rightMargin = Math.Max(RootGrid.ActualWidth, RootGrid.ActualHeight) / 30.0;
|
||||
}
|
||||
|
||||
private void UpdateLyricsStartY()
|
||||
{
|
||||
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsLayoutOrientation)
|
||||
{
|
||||
case LyricsLayoutOrientation.Horizontal:
|
||||
NowPlayingCanvas.LyricsStartY = 0;
|
||||
break;
|
||||
case LyricsLayoutOrientation.Vertical:
|
||||
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsDisplayType)
|
||||
{
|
||||
case LyricsDisplayType.LyricsOnly:
|
||||
NowPlayingCanvas.LyricsStartY = 0;
|
||||
break;
|
||||
case LyricsDisplayType.SplitView:
|
||||
NowPlayingCanvas.LyricsStartY = _topMargin;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLyricsOpacity()
|
||||
{
|
||||
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsDisplayType)
|
||||
{
|
||||
case LyricsDisplayType.AlbumArtOnly:
|
||||
NowPlayingCanvas.LyricsOpacity = 0;
|
||||
break;
|
||||
case LyricsDisplayType.LyricsOnly:
|
||||
case LyricsDisplayType.SplitView:
|
||||
NowPlayingCanvas.LyricsOpacity = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLyricsStartX()
|
||||
{
|
||||
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsLayoutOrientation)
|
||||
{
|
||||
case LyricsLayoutOrientation.Horizontal:
|
||||
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsDisplayType)
|
||||
{
|
||||
case LyricsDisplayType.LyricsOnly:
|
||||
NowPlayingCanvas.LyricsStartX = _leftMargin;
|
||||
break;
|
||||
case LyricsDisplayType.SplitView:
|
||||
NowPlayingCanvas.LyricsStartX = (RootGrid.ActualWidth - _leftMargin - _middleMargin - _rightMargin) / 2.0 + _leftMargin + _middleMargin;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case LyricsLayoutOrientation.Vertical:
|
||||
NowPlayingCanvas.LyricsStartX = _leftMargin;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLyricsWidth()
|
||||
{
|
||||
NowPlayingCanvas.LyricsWidth = Math.Max(RootGrid.ActualWidth - NowPlayingCanvas.LyricsStartX - _rightMargin, 0);
|
||||
}
|
||||
|
||||
private void OnLayoutChanged()
|
||||
{
|
||||
UpdateMargin();
|
||||
|
||||
UpdateAlbumArtSize();
|
||||
UpdateAlbumArtCornerRadius();
|
||||
UpdateAlbumArtTranslation();
|
||||
|
||||
UpdateLyricsStartX();
|
||||
UpdateLyricsStartY();
|
||||
|
||||
UpdateLyricsWidth();
|
||||
}
|
||||
|
||||
private int GetTitleFontSize()
|
||||
{
|
||||
var albumArtLayoutSettings = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings;
|
||||
if (albumArtLayoutSettings.IsAutoSongInfoFontSize)
|
||||
{
|
||||
return (int)Math.Clamp(Math.Min(RootGrid.ActualHeight, RootGrid.ActualWidth) / 20, 8, 72);
|
||||
}
|
||||
else
|
||||
{
|
||||
return albumArtLayoutSettings.SongInfoFontSize;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetArtistsAlbumFontSize()
|
||||
{
|
||||
return (int)(GetTitleFontSize() * 0.8);
|
||||
}
|
||||
|
||||
// ====
|
||||
|
||||
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
_rootGridSizeChangedTimer.Debounce(() =>
|
||||
{
|
||||
RenderSongInfo();
|
||||
|
||||
OnLayoutChanged();
|
||||
|
||||
if (e.NewSize.Width < 500 || e.NewSize.Height < 100)
|
||||
{
|
||||
if (BottomCommandGrid.Children.Count != 0)
|
||||
{
|
||||
BottomCommandGrid.Children.Remove(BottomCommandContent);
|
||||
BottomCommandFlyoutContainer.Children.Add(BottomCommandContent);
|
||||
}
|
||||
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BottomCommandFlyoutContainer.Children.Count != 0)
|
||||
{
|
||||
BottomCommandFlyout.Hide();
|
||||
BottomCommandFlyoutContainer.Children.Remove(BottomCommandContent);
|
||||
BottomCommandGrid.Children.Add(BottomCommandContent);
|
||||
}
|
||||
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 12, 0);
|
||||
}
|
||||
}, Constants.Time.DebounceTimeout);
|
||||
}
|
||||
|
||||
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
@@ -61,32 +392,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
PlaybackSettingsFlyout.ShowAt(BottomRightCommandStackPanel);
|
||||
}
|
||||
|
||||
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
ViewModel.RootWidth = e.NewSize.Width;
|
||||
ViewModel.RootHeight = e.NewSize.Height;
|
||||
|
||||
if (e.NewSize.Width < 500 || e.NewSize.Height < 100)
|
||||
{
|
||||
if (BottomCommandGrid.Children.Count != 0)
|
||||
{
|
||||
BottomCommandGrid.Children.Remove(BottomCommandContent);
|
||||
BottomCommandFlyoutContainer.Children.Add(BottomCommandContent);
|
||||
}
|
||||
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BottomCommandFlyoutContainer.Children.Count != 0)
|
||||
{
|
||||
BottomCommandFlyout.Hide();
|
||||
BottomCommandFlyoutContainer.Children.Remove(BottomCommandContent);
|
||||
BottomCommandGrid.Children.Add(BottomCommandContent);
|
||||
}
|
||||
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 12, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void VolumeButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
VolumeFlyout.ShowAt(BottomLeftCommandStackPanel);
|
||||
@@ -195,19 +500,118 @@ namespace BetterLyrics.WinUI3.Views
|
||||
Shadow.Receivers.Add(ShadowCastGrid);
|
||||
}
|
||||
|
||||
private void TitleTextBlock_Loaded(object sender, RoutedEventArgs e)
|
||||
private void AlbumArtWithSongInfoStackPanel_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
ViewModel.TitleTextBlock = TitleTextBlock;
|
||||
ViewModel.AlbumArtWithSongInfoStackPanelHeight = e.NewSize.Height;
|
||||
}
|
||||
|
||||
private void ArtistsTextBlock_Loaded(object sender, RoutedEventArgs e)
|
||||
private void TitleAutoScrollHoverEffectView_PointerCanceled(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
ViewModel.ArtistsTextBlock = ArtistsTextBlock;
|
||||
TitleAutoScrollHoverEffectView.IsPlaying = false;
|
||||
}
|
||||
|
||||
private void AlbumTextBlock_Loaded(object sender, RoutedEventArgs e)
|
||||
private void TitleAutoScrollHoverEffectView_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
ViewModel.AlbumTextBlock = AlbumTextBlock;
|
||||
TitleAutoScrollHoverEffectView.IsPlaying = true;
|
||||
}
|
||||
|
||||
private void TitleAutoScrollHoverEffectView_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
TitleAutoScrollHoverEffectView.IsPlaying = false;
|
||||
}
|
||||
|
||||
private void ArtistsAutoScrollHoverEffectView_PointerCanceled(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
ArtistsAutoScrollHoverEffectView.IsPlaying = false;
|
||||
}
|
||||
|
||||
private void ArtistsAutoScrollHoverEffectView_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
ArtistsAutoScrollHoverEffectView.IsPlaying = true;
|
||||
}
|
||||
|
||||
private void ArtistsAutoScrollHoverEffectView_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
ArtistsAutoScrollHoverEffectView.IsPlaying = false;
|
||||
}
|
||||
|
||||
private void AlbumAutoScrollHoverEffectView_PointerCanceled(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
AlbumAutoScrollHoverEffectView.IsPlaying = false;
|
||||
}
|
||||
|
||||
private void AlbumAutoScrollHoverEffectView_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
AlbumAutoScrollHoverEffectView.IsPlaying = true;
|
||||
}
|
||||
|
||||
private void AlbumAutoScrollHoverEffectView_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
AlbumAutoScrollHoverEffectView.IsPlaying = false;
|
||||
}
|
||||
|
||||
private void Page_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CompositionTarget.Rendering += CompositionTarget_Rendering;
|
||||
}
|
||||
|
||||
private void Page_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CompositionTarget.Rendering -= CompositionTarget_Rendering;
|
||||
}
|
||||
|
||||
// ====
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is AlbumArtLayoutSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(AlbumArtLayoutSettings.SongInfoFontSize))
|
||||
{
|
||||
RenderSongInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is AlbumArtLayoutSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(AlbumArtLayoutSettings.IsAutoSongInfoFontSize))
|
||||
{
|
||||
RenderSongInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCJKFontFamily))
|
||||
{
|
||||
RenderSongInfo();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsWesternFontFamily))
|
||||
{
|
||||
RenderSongInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
|
||||
{
|
||||
SongInfoStackPanel.Opacity = 0;
|
||||
await Task.Delay(Constants.Time.AnimationDuration);
|
||||
RenderSongInfo();
|
||||
SongInfoStackPanel.Opacity = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user