chores: fix floating animation; improve layout

This commit is contained in:
Zhe Fang
2025-11-30 20:14:47 -05:00
parent 401c33003c
commit 5c50bd569a
7 changed files with 210 additions and 81 deletions

View File

@@ -0,0 +1,76 @@
using BetterLyrics.WinUI3.Models;
using Microsoft.UI.Xaml;
using System;
namespace BetterLyrics.WinUI3.Helper
{
public static class LyricsLayoutHelper
{
// 硬性限制
private const float BaseMinFontSize = 14f;
private const float BaseMaxFontSize = 80f;
private const float TargetMinVisibleLines = 5f;
private const float WidthPaddingRatio = 0.85f;
// 比例配置
private const float RatioSongTitle = 1f;
private const float RatioArtist = 0.85f;
private const float RatioAlbum = 0.75f;
private const float RatioTranslation = 0.7f;
private const float RatioTransliteration = 0.55f;
private const float AbsoluteMinReadableSize = 10f;
public static LyricsLayoutMetrics CalculateLayout(double width, double height)
{
float baseSize = CalculateBaseFontSize(width, height);
return new LyricsLayoutMetrics
{
MainLyricsSize = baseSize,
TranslationSize = ApplyRatio(baseSize, RatioTranslation),
TransliterationSize = ApplyRatio(baseSize, RatioTransliteration),
SongTitleSize = ApplyRatio(baseSize, RatioSongTitle),
ArtistNameSize = ApplyRatio(baseSize, RatioArtist),
AlbumNameSize = ApplyRatio(baseSize, RatioAlbum)
};
}
private static float CalculateBaseFontSize(double width, double height)
{
float usableWidth = (float)width * WidthPaddingRatio;
// 宽度 300~500px 时,除以 14 (字大)
// 宽度 >1000px 时,除以 30 (字适中,展示更多内容)
float targetCharsPerLine;
if (width < 500)
{
targetCharsPerLine = 14f;
}
else if (width > 1000)
{
targetCharsPerLine = 30f;
}
else
{
// 平滑过渡
float t = (float)(width - 500) / 500f;
targetCharsPerLine = 14f + 16f * t;
}
float sizeByWidth = usableWidth / targetCharsPerLine;
float sizeByHeight = (float)height / TargetMinVisibleLines;
float targetSize = Math.Min(sizeByWidth, sizeByHeight);
// 窄屏时底线设高一点 (16px),宽屏如果高度不够可能允许更小
float currentMinLimit = (width < 400) ? 16f : BaseMinFontSize;
return Math.Clamp(targetSize, currentMinLimit, BaseMaxFontSize);
}
private static float ApplyRatio(float baseSize, float ratio)
{
return Math.Max(baseSize * ratio, AbsoluteMinReadableSize);
}
}
}

View File

@@ -27,6 +27,7 @@ namespace BetterLyrics.WinUI3.Helper
public T StartValue => _startValue;
public T TargetValue => _targetValue;
public EasingType? EasingType => _easingType;
public double Progress => _progress;
public ValueTransition(T initialValue, double durationSeconds, Func<T, T, double, T>? interpolator = null, EasingType? easingType = null, double delaySeconds = 0)
{

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Xaml;
@@ -41,13 +42,16 @@ namespace BetterLyrics.WinUI3.Logic
if (style.IsDynamicLyricsFontSize)
{
originalFontSize = (int)Math.Clamp(Math.Min(canvasHeight, canvasWidth) / 15, 18, 96);
translatedFontSize = phoneticFontSize = (int)(originalFontSize * 2.0 / 3.0);
var lyricsLayoutMetrics = LyricsLayoutHelper.CalculateLayout(canvasWidth, canvasHeight);
phoneticFontSize = (int)lyricsLayoutMetrics.TransliterationSize;
originalFontSize = (int)lyricsLayoutMetrics.MainLyricsSize;
translatedFontSize = (int)lyricsLayoutMetrics.TranslationSize;
}
else
{
originalFontSize = style.OriginalLyricsFontSize;
phoneticFontSize = style.PhoneticLyricsFontSize;
originalFontSize = style.OriginalLyricsFontSize;
translatedFontSize = style.TranslatedLyricsFontSize;
}

View File

@@ -0,0 +1,17 @@
using Microsoft.UI.Xaml;
namespace BetterLyrics.WinUI3.Models
{
public struct LyricsLayoutMetrics
{
public float MainLyricsSize;
public float TranslationSize;
public float TransliterationSize;
public float SongTitleSize;
public float ArtistNameSize;
public float AlbumNameSize;
public Thickness AlbumArtPadding;
}
}

View File

@@ -229,14 +229,19 @@ namespace BetterLyrics.WinUI3.Renderer
if (settings.IsLyricsFloatAnimationEnabled)
{
double targetFloatOffset = sourceCharRect.Height * 0.05;
double targetFloatOffset = sourceCharRect.Height * 0.1;
if (charIndex < curCharIndexInt) floatOffset = 0;
else if (charIndex == curCharIndexInt)
{
var p = exactProgressIndex - curCharIndexInt;
floatOffset = -targetFloatOffset + p * targetFloatOffset;
}
else floatOffset = -targetFloatOffset;
else
{
floatOffset = -targetFloatOffset;
}
// 制造句间上浮过度动画,这里用任何一个 Transition 都行,主要是获取当前行的进入视野的 Progress
floatOffset *= line.YOffsetTransition.Progress;
}
var parentSyllable = line.LyricsSyllables.FirstOrDefault(x => x.StartIndex <= charIndex && charIndex < x.StartIndex + x.Text.Length);

View File

@@ -31,9 +31,9 @@
<Grid x:Name="TrackSummaryGridContainer" Loaded="TrackSummaryGridContainer_Loaded">
<Grid.RowDefinitions>
<RowDefinition x:Name="TrackSummaryRowDef" Height="*" />
<RowDefinition x:Name="TrackSummaryRowDef" Height="0" />
<RowDefinition x:Name="MiddleGapRowDef" Height="0" />
<RowDefinition x:Name="LyricsRowDef" Height="2*" />
<RowDefinition x:Name="LyricsRowDef" Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftGapDef" Width="0" />
@@ -184,11 +184,14 @@
</Grid>
<Grid
x:Name="LyricsPlaceholder"
Grid.Row="2"
Grid.Column="3"
SizeChanged="LyricsPlaceholder_SizeChanged" />
<Grid x:Name="LyricsPlaceholder" SizeChanged="LyricsPlaceholder_SizeChanged">
<ScrollViewer
x:Name="LyricsScrollViewer"
PointerWheelChanged="LyricsScrollViewer_PointerWheelChanged"
Visibility="Collapsed">
<Grid />
</ScrollViewer>
</Grid>
</Grid>

View File

@@ -12,7 +12,8 @@ using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using DevWinUI;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;
@@ -21,7 +22,6 @@ using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Numerics;
using System.Threading.Tasks;
using System.Windows.Media.Media3D;
namespace BetterLyrics.WinUI3.Views
{
@@ -39,6 +39,8 @@ namespace BetterLyrics.WinUI3.Views
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
private readonly DispatcherQueueTimer _timer = App.Current.Resources.DispatcherQueue.CreateTimer();
public CornerRadius AlbumArtCornerRadius
{
get { return (CornerRadius)GetValue(AlbumArtCornerRadiusProperty); }
@@ -48,16 +50,6 @@ namespace BetterLyrics.WinUI3.Views
public static readonly DependencyProperty AlbumArtCornerRadiusProperty =
DependencyProperty.Register(nameof(AlbumArtCornerRadius), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(new CornerRadius(0)));
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 LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
public LyricsPage()
@@ -85,25 +77,7 @@ namespace BetterLyrics.WinUI3.Views
// ==== SongInfo
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 RenderTextBlock(TextBlock? sender, string? text, int fontSize)
private void RenderTextBlock(TextBlock? sender, string? text, double fontSize)
{
if (sender == null || string.IsNullOrEmpty(text) || fontSize == 0) return;
@@ -115,28 +89,23 @@ namespace BetterLyrics.WinUI3.Views
var fontFamilyName = LanguageHelper.IsCJK(ch) ? lyricsStyleSettings.LyricsCJKFontFamily : lyricsStyleSettings.LyricsWesternFontFamily;
sender.Inlines.Add(new Run { Text = $"{ch}", FontFamily = new FontFamily(fontFamilyName) });
}
sender.FontSize = fontSize;
sender.FontSize = (int)fontSize;
sender.Foreground = new SolidColorBrush(_mediaSessionsService.AlbumArtThemeColors.BgFontColor);
}
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());
var lyricsLayoutMetrics = LyricsLayoutHelper.CalculateLayout(RootGrid.ActualWidth, RootGrid.ActualHeight);
var albumArtLayoutSettings = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings;
var titleFontSize = albumArtLayoutSettings.IsAutoSongInfoFontSize ? lyricsLayoutMetrics.SongTitleSize : albumArtLayoutSettings.SongInfoFontSize;
var artistsFontSize = albumArtLayoutSettings.IsAutoSongInfoFontSize ? lyricsLayoutMetrics.ArtistNameSize : albumArtLayoutSettings.SongInfoFontSize * 0.8;
var albumFontSize = albumArtLayoutSettings.IsAutoSongInfoFontSize ? lyricsLayoutMetrics.AlbumNameSize : albumArtLayoutSettings.SongInfoFontSize * 0.8;
RenderTextBlock(TitleTextBlock, _mediaSessionsService.CurrentSongInfo?.Title, titleFontSize);
RenderTextBlock(ArtistsTextBlock, _mediaSessionsService.CurrentSongInfo?.DisplayArtists, artistsFontSize);
RenderTextBlock(AlbumTextBlock, _mediaSessionsService.CurrentSongInfo?.Album, albumFontSize);
}
private void UpdateSongInfoOpacity()
@@ -246,6 +215,43 @@ namespace BetterLyrics.WinUI3.Views
}
}
private void UpdateLyricsPlaceholderSpan()
{
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
switch (status.LyricsDisplayType)
{
case LyricsDisplayType.AlbumArtOnly:
break;
case LyricsDisplayType.LyricsOnly:
Grid.SetRow(LyricsPlaceholder, 0);
Grid.SetRowSpan(LyricsPlaceholder, 3);
Grid.SetColumn(LyricsPlaceholder, 1);
Grid.SetColumnSpan(LyricsPlaceholder, 3);
break;
case LyricsDisplayType.SplitView:
switch (status.LyricsLayoutOrientation)
{
case LyricsLayoutOrientation.Horizontal:
Grid.SetRow(LyricsPlaceholder, 0);
Grid.SetRowSpan(LyricsPlaceholder, 3);
Grid.SetColumn(LyricsPlaceholder, 3);
Grid.SetColumnSpan(LyricsPlaceholder, 1);
break;
case LyricsLayoutOrientation.Vertical:
Grid.SetRow(LyricsPlaceholder, 2);
Grid.SetRowSpan(LyricsPlaceholder, 1);
Grid.SetColumn(LyricsPlaceholder, 1);
Grid.SetColumnSpan(LyricsPlaceholder, 3);
break;
default:
break;
}
break;
default:
break;
}
}
// ====
private void UpdateAlbumArtGridSpan()
@@ -329,15 +335,19 @@ namespace BetterLyrics.WinUI3.Views
private void UpdateGap()
{
var lyricsLayoutMetrics = LyricsLayoutHelper.CalculateLayout(RootGrid.ActualWidth, RootGrid.ActualHeight);
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
double height = RootGrid.ActualHeight;
double width = RootGrid.ActualWidth;
double xMargin = 0;
double yMargin = 0;
double middleGapCol = 0;
double gapBetweenAlbumArtAndSongInfo = 0;
double trackSummaryRowHeight = 0;
double xMargin = 0;
double yMargin = 0;
if (height < 400)
{
@@ -354,18 +364,19 @@ namespace BetterLyrics.WinUI3.Views
}
else
{
gapBetweenAlbumArtAndSongInfo = GetTitleFontSize() / 2;
gapBetweenAlbumArtAndSongInfo = lyricsLayoutMetrics.SongTitleSize / 2;
}
switch (status.LyricsLayoutOrientation)
{
case LyricsLayoutOrientation.Horizontal:
xMargin = Math.Max(16, Math.Min(width, height) * 0.25);
yMargin = Math.Max(16, Math.Min(width, height) * 0.15);
xMargin = Math.Clamp(Math.Min(width, height) * 0.25, 16, 128);
yMargin = Math.Max(32, Math.Min(width, height) * 0.15);
break;
case LyricsLayoutOrientation.Vertical:
xMargin = Math.Max(16, Math.Min(width, height) * 0.15);
yMargin = Math.Max(16, Math.Min(width, height) * 0.10);
xMargin = Math.Max(16, Math.Min(width, height) * 0.05);
yMargin = Math.Max(16, Math.Min(width, height) * 0.05);
trackSummaryRowHeight = Math.Max(64, Math.Min(width, height) * 0.25);
break;
default:
break;
@@ -373,30 +384,36 @@ namespace BetterLyrics.WinUI3.Views
MiddleGapColDef.Width = new(middleGapCol);
TrackSummaryGridRow0.Height = TrackSummaryGridRow4.Height = new(yMargin);
TrackSummaryGridRow0.Height = new(yMargin);
TrackSummaryGridRow4.Height = new(yMargin);
LeftGapDef.Width = RightGapDef.Width = new(xMargin);
TrackSummaryRowDef.Height = new(trackSummaryRowHeight);
TrackSummaryGridCol1.Width = TrackSummaryGridRow2.Height = new(gapBetweenAlbumArtAndSongInfo);
}
private void OnLayoutChanged()
{
UpdateSongInfoOpacity();
_timer.Debounce(() =>
{
UpdateGap();
UpdateAlbumArtShadow();
UpdateAlbumArtOpacity();
UpdateSongInfoOpacity();
UpdateLyricsOpacity();
UpdateAlbumArtOpacity();
UpdateTrackSummaryGridSpan();
UpdateAlbumArtShadow();
UpdateAlbumArtGridSpan();
UpdateTrackSummaryGridSpan();
UpdateAlbumArtGridSpan();
UpdateSongInfoStackPanelSpan();
UpdateLyricsPlaceholderSpan();
UpdateSongInfoStackPanelSpan();
UpdateAlbumArtCornerRadius();
UpdateLyricsOpacity();
UpdateLyricsLayout();
UpdateAlbumArtCornerRadius();
UpdateGap();
UpdateLyricsLayout();
}, Constants.Time.DebounceTimeout);
}
// ====
@@ -763,5 +780,11 @@ namespace BetterLyrics.WinUI3.Views
}
}
private void LyricsScrollViewer_PointerWheelChanged(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
var pointerPoint = e.GetCurrentPoint(LyricsScrollViewer);
int mouseWheelDelta = pointerPoint.Properties.MouseWheelDelta;
NowPlayingCanvas.LyricsStartY += mouseWheelDelta;
}
}
}