mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
feat: enhance ValueTransition (support keyframes)
This commit is contained in:
@@ -282,12 +282,6 @@
|
|||||||
</dev:SettingsExpander.ItemsHeader>
|
</dev:SettingsExpander.ItemsHeader>
|
||||||
</dev:SettingsExpander>
|
</dev:SettingsExpander>
|
||||||
|
|
||||||
<dev:SettingsCard x:Uid="SettingsPageSettingsPlayHistory" Visibility="Collapsed">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
|
||||||
<Button x:Uid="SettingsPageExportPlayHistoryButton" Command="{x:Bind ViewModel.ExportPlayHistoryCommand}" />
|
|
||||||
</StackPanel>
|
|
||||||
</dev:SettingsCard>
|
|
||||||
|
|
||||||
<dev:SettingsCard x:Uid="SettingsPageFixedTimeStep" Visibility="Collapsed">
|
<dev:SettingsCard x:Uid="SettingsPageFixedTimeStep" Visibility="Collapsed">
|
||||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.AdvancedSettings.IsFixedTimeStep, Mode=TwoWay}" />
|
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.AdvancedSettings.IsFixedTimeStep, Mode=TwoWay}" />
|
||||||
</dev:SettingsCard>
|
</dev:SettingsCard>
|
||||||
|
|||||||
@@ -58,42 +58,42 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
|
|
||||||
private readonly ValueTransition<Color> _immersiveBgColorTransition = new(
|
private readonly ValueTransition<Color> _immersiveBgColorTransition = new(
|
||||||
initialValue: Colors.Transparent,
|
initialValue: Colors.Transparent,
|
||||||
durationSeconds: 0.3f,
|
defaultTotalDuration: 0.3f,
|
||||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||||
);
|
);
|
||||||
private readonly ValueTransition<double> _immersiveBgOpacityTransition = new(
|
private readonly ValueTransition<double> _immersiveBgOpacityTransition = new(
|
||||||
initialValue: 1f,
|
initialValue: 1f,
|
||||||
durationSeconds: 0.3f
|
defaultTotalDuration: 0.3f
|
||||||
);
|
);
|
||||||
private readonly ValueTransition<Color> _accentColor1Transition = new(
|
private readonly ValueTransition<Color> _accentColor1Transition = new(
|
||||||
initialValue: Colors.Transparent,
|
initialValue: Colors.Transparent,
|
||||||
durationSeconds: 0.3f,
|
defaultTotalDuration: 0.3f,
|
||||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||||
);
|
);
|
||||||
private readonly ValueTransition<Color> _accentColor2Transition = new(
|
private readonly ValueTransition<Color> _accentColor2Transition = new(
|
||||||
initialValue: Colors.Transparent,
|
initialValue: Colors.Transparent,
|
||||||
durationSeconds: 0.3f,
|
defaultTotalDuration: 0.3f,
|
||||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||||
);
|
);
|
||||||
private readonly ValueTransition<Color> _accentColor3Transition = new(
|
private readonly ValueTransition<Color> _accentColor3Transition = new(
|
||||||
initialValue: Colors.Transparent,
|
initialValue: Colors.Transparent,
|
||||||
durationSeconds: 0.3f,
|
defaultTotalDuration: 0.3f,
|
||||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||||
);
|
);
|
||||||
private readonly ValueTransition<Color> _accentColor4Transition = new(
|
private readonly ValueTransition<Color> _accentColor4Transition = new(
|
||||||
initialValue: Colors.Transparent,
|
initialValue: Colors.Transparent,
|
||||||
durationSeconds: 0.3f,
|
defaultTotalDuration: 0.3f,
|
||||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||||
);
|
);
|
||||||
private readonly ValueTransition<double> _canvasYScrollTransition = new(
|
private readonly ValueTransition<double> _canvasYScrollTransition = new(
|
||||||
initialValue: 0f,
|
initialValue: 0f,
|
||||||
durationSeconds: 0.3f,
|
defaultTotalDuration: 0.3f,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
private readonly ValueTransition<double> _mouseYScrollTransition = new(
|
private readonly ValueTransition<double> _mouseYScrollTransition = new(
|
||||||
initialValue: 0f,
|
initialValue: 0f,
|
||||||
durationSeconds: 0.3f,
|
defaultTotalDuration: 0.3f,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
|
|
||||||
private TimeSpan _songPositionWithOffset;
|
private TimeSpan _songPositionWithOffset;
|
||||||
@@ -292,7 +292,7 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
}
|
}
|
||||||
else if (e.Property == MouseScrollOffsetProperty)
|
else if (e.Property == MouseScrollOffsetProperty)
|
||||||
{
|
{
|
||||||
canvas._mouseYScrollTransition.StartTransition(Convert.ToDouble(e.NewValue));
|
canvas._mouseYScrollTransition.Start(Convert.ToDouble(e.NewValue));
|
||||||
}
|
}
|
||||||
else if (e.Property == MousePositionProperty)
|
else if (e.Property == MousePositionProperty)
|
||||||
{
|
{
|
||||||
@@ -318,11 +318,11 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
else if (e.Property == AlbumArtThemeColorsProperty)
|
else if (e.Property == AlbumArtThemeColorsProperty)
|
||||||
{
|
{
|
||||||
var albumArtThemeColors = (AlbumArtThemeColors)e.NewValue;
|
var albumArtThemeColors = (AlbumArtThemeColors)e.NewValue;
|
||||||
canvas._immersiveBgColorTransition.StartTransition(albumArtThemeColors.EnvColor);
|
canvas._immersiveBgColorTransition.Start(albumArtThemeColors.EnvColor);
|
||||||
canvas._accentColor1Transition.StartTransition(albumArtThemeColors.AccentColor1);
|
canvas._accentColor1Transition.Start(albumArtThemeColors.AccentColor1);
|
||||||
canvas._accentColor2Transition.StartTransition(albumArtThemeColors.AccentColor2);
|
canvas._accentColor2Transition.Start(albumArtThemeColors.AccentColor2);
|
||||||
canvas._accentColor3Transition.StartTransition(albumArtThemeColors.AccentColor3);
|
canvas._accentColor3Transition.Start(albumArtThemeColors.AccentColor3);
|
||||||
canvas._accentColor4Transition.StartTransition(albumArtThemeColors.AccentColor4);
|
canvas._accentColor4Transition.Start(albumArtThemeColors.AccentColor4);
|
||||||
|
|
||||||
canvas._albumArtThemeColors = albumArtThemeColors;
|
canvas._albumArtThemeColors = albumArtThemeColors;
|
||||||
canvas._isLayoutChanged = true;
|
canvas._isLayoutChanged = true;
|
||||||
@@ -344,7 +344,6 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
var lyricsEffect = _lyricsWindowStatus.LyricsEffectSettings;
|
var lyricsEffect = _lyricsWindowStatus.LyricsEffectSettings;
|
||||||
|
|
||||||
double songDuration = _gsmtcService.CurrentSongInfo.DurationMs;
|
double songDuration = _gsmtcService.CurrentSongInfo.DurationMs;
|
||||||
bool isForceWordByWord = _settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect;
|
|
||||||
|
|
||||||
Color overlayColor;
|
Color overlayColor;
|
||||||
double finalOpacity;
|
double finalOpacity;
|
||||||
@@ -410,7 +409,7 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
return _synchronizer.GetLinePlayingProgress(
|
return _synchronizer.GetLinePlayingProgress(
|
||||||
_songPositionWithOffset.TotalMilliseconds,
|
_songPositionWithOffset.TotalMilliseconds,
|
||||||
line,
|
line,
|
||||||
isForceWordByWord
|
lyricsEffect.WordByWordEffectMode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -486,9 +485,16 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
var targetScroll = LyricsLayoutManager.CalculateTargetScrollOffset(_renderLyricsLines, _primaryPlayingLineIndex);
|
var targetScroll = LyricsLayoutManager.CalculateTargetScrollOffset(_renderLyricsLines, _primaryPlayingLineIndex);
|
||||||
if (targetScroll.HasValue) _canvasTargetScrollOffset = targetScroll.Value;
|
if (targetScroll.HasValue) _canvasTargetScrollOffset = targetScroll.Value;
|
||||||
|
|
||||||
_canvasYScrollTransition.SetEasingType(lyricsEffect.LyricsScrollEasingType);
|
if (_isLayoutChanged)
|
||||||
_canvasYScrollTransition.SetDuration(lyricsEffect.LyricsScrollDuration / 1000.0);
|
{
|
||||||
_canvasYScrollTransition.StartTransition(_canvasTargetScrollOffset, _isLayoutChanged);
|
_canvasYScrollTransition.JumpTo(_canvasTargetScrollOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_canvasYScrollTransition.SetDurationMs(lyricsEffect.LyricsScrollDuration);
|
||||||
|
_canvasYScrollTransition.SetEasingType(lyricsEffect.LyricsScrollEasingType);
|
||||||
|
_canvasYScrollTransition.Start(_canvasTargetScrollOffset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_canvasYScrollTransition.Update(elapsedTime);
|
_canvasYScrollTransition.Update(elapsedTime);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,14 @@
|
|||||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||||
Text="Effect" />
|
Text="Effect" />
|
||||||
|
|
||||||
|
<dev:SettingsCard x:Uid="SettingsPageLyricsWordByWordEffectMode" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||||
|
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.WordByWordEffectMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||||
|
<ComboBoxItem x:Uid="SettingsPageLyricsWordByWordEffectModeAuto" />
|
||||||
|
<ComboBoxItem x:Uid="SettingsPageLyricsWordByWordEffectModeNever" />
|
||||||
|
<ComboBoxItem x:Uid="SettingsPageLyricsWordByWordEffectModeAlways" />
|
||||||
|
</ComboBox>
|
||||||
|
</dev:SettingsCard>
|
||||||
|
|
||||||
<!-- 模糊效果 -->
|
<!-- 模糊效果 -->
|
||||||
<dev:SettingsCard x:Uid="SettingsPageLyricsBlurEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
<dev:SettingsCard x:Uid="SettingsPageLyricsBlurEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||||
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsBlurEffectEnabled, Mode=TwoWay}" />
|
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsBlurEffectEnabled, Mode=TwoWay}" />
|
||||||
|
|||||||
@@ -365,10 +365,6 @@
|
|||||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ListenOnNewPlaybackSource, Mode=TwoWay}" />
|
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ListenOnNewPlaybackSource, Mode=TwoWay}" />
|
||||||
</dev:SettingsCard>
|
</dev:SettingsCard>
|
||||||
|
|
||||||
<dev:SettingsCard x:Uid="SettingsPageForceWordByWordEffect">
|
|
||||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IsForceWordByWordEffect, Mode=TwoWay}" />
|
|
||||||
</dev:SettingsCard>
|
|
||||||
|
|
||||||
<!-- Lyrics translation -->
|
<!-- Lyrics translation -->
|
||||||
<TextBlock x:Uid="SettingsPageTranslation" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
<TextBlock x:Uid="SettingsPageTranslation" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||||
<dev:SettingsExpander x:Uid="LyricsPageTranslationEnabled" IsExpanded="True">
|
<dev:SettingsExpander x:Uid="LyricsPageTranslationEnabled" IsExpanded="True">
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BetterLyrics.WinUI3.Enums
|
||||||
|
{
|
||||||
|
public enum WordByWordEffectMode
|
||||||
|
{
|
||||||
|
Auto,
|
||||||
|
Never,
|
||||||
|
Always,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,57 @@
|
|||||||
}
|
}
|
||||||
catch (Exception) { }
|
catch (Exception) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// https://learn.microsoft.com/zh-cn/dotnet/standard/io/how-to-copy-directories
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceDir"></param>
|
||||||
|
/// <param name="destinationDir"></param>
|
||||||
|
/// <param name="recursive"></param>
|
||||||
|
/// <exception cref="DirectoryNotFoundException"></exception>
|
||||||
|
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
|
||||||
|
{
|
||||||
|
// Get information about the source directory
|
||||||
|
var dir = new DirectoryInfo(sourceDir);
|
||||||
|
|
||||||
|
// Check if the source directory exists
|
||||||
|
if (!dir.Exists)
|
||||||
|
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||||
|
|
||||||
|
// Cache directories before we start copying
|
||||||
|
DirectoryInfo[] dirs = dir.GetDirectories();
|
||||||
|
|
||||||
|
// Create the destination directory
|
||||||
|
Directory.CreateDirectory(destinationDir);
|
||||||
|
|
||||||
|
// Get the files in the source directory and copy to the destination directory
|
||||||
|
foreach (FileInfo file in dir.GetFiles())
|
||||||
|
{
|
||||||
|
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
||||||
|
|
||||||
|
CopyLockedFile(file.FullName, targetFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If recursive and copying subdirectories, recursively call this method
|
||||||
|
if (recursive)
|
||||||
|
{
|
||||||
|
foreach (DirectoryInfo subDir in dirs)
|
||||||
|
{
|
||||||
|
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
||||||
|
CopyDirectory(subDir.FullName, newDestinationDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyLockedFile(string sourcePath, string targetPath)
|
||||||
|
{
|
||||||
|
using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
|
using (var destStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write))
|
||||||
|
{
|
||||||
|
sourceStream.CopyTo(destStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,19 +42,23 @@ namespace BetterLyrics.WinUI3.Helper
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<StorageFile?> PickSaveFileAsync<T>(IDictionary<string, IList<string>> fileTypeChoices)
|
public static async Task<StorageFile?> PickSaveFileAsync<T>(IDictionary<string, IList<string>> fileTypeChoices, string? suggestedFileName = null)
|
||||||
{
|
{
|
||||||
var window = WindowHook.GetWindow<T>();
|
var window = WindowHook.GetWindow<T>();
|
||||||
|
|
||||||
return await PickSaveFileAsync(window, fileTypeChoices);
|
return await PickSaveFileAsync(window, fileTypeChoices, suggestedFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<StorageFile?> PickSaveFileAsync<T>(T? window, IDictionary<string, IList<string>> fileTypeChoices)
|
public static async Task<StorageFile?> PickSaveFileAsync<T>(T? window, IDictionary<string, IList<string>> fileTypeChoices, string? suggestedFileName = null)
|
||||||
{
|
{
|
||||||
if (window == null) return null;
|
if (window == null) return null;
|
||||||
|
|
||||||
var picker = new Windows.Storage.Pickers.FileSavePicker();
|
var picker = new Windows.Storage.Pickers.FileSavePicker();
|
||||||
picker.FileTypeChoices.AddRange(fileTypeChoices);
|
picker.FileTypeChoices.AddRange(fileTypeChoices);
|
||||||
|
if (suggestedFileName != null)
|
||||||
|
{
|
||||||
|
picker.SuggestedFileName = suggestedFileName;
|
||||||
|
}
|
||||||
|
|
||||||
var hwnd = WindowNative.GetWindowHandle(window);
|
var hwnd = WindowNative.GetWindowHandle(window);
|
||||||
InitializeWithWindow.Initialize(picker, hwnd);
|
InitializeWithWindow.Initialize(picker, hwnd);
|
||||||
|
|||||||
@@ -1,163 +1,252 @@
|
|||||||
// 2025/6/23 by Zhe Fang
|
using BetterLyrics.WinUI3.Enums;
|
||||||
|
using BetterLyrics.WinUI3.Models;
|
||||||
using BetterLyrics.WinUI3.Enums;
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace BetterLyrics.WinUI3.Helper
|
namespace BetterLyrics.WinUI3.Helper
|
||||||
{
|
{
|
||||||
public class ValueTransition<T>
|
public class ValueTransition<T> where T : struct
|
||||||
where T : struct
|
|
||||||
{
|
{
|
||||||
|
// 状态变量
|
||||||
private T _currentValue;
|
private T _currentValue;
|
||||||
private double _durationSeconds;
|
|
||||||
private double _delaySeconds;
|
|
||||||
private double _delayRemaining;
|
|
||||||
private EasingType? _easingType;
|
|
||||||
private Func<T, T, double, T> _interpolator;
|
|
||||||
private bool _isTransitioning;
|
|
||||||
private double _progress;
|
|
||||||
private T _startValue;
|
private T _startValue;
|
||||||
private T _targetValue;
|
private T _targetValue;
|
||||||
|
|
||||||
public double DurationSeconds => _durationSeconds;
|
// 核心队列
|
||||||
public double DelaySeconds => _delaySeconds;
|
private readonly Queue<Keyframe<T>> _keyframeQueue = new Queue<Keyframe<T>>();
|
||||||
|
|
||||||
public bool IsTransitioning => _isTransitioning;
|
// 时间控制
|
||||||
|
private double _stepDuration; // 当前这一段的时长 (动态变化)
|
||||||
|
private double _totalDurationForAutoSplit; // 自动均分模式的总时长
|
||||||
|
private double _configuredDelaySeconds; // 配置的延迟时长
|
||||||
|
|
||||||
|
// 动画状态
|
||||||
|
private Enums.EasingType? _easingType;
|
||||||
|
private Func<T, T, double, T> _interpolator;
|
||||||
|
private bool _isTransitioning;
|
||||||
|
private double _progress; // 当前段的进度 (0.0 ~ 1.0)
|
||||||
|
|
||||||
|
// 公开属性
|
||||||
public T Value => _currentValue;
|
public T Value => _currentValue;
|
||||||
public T StartValue => _startValue;
|
public bool IsTransitioning => _isTransitioning;
|
||||||
public T TargetValue => _targetValue;
|
public T TargetValue => _targetValue; // 获取当前段的目标值
|
||||||
public EasingType? EasingType => _easingType;
|
public Enums.EasingType? EasingType => _easingType;
|
||||||
public double Progress => _progress;
|
public double DurationSeconds => _totalDurationForAutoSplit;
|
||||||
|
|
||||||
public ValueTransition(T initialValue, double durationSeconds, Func<T, T, double, T>? interpolator = null, EasingType? easingType = null, double delaySeconds = 0)
|
public ValueTransition(T initialValue, double defaultTotalDuration = 0.3, EasingType? defaultEasingType = null, Func<T, T, double, T>? interpolator = null)
|
||||||
{
|
{
|
||||||
_currentValue = initialValue;
|
_currentValue = initialValue;
|
||||||
_startValue = initialValue;
|
_startValue = initialValue;
|
||||||
_targetValue = initialValue;
|
_targetValue = initialValue;
|
||||||
_durationSeconds = durationSeconds;
|
_totalDurationForAutoSplit = defaultTotalDuration;
|
||||||
_delaySeconds = delaySeconds;
|
|
||||||
_delayRemaining = 0;
|
if (interpolator == null)
|
||||||
_progress = 1f;
|
{
|
||||||
_isTransitioning = false;
|
// 默认缓动
|
||||||
|
SetEasingType(Enums.EasingType.EaseInOutQuad);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_easingType = null;
|
||||||
|
_interpolator = interpolator;
|
||||||
|
}
|
||||||
|
|
||||||
if (interpolator != null)
|
if (interpolator != null)
|
||||||
{
|
{
|
||||||
_interpolator = interpolator;
|
_interpolator = interpolator;
|
||||||
_easingType = null;
|
_easingType = null;
|
||||||
}
|
}
|
||||||
else if (easingType.HasValue)
|
else if (defaultEasingType != null)
|
||||||
{
|
{
|
||||||
_easingType = easingType;
|
SetEasingType(defaultEasingType);
|
||||||
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_easingType = Enums.EasingType.EaseInOutQuad;
|
SetEasingType(Enums.EasingType.EaseInOutQuad);
|
||||||
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Configuration
|
||||||
|
|
||||||
public void SetDuration(double seconds)
|
public void SetDuration(double seconds)
|
||||||
{
|
{
|
||||||
if (seconds < 0)
|
if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds));
|
||||||
throw new ArgumentOutOfRangeException(nameof(seconds), "Duration must be positive.");
|
_totalDurationForAutoSplit = seconds;
|
||||||
_durationSeconds = seconds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDurationMs(double millionSeconds)
|
public void SetDurationMs(double millionSeconds) => SetDuration(millionSeconds / 1000.0);
|
||||||
{
|
|
||||||
SetDuration(millionSeconds / 1000.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetDuration(TimeSpan timeSpan)
|
|
||||||
{
|
|
||||||
SetDuration(timeSpan.TotalSeconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置启动延迟。
|
||||||
|
/// 原理:在动画队列最前方插入一个“数值不变”的关键帧。
|
||||||
|
/// </summary>
|
||||||
public void SetDelay(double seconds)
|
public void SetDelay(double seconds)
|
||||||
{
|
{
|
||||||
_delaySeconds = seconds;
|
_configuredDelaySeconds = seconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void JumpTo(T value)
|
public void SetEasingType(Enums.EasingType? easingType)
|
||||||
{
|
{
|
||||||
|
_easingType = easingType;
|
||||||
|
_interpolator = GetInterpolatorByEasingType(easingType);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Control Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 立即跳转到指定值(停止动画)
|
||||||
|
/// </summary>
|
||||||
|
public void JumpTo(T value)
|
||||||
|
{
|
||||||
|
_keyframeQueue.Clear();
|
||||||
_currentValue = value;
|
_currentValue = value;
|
||||||
_startValue = value;
|
_startValue = value;
|
||||||
_targetValue = value;
|
_targetValue = value;
|
||||||
_progress = 1f;
|
|
||||||
_delayRemaining = 0;
|
|
||||||
_isTransitioning = false;
|
_isTransitioning = false;
|
||||||
|
_progress = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset(T value)
|
/// <summary>
|
||||||
|
/// 模式 A: 精确控制模式
|
||||||
|
/// 显式指定每一段的目标值和时长。
|
||||||
|
/// </summary>
|
||||||
|
public void Start(params Keyframe<T>[] keyframes)
|
||||||
{
|
{
|
||||||
_currentValue = value;
|
if (keyframes == null || keyframes.Length == 0) return;
|
||||||
_startValue = value;
|
|
||||||
_targetValue = value;
|
|
||||||
_progress = 0f;
|
|
||||||
_delayRemaining = 0;
|
|
||||||
_isTransitioning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartTransition(T targetValue, bool jumpTo = false)
|
PrepareStart();
|
||||||
{
|
|
||||||
if (jumpTo)
|
// 1. 处理延迟 (插入静止帧)
|
||||||
|
if (_configuredDelaySeconds > 0)
|
||||||
{
|
{
|
||||||
JumpTo(targetValue);
|
_keyframeQueue.Enqueue(new Keyframe<T>(_currentValue, _configuredDelaySeconds));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetValue.Equals(_currentValue))
|
// 2. 入队用户帧
|
||||||
|
foreach (var kf in keyframes)
|
||||||
{
|
{
|
||||||
_startValue = _currentValue;
|
_keyframeQueue.Enqueue(kf);
|
||||||
_targetValue = targetValue;
|
|
||||||
_progress = 0f;
|
|
||||||
_delayRemaining = _delaySeconds;
|
|
||||||
_isTransitioning = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MoveToNextSegment(firstStart: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Equals(double x, double y, double tolerance)
|
/// <summary>
|
||||||
|
/// 模式 B: 自动均分模式 (兼容旧写法)
|
||||||
|
/// 指定一串目标值,系统根据 SetDuration 的总时长平均分配。
|
||||||
|
/// </summary>
|
||||||
|
public void Start(params T[] values)
|
||||||
{
|
{
|
||||||
var diff = Math.Abs(x - y);
|
if (values == null || values.Length == 0) return;
|
||||||
return diff <= tolerance || diff <= Math.Max(Math.Abs(x), Math.Abs(y)) * tolerance;
|
|
||||||
|
// 如果目标就是当前值且只有1帧,直接跳过以省性能
|
||||||
|
if (values.Length == 1 && values[0].Equals(_currentValue) && _configuredDelaySeconds <= 0) return;
|
||||||
|
|
||||||
|
PrepareStart();
|
||||||
|
|
||||||
|
// 1. 处理延迟
|
||||||
|
if (_configuredDelaySeconds > 0)
|
||||||
|
{
|
||||||
|
_keyframeQueue.Enqueue(new Keyframe<T>(_currentValue, _configuredDelaySeconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 计算均分时长
|
||||||
|
double autoStepDuration = _totalDurationForAutoSplit / values.Length;
|
||||||
|
|
||||||
|
// 3. 入队生成帧
|
||||||
|
foreach (var val in values)
|
||||||
|
{
|
||||||
|
_keyframeQueue.Enqueue(new Keyframe<T>(val, autoStepDuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveToNextSegment(firstStart: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Core Logic
|
||||||
|
|
||||||
|
private void PrepareStart()
|
||||||
|
{
|
||||||
|
_keyframeQueue.Clear();
|
||||||
|
_isTransitioning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveToNextSegment(bool firstStart = false)
|
||||||
|
{
|
||||||
|
if (_keyframeQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var kf = _keyframeQueue.Dequeue();
|
||||||
|
|
||||||
|
// 起点逻辑:如果是刚开始,起点是当前值;如果是中间切换,起点是上一段的终点
|
||||||
|
_startValue = firstStart ? _currentValue : _targetValue;
|
||||||
|
_targetValue = kf.Value;
|
||||||
|
_stepDuration = kf.Duration;
|
||||||
|
|
||||||
|
if (firstStart) _progress = 0f;
|
||||||
|
// 注意:非 firstStart 时不重置 _progress,保留溢出值以平滑过渡
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 队列耗尽,动画结束
|
||||||
|
_currentValue = _targetValue;
|
||||||
|
_isTransitioning = false;
|
||||||
|
_progress = 1f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(TimeSpan elapsedTime)
|
public void Update(TimeSpan elapsedTime)
|
||||||
{
|
{
|
||||||
if (!_isTransitioning) return;
|
if (!_isTransitioning) return;
|
||||||
|
|
||||||
if (_delayRemaining > 0)
|
double timeStep = elapsedTime.TotalSeconds;
|
||||||
{
|
|
||||||
double consume = Math.Min(_delayRemaining, elapsedTime.TotalSeconds);
|
|
||||||
_delayRemaining -= consume;
|
|
||||||
if (_delayRemaining > 0)
|
|
||||||
return;
|
|
||||||
elapsedTime = TimeSpan.FromSeconds(elapsedTime.TotalSeconds - consume);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_durationSeconds <= 0)
|
// 使用 while 处理单帧时间过长跨越多段的情况
|
||||||
|
while (timeStep > 0 && _isTransitioning)
|
||||||
{
|
{
|
||||||
_progress = 1f;
|
// 计算当前帧的步进比例
|
||||||
}
|
// 极小值保护,防止除以0
|
||||||
else
|
double progressDelta = (_stepDuration > 0.000001) ? (timeStep / _stepDuration) : 1.0;
|
||||||
{
|
|
||||||
_progress += elapsedTime.TotalSeconds / _durationSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_progress >= 1f)
|
if (_progress + progressDelta >= 1.0)
|
||||||
{
|
{
|
||||||
_progress = 1f;
|
// === 当前段结束 ===
|
||||||
_currentValue = _targetValue;
|
|
||||||
_isTransitioning = false;
|
// 1. 计算这一段实际消耗的时间
|
||||||
}
|
double timeConsumed = (1.0 - _progress) * _stepDuration;
|
||||||
else
|
|
||||||
{
|
// 2. 剩余时间留给下一段
|
||||||
_currentValue = _interpolator(_startValue, _targetValue, _progress);
|
timeStep -= timeConsumed;
|
||||||
|
|
||||||
|
// 3. 修正当前值到目标值
|
||||||
|
_progress = 1.0;
|
||||||
|
_currentValue = _targetValue;
|
||||||
|
|
||||||
|
// 4. 切换到下一段
|
||||||
|
MoveToNextSegment();
|
||||||
|
|
||||||
|
// 5. 如果还有下一段,进度归零
|
||||||
|
if (_isTransitioning) _progress = 0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// === 当前段进行中 ===
|
||||||
|
_progress += progressDelta;
|
||||||
|
timeStep = 0; // 时间耗尽
|
||||||
|
|
||||||
|
// 插值计算
|
||||||
|
_currentValue = _interpolator(_startValue, _targetValue, _progress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Func<T, T, double, T> GetInterpolatorByEasingType(EasingType? type)
|
#endregion
|
||||||
|
|
||||||
|
#region Interpolators
|
||||||
|
|
||||||
|
private Func<T, T, double, T> GetInterpolatorByEasingType(Enums.EasingType? type)
|
||||||
{
|
{
|
||||||
if (typeof(T) == typeof(double))
|
if (typeof(T) == typeof(double))
|
||||||
{
|
{
|
||||||
@@ -166,58 +255,32 @@ namespace BetterLyrics.WinUI3.Helper
|
|||||||
double s = (double)(object)start;
|
double s = (double)(object)start;
|
||||||
double e = (double)(object)end;
|
double e = (double)(object)end;
|
||||||
double t = progress;
|
double t = progress;
|
||||||
|
|
||||||
|
// 使用 EasingHelper (假设您的项目中已有此辅助类)
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case Enums.EasingType.EaseInOutSine:
|
case Enums.EasingType.EaseInOutSine: t = EasingHelper.EaseInOutSine(t); break;
|
||||||
t = EasingHelper.EaseInOutSine(t);
|
case Enums.EasingType.EaseInOutQuad: t = EasingHelper.EaseInOutQuad(t); break;
|
||||||
break;
|
case Enums.EasingType.EaseInOutCubic: t = EasingHelper.EaseInOutCubic(t); break;
|
||||||
case Enums.EasingType.EaseInOutQuad:
|
case Enums.EasingType.EaseInOutQuart: t = EasingHelper.EaseInOutQuart(t); break;
|
||||||
t = EasingHelper.EaseInOutQuad(t);
|
case Enums.EasingType.EaseInOutQuint: t = EasingHelper.EaseInOutQuint(t); break;
|
||||||
break;
|
case Enums.EasingType.EaseInOutExpo: t = EasingHelper.EaseInOutExpo(t); break;
|
||||||
case Enums.EasingType.EaseInOutCubic:
|
case Enums.EasingType.EaseInOutCirc: t = EasingHelper.EaseInOutCirc(t); break;
|
||||||
t = EasingHelper.EaseInOutCubic(t);
|
case Enums.EasingType.EaseInOutBack: t = EasingHelper.EaseInOutBack(t); break;
|
||||||
break;
|
case Enums.EasingType.EaseInOutElastic: t = EasingHelper.EaseInOutElastic(t); break;
|
||||||
case Enums.EasingType.EaseInOutQuart:
|
case Enums.EasingType.EaseInOutBounce: t = EasingHelper.EaseInOutBounce(t); break;
|
||||||
t = EasingHelper.EaseInOutQuart(t);
|
case Enums.EasingType.SmoothStep: t = EasingHelper.SmoothStep(t); break;
|
||||||
break;
|
case Enums.EasingType.Linear: t = EasingHelper.Linear(t); break;
|
||||||
case Enums.EasingType.EaseInOutQuint:
|
default: t = EasingHelper.EaseInOutQuad(t); break;
|
||||||
t = EasingHelper.EaseInOutQuint(t);
|
|
||||||
break;
|
|
||||||
case Enums.EasingType.EaseInOutExpo:
|
|
||||||
t = EasingHelper.EaseInOutExpo(t);
|
|
||||||
break;
|
|
||||||
case Enums.EasingType.EaseInOutCirc:
|
|
||||||
t = EasingHelper.EaseInOutCirc(t);
|
|
||||||
break;
|
|
||||||
case Enums.EasingType.EaseInOutBack:
|
|
||||||
t = EasingHelper.EaseInOutBack(t);
|
|
||||||
break;
|
|
||||||
case Enums.EasingType.EaseInOutElastic:
|
|
||||||
t = EasingHelper.EaseInOutElastic(t);
|
|
||||||
break;
|
|
||||||
case Enums.EasingType.EaseInOutBounce:
|
|
||||||
t = EasingHelper.EaseInOutBounce(t);
|
|
||||||
break;
|
|
||||||
case Enums.EasingType.SmoothStep:
|
|
||||||
t = EasingHelper.SmoothStep(t);
|
|
||||||
break;
|
|
||||||
case Enums.EasingType.Linear:
|
|
||||||
t = EasingHelper.Linear(t);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
t = EasingHelper.EaseInOutQuad(t);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (T)(object)(s + (e - s) * t);
|
return (T)(object)(s + (e - s) * t);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new NotSupportedException($"Easing type {type} is not supported for type {typeof(T)}.");
|
|
||||||
|
throw new NotSupportedException($"Type {typeof(T)} is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetEasingType(EasingType? easingType)
|
#endregion
|
||||||
{
|
|
||||||
_easingType = easingType;
|
|
||||||
_interpolator = GetInterpolatorByEasingType(easingType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,13 +95,13 @@ namespace BetterLyrics.WinUI3.Logic
|
|||||||
|
|
||||||
line.BlurAmountTransition.SetDuration(yScrollDuration);
|
line.BlurAmountTransition.SetDuration(yScrollDuration);
|
||||||
line.BlurAmountTransition.SetDelay(yScrollDelay);
|
line.BlurAmountTransition.SetDelay(yScrollDelay);
|
||||||
line.BlurAmountTransition.StartTransition(
|
line.BlurAmountTransition.Start(
|
||||||
(isMouseScrolling || isSecondaryLinePlaying) ? 0 :
|
(isMouseScrolling || isSecondaryLinePlaying) ? 0 :
|
||||||
(lyricsEffect.IsLyricsBlurEffectEnabled ? (5 * distanceFactor) : 0));
|
(lyricsEffect.IsLyricsBlurEffectEnabled ? (5 * distanceFactor) : 0));
|
||||||
|
|
||||||
line.ScaleTransition.SetDuration(yScrollDuration);
|
line.ScaleTransition.SetDuration(yScrollDuration);
|
||||||
line.ScaleTransition.SetDelay(yScrollDelay);
|
line.ScaleTransition.SetDelay(yScrollDelay);
|
||||||
line.ScaleTransition.StartTransition(
|
line.ScaleTransition.Start(
|
||||||
isSecondaryLinePlaying ? _highlightedScale :
|
isSecondaryLinePlaying ? _highlightedScale :
|
||||||
(lyricsEffect.IsLyricsOutOfSightEffectEnabled ?
|
(lyricsEffect.IsLyricsOutOfSightEffectEnabled ?
|
||||||
(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale)) :
|
(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale)) :
|
||||||
@@ -109,37 +109,37 @@ namespace BetterLyrics.WinUI3.Logic
|
|||||||
|
|
||||||
line.PhoneticOpacityTransition.SetDuration(yScrollDuration);
|
line.PhoneticOpacityTransition.SetDuration(yScrollDuration);
|
||||||
line.PhoneticOpacityTransition.SetDelay(yScrollDelay);
|
line.PhoneticOpacityTransition.SetDelay(yScrollDelay);
|
||||||
line.PhoneticOpacityTransition.StartTransition(
|
line.PhoneticOpacityTransition.Start(
|
||||||
isSecondaryLinePlaying ? phoneticOpacity :
|
isSecondaryLinePlaying ? phoneticOpacity :
|
||||||
CalculateTargetOpacity(phoneticOpacity, phoneticOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
CalculateTargetOpacity(phoneticOpacity, phoneticOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||||
|
|
||||||
// 原文不透明度(已播放)
|
// 原文不透明度(已播放)
|
||||||
line.PlayedOriginalOpacityTransition.SetDuration(yScrollDuration);
|
line.PlayedOriginalOpacityTransition.SetDuration(yScrollDuration);
|
||||||
line.PlayedOriginalOpacityTransition.SetDelay(yScrollDelay);
|
line.PlayedOriginalOpacityTransition.SetDelay(yScrollDelay);
|
||||||
line.PlayedOriginalOpacityTransition.StartTransition(
|
line.PlayedOriginalOpacityTransition.Start(
|
||||||
isSecondaryLinePlaying ? 1.0 :
|
isSecondaryLinePlaying ? 1.0 :
|
||||||
CalculateTargetOpacity(originalOpacity, 1.0, distanceFactor, isMouseScrolling, lyricsEffect));
|
CalculateTargetOpacity(originalOpacity, 1.0, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||||
// 原文不透明度(未播放)
|
// 原文不透明度(未播放)
|
||||||
line.UnplayedOriginalOpacityTransition.SetDuration(yScrollDuration);
|
line.UnplayedOriginalOpacityTransition.SetDuration(yScrollDuration);
|
||||||
line.UnplayedOriginalOpacityTransition.SetDelay(yScrollDelay);
|
line.UnplayedOriginalOpacityTransition.SetDelay(yScrollDelay);
|
||||||
line.UnplayedOriginalOpacityTransition.StartTransition(
|
line.UnplayedOriginalOpacityTransition.Start(
|
||||||
isSecondaryLinePlaying ? originalOpacity :
|
isSecondaryLinePlaying ? originalOpacity :
|
||||||
CalculateTargetOpacity(originalOpacity, originalOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
CalculateTargetOpacity(originalOpacity, originalOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||||
|
|
||||||
line.TranslatedOpacityTransition.SetDuration(yScrollDuration);
|
line.TranslatedOpacityTransition.SetDuration(yScrollDuration);
|
||||||
line.TranslatedOpacityTransition.SetDelay(yScrollDelay);
|
line.TranslatedOpacityTransition.SetDelay(yScrollDelay);
|
||||||
line.TranslatedOpacityTransition.StartTransition(
|
line.TranslatedOpacityTransition.Start(
|
||||||
isSecondaryLinePlaying ? translatedOpacity :
|
isSecondaryLinePlaying ? translatedOpacity :
|
||||||
CalculateTargetOpacity(translatedOpacity, translatedOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
CalculateTargetOpacity(translatedOpacity, translatedOpacity, distanceFactor, isMouseScrolling, lyricsEffect));
|
||||||
|
|
||||||
line.ColorTransition.SetDuration(yScrollDuration);
|
line.ColorTransition.SetDuration(yScrollDuration);
|
||||||
line.ColorTransition.SetDelay(yScrollDelay);
|
line.ColorTransition.SetDelay(yScrollDelay);
|
||||||
line.ColorTransition.StartTransition(isSecondaryLinePlaying ? fgColor : bgColor);
|
line.ColorTransition.Start(isSecondaryLinePlaying ? fgColor : bgColor);
|
||||||
|
|
||||||
line.AngleTransition.SetEasingType(canvasYScrollTransition.EasingType);
|
line.AngleTransition.SetEasingType(canvasYScrollTransition.EasingType);
|
||||||
line.AngleTransition.SetDuration(yScrollDuration);
|
line.AngleTransition.SetDuration(yScrollDuration);
|
||||||
line.AngleTransition.SetDelay(yScrollDelay);
|
line.AngleTransition.SetDelay(yScrollDelay);
|
||||||
line.AngleTransition.StartTransition(
|
line.AngleTransition.Start(
|
||||||
(lyricsEffect.IsFanLyricsEnabled && !isMouseScrolling) ?
|
(lyricsEffect.IsFanLyricsEnabled && !isMouseScrolling) ?
|
||||||
Math.PI * (lyricsEffect.FanLyricsAngle / 180.0) * distanceFactor * (i > primaryPlayingLineIndex ? 1 : -1) :
|
Math.PI * (lyricsEffect.FanLyricsAngle / 180.0) * distanceFactor * (i > primaryPlayingLineIndex ? 1 : -1) :
|
||||||
0);
|
0);
|
||||||
@@ -149,7 +149,7 @@ namespace BetterLyrics.WinUI3.Logic
|
|||||||
line.YOffsetTransition.SetDelay(yScrollDelay);
|
line.YOffsetTransition.SetDelay(yScrollDelay);
|
||||||
// 设计之初是当 isLayoutChanged 为真时 jumpTo
|
// 设计之初是当 isLayoutChanged 为真时 jumpTo
|
||||||
// 但考虑到动画视觉,强制使用动画
|
// 但考虑到动画视觉,强制使用动画
|
||||||
line.YOffsetTransition.StartTransition(targetYScrollOffset);
|
line.YOffsetTransition.Start(targetYScrollOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxAnimationDurationMs = Math.Max(line.EndMs - currentPositionMs, 0);
|
var maxAnimationDurationMs = Math.Max(line.EndMs - currentPositionMs, 0);
|
||||||
@@ -170,10 +170,15 @@ namespace BetterLyrics.WinUI3.Logic
|
|||||||
switch (lyricsEffect.LyricsGlowEffectScope)
|
switch (lyricsEffect.LyricsGlowEffectScope)
|
||||||
{
|
{
|
||||||
case Enums.LyricsEffectScope.LineStartToCurrentChar:
|
case Enums.LyricsEffectScope.LineStartToCurrentChar:
|
||||||
if (isSecondaryLinePlayingChanged)
|
if (isSecondaryLinePlayingChanged && isSecondaryLinePlaying)
|
||||||
{
|
{
|
||||||
renderChar.GlowTransition.SetDurationMs(Math.Min(Time.AnimationDuration.TotalMilliseconds, maxAnimationDurationMs));
|
var stepInOutDuration = Math.Min(Time.AnimationDuration.TotalMilliseconds, maxAnimationDurationMs) / 2.0 / 1000.0;
|
||||||
renderChar.GlowTransition.StartTransition(isSecondaryLinePlaying ? targetGlow : 0);
|
var stepLastingDuration = Math.Max(maxAnimationDurationMs / 1000.0 - stepInOutDuration * 2, 0);
|
||||||
|
renderChar.GlowTransition.Start(
|
||||||
|
new Models.Keyframe<double>(targetGlow, stepInOutDuration),
|
||||||
|
new Models.Keyframe<double>(targetGlow, stepLastingDuration),
|
||||||
|
new Models.Keyframe<double>(0, stepInOutDuration)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -188,12 +193,12 @@ namespace BetterLyrics.WinUI3.Logic
|
|||||||
|
|
||||||
if (isSecondaryLinePlayingChanged)
|
if (isSecondaryLinePlayingChanged)
|
||||||
{
|
{
|
||||||
renderChar.FloatTransition.StartTransition(isSecondaryLinePlaying ? targetFloat : 0);
|
renderChar.FloatTransition.Start(isSecondaryLinePlaying ? targetFloat : 0);
|
||||||
}
|
}
|
||||||
if (isCharPlayingChanged)
|
if (isCharPlayingChanged)
|
||||||
{
|
{
|
||||||
renderChar.FloatTransition.SetDurationMs(Math.Min(lyricsEffect.LyricsFloatAnimationDuration, maxAnimationDurationMs));
|
renderChar.FloatTransition.SetDurationMs(Math.Min(lyricsEffect.LyricsFloatAnimationDuration, maxAnimationDurationMs));
|
||||||
renderChar.FloatTransition.StartTransition(0);
|
renderChar.FloatTransition.Start(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,8 +228,14 @@ namespace BetterLyrics.WinUI3.Logic
|
|||||||
{
|
{
|
||||||
if (syllable.DurationMs >= lyricsEffect.LyricsScaleEffectLongSyllableDuration)
|
if (syllable.DurationMs >= lyricsEffect.LyricsScaleEffectLongSyllableDuration)
|
||||||
{
|
{
|
||||||
renderChar.ScaleTransition.SetDurationMs(Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0);
|
if (isSyllablePlaying)
|
||||||
renderChar.ScaleTransition.StartTransition(isSyllablePlaying ? targetScale : 1);
|
{
|
||||||
|
var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0;
|
||||||
|
renderChar.ScaleTransition.Start(
|
||||||
|
new Models.Keyframe<double>(targetScale, stepDuration),
|
||||||
|
new Models.Keyframe<double>(1.0, stepDuration)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,8 +250,14 @@ namespace BetterLyrics.WinUI3.Logic
|
|||||||
{
|
{
|
||||||
foreach (var renderChar in syllable.ChildrenRenderLyricsChars)
|
foreach (var renderChar in syllable.ChildrenRenderLyricsChars)
|
||||||
{
|
{
|
||||||
renderChar.GlowTransition.SetDurationMs(Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0);
|
if (isSyllablePlaying)
|
||||||
renderChar.GlowTransition.StartTransition(isSyllablePlaying ? targetGlow : 0);
|
{
|
||||||
|
var stepDuration = Math.Min(syllable.DurationMs, maxAnimationDurationMs) / 2.0 / 1000.0;
|
||||||
|
renderChar.GlowTransition.Start(
|
||||||
|
new Models.Keyframe<double>(targetGlow, stepDuration),
|
||||||
|
new Models.Keyframe<double>(0, stepDuration)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -256,20 +273,10 @@ namespace BetterLyrics.WinUI3.Logic
|
|||||||
// 更新动画
|
// 更新动画
|
||||||
foreach (var renderChar in line.PrimaryRenderChars)
|
foreach (var renderChar in line.PrimaryRenderChars)
|
||||||
{
|
{
|
||||||
renderChar.ScaleTransition.Update(elapsedTime);
|
renderChar.Update(elapsedTime);
|
||||||
renderChar.GlowTransition.Update(elapsedTime);
|
|
||||||
renderChar.FloatTransition.Update(elapsedTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
line.AngleTransition.Update(elapsedTime);
|
line.Update(elapsedTime);
|
||||||
line.ScaleTransition.Update(elapsedTime);
|
|
||||||
line.BlurAmountTransition.Update(elapsedTime);
|
|
||||||
line.PhoneticOpacityTransition.Update(elapsedTime);
|
|
||||||
line.PlayedOriginalOpacityTransition.Update(elapsedTime);
|
|
||||||
line.UnplayedOriginalOpacityTransition.Update(elapsedTime);
|
|
||||||
line.TranslatedOpacityTransition.Update(elapsedTime);
|
|
||||||
line.YOffsetTransition.Update(elapsedTime);
|
|
||||||
line.ColorTransition.Update(elapsedTime);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using BetterLyrics.WinUI3.Models;
|
using BetterLyrics.WinUI3.Enums;
|
||||||
|
using BetterLyrics.WinUI3.Models;
|
||||||
using BetterLyrics.WinUI3.Models.Lyrics;
|
using BetterLyrics.WinUI3.Models.Lyrics;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -67,7 +68,7 @@ namespace BetterLyrics.WinUI3.Logic
|
|||||||
public LinePlaybackState GetLinePlayingProgress(
|
public LinePlaybackState GetLinePlayingProgress(
|
||||||
double currentTimeMs,
|
double currentTimeMs,
|
||||||
RenderLyricsLine line,
|
RenderLyricsLine line,
|
||||||
bool isForceWordByWord)
|
WordByWordEffectMode wordByWordEffectMode)
|
||||||
{
|
{
|
||||||
var state = new LinePlaybackState { SyllableStartIndex = 0, SyllableLength = 0, SyllableProgress = 0 };
|
var state = new LinePlaybackState { SyllableStartIndex = 0, SyllableLength = 0, SyllableProgress = 0 };
|
||||||
|
|
||||||
@@ -87,23 +88,34 @@ namespace BetterLyrics.WinUI3.Logic
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 逐字
|
switch (wordByWordEffectMode)
|
||||||
if (line.PrimaryRenderSyllables != null && line.PrimaryRenderSyllables.Count > 1)
|
|
||||||
{
|
{
|
||||||
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
|
case WordByWordEffectMode.Auto:
|
||||||
}
|
if (line.PrimaryRenderSyllables.Count > 1)
|
||||||
|
{
|
||||||
// 强制逐字
|
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
|
||||||
if (isForceWordByWord && line.PrimaryText.Length > 0)
|
}
|
||||||
{
|
else
|
||||||
return CalculateSimulatedProgress(currentTimeMs, line, lineEndMs);
|
{
|
||||||
}
|
state.SyllableStartIndex = line.PrimaryText.Length;
|
||||||
else
|
state.SyllableProgress = 1f;
|
||||||
{
|
return state;
|
||||||
// 普通行
|
}
|
||||||
state.SyllableStartIndex = line.PrimaryText.Length;
|
case WordByWordEffectMode.Never:
|
||||||
state.SyllableProgress = 1f;
|
state.SyllableStartIndex = line.PrimaryText.Length;
|
||||||
return state;
|
state.SyllableProgress = 1f;
|
||||||
|
return state;
|
||||||
|
case WordByWordEffectMode.Always:
|
||||||
|
if (line.PrimaryRenderSyllables.Count > 1)
|
||||||
|
{
|
||||||
|
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return CalculateSimulatedProgress(currentTimeMs, line, lineEndMs);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Keyframe.cs
Normal file
18
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Keyframe.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BetterLyrics.WinUI3.Models
|
||||||
|
{
|
||||||
|
public struct Keyframe<T>
|
||||||
|
{
|
||||||
|
public T Value { get; }
|
||||||
|
public double Duration { get; }
|
||||||
|
|
||||||
|
public Keyframe(T value, double durationSeconds)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Duration = durationSeconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using BetterLyrics.WinUI3.Constants;
|
using BetterLyrics.WinUI3.Constants;
|
||||||
using BetterLyrics.WinUI3.Enums;
|
using BetterLyrics.WinUI3.Enums;
|
||||||
using BetterLyrics.WinUI3.Helper;
|
using BetterLyrics.WinUI3.Helper;
|
||||||
|
using System;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
|
|
||||||
namespace BetterLyrics.WinUI3.Models.Lyrics
|
namespace BetterLyrics.WinUI3.Models.Lyrics
|
||||||
@@ -19,20 +20,28 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
|
|||||||
{
|
{
|
||||||
ScaleTransition = new(
|
ScaleTransition = new(
|
||||||
initialValue: 1.0,
|
initialValue: 1.0,
|
||||||
durationSeconds: Time.AnimationDuration.TotalSeconds,
|
defaultTotalDuration: Time.AnimationDuration.TotalSeconds,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
GlowTransition = new(
|
GlowTransition = new(
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
durationSeconds: Time.AnimationDuration.TotalSeconds,
|
defaultTotalDuration: Time.AnimationDuration.TotalSeconds,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
FloatTransition = new(
|
FloatTransition = new(
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
durationSeconds: Time.LongAnimationDuration.TotalSeconds,
|
defaultTotalDuration: Time.LongAnimationDuration.TotalSeconds,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
LayoutRect = layoutRect;
|
LayoutRect = layoutRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Update(TimeSpan elapsedTime)
|
||||||
|
{
|
||||||
|
ScaleTransition.Update(elapsedTime);
|
||||||
|
GlowTransition.Update(elapsedTime);
|
||||||
|
FloatTransition.Update(elapsedTime);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Microsoft.Graphics.Canvas.Geometry;
|
|||||||
using Microsoft.Graphics.Canvas.Text;
|
using Microsoft.Graphics.Canvas.Text;
|
||||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||||
using Microsoft.UI;
|
using Microsoft.UI;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
@@ -77,47 +78,47 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
|
|||||||
{
|
{
|
||||||
AngleTransition = new(
|
AngleTransition = new(
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
durationSeconds: AnimationDuration,
|
defaultTotalDuration: AnimationDuration,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
BlurAmountTransition = new(
|
BlurAmountTransition = new(
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
durationSeconds: AnimationDuration,
|
defaultTotalDuration: AnimationDuration,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
PhoneticOpacityTransition = new(
|
PhoneticOpacityTransition = new(
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
durationSeconds: AnimationDuration,
|
defaultTotalDuration: AnimationDuration,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
PlayedOriginalOpacityTransition = new(
|
PlayedOriginalOpacityTransition = new(
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
durationSeconds: AnimationDuration,
|
defaultTotalDuration: AnimationDuration,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
UnplayedOriginalOpacityTransition = new(
|
UnplayedOriginalOpacityTransition = new(
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
durationSeconds: AnimationDuration,
|
defaultTotalDuration: AnimationDuration,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
TranslatedOpacityTransition = new(
|
TranslatedOpacityTransition = new(
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
durationSeconds: AnimationDuration,
|
defaultTotalDuration: AnimationDuration,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
ScaleTransition = new(
|
ScaleTransition = new(
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
durationSeconds: AnimationDuration,
|
defaultTotalDuration: AnimationDuration,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
YOffsetTransition = new(
|
YOffsetTransition = new(
|
||||||
initialValue: 0,
|
initialValue: 0,
|
||||||
durationSeconds: AnimationDuration,
|
defaultTotalDuration: AnimationDuration,
|
||||||
easingType: EasingType.EaseInOutSine
|
defaultEasingType: EasingType.EaseInOutSine
|
||||||
);
|
);
|
||||||
ColorTransition = new(
|
ColorTransition = new(
|
||||||
initialValue: Colors.Transparent,
|
initialValue: Colors.Transparent,
|
||||||
durationSeconds: 0.3f,
|
defaultTotalDuration: 0.3f,
|
||||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -282,5 +283,18 @@ namespace BetterLyrics.WinUI3.Models.Lyrics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Update(TimeSpan elapsedTime)
|
||||||
|
{
|
||||||
|
AngleTransition.Update(elapsedTime);
|
||||||
|
ScaleTransition.Update(elapsedTime);
|
||||||
|
BlurAmountTransition.Update(elapsedTime);
|
||||||
|
PhoneticOpacityTransition.Update(elapsedTime);
|
||||||
|
PlayedOriginalOpacityTransition.Update(elapsedTime);
|
||||||
|
UnplayedOriginalOpacityTransition.Update(elapsedTime);
|
||||||
|
TranslatedOpacityTransition.Update(elapsedTime);
|
||||||
|
YOffsetTransition.Update(elapsedTime);
|
||||||
|
ColorTransition.Update(elapsedTime);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
|||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LanguageCode { get; set; } = "";
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LanguageCode { get; set; } = "";
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LXMusicServer { get; set; } = string.Empty;
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LXMusicServer { get; set; } = string.Empty;
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string AmllTtmlDbBaseUrl { get; set; } = Constants.AmllTTmlDB.BaseUrl;
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string AmllTtmlDbBaseUrl { get; set; } = Constants.AmllTTmlDB.BaseUrl;
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsForceWordByWordEffect { get; set; } = true;
|
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ShowOrHideLyricsWindowShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "H" };
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ShowOrHideLyricsWindowShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "H" };
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ExitOnLyricsWindowClosed { get; set; } = false;
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ExitOnLyricsWindowClosed { get; set; } = false;
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ListenOnNewPlaybackSource { get; set; } = true;
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ListenOnNewPlaybackSource { get; set; } = true;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
|||||||
{
|
{
|
||||||
public partial class LyricsEffectSettings : ObservableRecipient, ICloneable
|
public partial class LyricsEffectSettings : ObservableRecipient, ICloneable
|
||||||
{
|
{
|
||||||
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial WordByWordEffectMode WordByWordEffectMode { get; set; } = WordByWordEffectMode.Auto;
|
||||||
|
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsBlurEffectEnabled { get; set; } = true;
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsBlurEffectEnabled { get; set; } = true;
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFadeOutEffectEnabled { get; set; } = true;
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFadeOutEffectEnabled { get; set; } = true;
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsOutOfSightEffectEnabled { get; set; } = true;
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsOutOfSightEffectEnabled { get; set; } = true;
|
||||||
@@ -54,6 +56,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
|||||||
{
|
{
|
||||||
return new LyricsEffectSettings(this.LyricsScrollTopDuration, this.LyricsScrollDuration, this.LyricsScrollBottomDuration, this.LyricsScrollEasingType)
|
return new LyricsEffectSettings(this.LyricsScrollTopDuration, this.LyricsScrollDuration, this.LyricsScrollBottomDuration, this.LyricsScrollEasingType)
|
||||||
{
|
{
|
||||||
|
WordByWordEffectMode = this.WordByWordEffectMode,
|
||||||
|
|
||||||
IsLyricsBlurEffectEnabled = this.IsLyricsBlurEffectEnabled,
|
IsLyricsBlurEffectEnabled = this.IsLyricsBlurEffectEnabled,
|
||||||
IsLyricsFadeOutEffectEnabled = this.IsLyricsFadeOutEffectEnabled,
|
IsLyricsFadeOutEffectEnabled = this.IsLyricsFadeOutEffectEnabled,
|
||||||
IsLyricsOutOfSightEffectEnabled = this.IsLyricsOutOfSightEffectEnabled,
|
IsLyricsOutOfSightEffectEnabled = this.IsLyricsOutOfSightEffectEnabled,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace BetterLyrics.WinUI3.Renderer
|
|||||||
|
|
||||||
public CoverBackgroundRenderer()
|
public CoverBackgroundRenderer()
|
||||||
{
|
{
|
||||||
_crossfadeTransition = new ValueTransition<double>(1.0, 0.7, easingType: EasingType.Linear);
|
_crossfadeTransition = new ValueTransition<double>(1.0, 0.7, defaultEasingType: EasingType.Linear);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCoverBitmap(CanvasBitmap? newBitmap)
|
public void SetCoverBitmap(CanvasBitmap? newBitmap)
|
||||||
@@ -73,18 +73,18 @@ namespace BetterLyrics.WinUI3.Renderer
|
|||||||
|
|
||||||
if (_currentBitmap == null)
|
if (_currentBitmap == null)
|
||||||
{
|
{
|
||||||
_crossfadeTransition.StartTransition(1.0, jumpTo: true);
|
_crossfadeTransition.JumpTo(1.0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_previousBitmap == null)
|
if (_previousBitmap == null)
|
||||||
{
|
{
|
||||||
_crossfadeTransition.StartTransition(1.0, jumpTo: true);
|
_crossfadeTransition.JumpTo(1.0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_crossfadeTransition.Reset(0.0);
|
_crossfadeTransition.JumpTo(0.0);
|
||||||
_crossfadeTransition.StartTransition(1.0);
|
_crossfadeTransition.Start(1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -251,11 +251,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
.Where(x => x.MediaFolderId == folder.Id)
|
.Where(x => x.MediaFolderId == folder.Id)
|
||||||
.ExecuteDeleteAsync();
|
.ExecuteDeleteAsync();
|
||||||
|
|
||||||
// VACUUM 是 SQLite 特有的命令
|
await context.Database.ExecuteSqlRawAsync("VACUUM");
|
||||||
if (context.Database.IsSqlite())
|
|
||||||
{
|
|
||||||
await context.Database.ExecuteSqlRawAsync("VACUUM");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ namespace BetterLyrics.WinUI3.Services.LyricsCacheService
|
|||||||
{
|
{
|
||||||
Task<LyricsCacheItem?> GetLyricsAsync(SongInfo songInfo, LyricsSearchProvider provider);
|
Task<LyricsCacheItem?> GetLyricsAsync(SongInfo songInfo, LyricsSearchProvider provider);
|
||||||
Task SaveLyricsAsync(SongInfo songInfo, LyricsCacheItem result);
|
Task SaveLyricsAsync(SongInfo songInfo, LyricsCacheItem result);
|
||||||
|
Task ClearCacheAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,5 +69,13 @@ namespace BetterLyrics.WinUI3.Services.LyricsCacheService
|
|||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ClearCacheAsync()
|
||||||
|
{
|
||||||
|
using var context = await _contextFactory.CreateDbContextAsync();
|
||||||
|
|
||||||
|
await context.LyricsCache.ExecuteDeleteAsync();
|
||||||
|
await context.Database.ExecuteSqlRawAsync("VACUUM;");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>فرض البقاء في المقدمة</value>
|
<value>فرض البقاء في المقدمة</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>فرض محاكاة تأثير كلمة بكلمة حتى إذا كانت الكلمات الحالية لا تحتوي على معلومات كلمة بكلمة</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>فرض تفعيل تأثير الكلمات كلمة بكلمة</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>معدل إطارات التصيير (FPS)</value>
|
<value>معدل إطارات التصيير (FPS)</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>دليل المستخدم</value>
|
<value>دليل المستخدم</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>Immer im Vordergrund erzwingen</value>
|
<value>Immer im Vordergrund erzwingen</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>Wort-für-Wort-Simulation erzwingen, auch wenn der aktuelle Songtext keine entsprechenden Infos enthält</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>Wort-für-Wort-Effekt erzwingen</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>Rendering-FPS</value>
|
<value>Rendering-FPS</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>Benutzerhandbuch</value>
|
<value>Benutzerhandbuch</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -891,12 +891,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>Force Always on Top</value>
|
<value>Force Always on Top</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>Force word-by-word simulation even if current lyrics don't have word-by-word info</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>Force Word-by-Word Effect</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>Render FPS</value>
|
<value>Render FPS</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1143,6 +1137,18 @@
|
|||||||
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
|
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
|
||||||
<value>Lyrics Window Status Switch Shortcut</value>
|
<value>Lyrics Window Status Switch Shortcut</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value>Word-by-word Animation Strategy</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value>Always</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
|
||||||
|
<value>Auto</value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value>Never</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
|
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
|
||||||
<value>Adjusting this value will affect sequential search and best match search results, but will not affect search results in the manual lyrics search interface</value>
|
<value>Adjusting this value will affect sequential search and best match search results, but will not affect search results in the manual lyrics search interface</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>Forzar siempre visible</value>
|
<value>Forzar siempre visible</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>Forzar simulación palabra por palabra incluso si la letra actual no tiene información palabra por palabra</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>Forzar efecto palabra por palabra</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>FPS de renderizado</value>
|
<value>FPS de renderizado</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>Guía de usuario</value>
|
<value>Guía de usuario</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>Forcer toujours au premier plan</value>
|
<value>Forcer toujours au premier plan</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>Forcer la simulation mot à mot même si les paroles n'ont pas d'infos mot à mot</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>Forcer l'effet mot à mot</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>FPS de rendu</value>
|
<value>FPS de rendu</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>Guide de l'utilisateur</value>
|
<value>Guide de l'utilisateur</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>हमेशा शीर्ष पर रखने के लिए बाध्य करें</value>
|
<value>हमेशा शीर्ष पर रखने के लिए बाध्य करें</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>शब्द-दर-शब्द प्रभाव का अनुकरण करने के लिए बाध्य करें, भले ही वर्तमान बोल में शब्द-दर-शब्द जानकारी न हो</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>शब्द-दर-शब्द प्रभाव सक्षम करने के लिए बाध्य करें</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>रेंडरिंग फ्रेम दर</value>
|
<value>रेंडरिंग फ्रेम दर</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>उपयोगकर्ता गाइड</value>
|
<value>उपयोगकर्ता गाइड</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>Paksa Selalu di Atas</value>
|
<value>Paksa Selalu di Atas</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>Paksa simulasi efek kata-demi-kata meskipun lirik saat ini tidak memiliki informasi kata-demi-kata</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>Paksa Aktifkan Efek Kata-demi-Kata</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>Frame Rate Rendering</value>
|
<value>Frame Rate Rendering</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>Panduan Pengguna</value>
|
<value>Panduan Pengguna</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>常にトップに強制的に表示</value>
|
<value>常にトップに強制的に表示</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>現在の歌詞に文字単位の情報がない場合でも、文字単位の歌詞シミュレーションをします</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>文字単位の歌詞効果を強制する</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>描画 FPS</value>
|
<value>描画 FPS</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>ユーザーガイド</value>
|
<value>ユーザーガイド</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>항상 위에 표시 강제</value>
|
<value>항상 위에 표시 강제</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>현재 가사에 글자 단위 정보가 없더라도 글자 단위 시뮬레이션을 강제합니다</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>단어 단위 효과 강제 적용</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>렌더링 FPS</value>
|
<value>렌더링 FPS</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>사용자 가이드</value>
|
<value>사용자 가이드</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>Paksa Sentiasa di Atas</value>
|
<value>Paksa Sentiasa di Atas</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>Paksa simulasi kesan perkataan-demi-perkataan walaupun lirik semasa tidak mempunyai maklumat perkataan-demi-perkataan</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>Paksa Dayakan Kesan Perkataan-demi-Perkataan</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>Kadar Bingkai Penyajian (FPS)</value>
|
<value>Kadar Bingkai Penyajian (FPS)</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>Panduan Pengguna</value>
|
<value>Panduan Pengguna</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>Forçar Sempre no Topo</value>
|
<value>Forçar Sempre no Topo</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>Forçar a simulação do efeito palavra por palavra, mesmo que a letra atual não tenha informações palavra por palavra</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>Forçar Efeito Palavra por Palavra</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>Taxa de Fotogramas de Renderização</value>
|
<value>Taxa de Fotogramas de Renderização</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>Guia do Utilizador</value>
|
<value>Guia do Utilizador</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>Принудительно поверх всех окон</value>
|
<value>Принудительно поверх всех окон</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>Принудительно имитировать пословный эффект, даже если текущий текст не имеет пословной информации</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>Принудительно включить пословный эффект</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>FPS рендеринга</value>
|
<value>FPS рендеринга</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>Руководство пользователя</value>
|
<value>Руководство пользователя</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>บังคับให้อยู่บนสุดเสมอ</value>
|
<value>บังคับให้อยู่บนสุดเสมอ</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>บังคับจำลองเอฟเฟกต์ทีละคำแม้ว่าเนื้อเพลงปัจจุบันจะไม่มีข้อมูลทีละคำ</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>บังคับเปิดใช้เอฟเฟกต์ทีละคำ</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>เฟรมเรตการเรนเดอร์</value>
|
<value>เฟรมเรตการเรนเดอร์</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>คู่มือผู้ใช้</value>
|
<value>คู่มือผู้ใช้</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>Buộc luôn ở trên cùng</value>
|
<value>Buộc luôn ở trên cùng</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>Buộc mô phỏng hiệu ứng từng từ ngay cả khi lời bài hát hiện tại không có thông tin từng từ</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>Buộc bật hiệu ứng từng từ</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>Tốc độ khung hình kết xuất</value>
|
<value>Tốc độ khung hình kết xuất</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>Hướng dẫn sử dụng</value>
|
<value>Hướng dẫn sử dụng</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -891,12 +891,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>强制置于顶层</value>
|
<value>强制置于顶层</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>即使当前歌词没有逐字信息,仍强制模拟逐字效果</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>强制启用逐字歌词效果</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>渲染帧率</value>
|
<value>渲染帧率</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1143,6 +1137,15 @@
|
|||||||
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
|
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
|
||||||
<value>歌词窗口状态切换快捷键</value>
|
<value>歌词窗口状态切换快捷键</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAuto.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
|
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
|
||||||
<value>调整此值将影响顺序搜索和最佳匹配搜索结果,但不会影响手动歌词搜索界面中的搜索结果</value>
|
<value>调整此值将影响顺序搜索和最佳匹配搜索结果,但不会影响手动歌词搜索界面中的搜索结果</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -888,12 +888,6 @@
|
|||||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||||
<value>強制置於頂層</value>
|
<value>強制置於頂層</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
|
||||||
<value>即使目前歌詞沒有逐字資訊,仍強制模擬逐字效果</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageForceWordByWordEffect.Header" xml:space="preserve">
|
|
||||||
<value>強制啟用逐字歌詞效果</value>
|
|
||||||
</data>
|
|
||||||
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
<data name="SettingsPageFPS.Header" xml:space="preserve">
|
||||||
<value>渲染畫面播放速率</value>
|
<value>渲染畫面播放速率</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1575,4 +1569,13 @@
|
|||||||
<data name="UserGuide.Content" xml:space="preserve">
|
<data name="UserGuide.Content" xml:space="preserve">
|
||||||
<value>使用指南</value>
|
<value>使用指南</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectMode.Header" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeAlways.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
|
<data name="SettingsPageLyricsWordByWordEffectModeNever.Content" xml:space="preserve">
|
||||||
|
<value />
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -2,27 +2,35 @@
|
|||||||
using BetterLyrics.WinUI3.Helper.BetterLyrics.WinUI3.Helper;
|
using BetterLyrics.WinUI3.Helper.BetterLyrics.WinUI3.Helper;
|
||||||
using BetterLyrics.WinUI3.Hooks;
|
using BetterLyrics.WinUI3.Hooks;
|
||||||
using BetterLyrics.WinUI3.Models.Settings;
|
using BetterLyrics.WinUI3.Models.Settings;
|
||||||
|
using BetterLyrics.WinUI3.Services.LyricsCacheService;
|
||||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||||
using BetterLyrics.WinUI3.Views;
|
using BetterLyrics.WinUI3.Views;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Windows.Storage;
|
||||||
|
using WinRT.Interop;
|
||||||
|
|
||||||
namespace BetterLyrics.WinUI3.ViewModels
|
namespace BetterLyrics.WinUI3.ViewModels
|
||||||
{
|
{
|
||||||
public partial class AboutControlViewModel : BaseViewModel
|
public partial class AboutControlViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
|
private readonly ILyricsCacheService _lyricsCacheService;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial AppSettings AppSettings { get; set; }
|
public partial AppSettings AppSettings { get; set; }
|
||||||
|
|
||||||
public AboutControlViewModel(ISettingsService settingsService)
|
public AboutControlViewModel(ISettingsService settingsService, ILyricsCacheService lyricsCacheService)
|
||||||
{
|
{
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
|
_lyricsCacheService = lyricsCacheService;
|
||||||
AppSettings = _settingsService.AppSettings;
|
AppSettings = _settingsService.AppSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,18 +55,34 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task ImportSettingsAsync()
|
private async Task ImportSettingsAsync()
|
||||||
{
|
{
|
||||||
var file = await PickerHelper.PickSingleFileAsync<SettingsWindow>([".json"]);
|
var file = await PickerHelper.PickSingleFileAsync<SettingsWindow>([".zip"]);
|
||||||
|
|
||||||
if (file != null)
|
if (file != null)
|
||||||
{
|
{
|
||||||
var succeed = _settingsService.ImportSettings(file.Path);
|
try
|
||||||
if (succeed)
|
|
||||||
{
|
{
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
|
||||||
|
SqliteConnection.ClearAllPools();
|
||||||
|
|
||||||
|
string tempExtractPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
Directory.CreateDirectory(tempExtractPath);
|
||||||
|
|
||||||
|
using (var stream = await file.OpenStreamForReadAsync())
|
||||||
|
{
|
||||||
|
ZipFile.ExtractToDirectory(stream, tempExtractPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryHelper.CopyDirectory(tempExtractPath, PathHelper.LocalFolder, true);
|
||||||
|
|
||||||
|
Directory.Delete(tempExtractPath, true);
|
||||||
|
|
||||||
WindowHook.RestartApp();
|
WindowHook.RestartApp();
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ToastHelper.ShowToast("ImportSettingsFailed", null, InfoBarSeverity.Error);
|
ToastHelper.ShowToast("ImportSettingsFailed", ex.Message, InfoBarSeverity.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,31 +90,49 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task ExportSettingsAsync()
|
private async Task ExportSettingsAsync()
|
||||||
{
|
{
|
||||||
var folder = await PickerHelper.PickSingleFolderAsync<SettingsWindow>();
|
try
|
||||||
|
|
||||||
if (folder != null)
|
|
||||||
{
|
{
|
||||||
_settingsService.ExportSettings(folder.Path);
|
var suggestedFileName = $"{Constants.App.AppName}_{_settingsService.AppSettings.Version}_{DateTime.Now:yyyyMMdd_HHmmss}";
|
||||||
|
IDictionary<string, IList<string>> fileTypeChoices = new Dictionary<string, IList<string>>()
|
||||||
|
{
|
||||||
|
{ "Zip Archive", new List<string>() { ".zip" } }
|
||||||
|
};
|
||||||
|
|
||||||
|
var destinationFile = await PickerHelper.PickSaveFileAsync<SettingsWindow>(fileTypeChoices, suggestedFileName);
|
||||||
|
if (destinationFile == null) return;
|
||||||
|
|
||||||
|
string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
|
||||||
|
DirectoryHelper.CopyDirectory(PathHelper.LocalFolder, tempDir, true);
|
||||||
|
|
||||||
|
string tempZipPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".zip");
|
||||||
|
|
||||||
|
ZipFile.CreateFromDirectory(tempDir, tempZipPath);
|
||||||
|
|
||||||
|
using (var sourceStream = File.OpenRead(tempZipPath))
|
||||||
|
using (var destStream = await destinationFile.OpenStreamForWriteAsync())
|
||||||
|
{
|
||||||
|
sourceStream.CopyTo(destStream);
|
||||||
|
destStream.SetLength(sourceStream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.Delete(tempDir, true);
|
||||||
|
File.Delete(tempZipPath);
|
||||||
|
|
||||||
ToastHelper.ShowToast("ExportSettingsSuccess", null, InfoBarSeverity.Success);
|
ToastHelper.ShowToast("ExportSettingsSuccess", null, InfoBarSeverity.Success);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ToastHelper.ShowToast("Error", ex.Message, InfoBarSeverity.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task ExportPlayHistoryAsync()
|
private async Task ClearCacheFilesAsync()
|
||||||
{
|
{
|
||||||
var folder = await PickerHelper.PickSingleFolderAsync<SettingsWindow>();
|
await _lyricsCacheService.ClearCacheAsync();
|
||||||
|
|
||||||
if (folder != null)
|
|
||||||
{
|
|
||||||
var dest = Path.Combine(folder.Path, $"BetterLyrics_Play_History_Export_{DateTime.Now:yyyyMMdd_HHmmss}.db");
|
|
||||||
await FileHelper.CopyFileAsync(PathHelper.PlayHistoryPath, dest);
|
|
||||||
ToastHelper.ShowToast("ExportSettingsSuccess", null, InfoBarSeverity.Success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void ClearCacheFiles()
|
|
||||||
{
|
|
||||||
DirectoryHelper.DeleteAllFiles(PathHelper.LogDirectory);
|
DirectoryHelper.DeleteAllFiles(PathHelper.LogDirectory);
|
||||||
DirectoryHelper.DeleteAllFiles(PathHelper.LyricsCacheDirectory);
|
DirectoryHelper.DeleteAllFiles(PathHelper.LyricsCacheDirectory);
|
||||||
DirectoryHelper.DeleteAllFiles(PathHelper.iTunesAlbumArtCacheDirectory);
|
DirectoryHelper.DeleteAllFiles(PathHelper.iTunesAlbumArtCacheDirectory);
|
||||||
|
|||||||
Reference in New Issue
Block a user