Compare commits

..

17 Commits

Author SHA1 Message Date
Zhe Fang
509079e8c7 Merge pull request #10 from jayfunc/dev
1.0.9.0
2025-07-07 17:32:48 -04:00
Zhe Fang
a29e5c98f8 fix 2025-07-07 17:30:44 -04:00
Zhe Fang
78a6ba8e1f add local machine translation func 2025-07-05 15:16:34 -04:00
Zhe Fang
352ceca81d fix 2025-07-04 07:25:38 -04:00
Zhe Fang
c50c180aa0 Merge pull request #9 from jayfunc/dev
v1.0.7.0 release
2025-06-30 20:56:34 -04:00
Zhe Fang
2f99d44b86 update readme 2025-06-30 20:50:45 -04:00
Zhe Fang
03386e72b2 fix display error for windows 10; improve desktop mode experience; improve album art transition animation; fix ttml parse issue 2025-06-30 20:08:39 -04:00
Zhe Fang
54ba0a0c85 fix missing ResourceLoader in App.xaml.cs 2025-06-29 21:31:42 -04:00
Zhe Fang
7bf8b2894d Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-06-29 21:25:41 -04:00
Zhe Fang
875da76e6b add adaptive font color settings and custom color settings 2025-06-29 21:25:39 -04:00
Zhe Fang
547ca6d631 update README.CN.md 2025-06-28 07:10:36 -04:00
Zhe Fang
60fb088bea update README.md 2025-06-28 07:09:30 -04:00
Zhe Fang
3a89236af0 update readme 2025-06-27 09:52:29 -04:00
Zhe Fang
7d16bdbc88 Update README.md 2025-06-27 09:21:02 -04:00
Zhe Fang
812d23a101 Update README.md 2025-06-27 09:18:55 -04:00
Zhe Fang
4381a34191 update README.md 2025-06-27 07:42:12 -04:00
Zhe Fang
6e21e5636b update README.md 2025-06-27 06:38:17 -04:00
111 changed files with 6228 additions and 9541 deletions

View File

@@ -5,52 +5,62 @@
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
xmlns:uap18="http://schemas.microsoft.com/appx/manifest/uap/windows10/18"
IgnorableNamespaces="uap rescap uap18">
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.6.0" />
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.9.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>BetterLyrics</DisplayName>
<PublisherDisplayName>founchoo</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Properties>
<DisplayName>BetterLyrics</DisplayName>
<PublisherDisplayName>founchoo</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Resources>
<Resource Language="en-US"/>
<Resource Language="zh-CN"/>
<Resource Language="zh-TW"/>
<Resource Language="ja-JP"/>
<Resource Language="ko-KR"/>
</Resources>
<Resources>
<Resource Language="en-US"/>
<Resource Language="zh-CN"/>
<Resource Language="zh-TW"/>
<Resource Language="ja-JP"/>
<Resource Language="ko-KR"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="BetterLyrics"
Description="BetterLyrics.WinUI3 (Package)"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
<uap:SplashScreen Image="Images\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="BetterLyrics"
Description="BetterLyrics.WinUI3 (Package)"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
<uap:SplashScreen Image="Images\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<uap5:Extension
Category="windows.startupTask">
<uap5:StartupTask
TaskId="AutoStartup"
Enabled="false"
DisplayName="BetterLyrics" />
</uap5:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -53,7 +53,7 @@
<x:Double x:Key="SettingsCardSpacing">4</x:Double>
<!-- Style (inc. the correct spacing) of a section header -->
<!-- Style -->
<Style
x:Key="SettingsSectionHeaderTextBlockStyle"
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
@@ -63,14 +63,16 @@
</Style.Setters>
</Style>
<Style x:Key="TitleBarButtonStyle" TargetType="Button">
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="16,0" />
<Setter Property="Padding" Value="16,9,16,11" />
<Setter Property="Margin" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="GhostButtonStyle" TargetType="Button">
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Padding" Value="8" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
@@ -86,11 +88,14 @@
<Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="8" />
<Setter Property="Background" Value="Transparent" />
</Style>
<!-- Dimensions -->
<!-- Fonts -->
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -1,13 +1,10 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Text;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -15,34 +12,27 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.ApplicationModel.Resources;
using Serilog;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
using ShadowViewer.Controls;
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class
/// </summary>
public partial class App : Application
{
#region Fields
/// <summary>
/// Defines the _logger
/// </summary>
private readonly ILogger<App> _logger;
#endregion
public static new App Current => (App)Application.Current;
public static DispatcherQueue? DispatcherQueue { get; private set; }
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
public static ResourceLoader? ResourceLoader { get; private set; }
#region Constructors
public NotificationPanel? LyricsWindowNotificationPanel { get; set; }
public NotificationPanel? SettingsWindowNotificationPanel { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
public App()
{
this.InitializeComponent();
@@ -55,7 +45,7 @@ namespace BetterLyrics.WinUI3
AppInfo.EnsureDirectories();
ConfigureServices();
_logger = Ioc.Default.GetService<ILogger<App>>()!;
_logger = Ioc.Default.GetRequiredService<ILogger<App>>();
UnhandledException += App_UnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
@@ -63,50 +53,29 @@ namespace BetterLyrics.WinUI3
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}
#endregion
#region Properties
/// <summary>
/// Gets the Current
/// </summary>
public static new App Current => (App)Application.Current;
/// <summary>
/// Gets the DispatcherQueue
/// </summary>
public static DispatcherQueue? DispatcherQueue { get; private set; }
/// <summary>
/// Gets the DispatcherQueueTimer
/// </summary>
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
/// <summary>
/// Gets the ResourceLoader
/// </summary>
public static ResourceLoader? ResourceLoader { get; private set; }
#endregion
#region Methods
/// <summary>
/// Invoked when the application is launched
/// </summary>
/// <param name="args">Details about the launch request and process</param>
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
WindowHelper.OpenLyricsWindow();
WindowHelper.OpenOrShowWindow<LyricsWindow>();
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow == null) return;
string[] commandLineArguments = Environment.GetCommandLineArgs();
if (commandLineArguments.Length > 1)
{
commandLineArguments = commandLineArguments.Skip(1).ToArray();
if (commandLineArguments.First() == AppInfo.UnlockWindowTag)
{
lyricsWindow.AutoSelectLyricsMode(AutoStartWindowType.DesktopMode, false);
return;
}
}
lyricsWindow.AutoSelectLyricsMode();
}
/// <summary>
/// The ConfigureServices
/// </summary>
private static void ConfigureServices()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
.WriteTo.File(AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
.CreateLogger();
@@ -123,70 +92,37 @@ namespace BetterLyrics.WinUI3
.AddSingleton<IPlaybackService, PlaybackService>()
.AddSingleton<IMusicSearchService, MusicSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>()
.AddSingleton<ILibreTranslateService, LibreTranslateService>()
// ViewModels
.AddTransient<HostWindowViewModel>()
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>()
.AddSingleton<SystemTrayViewModel>()
.AddSingleton<SettingsViewModel>()
.AddSingleton<SettingsPageViewModel>()
.AddSingleton<LyricsPageViewModel>()
.AddSingleton<LyricsRendererViewModel>()
.AddSingleton<LyricsSettingsControlViewModel>()
.BuildServiceProvider()
);
}
/// <summary>
/// The App_UnhandledException
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="Microsoft.UI.Xaml.UnhandledExceptionEventArgs"/></param>
private void App_UnhandledException(
object sender,
Microsoft.UI.Xaml.UnhandledExceptionEventArgs e
)
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
_logger.LogError(e.Exception, "App_UnhandledException");
e.Handled = true;
}
/// <summary>
/// The CurrentDomain_FirstChanceException
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs"/></param>
private void CurrentDomain_FirstChanceException(
object? sender,
System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e
)
private void CurrentDomain_FirstChanceException(object? sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
{
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
_logger.LogError(e.Exception, "CurrentDomain_FirstChanceException");
}
/// <summary>
/// The CurrentDomain_UnhandledException
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="System.UnhandledExceptionEventArgs"/></param>
private void CurrentDomain_UnhandledException(
object sender,
System.UnhandledExceptionEventArgs e
)
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
{
_logger.LogError(e.ExceptionObject.ToString(), "CurrentDomain_UnhandledException");
}
/// <summary>
/// The TaskScheduler_UnobservedTaskException
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="UnobservedTaskExceptionEventArgs"/></param>
private void TaskScheduler_UnobservedTaskException(
object? sender,
UnobservedTaskExceptionEventArgs e
)
private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
//_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
}
#endregion
}
}

View File

@@ -19,8 +19,8 @@
<PRIResource Remove="ViewModels\Lyrics\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Controls\DependenciesSettingsExpander.xaml" />
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Views\SettingsWindow.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Logo.ico" />
@@ -40,27 +40,28 @@
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="iTunesSearch" Version="1.0.44" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
<PackageReference Include="WinUIEx" Version="2.6.0" />
<PackageReference Include="z440.atl.core" Version="6.26.0" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\AI - 甜度爆表.mp3">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<PackageReference Include="z440.atl.core" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<Page Update="Rendering\InAppLyricsRenderer.xaml">
@@ -77,12 +78,12 @@
<TrimmerRootAssembly Include="TagLibSharp" />
</ItemGroup>
<ItemGroup>
<Page Update="Controls\SystemTray.xaml">
<Page Update="Views\SettingsWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\DependenciesSettingsExpander.xaml">
<Page Update="Controls\SystemTray.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>

View File

@@ -1,156 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.DependenciesSettingsExpander"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<controls:SettingsExpander
x:Uid="DependenciesSettingsExpander"
HeaderIcon="{ui:FontIcon Glyph=&#xE7B8;}"
IsExpanded="True">
<controls:SettingsExpander.Items>
<controls:SettingsCard Header="CommunityToolkit.Labs.WinUI.MarqueeText">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.Labs.WinUI.MarqueeText" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.Labs.WinUI.MarqueeText" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="CommunityToolkit.Labs.WinUI.OpacityMaskView">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.Labs.WinUI.OpacityMaskView" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.Labs.WinUI.OpacityMaskView" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="CommunityToolkit.Mvvm">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.Mvvm" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.Mvvm" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="CommunityToolkit.WinUI.Behaviors">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Behaviors" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Behaviors" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="CommunityToolkit.WinUI.Controls.Primitives">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.Primitives" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.Primitives" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="CommunityToolkit.WinUI.Controls.Segmented">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.Segmented" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.Segmented" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="CommunityToolkit.WinUI.Controls.SettingsControls">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.SettingsControls" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.SettingsControls" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="CommunityToolkit.WinUI.Converters">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Converters" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Converters" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="CommunityToolkit.WinUI.Extensions">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Extensions" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Extensions" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="CommunityToolkit.WinUI.Helpers">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Helpers" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Helpers" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="CommunityToolkit.WinUI.Media">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/CommunityToolkit.WinUI.Media" NavigateUri="https://www.nuget.org/packages/CommunityToolkit.WinUI.Media" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="H.NotifyIcon.WinUI">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/H.NotifyIcon.WinUI" NavigateUri="https://www.nuget.org/packages/H.NotifyIcon.WinUI" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="Lyricify.Lyrics.Helper-NativeAot">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/Lyricify.Lyrics.Helper-NativeAot" NavigateUri="https://www.nuget.org/packages/Lyricify.Lyrics.Helper-NativeAot" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="Microsoft.Extensions.DependencyInjection">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection" NavigateUri="https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="Microsoft.Extensions.Logging">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.Extensions.Logging" NavigateUri="https://www.nuget.org/packages/Microsoft.Extensions.Logging" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="Microsoft.Graphics.Win2D">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.Graphics.Win2D" NavigateUri="https://www.nuget.org/packages/Microsoft.Graphics.Win2D" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="Microsoft.Windows.SDK.BuildTools">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools" NavigateUri="https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="Microsoft.WindowsAppSDK">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.WindowsAppSDK" NavigateUri="https://www.nuget.org/packages/Microsoft.WindowsAppSDK" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="Microsoft.Xaml.Behaviors.WinUI.Managed">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.WinUI.Managed" NavigateUri="https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.WinUI.Managed" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="Serilog.Extensions.Logging">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/Serilog.Extensions.Logging" NavigateUri="https://www.nuget.org/packages/Serilog.Extensions.Logging" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="Serilog.Sinks.File">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/Serilog.Sinks.File" NavigateUri="https://www.nuget.org/packages/Serilog.Sinks.File" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="System.Drawing.Common">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/System.Drawing.Common" NavigateUri="https://www.nuget.org/packages/System.Drawing.Common" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="System.Text.Encoding.CodePages">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/System.Text.Encoding.CodePages" NavigateUri="https://www.nuget.org/packages/System.Text.Encoding.CodePages" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="TagLibSharp">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/TagLibSharp" NavigateUri="https://www.nuget.org/packages/TagLibSharp" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="Ude.NetStandard">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/Ude.NetStandard" NavigateUri="https://www.nuget.org/packages/Ude.NetStandard" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="WinUIEx">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/WinUIEx" NavigateUri="https://www.nuget.org/packages/WinUIEx" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
<controls:SettingsCard Header="z440.atl.core">
<controls:SettingsCard.Description>
<HyperlinkButton Content="https://www.nuget.org/packages/z440.atl.core" NavigateUri="https://www.nuget.org/packages/z440.atl.core" />
</controls:SettingsCard.Description>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</UserControl>

View File

@@ -1,28 +0,0 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class DependenciesSettingsExpander : UserControl
{
public DependenciesSettingsExpander()
{
InitializeComponent();
}
}
}

View File

@@ -1,22 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{

View File

@@ -7,21 +7,8 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Converter
{
/// <summary>
/// Defines the <see cref="ColorToBrushConverter" />
/// </summary>
public partial class ColorToBrushConverter : IValueConverter
{
#region Methods
/// <summary>
/// The Convert
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is Color color)
@@ -31,19 +18,9 @@ namespace BetterLyrics.WinUI3.Converter
return new SolidColorBrush();
}
/// <summary>
/// The ConvertBack
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
@@ -13,10 +9,9 @@ namespace BetterLyrics.WinUI3.Converter
{
if (value is Microsoft.UI.Xaml.CornerRadius cornerRadius)
{
// Convert CornerRadius to an integer value, e.g., using the top-left radius
return (double)cornerRadius.TopLeft;
}
return .0; // or handle the case where value is not a CornerRadius
return .0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)

View File

@@ -5,21 +5,8 @@ using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
/// <summary>
/// Defines the <see cref="EnumToIntConverter" />
/// </summary>
internal partial class EnumToIntConverter : IValueConverter
{
#region Methods
/// <summary>
/// The Convert
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is Enum)
@@ -29,14 +16,6 @@ namespace BetterLyrics.WinUI3.Converter
return 0;
}
/// <summary>
/// The ConvertBack
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is int && targetType.IsEnum)
@@ -45,7 +24,5 @@ namespace BetterLyrics.WinUI3.Converter
}
return Enum.ToObject(targetType, 0);
}
#endregion
}
}

View File

@@ -5,21 +5,8 @@ using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
/// <summary>
/// Defines the <see cref="IntToCornerRadius" />
/// </summary>
public partial class IntToCornerRadius : IValueConverter
{
#region Methods
/// <summary>
/// The Convert
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is int intValue && parameter is double controlHeight)
@@ -29,19 +16,9 @@ namespace BetterLyrics.WinUI3.Converter
return new Microsoft.UI.Xaml.CornerRadius(0);
}
/// <summary>
/// The ConvertBack
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -6,73 +6,32 @@ using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
/// <summary>
/// Defines the <see cref="LyricsSearchProviderToDisplayNameConverter" />
/// </summary>
public partial class LyricsSearchProviderToDisplayNameConverter : IValueConverter
{
#region Methods
/// <summary>
/// The Convert
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is LyricsSearchProvider provider)
{
return provider switch
{
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString(
"LyricsSearchProviderLrcLib"
),
LyricsSearchProvider.QQ => App.ResourceLoader!.GetString(
"LyricsSearchProviderQQ"
),
LyricsSearchProvider.Netease => App.ResourceLoader!.GetString(
"LyricsSearchProviderNetease"
),
LyricsSearchProvider.Kugou => App.ResourceLoader!.GetString(
"LyricsSearchProviderKugou"
),
LyricsSearchProvider.AmllTtmlDb => App.ResourceLoader!.GetString(
"LyricsSearchProviderAmllTtmlDb"
),
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString(
"LyricsSearchProviderLocalLrcFile"
),
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString(
"LyricsSearchProviderLocalMusicFile"
),
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString(
"LyricsSearchProviderEslrcFile"
),
LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString(
"LyricsSearchProviderTtmlFile"
),
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString("LyricsSearchProviderLrcLib"),
LyricsSearchProvider.QQ => App.ResourceLoader!.GetString("LyricsSearchProviderQQ"),
LyricsSearchProvider.Netease => App.ResourceLoader!.GetString("LyricsSearchProviderNetease"),
LyricsSearchProvider.Kugou => App.ResourceLoader!.GetString("LyricsSearchProviderKugou"),
LyricsSearchProvider.AmllTtmlDb => App.ResourceLoader!.GetString("LyricsSearchProviderAmllTtmlDb"),
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),
LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString("LyricsSearchProviderTtmlFile"),
_ => "",
};
}
return "";
}
/// <summary>
/// The ConvertBack
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -6,21 +6,8 @@ using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
/// <summary>
/// Defines the <see cref="MatchedLocalFilesPathToVisibilityConverter" />
/// </summary>
public partial class MatchedLocalFilesPathToVisibilityConverter : IValueConverter
{
#region Methods
/// <summary>
/// The Convert
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string path)
@@ -37,19 +24,9 @@ namespace BetterLyrics.WinUI3.Converter
return Visibility.Collapsed;
}
/// <summary>
/// The ConvertBack
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -2,23 +2,10 @@
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the AutoStartWindowType
/// </summary>
public enum AutoStartWindowType
{
/// <summary>
/// Defines the StandardMode
/// </summary>
StandardMode,
/// <summary>
/// Defines the DockMode
/// </summary>
DockMode,
DesktopMode,
}
#endregion
}

View File

@@ -1,45 +1,13 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the BackdropType
/// </summary>
public enum BackdropType
{
/// <summary>
/// Defines the None
/// </summary>
None = 0,
/// <summary>
/// Defines the Mica
/// </summary>
Mica = 1,
/// <summary>
/// Defines the MicaAlt
/// </summary>
MicaAlt = 2,
/// <summary>
/// Defines the DesktopAcrylic
/// </summary>
DesktopAcrylic = 3,
/// <summary>
/// Defines the Transparent
/// </summary>
Transparent = 4,
}
#endregion
}

View File

@@ -2,38 +2,19 @@
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the EasingType
/// </summary>
public enum EasingType
{
/// <summary>
/// Defines the EaseInOutQuad
/// </summary>
EaseInOutQuad,
/// <summary>
/// Defines the EaseInQuad
/// </summary>
EaseInQuad,
/// <summary>
/// Defines the EaseOutQuad
/// </summary>
EaseOutQuad,
/// <summary>
/// Defines the Linear
/// </summary>
Linear,
/// <summary>
/// Defines the SmootherStep
/// </summary>
SmootherStep,
SmoothStep,
EaseInOutSine,
EaseInOutQuad,
EaseInOutCubic,
EaseInOutQuart,
EaseInOutQuint,
EaseInOutExpo,
EaseInOutCirc,
EaseInOutBack,
EaseInOutElastic,
EaseInOutBounce,
}
#endregion
}

View File

@@ -8,43 +8,13 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the Language
/// </summary>
public enum Language
{
/// <summary>
/// Defines the FollowSystem
/// </summary>
FollowSystem,
/// <summary>
/// Defines the English
/// </summary>
English,
/// <summary>
/// Defines the SimplifiedChinese
/// </summary>
SimplifiedChinese,
/// <summary>
/// Defines the TraditionalChinese
/// </summary>
TraditionalChinese,
/// <summary>
/// Defines the Japanese
/// </summary>
Japanese,
/// <summary>
/// Defines the Korean
/// </summary>
Korean,
}
#endregion
}

View File

@@ -2,20 +2,9 @@
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LineMaskType
/// </summary>
public enum LineRenderingType
{
UntilCurrentChar,
/// <summary>
/// Current char only
/// </summary>
CurrentCharOnly,
}
#endregion
}

View File

@@ -1,30 +1,10 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LocalSearchTargetProps
/// </summary>
public enum LocalSearchTargetProps
{
/// <summary>
/// Defines the LyricsOnly
/// </summary>
LyricsOnly,
/// <summary>
/// Defines the LyricsAndAlbumArt
/// </summary>
LyricsAndAlbumArt,
}
#endregion
}

View File

@@ -1,35 +0,0 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsAlignmentType
/// </summary>
public enum LyricsAlignmentType
{
/// <summary>
/// Defines the Left
/// </summary>
Left,
/// <summary>
/// Defines the Center
/// </summary>
Center,
/// <summary>
/// Defines the Right
/// </summary>
Right,
}
#endregion
}

View File

@@ -2,33 +2,11 @@
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsDisplayType
/// </summary>
public enum LyricsDisplayType
{
/// <summary>
/// Defines the AlbumArtOnly
/// </summary>
AlbumArtOnly,
/// <summary>
/// Defines the LyricsOnly
/// </summary>
LyricsOnly,
/// <summary>
/// Defines the SplitView
/// </summary>
SplitView,
/// <summary>
/// Defines the PlaceholderOnly
/// </summary>
PlaceholderOnly,
}
#endregion
}

View File

@@ -1,30 +1,11 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsFontColorType
/// </summary>
public enum LyricsFontColorType
{
/// <summary>
/// Defines the Default
/// </summary>
Default,
/// <summary>
/// Defines the Dominant
/// </summary>
Dominant,
AdaptiveColored,
AdaptiveGrayed,
Custom,
}
#endregion
}

View File

@@ -1,92 +1,28 @@
// 2025/6/23 by Zhe Fang
using Microsoft.UI.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Text;
using Windows.UI.Text;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsFontWeight
/// </summary>
public enum LyricsFontWeight
{
/// <summary>
/// Defines the Thin
/// </summary>
Thin,
/// <summary>
/// Defines the ExtraLight
/// </summary>
ExtraLight,
/// <summary>
/// Defines the Light
/// </summary>
Light,
/// <summary>
/// Defines the SemiLight
/// </summary>
SemiLight,
/// <summary>
/// Defines the Normal
/// </summary>
Normal,
/// <summary>
/// Defines the Medium
/// </summary>
Medium,
/// <summary>
/// Defines the SemiBold
/// </summary>
SemiBold,
/// <summary>
/// Defines the Bold
/// </summary>
Bold,
/// <summary>
/// Defines the ExtraBold
/// </summary>
ExtraBold,
/// <summary>
/// Defines the Black
/// </summary>
Black,
/// <summary>
/// Defines the ExtraBlack
/// </summary>
ExtraBlack,
}
#endregion
/// <summary>
/// Defines the <see cref="LyricsFontWeightExtensions" />
/// </summary>
public static class LyricsFontWeightExtensions
{
#region Methods
/// <summary>
/// The ToFontWeight
/// </summary>
/// <param name="weight">The weight<see cref="LyricsFontWeight"/></param>
/// <returns>The <see cref="FontWeight"/></returns>
public static FontWeight ToFontWeight(this LyricsFontWeight weight)
{
return weight switch
@@ -102,14 +38,8 @@ namespace BetterLyrics.WinUI3.Enums
LyricsFontWeight.ExtraBold => FontWeights.ExtraBold,
LyricsFontWeight.Black => FontWeights.Black,
LyricsFontWeight.ExtraBlack => FontWeights.ExtraBlack,
LyricsFontWeight _ => throw new ArgumentOutOfRangeException(
nameof(weight),
weight,
null
),
LyricsFontWeight _ => throw new ArgumentOutOfRangeException(nameof(weight), weight, null),
};
}
#endregion
}
}

View File

@@ -1,70 +1,48 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsFormat
/// </summary>
public enum LyricsFormat
{
/// <summary>
/// Defines the Lrc
/// </summary>
Lrc,
/// <summary>
/// Defines the Eslrc
/// </summary>
Eslrc,
/// <summary>
/// Defines the Ttml
/// </summary>
Ttml,
Qrc,
Krc,
NotSpecified,
}
#endregion
/// <summary>
/// Defines the <see cref="LyricsFormatExtensions" />
/// </summary>
public static class LyricsFormatExtensions
{
#region Methods
/// <summary>
/// The Detect
/// </summary>
/// <param name="content">The content<see cref="string"/></param>
/// <returns>The <see cref="LyricsFormat?"/></returns>
public static LyricsFormat? DetectFormat(this string content)
{
if (
content.StartsWith("<?xml")
&& System.Text.RegularExpressions.Regex.IsMatch(content, @"<tt(:\w+)?\b")
)
if (string.IsNullOrWhiteSpace(content))
return null;
// TTML
if (content.StartsWith("<?xml") && System.Text.RegularExpressions.Regex.IsMatch(content, @"<tt(:\w+)?\b"))
{
return LyricsFormat.Ttml;
}
// 检测标准LRC和增强型LRC
else if (
System.Text.RegularExpressions.Regex.IsMatch(content, @"\[\d{1,2}:\d{2}")
|| System.Text.RegularExpressions.Regex.IsMatch(
content,
@"<\d{1,2}:\d{2}\.\d{2,3}>"
)
)
// KRC: 检测主内容格式 [start,duration]<offset,duration,0>字...
else if (System.Text.RegularExpressions.Regex.IsMatch(
content,
@"^\[\d+,\d+\](<\d+,\d+,0>.+)+",
System.Text.RegularExpressions.RegexOptions.Multiline))
{
return LyricsFormat.Krc;
}
// QRC: 检测主内容格式 [start,duration]字(offset,duration)
else if (System.Text.RegularExpressions.Regex.IsMatch(
content,
@"^\[\d+,\d+\].*?\(\d+,\d+\)",
System.Text.RegularExpressions.RegexOptions.Multiline))
{
return LyricsFormat.Qrc;
}
// 标准LRC和增强型LRC
else if (System.Text.RegularExpressions.Regex.IsMatch(content, @"\[\d{1,2}:\d{2}") ||
System.Text.RegularExpressions.Regex.IsMatch(content, @"<\d{1,2}:\d{2}\.\d{2,3}>"))
{
return LyricsFormat.Lrc;
}
@@ -74,11 +52,6 @@ namespace BetterLyrics.WinUI3.Enums
}
}
/// <summary>
/// The ToFileExtension
/// </summary>
/// <param name="format">The format<see cref="LyricsFormat"/></param>
/// <returns>The <see cref="string"/></returns>
public static string ToFileExtension(this LyricsFormat format)
{
return format switch
@@ -91,7 +64,5 @@ namespace BetterLyrics.WinUI3.Enums
_ => ".*",
};
}
#endregion
}
}

View File

@@ -1,35 +0,0 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsPlayingState
/// </summary>
public enum LyricsPlayingState
{
/// <summary>
/// Defines the NotPlayed
/// </summary>
NotPlayed,
/// <summary>
/// Defines the Playing
/// </summary>
Playing,
/// <summary>
/// Defines the Played
/// </summary>
Played,
}
#endregion
}

View File

@@ -4,62 +4,21 @@ using BetterLyrics.WinUI3.Helper;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsSearchProvider
/// </summary>
public enum LyricsSearchProvider
{
QQ,
Kugou,
Netease,
LrcLib,
AmllTtmlDb,
/// <summary>
/// Defines the LocalMusicFile
/// </summary>
LocalMusicFile,
/// <summary>
/// Defines the LocalLrcFile
/// </summary>
LocalLrcFile,
/// <summary>
/// Defines the LocalEslrcFile
/// </summary>
LocalEslrcFile,
/// <summary>
/// Defines the LocalTtmlFile
/// </summary>
LocalTtmlFile,
}
public static class LyricsSearchProviderExtensions
{
/// <summary>
/// The IsLocal
/// </summary>
/// <param name="provider">The provider<see cref="LyricsSearchProvider"/></param>
/// <returns>The <see cref="bool"/></returns>
public static bool IsLocal(this LyricsSearchProvider provider)
{
return provider
is LyricsSearchProvider.LocalMusicFile
or LyricsSearchProvider.LocalLrcFile
or LyricsSearchProvider.LocalEslrcFile
or LyricsSearchProvider.LocalTtmlFile;
}
public static bool IsRemote(this LyricsSearchProvider provider)
{
return !provider.IsLocal();
}
public static string GetCacheDirectory(this LyricsSearchProvider provider)
{
return provider switch
@@ -88,7 +47,19 @@ namespace BetterLyrics.WinUI3.Enums
_ => LyricsFormat.NotSpecified,
};
}
}
#endregion
public static bool IsLocal(this LyricsSearchProvider provider)
{
return provider
is LyricsSearchProvider.LocalMusicFile
or LyricsSearchProvider.LocalLrcFile
or LyricsSearchProvider.LocalEslrcFile
or LyricsSearchProvider.LocalTtmlFile;
}
public static bool IsRemote(this LyricsSearchProvider provider)
{
return !provider.IsLocal();
}
}
}

View File

@@ -1,35 +0,0 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsStatus
/// </summary>
public enum LyricsStatus
{
/// <summary>
/// Defines the NotFound
/// </summary>
NotFound,
/// <summary>
/// Defines the Found
/// </summary>
Found,
/// <summary>
/// Defines the Loading
/// </summary>
Loading,
}
#endregion
}

View File

@@ -1,30 +0,0 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsType
/// </summary>
public enum LyricsType
{
/// <summary>
/// Defines the InAppLyrics
/// </summary>
InAppLyrics,
/// <summary>
/// Defines the DesktopLyrics
/// </summary>
DesktopLyrics,
}
#endregion
}

View File

@@ -2,23 +2,9 @@
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the MusicSearchMatchMode
/// </summary>
public enum MusicSearchMatchMode
{
/// <summary>
/// Defines the TitleAndArtist
/// </summary>
TitleAndArtist,
/// <summary>
/// Defines the TitleArtistAlbumAndDuration
/// </summary>
TitleArtistAlbumAndDuration,
}
#endregion
}

View File

@@ -0,0 +1,28 @@
// 2025/6/23 by Zhe Fang
using Microsoft.Graphics.Canvas.Text;
using System;
namespace BetterLyrics.WinUI3.Enums
{
public enum TextAlignmentType
{
Left,
Center,
Right,
}
public static class LyricsAlignmentTypeExtensions
{
public static CanvasHorizontalAlignment ToCanvasHorizontalAlignment(this TextAlignmentType alignmentType)
{
return alignmentType switch
{
TextAlignmentType.Left => CanvasHorizontalAlignment.Left,
TextAlignmentType.Center => CanvasHorizontalAlignment.Center,
TextAlignmentType.Right => CanvasHorizontalAlignment.Right,
_ => throw new ArgumentOutOfRangeException(nameof(alignmentType), alignmentType, null),
};
}
}
}

View File

@@ -1,59 +0,0 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the TitleBarType
/// </summary>
public enum TitleBarType
{
/// <summary>
/// Defines the Compact
/// </summary>
Compact,
/// <summary>
/// Defines the Extended
/// </summary>
Extended,
}
#endregion
/// <summary>
/// Defines the <see cref="TitleBarTypeExtensions" />
/// </summary>
public static class TitleBarTypeExtensions
{
#region Methods
/// <summary>
/// The GetHeight
/// </summary>
/// <param name="titleBarType">The titleBarType<see cref="TitleBarType"/></param>
/// <returns>The <see cref="double"/></returns>
public static double GetHeight(this TitleBarType titleBarType)
{
return titleBarType switch
{
TitleBarType.Compact => 32.0,
TitleBarType.Extended => 48.0,
_ => throw new ArgumentOutOfRangeException(
nameof(titleBarType),
titleBarType,
null
),
};
}
#endregion
}
}

View File

@@ -0,0 +1,9 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum WindowColorSampleMode
{
BelowWindow,
WindowArea,
WindowEdge,
}
}

View File

@@ -4,18 +4,8 @@ using System;
namespace BetterLyrics.WinUI3.Events
{
/// <summary>
/// Defines the <see cref="IsPlayingChangedEventArgs" />
/// </summary>
public class IsPlayingChangedEventArgs(bool isPlaying) : EventArgs
{
#region Properties
/// <summary>
/// Gets or sets a value indicating whether IsPlaying
/// </summary>
public bool IsPlaying { get; set; } = isPlaying;
#endregion
}
}

View File

@@ -9,45 +9,10 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Events
{
/// <summary>
/// Defines the <see cref="LibChangedEventArgs" />
/// </summary>
public class LibChangedEventArgs : EventArgs
public class LibChangedEventArgs(string folder, string filePath, WatcherChangeTypes changeType) : EventArgs
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LibChangedEventArgs"/> class.
/// </summary>
/// <param name="folder">The folder<see cref="string"/></param>
/// <param name="filePath">The filePath<see cref="string"/></param>
/// <param name="changeType">The changeType<see cref="WatcherChangeTypes"/></param>
public LibChangedEventArgs(string folder, string filePath, WatcherChangeTypes changeType)
{
Folder = folder;
FilePath = filePath;
ChangeType = changeType;
}
#endregion
#region Properties
/// <summary>
/// Gets the ChangeType
/// </summary>
public WatcherChangeTypes ChangeType { get; }
/// <summary>
/// Gets the FilePath
/// </summary>
public string FilePath { get; }
/// <summary>
/// Gets the Folder
/// </summary>
public string Folder { get; }
#endregion
public WatcherChangeTypes ChangeType { get; } = changeType;
public string FilePath { get; } = filePath;
public string Folder { get; } = folder;
}
}

View File

@@ -0,0 +1,14 @@
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Events
{
public class MediaSourceProvidersInfoEventArgs(List<MediaSourceProviderInfo> sessionIds):EventArgs
{
public List<MediaSourceProviderInfo> MediaSourceProviersInfo { get; set; } = sessionIds;
}
}

View File

@@ -4,18 +4,8 @@ using System;
namespace BetterLyrics.WinUI3.Events
{
/// <summary>
/// Defines the <see cref="PositionChangedEventArgs" />
/// </summary>
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
{
#region Properties
/// <summary>
/// Gets or sets the Position
/// </summary>
public TimeSpan Position { get; set; } = position;
#endregion
}
}

View File

@@ -1,26 +1,12 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Events
{
/// <summary>
/// Defines the <see cref="SongInfoChangedEventArgs" />
/// </summary>
public class SongInfoChangedEventArgs(SongInfo? songInfo) : EventArgs
{
#region Properties
/// <summary>
/// Gets or sets the SongInfo
/// </summary>
public SongInfo? SongInfo { get; set; } = songInfo;
#endregion
}
}

View File

@@ -1,97 +1,37 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Diagnostics;
using BetterLyrics.WinUI3.Enums;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="AnimationHelper" />
/// </summary>
public class AnimationHelper
{
#region Constants
/// <summary>
/// Defines the DebounceDefaultDuration
/// </summary>
public const int DebounceDefaultDuration = 200;
/// <summary>
/// Defines the StackedNotificationsShowingDuration
/// </summary>
public const int StackedNotificationsShowingDuration = 3900;
/// <summary>
/// Defines the StoryboardDefaultDuration
/// </summary>
public const int StoryboardDefaultDuration = 200;
#endregion
}
/// <summary>
/// Defines the <see cref="ValueTransition{T}" />
/// </summary>
/// <typeparam name="T"></typeparam>
public class ValueTransition<T>
where T : struct
{
#region Fields
/// <summary>
/// Defines the _currentValue
/// </summary>
private T _currentValue;
/// <summary>
/// Defines the _durationSeconds
/// </summary>
private float _durationSeconds;
/// <summary>
/// Defines the _interpolator
/// </summary>
private EasingType? _easingType;
private Func<T, T, float, T> _interpolator;
/// <summary>
/// Defines the _isTransitioning
/// </summary>
private bool _isTransitioning;
/// <summary>
/// Defines the _progress
/// </summary>
private float _progress;
/// <summary>
/// Defines the _startValue
/// </summary>
private T _startValue;
/// <summary>
/// Defines the _targetValue
/// </summary>
private T _targetValue;
private EasingType? _easingType;
public float DurationSeconds => _durationSeconds;
#endregion
public bool IsTransitioning => _isTransitioning;
public T Value => _currentValue;
public T TargetValue => _targetValue;
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ValueTransition{T}"/> class.
/// </summary>
/// <param name="initialValue">The initialValue<see cref="T"/></param>
/// <param name="durationSeconds">The durationSeconds<see cref="float"/></param>
/// <param name="interpolator">The interpolator<see cref="Func{T, T, float, T}"/></param>
public ValueTransition(
T initialValue,
float durationSeconds,
Func<T, T, float, T>? interpolator = null,
EasingType? easingType = null
)
public ValueTransition(T initialValue, float durationSeconds, Func<T, T, float, T>? interpolator = null, EasingType? easingType = null)
{
_currentValue = initialValue;
_startValue = initialValue;
@@ -108,73 +48,24 @@ namespace BetterLyrics.WinUI3.Helper
else if (easingType.HasValue)
{
_easingType = easingType;
_interpolator = GetInterpolatorByEasingType(easingType.Value);
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
}
else
{
_interpolator = GetInterpolatorByEasingType(EasingType.Linear);
_easingType = EasingType.Linear;
_easingType = EasingType.SmoothStep;
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
}
}
#endregion
#region Properties
/// <summary>
/// Gets a value indicating whether IsTransitioning
/// </summary>
public bool IsTransitioning => _isTransitioning;
/// <summary>
/// Gets the Value
/// </summary>
public T Value => _currentValue;
#endregion
#region Methods
private Func<T, T, float, T> GetInterpolatorByEasingType(EasingType type)
private void JumpTo(T value)
{
// 这里只以float为例实际可根据T类型扩展
if (typeof(T) == typeof(float))
{
return (start, end, progress) =>
{
float s = (float)(object)start;
float e = (float)(object)end;
float t = progress;
switch (type)
{
case EasingType.EaseInOutQuad:
t = EasingHelper.EaseInOutQuad(t);
break;
case EasingType.EaseInQuad:
t = EasingHelper.EaseInQuad(t);
break;
case EasingType.EaseOutQuad:
t = EasingHelper.EaseOutQuad(t);
break;
case EasingType.Linear:
t = EasingHelper.Linear(t);
break;
case EasingType.SmootherStep:
t = EasingHelper.SmootherStep(t);
break;
default:
break;
}
return (T)(object)(s + (e - s) * t);
};
}
throw new NotSupportedException("当前类型未实现默认缓动插值");
_currentValue = value;
_startValue = value;
_targetValue = value;
_progress = 1f;
_isTransitioning = false;
}
/// <summary>
/// The Reset
/// </summary>
/// <param name="value">The value<see cref="T"/></param>
public void Reset(T value)
{
_currentValue = value;
@@ -184,12 +75,14 @@ namespace BetterLyrics.WinUI3.Helper
_isTransitioning = false;
}
/// <summary>
/// The StartTransition
/// </summary>
/// <param name="targetValue">The targetValue<see cref="T"/></param>
public void StartTransition(T targetValue)
public void StartTransition(T targetValue, bool jumpTo = false)
{
if (jumpTo)
{
JumpTo(targetValue);
return;
}
if (!targetValue.Equals(_currentValue))
{
_startValue = _currentValue;
@@ -199,27 +92,15 @@ namespace BetterLyrics.WinUI3.Helper
}
}
/// <summary>
/// 立即跳转到指定值,无动画
/// </summary>
/// <param name="value">目标值</param>
public void JumpTo(T value)
public static bool Equals(double x, double y, double tolerance)
{
_currentValue = value;
_startValue = value;
_targetValue = value;
_progress = 1f;
_isTransitioning = false;
var diff = Math.Abs(x - y);
return diff <= tolerance || diff <= Math.Max(Math.Abs(x), Math.Abs(y)) * tolerance;
}
/// <summary>
/// The Update
/// </summary>
/// <param name="elapsedTime">The elapsedTime<see cref="TimeSpan"/></param>
public void Update(TimeSpan elapsedTime)
{
if (!_isTransitioning)
return;
if (!_isTransitioning) return;
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
if (_progress >= 1f)
@@ -234,6 +115,66 @@ namespace BetterLyrics.WinUI3.Helper
}
}
#endregion
private Func<T, T, float, T> GetInterpolatorByEasingType(EasingType type)
{
if (typeof(T) == typeof(float))
{
return (start, end, progress) =>
{
float s = (float)(object)start;
float e = (float)(object)end;
float t = progress;
switch (type)
{
case EasingType.EaseInOutSine:
t = EasingHelper.EaseInOutSine(t);
break;
case EasingType.EaseInOutQuad:
t = EasingHelper.EaseInOutQuad(t);
break;
case EasingType.EaseInOutCubic:
t = EasingHelper.EaseInOutCubic(t);
break;
case EasingType.EaseInOutQuart:
t = EasingHelper.EaseInOutQuart(t);
break;
case EasingType.EaseInOutQuint:
t = EasingHelper.EaseInOutQuint(t);
break;
case EasingType.EaseInOutExpo:
t = EasingHelper.EaseInOutExpo(t);
break;
case EasingType.EaseInOutCirc:
t = EasingHelper.EaseInOutCirc(t);
break;
case EasingType.EaseInOutBack:
t = EasingHelper.EaseInOutBack(t);
break;
case EasingType.EaseInOutElastic:
t = EasingHelper.EaseInOutElastic(t);
break;
case EasingType.EaseInOutBounce:
t = EasingHelper.EaseInOutBounce(t);
break;
case EasingType.SmoothStep:
t = EasingHelper.SmoothStep(t);
break;
case EasingType.Linear:
t = EasingHelper.Linear(t);
break;
default:
break;
}
return (T)(object)(s + (e - s) * t);
};
}
throw new NotSupportedException($"Easing type {type} is not supported for type {typeof(T)}.");
}
public void SetEasingType(EasingType easingType)
{
_easingType = easingType;
_interpolator = GetInterpolatorByEasingType(easingType);
}
}
}

View File

@@ -2,46 +2,21 @@
namespace BetterLyrics.WinUI3.Helper
{
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Storage.FileProperties;
/// <summary>
/// Defines the <see cref="AppInfo" />
/// </summary>
public static class AppInfo
{
#region Constants
/// <summary>
/// Defines the AppAuthor
/// </summary>
public const string AppAuthor = "Zhe Fang";
/// <summary>
/// Defines the AppDisplayName
/// </summary>
public const string AppDisplayName = "Better Lyrics";
// App Metadata
/// <summary>
/// Defines the AppName
/// </summary>
public const string AppName = "BetterLyrics";
/// <summary>
/// Defines the GithubUrl
/// </summary>
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
#endregion
#region Properties
/// <summary>
/// Gets the AppVersion
/// </summary>
public static string AppVersion
{
get
@@ -51,81 +26,90 @@ namespace BetterLyrics.WinUI3.Helper
}
}
/// <summary>
/// Gets the AssetsFolder
/// </summary>
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
/// <summary>
/// Gets the CacheFolder
/// </summary>
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
// Environment Info
// Data Files
public const string UnlockWindowTag = "UnlockWindow";
/// <summary>
/// Gets the LogDirectory
/// </summary>
public static string AmllTtmlDbIndexPath => Path.Combine(CacheFolder, "amll-ttml-db-index.json");
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
/// <summary>
/// Gets the LogFilePattern
/// </summary>
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
/// <summary>
/// Gets the OnlineLyricsCacheDirectory
/// </summary>
public static string LrcLibLyricsCacheDirectory =>
Path.Combine(CacheFolder, "lrclib-lyrics");
public static string AmllTtmlDbLyricsCacheDirectory =>
Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
public static string LrcLibLyricsCacheDirectory => Path.Combine(CacheFolder, "lrclib-lyrics");
public static string NeteaseLyricsCacheDirectory => Path.Combine(CacheFolder, "netease-lyrics");
public static string QQLyricsCacheDirectory => Path.Combine(CacheFolder, "qq-lyrics");
public static string KugouLyricsCacheDirectory => Path.Combine(CacheFolder, "kugou-lyrics");
public static string NeteaseLyricsCacheDirectory =>
Path.Combine(CacheFolder, "netease-lyrics");
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
public static string AmllTtmlDbIndexPath =>
Path.Combine(CacheFolder, "amll-ttml-db-index.json");
public static string iTunesAlbumArtCacheDirectory => Path.Combine(CacheFolder, "itunes-album-art");
/// <summary>
/// Gets the TestMusicPath
/// </summary>
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
// Base Folders
/// <summary>
/// Gets the LocalFolder
/// </summary>
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
/// <summary>
/// Gets the TestMusicFileName
/// </summary>
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
#endregion
#region Methods
/// <summary>
/// The EnsureDirectories
/// </summary>
public static void EnsureDirectories()
{
Directory.CreateDirectory(LocalFolder);
Directory.CreateDirectory(LogDirectory);
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
Directory.CreateDirectory(QQLyricsCacheDirectory);
Directory.CreateDirectory(KugouLyricsCacheDirectory);
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
}
#endregion
public static async Task<DateTime> GetBuildDate()
{
var assembly = Assembly.GetExecutingAssembly();
var filePath = assembly.Location;
if (!File.Exists(filePath))
return DateTime.MinValue;
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
// 获取文件基本属性
BasicProperties props = await file.GetBasicPropertiesAsync();
// 返回修改日期
return props.DateModified.DateTime;
}
public static List<LanguageInfo> GetAllTranslationLanguagesInfo() =>
[
new LanguageInfo("ar", "العربية"),
new LanguageInfo("az", "Azərbaycan dili"),
new LanguageInfo("zh", "中文"),
new LanguageInfo("cs", "Čeština"),
new LanguageInfo("da", "Dansk"),
new LanguageInfo("nl", "Nederlands"),
new LanguageInfo("en", "English"),
new LanguageInfo("eo", "Esperanto"),
new LanguageInfo("fi", "Suomi"),
new LanguageInfo("fr", "Français"),
new LanguageInfo("de", "Deutsch"),
new LanguageInfo("el", "Ελληνικά"),
new LanguageInfo("he", "עברית"),
new LanguageInfo("hi", "हिन्दी"),
new LanguageInfo("hu", "Magyar"),
new LanguageInfo("id", "Bahasa Indonesia"),
new LanguageInfo("ga", "Gaeilge"),
new LanguageInfo("it", "Italiano"),
new LanguageInfo("ja", "日本語"),
new LanguageInfo("ko", "한국어"),
new LanguageInfo("fa", "فارسی"),
new LanguageInfo("pl", "Polski"),
new LanguageInfo("pt", "Português"),
new LanguageInfo("ru", "Русский"),
new LanguageInfo("sk", "Slovenčina"),
new LanguageInfo("es", "Español"),
new LanguageInfo("sv", "Svenska"),
new LanguageInfo("tr", "Türkçe"),
new LanguageInfo("uk", "Українська"),
new LanguageInfo("vi", "Tiếng Việt"),
];
}
}

View File

@@ -1,34 +1,15 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="CollectionHelper" />
/// </summary>
public static class CollectionHelper
{
#region Methods
/// <summary>
/// The SafeGet
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list">The list<see cref="IList{T}"/></param>
/// <param name="index">The index<see cref="int"/></param>
/// <returns>The <see cref="T?"/></returns>
public static T? SafeGet<T>(this IList<T> list, int index)
{
if (list == null || index < 0 || index >= list.Count)
return default;
if (list == null || index < 0 || index >= list.Count) return default;
return list[index];
}
#endregion
}
}

View File

@@ -1,28 +1,51 @@
// 2025/6/23 by Zhe Fang
using System;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Windows.UI;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="ColorHelper" />
/// </summary>
public static class ColorHelper
{
#region Methods
public static ElementTheme GetElementThemeFromBackgroundColor(Color backgroundColor)
{
// 计算亮度YIQ公式
double yiq =
((backgroundColor.R * 299) + (backgroundColor.G * 587) + (backgroundColor.B * 114))
/ 1000.0;
return yiq >= 128 ? ElementTheme.Light : ElementTheme.Dark;
}
/// <summary>
/// The GetInterpolatedColor
/// </summary>
/// <param name="progress">The progress<see cref="float"/></param>
/// <param name="startColor">The startColor<see cref="Color"/></param>
/// <param name="targetColor">The targetColor<see cref="Color"/></param>
/// <returns>The <see cref="Color"/></returns>
public static Color GetInterpolatedColor(
float progress,
Color startColor,
Color targetColor
)
public static Color GetForegroundColor(Color background)
{
// 转为 HSL
var hsl = CommunityToolkit.WinUI.Helpers.ColorHelper.ToHsl(background);
double h = hsl.H;
double s = hsl.S;
double l = hsl.L;
// 目标亮度与背景错开,但不极端
double targetL;
if (l >= 0.7)
targetL = 0.35; // 背景很亮,前景适中偏暗
else if (l <= 0.3)
targetL = 0.75; // 背景很暗,前景适中偏亮
else
targetL = l > 0.5 ? l - 0.35 : l + 0.35; // 其余情况适度错开
// 保持色相,适当提升饱和度
double targetS = Math.Min(1.0, s + 0.2);
// 转回 Color
var fg = CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, targetS, targetL);
// 保持不透明
return Color.FromArgb(255, fg.R, fg.G, fg.B);
}
public static Color GetInterpolatedColor(float progress, Color startColor, Color targetColor)
{
byte Lerp(byte a, byte b) => (byte)(a + (progress * (b - a)));
return Color.FromArgb(
@@ -33,16 +56,47 @@ namespace BetterLyrics.WinUI3.Helper
);
}
/// <summary>
/// The ToWindowsUIColor
/// </summary>
/// <param name="color">The color<see cref="System.Drawing.Color"/></param>
/// <returns>The <see cref="Windows.UI.Color"/></returns>
public static Windows.UI.Color ToWindowsUIColor(this System.Drawing.Color color)
public static Color ToColor(this int argb)
{
return Windows.UI.Color.FromArgb(color.A, color.R, color.G, color.B);
byte a = (byte)(argb >> 24);
byte r = (byte)(argb >> 16);
byte g = (byte)(argb >> 8);
byte b = (byte)argb;
// 还原非预乘分量
if (a == 0)
return Color.FromArgb(0, 0, 0, 0);
// 预乘解码
// 这里 a+1 是编码时的分母
int ap1 = a + 1;
r = (byte)Math.Min(255, (r * 255 + (ap1 / 2)) / ap1);
g = (byte)Math.Min(255, (g * 255 + (ap1 / 2)) / ap1);
b = (byte)Math.Min(255, (b * 255 + (ap1 / 2)) / ap1);
return Color.FromArgb(a, r, g, b);
}
#endregion
public static Color ToColor(this System.Drawing.Color color)
{
return Color.FromArgb(color.A, color.R, color.G, color.B);
}
public static Color WithAlpha(this Color color, byte alpha)
{
return Color.FromArgb(alpha, color.R, color.G, color.B);
}
public static Color WithBrightness(this Color color, double brightness)
{
// 确保亮度因子在合理范围内
brightness = Math.Max(0, Math.Min(1, brightness));
var hsl = CommunityToolkit.WinUI.Helpers.ColorHelper.ToHsl(color);
double h = hsl.H;
double s = hsl.S;
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, brightness);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,11 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.UI.Xaml;
using Vanara.PInvoke;
using WinRT.Interop;
using WinUIEx;
@@ -9,65 +13,15 @@ namespace BetterLyrics.WinUI3.Helper
{
public static class DesktopModeHelper
{
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private static readonly Dictionary<IntPtr, bool> _clickThroughStates = [];
private static readonly Dictionary<IntPtr, bool> _originalTopmostStates = [];
private static readonly Dictionary<
IntPtr,
(double X, double Y, double Width, double Height)
> _originalWindowBounds = [];
private static readonly Dictionary<IntPtr, (double X, double Y, double Width, double Height)> _originalWindowBounds = [];
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
// <20><><EFBFBD><EFBFBD><E0BBAF><EFBFBD><EFBFBD>
private delegate nint WndProcDelegate(nint hWnd, uint msg, nint wParam, nint lParam);
public static void Enable(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// <20><>¼ԭʼ<D4AD><CABC><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
var windowManager = WindowManager.Get(window);
if (!_originalWindowBounds.ContainsKey(hwnd))
{
_originalWindowBounds[hwnd] = (
windowManager.AppWindow.Position.X,
windowManager.AppWindow.Position.Y,
windowManager.Width,
windowManager.Height
);
}
// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
var displayArea = Microsoft.UI.Windowing.DisplayArea.GetFromWindowId(
windowManager.AppWindow.Id,
Microsoft.UI.Windowing.DisplayAreaFallback.Primary
);
var workArea = displayArea.WorkArea;
// <20><><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD>λ<EFBFBD><CEBB>
int targetWidth = workArea.Width / 3;
int targetHeight = workArea.Height / 4;
int targetX = workArea.X + (workArea.Width - targetWidth) / 2; // <20><><EFBFBD><EFBFBD>
int targetY = workArea.Y + workArea.Height - targetHeight - 64;
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
windowManager.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
);
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
if (!_originalWindowStyles.ContainsKey(hwnd))
_originalWindowStyles[hwnd] = window.GetWindowStyle();
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
if (!_originalTopmostStates.ContainsKey(hwnd))
_originalTopmostStates[hwnd] = window.GetIsAlwaysOnTop();
// <20><><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD>ö<EFBFBD>
window.SetIsAlwaysOnTop(true);
window.SetIsShownInSwitchers(false);
}
public static void Disable(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
@@ -80,10 +34,9 @@ namespace BetterLyrics.WinUI3.Helper
}
// <20>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
var windowManager = WindowManager.Get(window);
if (_originalWindowBounds.TryGetValue(hwnd, out var bounds))
{
windowManager.AppWindow.MoveAndResize(
window.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(
(int)bounds.X,
(int)bounds.Y,
@@ -104,17 +57,73 @@ namespace BetterLyrics.WinUI3.Helper
window.SetIsShownInSwitchers(true);
}
public static void Lock(Window window)
public static void Enable(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// <20><>¼ԭʼ<D4AD><CABC><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
if (!_originalWindowBounds.ContainsKey(hwnd))
{
_originalWindowBounds[hwnd] = (
window.AppWindow.Position.X,
window.AppWindow.Position.Y,
window.AppWindow.Size.Width,
window.AppWindow.Size.Height
);
}
// <20>Ӵ洢<D3B4><E6B4A2><EFBFBD><EFBFBD>ȡĿ<C8A1><C4BF><EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD>λ<EFBFBD><CEBB>
int targetWidth = _settingsService.DesktopWindowWidth;
int targetHeight = _settingsService.DesktopWindowHeight;
int targetX = _settingsService.DesktopWindowLeft;
int targetY = _settingsService.DesktopWindowTop;
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
window.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
);
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
if (!_originalWindowStyles.ContainsKey(hwnd))
_originalWindowStyles[hwnd] = window.GetWindowStyle();
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
if (!_originalTopmostStates.ContainsKey(hwnd))
_originalTopmostStates[hwnd] = window.GetIsAlwaysOnTop();
// <20><><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD>ö<EFBFBD>
window.SetIsAlwaysOnTop(true);
window.SetIsShownInSwitchers(false);
}
public static void Lock(Window window)
{
window.SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
// <20><><EFBFBD><EFBFBD><EFBFBD>ޱ߿<DEB1><DFBF><EFBFBD>͸<EFBFBD><CDB8>
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
window.ExtendsContentIntoTitleBar = false;
SetClickThrough(window, true);
}
public static void SetClickThrough(Window window, bool enable)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
int exStyle = User32.GetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE);
if (enable)
{
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle | (int)User32.WindowStylesEx.WS_EX_TRANSPARENT | (int)User32.WindowStylesEx.WS_EX_LAYERED);
_clickThroughStates[hwnd] = true;
}
else
{
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle & ~(int)User32.WindowStylesEx.WS_EX_TRANSPARENT);
_clickThroughStates[hwnd] = false;
}
}
public static void Unlock(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
@@ -127,39 +136,9 @@ namespace BetterLyrics.WinUI3.Helper
window.ExtendsContentIntoTitleBar = true;
SetClickThrough(window, false);
// To recover the system backdrop, we need to reopen the window
WindowHelper.RestartApp(AppInfo.UnlockWindowTag);
}
/// <summary>
/// <20>л<EFBFBD><D0BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͸״̬
/// </summary>
public static void SetClickThrough(Window window, bool enable)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
if (enable)
{
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TRANSPARENT | WS_EX_LAYERED);
_clickThroughStates[hwnd] = true;
}
else
{
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TRANSPARENT);
_clickThroughStates[hwnd] = false;
}
}
#region Win32
private const int GWL_EXSTYLE = -20;
private const int WS_EX_TRANSPARENT = 0x00000020;
private const int WS_EX_LAYERED = 0x00080000;
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
#endregion
}
}

View File

@@ -1,11 +1,12 @@
using System;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using Vanara.PInvoke;
using WinRT.Interop;
using WinUIEx;
@@ -14,9 +15,7 @@ namespace BetterLyrics.WinUI3.Helper
public static class DockModeHelper
{
private static readonly HashSet<IntPtr> _registered = [];
private static readonly Dictionary<IntPtr, RECT> _originalPositions = [];
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyle = [];
public static void Disable(Window window)
@@ -32,14 +31,14 @@ namespace BetterLyrics.WinUI3.Helper
if (_originalPositions.TryGetValue(hwnd, out var rect))
{
SetWindowPos(
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
rect.Left,
rect.Top,
rect.Right - rect.Left,
rect.Bottom - rect.Top,
User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
_originalPositions.Remove(hwnd);
}
@@ -63,7 +62,7 @@ namespace BetterLyrics.WinUI3.Helper
if (!_originalPositions.ContainsKey(hwnd))
{
if (GetWindowRect(hwnd, out var rect))
if (User32.GetWindowRect(hwnd, out var rect))
{
_originalPositions[hwnd] = rect;
}
@@ -71,72 +70,39 @@ namespace BetterLyrics.WinUI3.Helper
RegisterAppBar(hwnd, appBarHeight);
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
SetWindowPos(
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
0,
0,
screenWidth,
appBarHeight,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
#region AppBar registration
private const uint ABM_NEW = 0x00000000;
private const uint ABM_REMOVE = 0x00000001;
private const uint ABM_SETPOS = 0x00000003;
private const int ABE_TOP = 1;
[StructLayout(LayoutKind.Sequential)]
private struct APPBARDATA
{
public int cbSize;
public IntPtr hWnd;
public uint uCallbackMessage;
public uint uEdge;
public RECT rc;
public int lParam;
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left,
top,
right,
bottom;
}
[DllImport("shell32.dll", SetLastError = true)]
private static extern uint SHAppBarMessage(uint dwMessage, ref APPBARDATA pData);
private static void RegisterAppBar(IntPtr hwnd, int height)
{
if (_registered.Contains(hwnd))
return;
if (_registered.Contains(hwnd)) return;
APPBARDATA abd = new()
Shell32.APPBARDATA abd = new()
{
cbSize = Marshal.SizeOf<APPBARDATA>(),
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd,
uEdge = ABE_TOP,
uEdge = Shell32.ABE.ABE_TOP,
rc = new RECT
{
left = 0,
top = 0,
right = GetSystemMetrics(SM_CXSCREEN),
bottom = height,
Left = 0,
Top = 0,
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
Bottom = height,
},
};
SHAppBarMessage(ABM_NEW, ref abd);
SHAppBarMessage(ABM_SETPOS, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_NEW, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
_registered.Add(hwnd);
}
@@ -146,73 +112,47 @@ namespace BetterLyrics.WinUI3.Helper
if (!_registered.Contains(hwnd))
return;
APPBARDATA abd = new() { cbSize = Marshal.SizeOf<APPBARDATA>(), hWnd = hwnd };
Shell32.APPBARDATA abd = new()
{
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd
};
SHAppBarMessage(ABM_REMOVE, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
_registered.Remove(hwnd);
}
#endregion
#region Win32 Helper and Constants
private const int SWP_NOACTIVATE = 0x0010;
private const int SWP_NOOWNERZORDER = 0x0200;
private const int SWP_SHOWWINDOW = 0x0040;
private const int SM_CXSCREEN = 0;
private const int SM_CYSCREEN = 0;
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(
IntPtr hWnd,
IntPtr hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
uint uFlags
);
/// <summary>
/// 更改已注册 AppBar 的高度。
/// </summary>
/// <param name="window">目标窗口</param>
/// <param name="newHeight">新的高度</param>
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight)
{
if (!_registered.Contains(hwnd))
return;
APPBARDATA abd = new()
Shell32.APPBARDATA abd = new()
{
cbSize = Marshal.SizeOf<APPBARDATA>(),
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
hWnd = hwnd,
uEdge = ABE_TOP,
uEdge = Shell32.ABE.ABE_TOP,
rc = new RECT
{
left = 0,
top = 0,
right = GetSystemMetrics(SM_CXSCREEN),
bottom = newHeight,
Left = 0,
Top = 0,
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
Bottom = newHeight,
},
};
SHAppBarMessage(ABM_SETPOS, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
// 同步窗口实际高度
SetWindowPos(
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
0,
0,
GetSystemMetrics(SM_CXSCREEN),
User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
newHeight,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
}
#endregion
}
}
}

View File

@@ -8,64 +8,105 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="EasingHelper" />
/// </summary>
public class EasingHelper
{
#region Methods
/// <summary>
/// Acceleration until halfway then deceleration
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float EaseInOutSine(float t)
{
return -(MathF.Cos(MathF.PI * t) - 1f) / 2f;
}
public static float EaseInOutQuad(float t)
{
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
/// <summary>
/// Accelerating from 0
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float EaseInQuad(float t) => t * t;
/// <summary>
/// Decelerating to 0
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float EaseOutQuad(float t) => t * (2 - t);
/// <summary>
/// No easing
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float Linear(float t) => t;
/// <summary>
/// Even smoother transition with continuous first and second derivatives
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float SmootherStep(float t)
public static float EaseInOutCubic(float t)
{
return t * t * t * (t * (6 * t - 15) + 10);
return t < 0.5f ? 4 * t * t * t : 1 - MathF.Pow(-2 * t + 2, 3) / 2;
}
public static float EaseInOutQuart(float t)
{
return t < 0.5f ? 8 * t * t * t * t : 1 - MathF.Pow(-2 * t + 2, 4) / 2;
}
public static float EaseInOutQuint(float t)
{
return t < 0.5f ? 16 * t * t * t * t * t : 1 - MathF.Pow(-2 * t + 2, 5) / 2;
}
public static float EaseInOutExpo(float t)
{
return t == 0
? 0
: t == 1
? 1
: t < 0.5 ? MathF.Pow(2, 20 * t - 10) / 2
: (2 - MathF.Pow(2, -20 * t + 10)) / 2;
}
public static float EaseInOutCirc(float t)
{
return t < 0.5f
? (1 - MathF.Sqrt(1 - MathF.Pow(2 * t, 2))) / 2
: (MathF.Sqrt(1 - MathF.Pow(-2 * t + 2, 2)) + 1) / 2;
}
public static float EaseInOutBack(float t)
{
float c1 = 1.70158f;
float c2 = c1 * 1.525f;
return t < 0.5
? (MathF.Pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
: (MathF.Pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
}
public static float EaseInOutElastic(float t)
{
if (t == 0 || t == 1) return t;
float p = 0.3f;
float s = p / 4;
return t < 0.5f
? -(MathF.Pow(2, 20 * t - 10) * MathF.Sin((20 * t - 11.125f) * (2 * MathF.PI) / p)) / 2
: (MathF.Pow(2, -20 * t + 10) * MathF.Sin((20 * t - 11.125f) * (2 * MathF.PI) / p)) / 2 + 1;
}
private static float EaseOutBounce(float t)
{
if (t < 4 / 11f)
{
return (121 * t * t) / 16f;
}
else if (t < 8 / 11f)
{
return (363 / 40f * t * t) - (99 / 10f * t) + 17 / 5f;
}
else if (t < 9 / 10f)
{
return (4356 / 361f * t * t) - (35442 / 1805f * t) + 16061 / 1805f;
}
else
{
return (54 / 5f * t * t) - (513 / 25f * t) + 268 / 25f;
}
}
public static float EaseInOutBounce(float t)
{
if (t < 0.5f)
{
return (1 - EaseOutBounce(1 - 2 * t)) / 2;
}
else
{
return (1 + EaseOutBounce(2 * t - 1)) / 2;
}
}
/// <summary>
/// Smoother transition than linear
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float SmoothStep(float t)
{
return t * t * (3 - 2 * t);
return t * t * (3f - 2f * t);
}
#endregion
public static float Linear(float t) => t;
}
}

View File

@@ -1,27 +1,13 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ude;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="FileHelper" />
/// </summary>
public class FileHelper
{
#region Methods
/// <summary>
/// The GetEncoding
/// </summary>
/// <param name="filename">The filename<see cref="string"/></param>
/// <returns>The <see cref="Encoding"/></returns>
public static Encoding GetEncoding(string filename)
{
var bytes = File.ReadAllBytes(filename);
@@ -35,7 +21,5 @@ namespace BetterLyrics.WinUI3.Helper
}
return Encoding.GetEncoding(encoding);
}
#endregion
}
}

View File

@@ -5,32 +5,29 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using Vanara.PInvoke;
using Windows.System;
namespace BetterLyrics.WinUI3.Helper
{
public class ForegroundWindowWatcherHelper
{
private readonly WinEventDelegate _winEventDelegate;
private readonly List<IntPtr> _hooks = new();
private IntPtr _currentForeground = IntPtr.Zero;
private readonly User32.WinEventProc _winEventDelegate;
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
private HWND _currentForeground = HWND.NULL;
private readonly IntPtr _selfHwnd;
private readonly DispatcherTimer _pollingTimer;
private DateTime _lastEventTime = DateTime.MinValue;
private const int ThrottleIntervalMs = 100;
private const int ThrottleIntervalMs = 1000;
public delegate void WindowChangedHandler(IntPtr hwnd);
public delegate void WindowChangedHandler(HWND hwnd);
private readonly WindowChangedHandler _onWindowChanged;
private const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
private const uint EVENT_SYSTEM_MINIMIZEEND = 0x0017;
private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
public ForegroundWindowWatcherHelper(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
{
_selfHwnd = selfHwnd;
_onWindowChanged = onWindowChanged;
_winEventDelegate = new WinEventDelegate(WinEventProc);
_winEventDelegate = new User32.WinEventProc(WinEventProc);
_pollingTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
_pollingTimer.Tick += (_, _) =>
@@ -44,27 +41,27 @@ namespace BetterLyrics.WinUI3.Helper
{
// Hook: foreground changes and minimize end
_hooks.Add(
SetWinEventHook(
EVENT_SYSTEM_FOREGROUND,
EVENT_SYSTEM_MINIMIZEEND,
IntPtr.Zero,
User32.SetWinEventHook(
User32.EventConstants.EVENT_SYSTEM_FOREGROUND,
User32.EventConstants.EVENT_SYSTEM_MINIMIZEEND,
HINSTANCE.NULL,
_winEventDelegate,
0,
0,
WINEVENT_OUTOFCONTEXT
User32.WINEVENT.WINEVENT_OUTOFCONTEXT
)
);
// Hook: window move/resize (location change)
_hooks.Add(
SetWinEventHook(
EVENT_OBJECT_LOCATIONCHANGE,
EVENT_OBJECT_LOCATIONCHANGE,
IntPtr.Zero,
User32.SetWinEventHook(
User32.EventConstants.EVENT_OBJECT_LOCATIONCHANGE,
User32.EventConstants.EVENT_OBJECT_LOCATIONCHANGE,
HINSTANCE.NULL,
_winEventDelegate,
0,
0,
WINEVENT_OUTOFCONTEXT
User32.WINEVENT.WINEVENT_OUTOFCONTEXT
)
);
@@ -74,16 +71,16 @@ namespace BetterLyrics.WinUI3.Helper
public void Stop()
{
foreach (var hook in _hooks)
UnhookWinEvent(hook);
User32.UnhookWinEvent(hook);
_hooks.Clear();
_pollingTimer.Stop();
}
private void WinEventProc(
IntPtr hWinEventHook,
User32.HWINEVENTHOOK hWinEventHook,
uint eventType,
IntPtr hwnd,
HWND hwnd,
int idObject,
int idChild,
uint dwEventThread,
@@ -99,44 +96,15 @@ namespace BetterLyrics.WinUI3.Helper
_lastEventTime = now;
if (eventType == EVENT_SYSTEM_FOREGROUND)
if (eventType == User32.EventConstants.EVENT_SYSTEM_FOREGROUND)
{
_currentForeground = hwnd;
_onWindowChanged?.Invoke(hwnd);
}
else if (
(eventType == EVENT_OBJECT_LOCATIONCHANGE || eventType == EVENT_SYSTEM_MINIMIZEEND)
&& hwnd == _currentForeground
)
else if ((eventType == User32.EventConstants.EVENT_OBJECT_LOCATIONCHANGE || eventType == User32.EventConstants.EVENT_SYSTEM_MINIMIZEEND) && hwnd == _currentForeground)
{
_onWindowChanged?.Invoke(hwnd);
}
}
#region WinAPI
private delegate void WinEventDelegate(
IntPtr hWinEventHook,
uint eventType,
IntPtr hwnd,
int idObject,
int idChild,
uint dwEventThread,
uint dwmsEventTime
);
[DllImport("user32.dll")]
private static extern IntPtr SetWinEventHook(
uint eventMin,
uint eventMax,
IntPtr hmodWinEventProc,
WinEventDelegate lpfnWinEventProc,
uint idProcess,
uint idThread,
uint dwFlags
);
[DllImport("user32.dll")]
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
#endregion
}
}

View File

@@ -1,11 +1,5 @@
// 2025/6/23 by Zhe Fang
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.IO;
@@ -13,43 +7,20 @@ using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="ImageHelper" />
/// </summary>
public class ImageHelper
{
#region Constants
/// <summary>
/// Defines the AccentColorCount
/// </summary>
public const int AccentColorCount = 3;
#endregion
#region Fields
/// <summary>
/// Defines the _colorThief
/// </summary>
private static readonly ColorThief _colorThief = new();
#endregion
#region Methods
/// <summary>
/// The ByteArrayToStream
/// </summary>
/// <param name="bytes">The bytes<see cref="byte[]"/></param>
/// <returns>The <see cref="Task{InMemoryRandomAccessStream}"/></returns>
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
{
var stream = new InMemoryRandomAccessStream();
@@ -59,18 +30,7 @@ namespace BetterLyrics.WinUI3.Helper
return stream;
}
/// <summary>
/// The CreateTextPlaceholderBytesAsync
/// </summary>
/// <param name="text">The text<see cref="string"/></param>
/// <param name="width">The width<see cref="int"/></param>
/// <param name="height">The height<see cref="int"/></param>
/// <returns>The <see cref="Task{byte[]}"/></returns>
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(
string text,
int width,
int height
)
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(string text, int width, int height)
{
var device = CanvasDevice.GetSharedDevice();
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
@@ -142,25 +102,42 @@ namespace BetterLyrics.WinUI3.Helper
}
}
/// <summary>
/// The GetAccentColorsFromByte
/// </summary>
/// <param name="bytes">The bytes<see cref="byte[]"/></param>
/// <returns>The <see cref="Task{List{Color}}"/></returns>
public static async Task<List<Color>> GetAccentColorsFromByte(byte[] bytes) =>
[
.. (
await _colorThief.GetPalette(await GetDecoderFromByte(bytes), AccentColorCount)
).Select(color =>
Color.FromArgb(color.Color.A, color.Color.R, color.Color.G, color.Color.B)
),
];
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes)
{
// 使用 ImageSharp 读取图片
using var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(bytes);
// 简单聚类法:统计所有像素出现频率,取出现最多的前 AccentColorCount 个颜色
var colorCount = new Dictionary<SixLabors.ImageSharp.PixelFormats.Rgba32, int>();
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
var color = image[x, y];
// 可选:忽略透明像素
if (color.A < 32) continue;
if (colorCount.ContainsKey(color))
colorCount[color]++;
else
colorCount[color] = 1;
}
}
// 按出现次数排序,取前 AccentColorCount 个
var topColors = colorCount
.OrderByDescending(kv => kv.Value)
.Take(AccentColorCount)
.Select(kv => kv.Key)
.ToList();
// 转换为 Windows.UI.Color
return topColors
.Select(c => Windows.UI.Color.FromArgb(c.A, c.R, c.G, c.B))
.ToList();
}
/// <summary>
/// The GetBitmapImageFromBytesAsync
/// </summary>
/// <param name="imageBytes">The imageBytes<see cref="byte[]"/></param>
/// <returns>The <see cref="Task{BitmapImage}"/></returns>
public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
{
var stream = new InMemoryRandomAccessStream();
@@ -173,22 +150,10 @@ namespace BetterLyrics.WinUI3.Helper
return bitmapImage;
}
/// <summary>
/// The GetDecoderFromByte
/// </summary>
/// <param name="bytes">The bytes<see cref="byte[]"/></param>
/// <returns>The <see cref="Task{BitmapDecoder}"/></returns>
public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
/// <summary>
/// The GetStreamFromBytesAsync
/// </summary>
/// <param name="imageBytes">The imageBytes<see cref="byte[]"/></param>
/// <returns>The <see cref="Task{InMemoryRandomAccessStream}"/></returns>
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(
byte[] imageBytes
)
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
{
if (imageBytes == null || imageBytes.Length == 0)
return null;
@@ -199,11 +164,6 @@ namespace BetterLyrics.WinUI3.Helper
return stream;
}
/// <summary>
/// The ToByteArrayAsync
/// </summary>
/// <param name="streamRef">The streamRef<see cref="IRandomAccessStreamReference"/></param>
/// <returns>The <see cref="Task{byte[]}"/></returns>
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
{
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
@@ -212,6 +172,21 @@ namespace BetterLyrics.WinUI3.Helper
return memoryStream.ToArray();
}
#endregion
public static float GetAverageLuminance(CanvasBitmap bitmap)
{
var pixels = bitmap.GetPixelBytes();
double sum = 0;
for (int i = 0; i < pixels.Length; i += 4)
{
// BGRA
byte b = pixels[i];
byte g = pixels[i + 1];
byte r = pixels[i + 2];
// 忽略A
double y = 0.299 * r + 0.587 * g + 0.114 * b;
sum += y / 255.0;
}
return (float)(sum / (pixels.Length / 4));
}
}
}

View File

@@ -1,84 +1,64 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using Lyricify.Lyrics.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using Lyricify.Lyrics.Models;
using Microsoft.UI.Xaml.Shapes;
using Windows.Globalization.Fonts;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="LyricsParser" />
/// </summary>
public class LyricsParser
{
#region Fields
/// <summary>
/// Defines the _multiLangLyricsLines
/// </summary>
private List<List<LyricsLine>> _multiLangLyricsLines = [];
#endregion
#region Methods
/// <summary>
/// The Parse
/// </summary>
/// <param name="raw">The raw<see cref="string"/></param>
/// <param name="lyricsFormat">The lyricsFormat<see cref="LyricsFormat?"/></param>
/// <param name="title">The title<see cref="string?"/></param>
/// <param name="artist">The artist<see cref="string?"/></param>
/// <param name="durationMs">The durationMs<see cref="int"/></param>
/// <returns>The <see cref="List{List{LyricsLine}}"/></returns>
public List<List<LyricsLine>> Parse(
string raw,
LyricsFormat? lyricsFormat = null,
string? title = null,
string? artist = null,
int durationMs = 0
)
public List<List<LyricsLine>> Parse(string? raw, int durationMs)
{
_multiLangLyricsLines = [];
switch (lyricsFormat)
if (raw == null)
{
case LyricsFormat.Lrc:
case LyricsFormat.Eslrc:
ParseLrc(raw, durationMs);
break;
case LyricsFormat.Qrc:
ParseUsingLyricify(
Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines,
durationMs
);
break;
case LyricsFormat.Krc:
ParseUsingLyricify(
Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines,
durationMs
);
break;
case LyricsFormat.Ttml:
ParseTtml(raw, durationMs);
break;
default:
break;
_multiLangLyricsLines.Add(
[
new LyricsLine
{
StartMs = 0,
EndMs = durationMs,
OriginalText = App.ResourceLoader!.GetString("LyricsNotFound"),
CharTimings = [],
},
]
);
}
else
{
switch (raw.DetectFormat())
{
case LyricsFormat.Lrc:
case LyricsFormat.Eslrc:
ParseLrc(raw);
break;
case LyricsFormat.Qrc:
ParseUsingLyricify(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
break;
case LyricsFormat.Krc:
ParseUsingLyricify(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
break;
case LyricsFormat.Ttml:
ParseTtml(raw);
break;
default:
break;
}
}
PostProcessLyricsLines(durationMs);
return _multiLangLyricsLines;
}
/// <summary>
/// The ParseLrc
/// </summary>
/// <param name="raw">The raw<see cref="string"/></param>
/// <param name="durationMs">The durationMs<see cref="int"/></param>
private void ParseLrc(string raw, int durationMs)
private void ParseLrc(string raw)
{
var lines = raw.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
var lrcLines =
@@ -156,7 +136,7 @@ namespace BetterLyrics.WinUI3.Helper
{
StartMs = start,
EndMs = 0, // 稍后统一修正
Text = text,
OriginalText = text,
CharTimings = [],
};
if (syllables != null && syllables.Count > 0)
@@ -181,120 +161,16 @@ namespace BetterLyrics.WinUI3.Helper
_multiLangLyricsLines[langIdx].Add(line);
}
}
// 修正 EndMs
for (int langIdx = 0; langIdx < languageCount; langIdx++)
{
var linesInSingleLang = _multiLangLyricsLines[langIdx];
for (int i = 0; i < linesInSingleLang.Count; i++)
{
if (i + 1 < linesInSingleLang.Count)
{
linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs;
}
else
{
linesInSingleLang[i].EndMs = durationMs;
}
// 修正 CharTimings 的 EndMs
var timings = linesInSingleLang[i].CharTimings;
if (timings.Count > 0)
{
for (int j = 0; j < timings.Count; j++)
{
if (j + 1 < timings.Count)
{
timings[j].EndMs = timings[j + 1].StartMs;
}
else
{
timings[j].EndMs = linesInSingleLang[i].EndMs;
}
}
}
}
PostProcessLyricsLines(linesInSingleLang);
}
}
private void ParseUsingLyricify(List<ILineInfo>? lines, int durationMs)
{
List<LyricsLine> lyricsLines = [];
if (lines != null && lines.Count > 0)
{
lyricsLines = [];
for (int lineIndex = 0; lineIndex < lines.Count; lineIndex++)
{
var lineRead = lines[lineIndex];
var lineWrite = new LyricsLine
{
StartMs = lineRead.StartTime ?? 0,
Text = lineRead.Text,
CharTimings = [],
};
if (lineIndex + 1 < lines.Count)
{
lineWrite.EndMs = lines[lineIndex + 1].StartTime ?? 0;
}
else
{
lineWrite.EndMs = durationMs;
}
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
if (syllables != null)
{
int startIndex = 0;
for (
int syllableIndex = 0;
syllableIndex < syllables.Count;
syllableIndex++
)
{
var syllable = syllables[syllableIndex];
var charTiming = new CharTiming
{
StartMs = syllable.StartTime,
Text = syllable.Text,
StartIndex = startIndex,
};
if (syllableIndex + 1 < syllables.Count)
{
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
}
else
{
charTiming.EndMs = lineWrite.EndMs;
}
lineWrite.CharTimings.Add(charTiming);
startIndex += syllable.Text.Length;
}
}
lyricsLines.Add(lineWrite);
}
}
_multiLangLyricsLines.Add(lyricsLines);
}
/// <summary>
/// The ParseTtml
/// </summary>
/// <param name="raw">The raw<see cref="string"/></param>
/// <param name="durationMs">The durationMs<see cref="int"/></param>
private void ParseTtml(string raw, int durationMs)
private void ParseTtml(string raw)
{
try
{
List<LyricsLine> singleLangLyricsLine = [];
var xdoc = XDocument.Parse(raw);
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
if (body == null)
return;
if (body == null) return;
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
foreach (var p in ps)
{
@@ -305,17 +181,13 @@ namespace BetterLyrics.WinUI3.Helper
int pEndMs = ParseTtmlTime(pEnd);
// 处理分词分时
var spans = p.Elements()
.Where(s =>
s.Name.LocalName == "span"
&& s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))
== null
)
.ToList();
var spans = p.Elements().Where(s => s.Name.LocalName == "span").ToList();
string text = string.Concat(spans.Select(s => s));
string text = string.Concat(spans.Select(s => s.Value));
var charTimings = new List<CharTiming>();
int startIndex = 0;
for (int i = 0; i < spans.Count; i++)
{
var span = spans[i];
@@ -333,7 +205,8 @@ namespace BetterLyrics.WinUI3.Helper
? ParseTtmlTime(spans[i + 1].Attribute("begin")?.Value)
: pEndMs;
charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = sEndMs });
charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = 0, StartIndex = startIndex, Text = span.Value });
startIndex += span.Value.Length;
}
if (spans.Count == 0)
@@ -343,13 +216,12 @@ namespace BetterLyrics.WinUI3.Helper
new LyricsLine
{
StartMs = pStartMs,
EndMs = pEndMs,
Text = text,
EndMs = 0,
OriginalText = text,
CharTimings = charTimings,
}
);
}
PostProcessLyricsLines(singleLangLyricsLine);
_multiLangLyricsLines.Add(singleLangLyricsLine);
}
catch
@@ -358,11 +230,6 @@ namespace BetterLyrics.WinUI3.Helper
}
}
/// <summary>
/// The ParseTtmlTime
/// </summary>
/// <param name="t">The t<see cref="string?"/></param>
/// <returns>The <see cref="int"/></returns>
private int ParseTtmlTime(string? t)
{
if (string.IsNullOrWhiteSpace(t))
@@ -424,27 +291,110 @@ namespace BetterLyrics.WinUI3.Helper
return 0;
}
/// <summary>
/// The PostProcessLyricsLines
/// </summary>
/// <param name="lines">The lines<see cref="List{LyricsLine}"/></param>
private void PostProcessLyricsLines(List<LyricsLine> lines)
private void ParseUsingLyricify(List<ILineInfo>? lines)
{
if (lines.Count > 0 && lines[0].StartMs > 0)
lines = lines?.Where(x => x.Text != string.Empty).ToList();
List<LyricsLine> lyricsLines = [];
if (lines != null && lines.Count > 0)
{
lines.Insert(
0,
new LyricsLine
lyricsLines = [];
for (int lineIndex = 0; lineIndex < lines.Count; lineIndex++)
{
var lineRead = lines[lineIndex];
var lineWrite = new LyricsLine
{
StartMs = 0,
EndMs = lines[0].StartMs,
Text = "● ● ●",
StartMs = lineRead.StartTime ?? 0,
EndMs = 0,
OriginalText = lineRead.Text,
CharTimings = [],
};
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
if (syllables != null)
{
int startIndex = 0;
for (
int syllableIndex = 0;
syllableIndex < syllables.Count;
syllableIndex++
)
{
var syllable = syllables[syllableIndex];
var charTiming = new CharTiming
{
StartMs = syllable.StartTime,
EndMs = 0,
Text = syllable.Text,
StartIndex = startIndex,
};
if (syllableIndex + 1 < syllables.Count)
{
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
}
else
{
charTiming.EndMs = lineWrite.EndMs;
}
lineWrite.CharTimings.Add(charTiming);
startIndex += syllable.Text.Length;
}
}
);
lyricsLines.Add(lineWrite);
}
}
_multiLangLyricsLines.Add(lyricsLines);
}
#endregion
private void PostProcessLyricsLines(int durationMs)
{
for (int langIdx = 0; langIdx < _multiLangLyricsLines.Count; langIdx++)
{
var linesInSingleLang = _multiLangLyricsLines[langIdx];
for (int i = 0; i < linesInSingleLang.Count; i++)
{
if (i + 1 < linesInSingleLang.Count)
{
linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs;
}
else
{
linesInSingleLang[i].EndMs = durationMs;
}
// 修正 CharTimings 的 EndMs
var timings = linesInSingleLang[i].CharTimings;
if (timings.Count > 0)
{
for (int j = 0; j < timings.Count; j++)
{
if (j + 1 < timings.Count)
{
timings[j].EndMs = timings[j + 1].StartMs;
}
else
{
timings[j].EndMs = linesInSingleLang[i].EndMs;
}
}
}
}
if (linesInSingleLang.Count > 0 && linesInSingleLang[0].StartMs > 0)
{
linesInSingleLang.Insert(
0,
new LyricsLine
{
StartMs = 0,
EndMs = linesInSingleLang[0].StartMs,
OriginalText = "● ● ●",
CharTimings = [],
}
);
}
}
}
}
}

View File

@@ -6,18 +6,8 @@ using Microsoft.UI.Xaml.Media;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="SystemBackdropHelper" />
/// </summary>
public class SystemBackdropHelper
{
#region Methods
/// <summary>
/// The CreateSystemBackdrop
/// </summary>
/// <param name="backdropType">The backdropType<see cref="BackdropType"/></param>
/// <returns>The <see cref="SystemBackdrop?"/></returns>
public static SystemBackdrop? CreateSystemBackdrop(BackdropType backdropType)
{
return backdropType switch
@@ -30,7 +20,5 @@ namespace BetterLyrics.WinUI3.Helper
_ => null,
};
}
#endregion
}
}

View File

@@ -1,22 +1,111 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml;
using Vanara.PInvoke;
namespace BetterLyrics.WinUI3.Helper
{
public static class WindowColorHelper
{
public static Color GetDominantColorBelow(IntPtr myHwnd)
public static Color GetDominantColor(IntPtr myHwnd, WindowColorSampleMode mode)
{
if (!GetWindowRect(myHwnd, out RECT myRect))
return Color.Transparent;
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return Color.Transparent;
int screenWidth = GetSystemMetrics(SystemMetric.SM_CXSCREEN);
int sampleHeight = 1;
int sampleY = myRect.Bottom + 1;
switch (mode)
{
case WindowColorSampleMode.BelowWindow:
{
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
int sampleHeight = 1;
int sampleY = myRect.Bottom + 1;
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
}
case WindowColorSampleMode.WindowArea:
{
int width = myRect.Right - myRect.Left;
int height = myRect.Bottom - myRect.Top;
if (width <= 0 || height <= 0)
return Color.Transparent;
// 采集窗口区域的平均色
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top, width, height);
}
case WindowColorSampleMode.WindowEdge:
{
int width = myRect.Right - myRect.Left;
int height = myRect.Bottom - myRect.Top;
if (width <= 0 || height <= 0)
return Color.Transparent;
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
var edgeThickness = new Thickness(36, 0, 36, 0);
List<Color> edgeColors = [];
// Top edge
if (edgeThickness.Top > 0 && edgeThickness.Top < height)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Top,
width,
(int)edgeThickness.Top
)
);
// Bottom edge
if (edgeThickness.Bottom > 0 && edgeThickness.Bottom < height)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Bottom - (int)edgeThickness.Bottom,
width,
(int)edgeThickness.Bottom
)
);
// Left edge
if (edgeThickness.Left > 0 && edgeThickness.Left < width)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Left,
myRect.Top + (int)edgeThickness.Top,
(int)edgeThickness.Left,
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
)
);
// Right edge
if (edgeThickness.Right > 0 && edgeThickness.Right < width)
edgeColors.Add(
GetAverageColorFromScreenRegion(
myRect.Right - (int)edgeThickness.Right,
myRect.Top + (int)edgeThickness.Top,
(int)edgeThickness.Right,
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
)
);
// 合并四边平均色
if (edgeColors.Count == 0)
return Color.Transparent;
long r = 0,
g = 0,
b = 0;
foreach (var c in edgeColors)
{
r += c.R;
g += c.G;
b += c.B;
}
return Color.FromArgb(
255,
(int)(r / edgeColors.Count),
(int)(g / edgeColors.Count),
(int)(b / edgeColors.Count)
);
}
default:
return Color.Transparent;
}
}
private static Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
@@ -25,21 +114,19 @@ namespace BetterLyrics.WinUI3.Helper
using Graphics gDest = Graphics.FromImage(bmp);
IntPtr hdcDest = gDest.GetHdc();
IntPtr hdcSrc = GetDC(IntPtr.Zero); // Entire screen
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, SRCCOPY);
Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, Gdi32.RasterOperationMode.SRCCOPY);
gDest.ReleaseHdc(hdcDest);
ReleaseDC(IntPtr.Zero, hdcSrc);
User32.ReleaseDC(IntPtr.Zero, hdcSrc);
return ComputeAverageColor(bmp);
}
private static Color ComputeAverageColor(Bitmap bmp)
{
long r = 0,
g = 0,
b = 0;
long r = 0, g = 0, b = 0;
int count = 0;
for (int y = 0; y < bmp.Height; y++)
@@ -54,53 +141,8 @@ namespace BetterLyrics.WinUI3.Helper
}
}
if (count == 0)
return Color.Transparent;
if (count == 0) return Color.Transparent;
return Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
}
#region Win32 Imports & Structs
private const int SRCCOPY = 0x00CC0020;
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
private static extern bool BitBlt(
IntPtr hdcDest,
int nXDest,
int nYDest,
int nWidth,
int nHeight,
IntPtr hdcSrc,
int nXSrc,
int nYSrc,
int dwRop
);
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(SystemMetric smIndex);
private enum SystemMetric
{
SM_CXSCREEN = 0,
SM_CYSCREEN = 1,
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
#endregion
}
}

View File

@@ -5,224 +5,100 @@ using System.Collections.Generic;
using BetterLyrics.WinUI3.Views;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Windows.ApplicationModel.Core;
using WinRT.Interop;
using WinUIEx;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="WindowHelper" />
/// </summary>
public static class WindowHelper
{
#region Fields
private static List<object> _activeWindows = [];
/// <summary>
/// Defines the _windowCache
/// </summary>
private static readonly Dictionary<Type, Window> _windowCache = new();
/// <summary>
/// Defines the _activeWindows
/// </summary>
private static List<Window> _activeWindows = new List<Window>();
#endregion
#region Properties
/// <summary>
/// Gets the ActiveWindows
/// </summary>
public static List<Window> ActiveWindows
public static void CloseWindow<T>()
{
get { return _activeWindows; }
var window = _activeWindows.Find(w => w is T);
if (window is Window w)
{
w.Close();
_activeWindows.Remove(w);
}
}
#endregion
#region Methods
/// <summary>
/// The GetWindowByFramePageType
/// </summary>
/// <param name="type">The type<see cref="Type"/></param>
/// <returns>The <see cref="Window"/></returns>
public static Window GetWindowByFramePageType(Type type)
public static void ExitAllWindows()
{
foreach (var cachedWindow in _windowCache)
while (_activeWindows.Count > 0)
{
if (cachedWindow.Key == type)
var window = _activeWindows[0];
((Window)window).Close();
_activeWindows.Remove(window);
}
App.Current.Exit();
}
public static T? GetWindowByWindowType<T>()
{
foreach (var window in _activeWindows)
{
if (window is T castedWindow)
{
return cachedWindow.Value;
return castedWindow;
}
}
return null;
return default;
}
/// <summary>
/// The GetWindowForElement
/// </summary>
/// <param name="element">The element<see cref="UIElement"/></param>
/// <returns>The <see cref="Window"/></returns>
public static Window GetWindowForElement(UIElement element)
public static void OpenOrShowWindow<T>()
{
if (element.XamlRoot != null)
var window = _activeWindows.Find(w => w is T);
if (window != null)
{
foreach (Window window in _activeWindows)
{
if (element.XamlRoot == window.Content.XamlRoot)
{
return window;
}
}
}
return null;
}
/// <summary>
/// The HideSystemTitleBar
/// </summary>
/// <param name="window">The window<see cref="Window"/></param>
public static void HideSystemTitleBar(this Window window)
{
window.ExtendsContentIntoTitleBar = true;
window.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
}
/// <summary>
/// The HideSystemTitleBarAndSetCustomTitleBar
/// </summary>
/// <param name="window">The window<see cref="Window"/></param>
/// <param name="titleBar">The titleBar<see cref="UIElement"/></param>
public static void HideSystemTitleBarAndSetCustomTitleBar(
this Window window,
UIElement titleBar
)
{
window.HideSystemTitleBar();
window.SetTitleBar(titleBar);
}
/// <summary>
/// The OpenLyricsWindow
/// </summary>
public static void OpenLyricsWindow()
{
OpenOrShowWindow(typeof(LyricsPage));
}
/// <summary>
/// The OpenSettingsWindow
/// </summary>
public static void OpenSettingsWindow()
{
OpenOrShowWindow(typeof(SettingsPage));
}
/// <summary>
/// The TrackWindow
/// </summary>
/// <param name="window">The window<see cref="Window"/></param>
/// <param name="pageType">The pageType<see cref="Type"/></param>
public static void TrackWindow(Window window, Type pageType = null)
{
if (pageType != null)
{
_windowCache[pageType] = window;
}
if (!_activeWindows.Contains(window))
_activeWindows.Add(window);
window.Closed -= Window_Closed;
window.Closed += Window_Closed;
}
private static void Window_Closed(object sender, WindowEventArgs e)
{
if (sender is Window closedWindow)
{
_activeWindows.Remove(closedWindow);
// 从缓存移除
foreach (var kvp in _windowCache)
{
if (kvp.Value == closedWindow)
{
_windowCache.Remove(kvp.Key);
break;
}
}
}
}
/// <summary>
/// The TryHide
/// </summary>
/// <param name="window">The window<see cref="Window"/></param>
public static void TryHide(this Window window)
{
if (window is not null)
{
window.Hide();
}
}
/// <summary>
/// The TryShow
/// </summary>
/// <param name="window">The window<see cref="Window"/></param>
public static void TryShow(this Window window)
{
if (window is not null)
{
window.Activate();
}
}
/// <summary>
/// The OpenOrShowWindow
/// </summary>
/// <param name="pageType">The pageType<see cref="Type"/></param>
private static void OpenOrShowWindow(Type pageType)
{
if (_windowCache.TryGetValue(pageType, out var window))
{
window.TryShow();
var castedWindow = (Window)window;
castedWindow.Restore();
}
else
{
var newWindow = new HostWindow();
TrackWindow(newWindow, pageType);
newWindow.ViewModel.FramePageType = pageType;
newWindow.Navigate(pageType);
newWindow.Activate();
}
}
// get dpi for an element
/// <summary>
/// The GetRasterizationScaleForElement
/// </summary>
/// <param name="element">The element<see cref="UIElement"/></param>
/// <returns>The <see cref="double"/></returns>
static public double GetRasterizationScaleForElement(UIElement element)
{
if (element.XamlRoot != null)
{
foreach (Window window in _activeWindows)
object newWindow;
if (typeof(T) == typeof(LyricsWindow))
{
if (element.XamlRoot == window.Content.XamlRoot)
{
return element.XamlRoot.RasterizationScale;
}
newWindow = new LyricsWindow();
}
else if (typeof(T) == typeof(SettingsWindow))
{
newWindow = new SettingsWindow();
}
else
{
throw new ArgumentException("Unsupported window type", nameof(T));
}
((Window)newWindow).Activate();
TrackWindow(newWindow);
}
return 0.0;
}
#endregion
public static void RestartApp(string args = "")
{
// The restart will be executed immediately.
AppRestartFailureReason failureReason =
Microsoft.Windows.AppLifecycle.AppInstance.Restart(args);
// If the restart fails, handle it here.
switch (failureReason)
{
case AppRestartFailureReason.RestartPending:
break;
case AppRestartFailureReason.NotInForeground:
break;
case AppRestartFailureReason.InvalidUser:
break;
default: //AppRestartFailureReason.Other
break;
}
}
private static void TrackWindow(object window)
{
if (!_activeWindows.Contains(window))
_activeWindows.Add(window);
}
}
}

View File

@@ -1,22 +0,0 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Messages
{
/// <summary>
/// Defines the <see cref="ShowNotificatonMessage" />
/// </summary>
public class ShowNotificatonMessage(Notification value)
: ValueChangedMessage<Notification>(value)
{
}
}

View File

@@ -1,34 +1,12 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="CharTiming" />
/// </summary>
public class CharTiming
{
#region Properties
/// <summary>
/// Gets or sets the EndMs
/// </summary>
public int EndMs { get; set; }
/// <summary>
/// Gets or sets the StartMs
/// </summary>
public int StartMs { get; set; }
public string Text { get; set; } = string.Empty;
public int StartIndex { get; set; }
#endregion
public int StartMs { get; set; }
public string Text { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public class DetectLanguageResult
{
[JsonPropertyName("confidence")]
public double Confidence { get; set; }
[JsonPropertyName("language")]
public string Language { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public partial class LanguageInfo : ObservableObject
{
[ObservableProperty]
public partial string Code { get; set; }
[ObservableProperty]
public partial string Name { get; set; }
public LanguageInfo(string code, string name)
{
Code = code;
Name = name;
}
}
}

View File

@@ -4,47 +4,20 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="LocalLyricsFolder" />
/// </summary>
public partial class LocalLyricsFolder : ObservableObject
{
#region Constructors
[ObservableProperty]
public partial bool IsEnabled { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="LocalLyricsFolder"/> class.
/// </summary>
public LocalLyricsFolder()
{
}
[ObservableProperty]
public partial string Path { get; set; }
public LocalLyricsFolder() { }
/// <summary>
/// Initializes a new instance of the <see cref="LocalLyricsFolder"/> class.
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
/// <param name="isEnabled">The isEnabled<see cref="bool"/></param>
public LocalLyricsFolder(string path, bool isEnabled)
{
Path = path;
IsEnabled = isEnabled;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether IsEnabled
/// </summary>
[ObservableProperty]
public partial bool IsEnabled { get; set; }
/// <summary>
/// Gets or sets the Path
/// </summary>
[ObservableProperty]
public partial string Path { get; set; }
#endregion
}
}

View File

@@ -1,31 +0,0 @@
// 2025/6/23 by Zhe Fang
using System.Collections.Generic;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="LyricsData" />
/// </summary>
public class LyricsData
{
#region Properties
/// <summary>
/// Gets or sets the LanguageIndex
/// </summary>
public int LanguageIndex { get; set; } = 0;
/// <summary>
/// Gets the LyricsLines
/// </summary>
public List<LyricsLine> LyricsLines => MultiLangLyricsLines[LanguageIndex];
/// <summary>
/// Gets or sets the MultiLangLyricsLines
/// </summary>
public List<List<LyricsLine>> MultiLangLyricsLines { get; set; } = [];
#endregion
}
}

View File

@@ -7,68 +7,27 @@ using Microsoft.Graphics.Canvas.Text;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="LyricsLine" />
/// </summary>
public class LyricsLine
{
#region Properties
private const float _animationDuration = 0.3f;
public ValueTransition<float> AngleTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> BlurAmountTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> HighlightOpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> OpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> ScaleTransition { get; set; } = new(initialValue: 0.95f, durationSeconds: _animationDuration);
/// <summary>
/// Gets or sets the BlurAmountTransition
/// </summary>
public ValueTransition<float> BlurAmountTransition { get; set; } =
new(initialValue: 0f, durationSeconds: 0.3f);
/// <summary>
/// Gets or sets the CanvasTextLayout
/// </summary>
public CanvasTextLayout? CanvasTextLayout { get; set; }
/// <summary>
/// Gets or sets the CenterPosition
/// </summary>
public Vector2 CenterPosition { get; set; }
/// <summary>
/// Gets or sets the CharTimings
/// </summary>
public List<CharTiming> CharTimings { get; set; } = [];
/// <summary>
/// Gets the DurationMs
/// </summary>
public int DurationMs => EndMs - StartMs;
/// <summary>
/// Gets or sets the EndMs
/// </summary>
public int EndMs { get; set; }
public ValueTransition<float> HighlightOpacityTransition { get; set; } =
new(initialValue: 0f, durationSeconds: 0.3f);
/// <summary>
/// Gets or sets the Position
/// </summary>
public Vector2 Position { get; set; }
/// <summary>
/// Gets or sets the ScaleTransition
/// </summary>
public ValueTransition<float> ScaleTransition { get; set; } =
new(initialValue: 0.95f, durationSeconds: 0.3f);
public List<CharTiming> CharTimings { get; set; } = [];
/// <summary>
/// Gets or sets the StartMs
/// </summary>
public int DurationMs => EndMs - StartMs;
public int EndMs { get; set; }
public int StartMs { get; set; }
/// <summary>
/// Gets or sets the Text
/// </summary>
public string Text { get; set; } = "";
#endregion
public string DisplayedText { get; set; } = "";
public string OriginalText { get; set; } = "";
}
}

View File

@@ -5,47 +5,21 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="LyricsSearchProviderInfo" />
/// </summary>
public partial class LyricsSearchProviderInfo : ObservableObject
public partial class MediaSourceProviderInfo : ObservableObject
{
#region Constructors
[ObservableProperty]
public partial bool IsEnabled { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="LyricsSearchProviderInfo"/> class.
/// </summary>
public LyricsSearchProviderInfo()
{
}
[ObservableProperty]
public partial string Provider { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="LyricsSearchProviderInfo"/> class.
/// </summary>
/// <param name="provider">The provider<see cref="LyricsSearchProvider"/></param>
/// <param name="isEnabled">The isEnabled<see cref="bool"/></param>
public LyricsSearchProviderInfo(LyricsSearchProvider provider, bool isEnabled)
public MediaSourceProviderInfo() { }
public MediaSourceProviderInfo(string provider, bool isEnabled)
{
Provider = provider;
IsEnabled = isEnabled;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether IsEnabled
/// </summary>
[ObservableProperty]
public partial bool IsEnabled { get; set; }
/// <summary>
/// Gets or sets the Provider
/// </summary>
[ObservableProperty]
public partial LyricsSearchProvider Provider { get; set; }
#endregion
}
}

View File

@@ -0,0 +1,25 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
public partial class LyricsSearchProviderInfo : ObservableObject
{
[ObservableProperty]
public partial bool IsEnabled { get; set; }
[ObservableProperty]
public partial LyricsSearchProvider Provider { get; set; }
public LyricsSearchProviderInfo() { }
public LyricsSearchProviderInfo(LyricsSearchProvider provider, bool isEnabled)
{
Provider = provider;
IsEnabled = isEnabled;
}
}
}

View File

@@ -3,34 +3,27 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="Notification" />
/// </summary>
public partial class Notification : ObservableObject
{
#region Constructors
[ObservableProperty]
public partial bool IsForeverDismissable { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Notification"/> class.
/// </summary>
/// <param name="message">The message<see cref="string?"/></param>
/// <param name="severity">The severity<see cref="InfoBarSeverity"/></param>
/// <param name="isForeverDismissable">The isForeverDismissable<see cref="bool"/></param>
/// <param name="relatedSettingsKeyName">The relatedSettingsKeyName<see cref="string?"/></param>
public Notification(
string? message = null,
InfoBarSeverity severity = InfoBarSeverity.Informational,
bool isForeverDismissable = false,
string? relatedSettingsKeyName = null
)
[ObservableProperty]
public partial string? Message { get; set; }
[ObservableProperty]
public partial string? RelatedSettingsKeyName { get; set; }
[ObservableProperty]
public partial InfoBarSeverity Severity { get; set; }
[ObservableProperty]
public partial Visibility Visibility { get; set; }
public Notification(string? message = null, InfoBarSeverity severity = InfoBarSeverity.Informational, bool isForeverDismissable = false, string? relatedSettingsKeyName = null)
{
Message = message;
Severity = severity;
@@ -38,41 +31,5 @@ namespace BetterLyrics.WinUI3.Models
Visibility = IsForeverDismissable ? Visibility.Visible : Visibility.Collapsed;
RelatedSettingsKeyName = relatedSettingsKeyName;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether IsForeverDismissable
/// </summary>
[ObservableProperty]
public partial bool IsForeverDismissable { get; set; }
/// <summary>
/// Gets or sets the Message
/// </summary>
[ObservableProperty]
public partial string? Message { get; set; }
/// <summary>
/// Gets or sets the RelatedSettingsKeyName
/// </summary>
[ObservableProperty]
public partial string? RelatedSettingsKeyName { get; set; }
/// <summary>
/// Gets or sets the Severity
/// </summary>
[ObservableProperty]
public partial InfoBarSeverity Severity { get; set; }
/// <summary>
/// Gets or sets the Visibility
/// </summary>
[ObservableProperty]
public partial Visibility Visibility { get; set; }
#endregion
}
}

View File

@@ -1,63 +1,32 @@
// 2025/6/23 by Zhe Fang
using CommunityToolkit.Mvvm.ComponentModel;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="SongInfo" />
/// </summary>
public partial class SongInfo : ObservableObject
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SongInfo"/> class.
/// </summary>
public SongInfo()
{
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the Album
/// </summary>
[ObservableProperty]
public partial string? Album { get; set; }
/// <summary>
/// Gets or sets the AlbumArt
/// </summary>
public byte[]? AlbumArt { get; set; } = null;
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = null;
public Color? AlbumArtAccentColor { get; set; } = null;
/// <summary>
/// Gets or sets the Artist
/// </summary>
[ObservableProperty]
public partial string Artist { get; set; }
/// <summary>
/// Gets or sets the DurationMs
/// In milliseconds
/// </summary>
[ObservableProperty]
public partial double? DurationMs { get; set; }
/// <summary>
/// Gets or sets the SourceAppUserModelId
/// </summary>
[ObservableProperty]
public partial string? SourceAppUserModelId { get; set; } = null;
/// <summary>
/// Gets or sets the Title
/// </summary>
[ObservableProperty]
public partial string Title { get; set; }
#endregion
public SongInfo() { }
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public class TranslateResponse
{
[JsonPropertyName("translatedText")]
public string TranslatedText { get; set; }
}
}

View File

@@ -5,6 +5,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Renderer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
@@ -14,5 +15,15 @@
x:Name="LyricsCanvas"
Draw="LyricsCanvas_Draw"
Update="LyricsCanvas_Update" />
<Grid
Margin="36"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Visibility="{x:Bind ViewModel.IsTranslating, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<FontIcon
x:Name="RotatingIcon"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE8C1;" />
</Grid>
</Grid>
</UserControl>

View File

@@ -1,74 +1,30 @@
// 2025/6/23 by Zhe Fang
using System.Diagnostics;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
using Microsoft.UI.Xaml.Media.Animation;
namespace BetterLyrics.WinUI3.Renderer
{
/// <summary>
/// Defines the <see cref="LyricsRenderer" />
/// </summary>
public sealed partial class LyricsRenderer : UserControl
{
#region Constructors
public LyricsRendererViewModel ViewModel { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="LyricsRenderer"/> class.
/// </summary>
public LyricsRenderer()
{
InitializeComponent();
ViewModel = Ioc.Default.GetRequiredService<LyricsRendererViewModel>();
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the ViewModel
/// </summary>
public LyricsRendererViewModel ViewModel { get; set; }
#endregion
#region Methods
/// <summary>
/// The LyricsCanvas_Draw
/// </summary>
/// <param name="sender">The sender<see cref="Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl"/></param>
/// <param name="args">The args<see cref="Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs"/></param>
private void LyricsCanvas_Draw(
Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender,
Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args
)
private void LyricsCanvas_Draw(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
{
ViewModel.Draw(sender, args.DrawingSession);
}
/// <summary>
/// The LyricsCanvas_Update
/// </summary>
/// <param name="sender">The sender<see cref="Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl"/></param>
/// <param name="args">The args<see cref="Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs"/></param>
private void LyricsCanvas_Update(
Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender,
Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args
)
private void LyricsCanvas_Update(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args)
{
ViewModel.Update(sender, args);
}
#endregion
}
}

View File

@@ -1,21 +1,20 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Models;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Serialization
{
/// <summary>
/// Defines the <see cref="SourceGenerationContext" />
/// </summary>
[JsonSerializable(typeof(List<LyricsSearchProviderInfo>))]
[JsonSerializable(typeof(List<MediaSourceProviderInfo>))]
[JsonSerializable(typeof(List<LocalLyricsFolder>))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(List<DetectLanguageResult>))]
[JsonSerializable(typeof(TranslateResponse))]
[JsonSerializable(typeof(JsonElement))]
[JsonSourceGenerationOptions(WriteIndented = true)]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}
internal partial class SourceGenerationContext : JsonSerializerContext { }
}

View File

@@ -1,41 +1,19 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Services
{
#region Interfaces
/// <summary>
/// Defines the <see cref="ILibWatcherService" />
/// </summary>
public interface ILibWatcherService
{
#region Events
/// <summary>
/// Defines the MusicLibraryFilesChanged
/// </summary>
event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
#endregion
#region Methods
/// <summary>
/// The UpdateWatchers
/// </summary>
/// <param name="folders">The folders<see cref="List{LocalLyricsFolder}"/></param>
public void UpdateWatchers(List<LocalLyricsFolder> folders);
#endregion
}
#endregion
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
{
public interface ILibreTranslateService
{
Task<string> TranslateAsync(string text, CancellationToken? token);
}
}

View File

@@ -1,47 +1,22 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
namespace BetterLyrics.WinUI3.Services
{
#region Interfaces
/// <summary>
/// Defines the <see cref="IMusicSearchService" />
/// </summary>
public interface IMusicSearchService
{
#region Methods
Task <byte[]?> SearchAlbumArtAsync(string title, string artist, string album);
/// <summary>
/// The SearchAlbumArtAsync
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <returns>The <see cref="byte[]?"/></returns>
byte[]? SearchAlbumArtAsync(string title, string artist);
/// <summary>
/// The SearchLyricsAsync
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="album">The album<see cref="string"/></param>
/// <param name="durationMs">The durationMs<see cref="double"/></param>
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
/// <returns>The <see cref="Task{(string?, LyricsFormat?)}"/></returns>
Task<(string?, LyricsFormat?)> SearchLyricsAsync(
Task<string?> SearchLyricsAsync(
string title,
string artist,
string album = "",
double durationMs = 0.0,
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
string album,
double durationMs,
CancellationToken token
);
#endregion
}
#endregion
}

View File

@@ -1,56 +1,19 @@
// 2025/6/23 by Zhe Fang
using System;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
using System;
namespace BetterLyrics.WinUI3.Services
{
#region Interfaces
/// <summary>
/// Defines the <see cref="IPlaybackService" />
/// </summary>
public interface IPlaybackService
{
#region Events
/// <summary>
/// Defines the IsPlayingChanged
/// </summary>
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
/// <summary>
/// Defines the PositionChanged
/// </summary>
event EventHandler<PositionChangedEventArgs>? PositionChanged;
/// <summary>
/// Defines the SongInfoChanged
/// </summary>
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
#endregion
#region Properties
/// <summary>
/// Gets a value indicating whether IsPlaying
/// </summary>
bool IsPlaying { get; }
/// <summary>
/// Gets the Position
/// </summary>
TimeSpan Position { get; }
/// <summary>
/// Gets the SongInfo
/// </summary>
SongInfo? SongInfo { get; }
#endregion
event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
}
#endregion
}

View File

@@ -5,143 +5,81 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Windows.UI;
using Windows.UI.Text;
namespace BetterLyrics.WinUI3.Services
{
#region Interfaces
/// <summary>
/// Defines the <see cref="ISettingsService" />
/// </summary>
public interface ISettingsService
{
#region Properties
// App behavior
/// <summary>
/// Gets or sets the AutoStartWindowType
/// </summary>
AutoStartWindowType AutoStartWindowType { get; set; }
/// <summary>
/// Gets or sets the BackdropType
/// </summary>
BackdropType BackdropType { get; set; }
// Album art cover style
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
int CoverImageRadius { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayBlurAmount
/// </summary>
int CoverOverlayBlurAmount { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayOpacity
/// </summary>
int CoverOverlayOpacity { get; set; }
// Album art background
/// <summary>
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
/// </summary>
bool IsCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
bool IsDynamicCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsFirstRun
/// </summary>
bool IsFanLyricsEnabled { get; set; }
bool IsFirstRun { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
/// </summary>
bool IsLyricsGlowEffectEnabled { get; set; }
/// <summary>
/// Gets or sets the Language
/// </summary>
Language Language { get; set; }
int DesktopWindowLeft { get; set; }
int DesktopWindowTop { get; set; }
int DesktopWindowWidth { get; set; }
int DesktopWindowHeight { get; set; }
int StandardWindowWidth { get; set; }
int StandardWindowHeight { get; set; }
int StandardWindowLeft { get; set; }
int StandardWindowTop { get; set; }
bool AutoLockOnDesktopMode { get; set; }
string LibreTranslateServer { get; set; }
int SelectedTargetLanguageIndex { get; set; }
// Lyrics lib
/// <summary>
/// Gets or sets the LocalLyricsFolders
/// </summary>
List<LocalLyricsFolder> LocalLyricsFolders { get; set; }
// Lyrics style and effetc
/// <summary>
/// Gets or sets the LyricsAlignmentType
/// </summary>
LyricsAlignmentType LyricsAlignmentType { get; set; }
TextAlignmentType LyricsAlignmentType { get; set; }
TextAlignmentType SongInfoAlignmentType { get; set; }
/// <summary>
/// Gets or sets the LyricsBlurAmount
/// </summary>
int LyricsBlurAmount { get; set; }
/// <summary>
/// Gets or sets the LyricsFontColorType
/// </summary>
LyricsFontColorType LyricsFontColorType { get; set; }
Color LyricsCustomBgFontColor { get; set; }
Color LyricsCustomFgFontColor { get; set; }
Color LyricsCustomStrokeFontColor { get; set; }
LyricsFontColorType LyricsBgFontColorType { get; set; }
LyricsFontColorType LyricsFgFontColorType { get; set; }
LyricsFontColorType LyricsStrokeFontColorType { get; set; }
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
int LyricsFontSize { get; set; }
/// <summary>
/// Gets or sets the LyricsFontWeight
/// </summary>
ElementTheme LyricsBackgroundTheme { get; set; }
int LyricsFontStrokeWidth { get; set; }
LyricsFontWeight LyricsFontWeight { get; set; }
/// <summary>
/// Gets or sets the LyricsGlowEffectScope
/// </summary>
LineRenderingType LyricsGlowEffectScope { get; set; }
/// <summary>
/// Gets or sets the LyricsLineSpacingFactor
/// </summary>
float LyricsLineSpacingFactor { get; set; }
/// <summary>
/// Gets or sets the LyricsSearchProvidersInfo
/// </summary>
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
/// <summary>
/// Gets or sets the LyricsVerticalEdgeOpacity
/// </summary>
List<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
int LyricsVerticalEdgeOpacity { get; set; }
// App appearance
bool IgnoreFullscreenWindow { get; set; }
/// <summary>
/// Gets or sets the ThemeType
/// </summary>
ElementTheme ThemeType { get; set; }
bool IsTranslationEnabled { get; set; }
/// <summary>
/// Gets or sets the TitleBarType
/// </summary>
TitleBarType TitleBarType { get; set; }
#endregion
LyricsDisplayType PreferredDisplayType { get; set; }
}
#endregion
}

View File

@@ -2,136 +2,83 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Services
{
using global::BetterLyrics.WinUI3.Events;
using global::BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace BetterLyrics.WinUI3.Services
public class LibWatcherService : IDisposable, ILibWatcherService
{
/// <summary>
/// Defines the <see cref="LibWatcherService" />
/// </summary>
public class LibWatcherService : IDisposable, ILibWatcherService
private readonly ISettingsService _settingsService;
private readonly Dictionary<string, FileSystemWatcher> _watchers = [];
public LibWatcherService(ISettingsService settingsService)
{
#region Fields
_settingsService = settingsService;
UpdateWatchers(_settingsService.LocalLyricsFolders);
}
/// <summary>
/// Defines the _settingsService
/// </summary>
private readonly ISettingsService _settingsService;
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
/// <summary>
/// Defines the _watchers
/// </summary>
private readonly Dictionary<string, FileSystemWatcher> _watchers = [];
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LibWatcherService"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
public LibWatcherService(ISettingsService settingsService)
public void Dispose()
{
foreach (var watcher in _watchers.Values)
{
_settingsService = settingsService;
UpdateWatchers(_settingsService.LocalLyricsFolders);
watcher.Dispose();
}
_watchers.Clear();
}
#endregion
#region Events
/// <summary>
/// Defines the MusicLibraryFilesChanged
/// </summary>
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
#endregion
#region Methods
/// <summary>
/// The Dispose
/// </summary>
public void Dispose()
public void UpdateWatchers(List<LocalLyricsFolder> folders)
{
// 移除不再监听的
foreach (var key in _watchers.Keys.ToList())
{
foreach (var watcher in _watchers.Values)
if (!folders.Any(x => x.Path == key && x.IsEnabled))
{
watcher.Dispose();
}
_watchers.Clear();
}
/// <summary>
/// The UpdateWatchers
/// </summary>
/// <param name="folders">The folders<see cref="List{LocalLyricsFolder}"/></param>
public void UpdateWatchers(List<LocalLyricsFolder> folders)
{
// 移除不再监听的
foreach (var key in _watchers.Keys.ToList())
{
if (!folders.Any(x => x.Path == key && x.IsEnabled))
{
_watchers[key].Dispose();
_watchers.Remove(key);
}
}
// 添加新的监听
foreach (var folder in folders)
{
if (
!_watchers.ContainsKey(folder.Path)
&& Directory.Exists(folder.Path)
&& folder.IsEnabled
)
{
var watcher = new FileSystemWatcher(folder.Path)
{
IncludeSubdirectories = true,
EnableRaisingEvents = true,
};
watcher.Created += (s, e) => OnChanged(folder.Path, e);
watcher.Changed += (s, e) => OnChanged(folder.Path, e);
watcher.Deleted += (s, e) => OnChanged(folder.Path, e);
watcher.Renamed += (s, e) => OnChanged(folder.Path, e);
_watchers[folder.Path] = watcher;
}
_watchers[key].Dispose();
_watchers.Remove(key);
}
}
/// <summary>
/// The OnChanged
/// </summary>
/// <param name="folder">The folder<see cref="string"/></param>
/// <param name="e">The e<see cref="FileSystemEventArgs"/></param>
private void OnChanged(string folder, FileSystemEventArgs e)
// 添加新的监听
foreach (var folder in folders)
{
App.DispatcherQueue!.TryEnqueue(
Microsoft.UI.Dispatching.DispatcherQueuePriority.High,
() =>
if (
!_watchers.ContainsKey(folder.Path)
&& Directory.Exists(folder.Path)
&& folder.IsEnabled
)
{
var watcher = new FileSystemWatcher(folder.Path)
{
MusicLibraryFilesChanged?.Invoke(
this,
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
);
}
);
IncludeSubdirectories = true,
EnableRaisingEvents = true,
};
watcher.Created += (s, e) => OnChanged(folder.Path, e);
watcher.Changed += (s, e) => OnChanged(folder.Path, e);
watcher.Deleted += (s, e) => OnChanged(folder.Path, e);
watcher.Renamed += (s, e) => OnChanged(folder.Path, e);
_watchers[folder.Path] = watcher;
}
}
}
#endregion
private void OnChanged(string folder, FileSystemEventArgs e)
{
App.DispatcherQueue!.TryEnqueue(
Microsoft.UI.Dispatching.DispatcherQueuePriority.High,
() =>
{
MusicLibraryFilesChanged?.Invoke(
this,
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
);
}
);
}
}
}

View File

@@ -0,0 +1,78 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
{
public class LibreTranslateService : ILibreTranslateService
{
private readonly ISettingsService _settingsService;
private readonly HttpClient _httpClient;
public LibreTranslateService(ISettingsService settingsService)
{
_settingsService = settingsService;
_httpClient = new HttpClient();
}
public async Task<string> TranslateAsync(string text, CancellationToken? token)
{
if (string.IsNullOrWhiteSpace(text))
{
throw new ArgumentException("Text and target language must be provided.");
}
string targetLangCode = AppInfo.GetAllTranslationLanguagesInfo()[_settingsService.SelectedTargetLanguageIndex].Code;
string originalLangCode = await DetectLanguageCode(text);
token?.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(originalLangCode) || originalLangCode == targetLangCode)
{
return text; // No translation needed
}
var url = $"{_settingsService.LibreTranslateServer}/translate";
var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(
[
new("q", text),
new("source", originalLangCode),
new("target", targetLangCode),
]));
token?.ThrowIfCancellationRequested();
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
token?.ThrowIfCancellationRequested();
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.TranslateResponse);
return result?.TranslatedText ?? string.Empty;
}
private async Task<string> DetectLanguageCode(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
throw new ArgumentException("Text must be provided.");
}
var url = $"{_settingsService.LibreTranslateServer}/detect";
var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(
[
new("q", text),
]));
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var resultList = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.ListDetectLanguageResult);
return resultList?.OrderByDescending(x => x.Confidence).FirstOrDefault()?.Language ?? string.Empty;
}
}
}

View File

@@ -1,81 +1,94 @@
// 2025/6/23 by Zhe Fang
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using CommunityToolkit.Mvvm.DependencyInjection;
using iTunesSearch.Library;
using Lyricify.Lyrics.Providers.Web.Kugou;
using Lyricify.Lyrics.Searchers;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Windows.Storage;
using Windows.Storage.FileProperties;
namespace BetterLyrics.WinUI3.Services
{
/// <summary>
/// Defines the <see cref="MusicSearchService" />
/// </summary>
public class MusicSearchService : IMusicSearchService
{
#region Fields
/// <summary>
/// Defines the _httpClient
/// </summary>
private readonly HttpClient _lrcLibHttpClient;
private readonly HttpClient _amllTtmlDbHttpClient;
private readonly HttpClient _lrcLibHttpClient;
private readonly HttpClient _iTunesHttpClinet;
/// <summary>
/// Defines the _settingsService
/// </summary>
private readonly ISettingsService _settingsService;
private readonly ILogger _logger;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MusicSearchService"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
public MusicSearchService(ISettingsService settingsService)
{
_settingsService = settingsService;
_lrcLibHttpClient = new HttpClient();
_logger = Ioc.Default.GetRequiredService<ILogger<MusicSearchService>>();
_lrcLibHttpClient = new();
_lrcLibHttpClient.DefaultRequestHeaders.Add(
"User-Agent",
$"{AppInfo.AppName} {AppInfo.AppVersion} ({AppInfo.GithubUrl})"
);
_amllTtmlDbHttpClient = new HttpClient();
_amllTtmlDbHttpClient = new();
_iTunesHttpClinet = new();
}
#endregion
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
{
const string url = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-img-index.jsonl";
try
{
using var response = await _amllTtmlDbHttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode) return false;
#region Methods
await using var stream = await response.Content.ReadAsStreamAsync();
await using var fs = new FileStream(
AppInfo.AmllTtmlDbIndexPath,
FileMode.Create,
FileAccess.Write,
FileShare.None
);
await stream.CopyToAsync(fs);
/// <summary>
/// The SearchAlbumArtAsync
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <returns>The <see cref="byte[]?"/></returns>
public byte[]? SearchAlbumArtAsync(string title, string artist)
return true;
}
catch
{
return false;
}
}
private static string GuessCountryCode(string album, string artist)
{
string s = album + artist;
if (s.Any(c => c >= 0x4e00 && c <= 0x9fff)) // 中文
return "cn";
if (s.Any(c => (c >= 0x3040 && c <= 0x30ff) || (c >= 0x31f0 && c <= 0x31ff))) // 日文
return "jp";
if (s.Any(c => c >= 0xac00 && c <= 0xd7af)) // 韩文
return "kr";
if (s.Any(c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) // 英文
return "us";
// 其他情况
return "us";
}
public async Task<byte[]?> SearchAlbumArtAsync(string title, string artist, string album)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (
var file in Directory.GetFiles(
folder.Path,
$"*.*",
SearchOption.AllDirectories
)
)
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
{
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
@@ -90,26 +103,63 @@ namespace BetterLyrics.WinUI3.Services
}
}
return await SearchiTunesAlbumArtAsync(artist, album);
}
private async Task<byte[]?> SearchiTunesAlbumArtAsync(string artist, string album)
{
// Source: https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce
try
{
string format = ".jpg";
var cachedAlbumArt = ReadAlbumArtCache(artist, album, format, AppInfo.iTunesAlbumArtCacheDirectory);
if (cachedAlbumArt != null)
{
return cachedAlbumArt;
}
// Build the iTunes API URL
string url = $"https://itunes.apple.com/search?term=" + artist + "+" + album + "&country=" + GuessCountryCode(album, artist) + "&entity=album";
url.Replace(" ", "-");
// Make a request to the API
HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
// Parse the JSON response
var data = JsonSerializer.Deserialize(responseBody, Serialization.SourceGenerationContext.Default.JsonElement);
if (data.TryGetProperty("results", out var results) && results.ValueKind == JsonValueKind.Array && results.GetArrayLength() > 0)
{
// Get the first result
var result = results[0];
if (result.TryGetProperty("artworkUrl100", out var artworkUrlProp))
{
string artworkUrl = artworkUrlProp.GetString()?.Replace("100x100bb.jpg", "1200x1200bb.jpg") ?? string.Empty;
var fetched = await _iTunesHttpClinet.GetByteArrayAsync(artworkUrl);
if (fetched != null && fetched.Length > 0)
{
// Write to cache
WriteAlbumArtCache(artist, album, fetched, format, AppInfo.iTunesAlbumArtCacheDirectory);
return fetched;
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error searching iTunes album art for {Artist} - {Album}", artist, album);
}
return null;
}
/// <summary>
/// The SearchLyricsAsync
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="album">The album<see cref="string"/></param>
/// <param name="durationMs">The durationMs<see cref="double"/></param>
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
/// <returns>The <see cref="Task{(string?, LyricsFormat?)}"/></returns>
public async Task<(string?, LyricsFormat?)> SearchLyricsAsync(
string title,
string artist,
string album = "",
double durationMs = 0.0,
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
)
public async Task<string?> SearchLyricsAsync(string title, string artist, string album, double durationMs, CancellationToken token)
{
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
{
if (!provider.IsEnabled)
@@ -123,15 +173,10 @@ namespace BetterLyrics.WinUI3.Services
// Check cache first
if (provider.Provider.IsRemote())
{
cachedLyrics = ReadCache(
title,
artist,
lyricsFormat,
provider.Provider.GetCacheDirectory()
);
cachedLyrics = ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
return (cachedLyrics, lyricsFormat);
return cachedLyrics;
}
}
@@ -145,11 +190,7 @@ namespace BetterLyrics.WinUI3.Services
}
else
{
searchedLyrics = await LocalLyricsSearchInLyricsFiles(
title,
artist,
lyricsFormat
);
searchedLyrics = await LocalLyricsSearchInLyricsFiles(title, artist, lyricsFormat);
}
}
else
@@ -157,40 +198,16 @@ namespace BetterLyrics.WinUI3.Services
switch (provider.Provider)
{
case LyricsSearchProvider.LrcLib:
searchedLyrics = await SearchLrcLibAsync(
title,
artist,
album,
(int)(durationMs / 1000),
matchMode
);
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
break;
case LyricsSearchProvider.QQ:
searchedLyrics = await SearchQQAsync(
title,
artist,
album,
(int)durationMs,
matchMode
);
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
break;
case LyricsSearchProvider.Kugou:
searchedLyrics = await SearchKugouAsync(
title,
artist,
album,
(int)durationMs,
matchMode
);
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
break;
case LyricsSearchProvider.Netease:
searchedLyrics = await SearchNeteaseAsync(
title,
artist,
album,
(int)durationMs,
matchMode
);
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, Searchers.Netease);
break;
case LyricsSearchProvider.AmllTtmlDb:
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
@@ -200,42 +217,40 @@ namespace BetterLyrics.WinUI3.Services
}
}
token.ThrowIfCancellationRequested();
if (!string.IsNullOrWhiteSpace(searchedLyrics))
{
if (provider.Provider.IsRemote())
{
WriteCache(
title,
artist,
searchedLyrics,
lyricsFormat,
provider.Provider.GetCacheDirectory()
);
WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
}
return (
searchedLyrics,
lyricsFormat == LyricsFormat.NotSpecified
? searchedLyrics.DetectFormat()
: lyricsFormat
);
return searchedLyrics;
}
}
return (null, null);
return null;
}
private static bool MusicMatch(string fileName, string title, string artist)
{
return fileName.Contains(title) && fileName.Contains(artist);
var normFileName = Normalize(fileName);
var normTitle = Normalize(title);
var normArtist = Normalize(artist);
// 常见两种顺序
return normFileName == normTitle + normArtist
|| normFileName == normArtist + normTitle;
}
/// <summary>
/// The SanitizeFileName
/// </summary>
/// <param name="fileName">The fileName<see cref="string"/></param>
/// <param name="replacement">The replacement<see cref="char"/></param>
/// <returns>The <see cref="string"/></returns>
// 预处理:去除空格、括号、下划线、横杠、点、大小写等
static string Normalize(string s) =>
new string(s
.Where(c => char.IsLetterOrDigit(c))
.ToArray())
.ToLowerInvariant();
private static string SanitizeFileName(string fileName, char replacement = '_')
{
var invalidChars = Path.GetInvalidFileNameChars();
@@ -247,37 +262,17 @@ namespace BetterLyrics.WinUI3.Services
return sb.ToString();
}
/// <summary>
/// The LocalLyricsSearchInLyricsFiles
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="format">The format<see cref="LyricsFormat"/></param>
/// <returns>The <see cref="Task{string?}"/></returns>
private async Task<string?> LocalLyricsSearchInLyricsFiles(
string title,
string artist,
LyricsFormat format
)
private async Task<string?> LocalLyricsSearchInLyricsFiles(string title, string artist, LyricsFormat format)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (
var file in Directory.GetFiles(
folder.Path,
$"*{format.ToFileExtension()}",
SearchOption.AllDirectories
)
)
foreach (var file in Directory.GetFiles(folder.Path, $"*{format.ToFileExtension()}", SearchOption.AllDirectories))
{
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
string? raw = await File.ReadAllTextAsync(
file,
FileHelper.GetEncoding(file)
);
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
if (raw != null)
{
return raw;
@@ -289,32 +284,16 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
/// <summary>
/// The LocalLyricsSearchInMusicFiles
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <returns>The <see cref="string?"/></returns>
private string? LocalLyricsSearchInMusicFiles(string title, string artist)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (
var file in Directory.GetFiles(
folder.Path,
$"*.*",
SearchOption.AllDirectories
)
)
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
{
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
//Track track = new(file);
//var test1 = track.Lyrics.SynchronizedLyrics;
//var test2 = track.Lyrics.UnsynchronizedLyrics;
try
{
var plain = TagLib.File.Create(file).Tag.Lyrics;
@@ -323,10 +302,7 @@ namespace BetterLyrics.WinUI3.Services
return plain;
}
}
catch (Exception e)
{
throw e;
}
catch (Exception) { }
}
}
}
@@ -335,26 +311,11 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
/// <summary>
/// The ReadCache
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="format">The format<see cref="LyricsFormat"/></param>
/// <returns>The <see cref="string?"/></returns>
private string? ReadCache(
string title,
string artist,
LyricsFormat format,
string cacheFolderPath
)
private string? ReadLyricsCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
{
var safeArtist = SanitizeFileName(artist);
var safeTitle = SanitizeFileName(title);
var cacheFilePath = Path.Combine(
cacheFolderPath,
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
);
var cacheFilePath = Path.Combine(cacheFolderPath, $"{safeArtist} - {safeTitle}{format.ToFileExtension()}");
if (File.Exists(cacheFilePath))
{
return File.ReadAllText(cacheFilePath);
@@ -362,184 +323,18 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
/// <summary>
/// The SearchLrcLib
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="album">The album<see cref="string"/></param>
/// <param name="duration">The duration<see cref="int"/></param>
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
/// <returns>The <see cref="Task{string?}"/></returns>
private async Task<string?> SearchLrcLibAsync(
string title,
string artist,
string album,
int duration,
MusicSearchMatchMode matchMode
)
private byte[]? ReadAlbumArtCache(string album, string artist, string format, string cacheFolderPath)
{
// Build API query URL
var url =
$"https://lrclib.net/api/search?"
+ $"track_name={Uri.EscapeDataString(title)}&"
+ $"artist_name={Uri.EscapeDataString(artist)}";
if (matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration)
var safeArtist = SanitizeFileName(artist);
var safeAlbum = SanitizeFileName(album);
var cacheFilePath = Path.Combine(cacheFolderPath, $"{safeArtist} - {safeAlbum}{format}");
if (File.Exists(cacheFilePath))
{
url +=
$"&album_name={Uri.EscapeDataString(album)}"
+ $"&durationMs={Uri.EscapeDataString(duration.ToString())}";
}
var response = await _lrcLibHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
return null;
var json = await response.Content.ReadAsStringAsync();
var jArr = JsonSerializer.Deserialize(
json,
Serialization.SourceGenerationContext.Default.JsonElement
);
if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
{
var first = jArr[0];
var syncedLyrics = first.GetProperty("syncedLyrics").GetString();
var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
if (!string.IsNullOrWhiteSpace(result))
{
return result;
}
}
return null;
}
private async Task<string?> SearchQQAsync(
string title,
string artist,
string album,
int durationMs,
MusicSearchMatchMode matchMode
)
{
string? queryId = (
(
await new Lyricify.Lyrics.Searchers.QQMusicSearcher().SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? durationMs
: null,
Album =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? album
: null,
AlbumArtists = [artist],
Artists = [artist],
Title = title,
}
)
) as Lyricify.Lyrics.Searchers.QQMusicSearchResult
)?.Id;
if (queryId is string id)
{
return (await Lyricify.Lyrics.Decrypter.Qrc.Helper.GetLyricsAsync(id))?.Lyrics;
return File.ReadAllBytes(cacheFilePath);
}
return null;
}
private async Task<string?> SearchKugouAsync(
string title,
string artist,
string album,
int durationMs,
MusicSearchMatchMode matchMode
)
{
string? queryHash = (
(
await new Lyricify.Lyrics.Searchers.KugouSearcher().SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? durationMs
: null,
Album =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? album
: null,
AlbumArtists = [artist],
Artists = [artist],
Title = title,
}
)
) as Lyricify.Lyrics.Searchers.KugouSearchResult
)?.Hash;
if (queryHash != null)
{
var candidate = (
await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(
hash: queryHash
)
)?.Candidates.FirstOrDefault();
if (candidate != null)
{
return await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(
candidate.Id,
candidate.AccessKey
);
}
}
return null;
}
private async Task<string?> SearchNeteaseAsync(
string title,
string artist,
string album,
int durationMs,
MusicSearchMatchMode matchMode
)
{
string? queryId = (
(
await new Lyricify.Lyrics.Searchers.NeteaseSearcher().SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? durationMs
: null,
Album =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? album
: null,
AlbumArtists = [artist],
Artists = [artist],
Title = title,
}
)
) as Lyricify.Lyrics.Searchers.NeteaseSearchResult
)?.Id;
if (queryId != null)
{
return (await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(queryId))
?.Lrc
.Lyric;
}
return null;
}
/// <summary>
/// 本地检索 amll-ttml-db 索引并下载歌词内容
/// </summary>
/// <param name="title">歌曲名</param>
/// <param name="artist">歌手名</param>
/// <returns>歌词内容字符串,找不到返回 null</returns>
private async Task<string?> SearchAmllTtmlDbAsync(string title, string artist)
{
// 检索本地 JSONL 索引文件,查找 rawLyricFile
@@ -551,7 +346,7 @@ namespace BetterLyrics.WinUI3.Services
}
string? rawLyricFile = null;
foreach (var line in File.ReadLines(AppInfo.AmllTtmlDbIndexPath))
await foreach (var line in File.ReadLinesAsync(AppInfo.AmllTtmlDbIndexPath))
{
if (string.IsNullOrWhiteSpace(line))
continue;
@@ -593,8 +388,7 @@ namespace BetterLyrics.WinUI3.Services
return null;
// 下载歌词内容
var url =
$"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-img/{rawLyricFile}";
try
{
var response = await _amllTtmlDbHttpClient.GetAsync(url);
@@ -608,48 +402,85 @@ namespace BetterLyrics.WinUI3.Services
}
}
/// <summary>
/// 下载 amll-ttml-db 的 JSONL 索引文件到本地缓存目录
/// </summary>
/// <returns>下载成功返回 true否则 false</returns>
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
private async Task<string?> SearchLrcLibAsync(string title, string artist, string album, int duration)
{
const string url =
"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
try
{
using var response = await _amllTtmlDbHttpClient.GetAsync(
url,
HttpCompletionOption.ResponseHeadersRead
);
if (!response.IsSuccessStatusCode)
return false;
// Build API query URL
var url =
$"https://lrclib.net/api/search?" +
$"track_name={Uri.EscapeDataString(title)}&" +
$"artist_name={Uri.EscapeDataString(artist)}&" +
$"&album_name={Uri.EscapeDataString(album)}" +
$"&durationMs={Uri.EscapeDataString(duration.ToString())}";
await using var stream = await response.Content.ReadAsStreamAsync();
await using var fs = new FileStream(
AppInfo.AmllTtmlDbIndexPath,
FileMode.Create,
FileAccess.Write,
FileShare.None
);
await stream.CopyToAsync(fs);
var response = await _lrcLibHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
return null;
return true;
}
catch
var json = await response.Content.ReadAsStringAsync();
var jArr = JsonSerializer.Deserialize(
json,
Serialization.SourceGenerationContext.Default.JsonElement
);
if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
{
return false;
var first = jArr[0];
var syncedLyrics = first.GetProperty("syncedLyrics").GetString();
var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
if (!string.IsNullOrWhiteSpace(result))
{
return result;
}
}
return null;
}
/// <summary>
/// The WriteCache
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="lyrics">The lyrics<see cref="string"/></param>
/// <param name="format">The format<see cref="LyricsFormat"/></param>
private void WriteCache(
private async Task<string?> SearchUsingLyricifyAsync(
string title,
string artist,
string album,
int durationMs,
Searchers searchers
)
{
var result = await SearchersHelper.GetSearcher(searchers).SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs = durationMs,
Album = album,
Artists = [artist],
Title = title,
}
);
if (result is QQMusicSearchResult qqResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.QQMusicApi.GetLyricsAsync(qqResult.Id);
var original = response?.Lyrics;
return original;
}
else if (result is NeteaseSearchResult neteaseResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
return response?.Lrc.Lyric;
}
else if (result is KugouSearchResult kugouResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(hash: kugouResult.Hash);
if (response?.Candidates.FirstOrDefault() is SearchLyricsResponse.Candidate candidate)
{
return Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyrics(
candidate.Id,
candidate.AccessKey
);
}
}
return null;
}
private void WriteLyricsCache(
string title,
string artist,
string lyrics,
@@ -666,6 +497,21 @@ namespace BetterLyrics.WinUI3.Services
File.WriteAllText(cacheFilePath, lyrics);
}
#endregion
private void WriteAlbumArtCache(
string album,
string artist,
byte[] img,
string format,
string cacheFolderPath
)
{
var safeArtist = SanitizeFileName(artist);
var safeAlbum = SanitizeFileName(album);
var cacheFilePath = Path.Combine(
cacheFolderPath,
$"{safeArtist} - {safeAlbum}{format}"
);
File.WriteAllBytes(cacheFilePath, img);
}
}
}

View File

@@ -1,309 +1,257 @@
// 2025/6/23 by Zhe Fang
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.WinUI;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using Windows.Graphics.Imaging;
using Windows.Media.Control;
using Windows.Storage.Streams;
using WindowsMediaController;
namespace BetterLyrics.WinUI3.Services
{
/// <summary>
/// Defines the <see cref="PlaybackService" />
/// </summary>
public partial class PlaybackService : IPlaybackService
public partial class PlaybackService : BaseViewModel, IPlaybackService, IRecipient<PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>>>
{
#region Fields
/// <summary>
/// Defines the _dispatcherQueue
/// </summary>
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
/// <summary>
/// Defines the _musicSearchService
/// </summary>
private readonly IMusicSearchService _musicSearchService;
private readonly ILogger<PlaybackService> _logger;
/// <summary>
/// Defines the _currentSession
/// </summary>
private GlobalSystemMediaTransportControlsSession? _currentSession = null;
private readonly MediaManager _mediaManager = new();
/// <summary>
/// Defines the _sessionManager
/// </summary>
private GlobalSystemMediaTransportControlsSessionManager? _sessionManager = null;
private CancellationTokenSource? _mediaPropsCts;
#endregion
private List<MediaSourceProviderInfo> _mediaSourceProvidersInfo;
#region Constructors
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
public event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
/// <summary>
/// Initializes a new instance of the <see cref="PlaybackService"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
/// <param name="musicSearchService">The musicSearchService<see cref="IMusicSearchService"/></param>
public PlaybackService(
ISettingsService settingsService,
IMusicSearchService musicSearchService
)
public PlaybackService(ISettingsService settingsService, IMusicSearchService musicSearchService) : base(settingsService)
{
_musicSearchService = musicSearchService;
InitMediaManager().ConfigureAwait(true);
_logger = Ioc.Default.GetRequiredService<ILogger<PlaybackService>>();
_mediaSourceProvidersInfo = _settingsService.MediaSourceProvidersInfo;
InitMediaManager();
}
#endregion
#region Events
/// <summary>
/// Defines the IsPlayingChanged
/// </summary>
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
/// <summary>
/// Defines the PositionChanged
/// </summary>
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
/// <summary>
/// Defines the SongInfoChanged
/// </summary>
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
#endregion
#region Properties
/// <summary>
/// Gets a value indicating whether IsPlaying
/// </summary>
public bool IsPlaying { get; private set; }
/// <summary>
/// Gets the Position
/// </summary>
public TimeSpan Position { get; private set; }
/// <summary>
/// Gets the SongInfo
/// </summary>
public SongInfo? SongInfo { get; private set; }
#endregion
#region Methods
/// <summary>
/// Note: this func is invoked by non-UI thread
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private async void CurrentSession_MediaPropertiesChanged(
GlobalSystemMediaTransportControlsSession? sender,
MediaPropertiesChangedEventArgs? args
)
private bool IsMediaSourceEnabled(string id)
{
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
if (sender == null)
return _mediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id)?.IsEnabled ?? true;
}
private void InitMediaManager()
{
_mediaManager.Start();
_mediaManager.OnAnySessionOpened += MediaManager_OnAnySessionOpened;
_mediaManager.OnAnySessionClosed += MediaManager_OnAnySessionClosed;
_mediaManager.OnFocusedSessionChanged += MediaManager_OnFocusedSessionChanged;
_mediaManager.OnAnyMediaPropertyChanged += MediaManager_OnAnyMediaPropertyChanged;
_mediaManager.OnAnyPlaybackStateChanged += MediaManager_OnAnyPlaybackStateChanged;
_mediaManager.OnAnyTimelinePropertyChanged += MediaManager_OnAnyTimelinePropertyChanged;
MediaManager_OnFocusedSessionChanged(_mediaManager.GetFocusedSession());
}
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession mediaSession)
{
if (mediaSession == null || !IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId))
{
SongInfo = null;
SendNullMessages();
}
else
{
try
_dispatcherQueue.TryEnqueue(async () =>
{
mediaProps = await sender.TryGetMediaPropertiesAsync();
}
catch (Exception) { }
if (mediaProps == null)
{
SongInfo = null;
}
else
{
SongInfo = new SongInfo
try
{
Title = mediaProps.Title,
Artist = mediaProps.Artist,
Album = mediaProps?.AlbumTitle ?? string.Empty,
DurationMs = _currentSession
?.GetTimelineProperties()
.EndTime.TotalMilliseconds,
SourceAppUserModelId = _currentSession?.SourceAppUserModelId,
};
if (
SongInfo.SourceAppUserModelId?.Contains(Package.Current.Id.FamilyName)
?? false
)
{
SongInfo.Title = "甜度爆表";
SongInfo.Artist = "AI";
var props = await mediaSession.ControlSession.TryGetMediaPropertiesAsync();
MediaManager_OnAnyMediaPropertyChanged(mediaSession, props);
MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo());
}
if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference)
catch (Exception ex)
{
SongInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(streamReference);
_logger.LogWarning(ex, "TryGetMediaPropertiesAsync failed");
SendNullMessages();
}
});
}
}
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties)
{
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return;
_dispatcherQueue.TryEnqueue(
DispatcherQueuePriority.High,
() =>
{
PositionChanged?.Invoke(this, new PositionChangedEventArgs(timelineProperties.Position));
}
);
}
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
{
RecordMediaSourceProviderInfo(mediaSession);
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
{
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(playbackInfo.PlaybackStatus switch
{
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
_ => false,
}));
}
);
}
private async void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
{
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
RecordMediaSourceProviderInfo(mediaSession);
string id = mediaSession.ControlSession.SourceAppUserModelId;
if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return;
_mediaPropsCts?.Cancel();
var cts = new CancellationTokenSource();
_mediaPropsCts = cts;
var token = cts.Token;
try
{
SongInfo? songInfo;
token.ThrowIfCancellationRequested();
songInfo = new SongInfo
{
Title = mediaProperties.Title,
Artist = mediaProperties.Artist,
Album = mediaProperties.AlbumTitle,
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
SourceAppUserModelId = id,
};
byte[]? bytes;
bytes = await _musicSearchService.SearchAlbumArtAsync(
songInfo.Title,
songInfo.Artist,
songInfo.Album
);
token.ThrowIfCancellationRequested();
if (bytes == null)
{
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
{
bytes = await ImageHelper.ToByteArrayAsync(streamReference);
token.ThrowIfCancellationRequested();
}
else
{
SongInfo.AlbumArt = _musicSearchService.SearchAlbumArtAsync(
SongInfo.Title,
SongInfo.Artist
);
if (SongInfo.AlbumArt == null)
{
SongInfo.AlbumArt = await ImageHelper.CreateTextPlaceholderBytesAsync(
$"{SongInfo.Artist} - {SongInfo.Title}",
400,
400
);
}
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync($"{songInfo.Artist} - {songInfo.Title}", 400, 400);
token.ThrowIfCancellationRequested();
}
}
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
token.ThrowIfCancellationRequested();
var decoder = await BitmapDecoder.CreateAsync(stream);
token.ThrowIfCancellationRequested();
songInfo.AlbumArtSwBitmap?.Dispose();
songInfo.AlbumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
token.ThrowIfCancellationRequested();
songInfo.AlbumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
if (!token.IsCancellationRequested)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
{
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(songInfo));
});
}
}
_dispatcherQueue.TryEnqueue(
DispatcherQueuePriority.High,
catch (OperationCanceledException) { }
catch (Exception) { }
}
private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession)
{
if (_mediaManager.CurrentMediaSessions.Count == 0)
{
SendNullMessages();
}
}
private void MediaManager_OnAnySessionOpened(MediaManager.MediaSession mediaSession)
{
RecordMediaSourceProviderInfo(mediaSession);
}
private void RecordMediaSourceProviderInfo(MediaManager.MediaSession mediaSession)
{
var id = mediaSession?.ControlSession?.SourceAppUserModelId;
if (string.IsNullOrEmpty(id)) return;
var found = _mediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == id);
if (found == null)
{
_mediaSourceProvidersInfo.Add(new MediaSourceProviderInfo(id, true));
_settingsService.MediaSourceProvidersInfo = _mediaSourceProvidersInfo;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
{
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
}
);
MediaSourceProvidersInfoChanged?.Invoke(this, new MediaSourceProvidersInfoEventArgs(_mediaSourceProvidersInfo));
});
}
}
/// <summary>
/// Note: Non-UI thread
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void CurrentSession_PlaybackInfoChanged(
GlobalSystemMediaTransportControlsSession? sender,
PlaybackInfoChangedEventArgs? args
)
private void SendNullMessages()
{
if (sender == null)
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
{
IsPlaying = false;
}
else
{
var playbackState = sender.GetPlaybackInfo().PlaybackStatus;
// _logger.LogDebug(playbackState.ToString());
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(null));
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(false));
PositionChanged?.Invoke(this, new PositionChangedEventArgs(TimeSpan.Zero));
});
}
switch (playbackState)
public void Receive(PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>> message)
{
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.MediaSourceProvidersInfo))
{
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Opened:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Changing:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Stopped:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Paused:
IsPlaying = false;
break;
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing:
IsPlaying = true;
break;
default:
break;
_mediaSourceProvidersInfo = [.. message.NewValue];
_settingsService.MediaSourceProvidersInfo = _mediaSourceProvidersInfo;
MediaManager_OnFocusedSessionChanged(_mediaManager.GetFocusedSession());
}
}
_dispatcherQueue.TryEnqueue(
DispatcherQueuePriority.High,
() =>
{
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(IsPlaying));
}
);
}
/// <summary>
/// The CurrentSession_TimelinePropertiesChanged
/// </summary>
/// <param name="sender">The sender<see cref="GlobalSystemMediaTransportControlsSession?"/></param>
/// <param name="args">The args<see cref="TimelinePropertiesChangedEventArgs?"/></param>
private void CurrentSession_TimelinePropertiesChanged(
GlobalSystemMediaTransportControlsSession? sender,
TimelinePropertiesChangedEventArgs? args
)
{
if (sender == null)
{
Position = TimeSpan.Zero;
}
else
{
Position = sender.GetTimelineProperties().Position;
}
_dispatcherQueue.TryEnqueue(
DispatcherQueuePriority.High,
() =>
{
PositionChanged?.Invoke(this, new PositionChangedEventArgs(Position));
}
);
}
/// <summary>
/// The InitMediaManager
/// </summary>
/// <returns>The <see cref="Task"/></returns>
private async Task InitMediaManager()
{
_sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
_sessionManager.CurrentSessionChanged += SessionManager_CurrentSessionChanged;
SessionManager_CurrentSessionChanged(_sessionManager, null);
}
/// <summary>
/// The SessionManager_CurrentSessionChanged
/// </summary>
/// <param name="sender">The sender<see cref="GlobalSystemMediaTransportControlsSessionManager"/></param>
/// <param name="args">The args<see cref="CurrentSessionChangedEventArgs?"/></param>
private void SessionManager_CurrentSessionChanged(
GlobalSystemMediaTransportControlsSessionManager sender,
CurrentSessionChangedEventArgs? args
)
{
// _logger.LogDebug("SessionManager_CurrentSessionChanged");
// Unregister events associated with the previous session
if (_currentSession != null)
{
_currentSession.MediaPropertiesChanged -= CurrentSession_MediaPropertiesChanged;
_currentSession.PlaybackInfoChanged -= CurrentSession_PlaybackInfoChanged;
_currentSession.TimelinePropertiesChanged -=
CurrentSession_TimelinePropertiesChanged;
}
// Record and register events for current session
_currentSession = sender.GetCurrentSession();
if (_currentSession != null)
{
_currentSession.MediaPropertiesChanged += CurrentSession_MediaPropertiesChanged;
_currentSession.PlaybackInfoChanged += CurrentSession_PlaybackInfoChanged;
_currentSession.TimelinePropertiesChanged +=
CurrentSession_TimelinePropertiesChanged;
}
CurrentSession_MediaPropertiesChanged(_currentSession, null);
CurrentSession_PlaybackInfoChanged(_currentSession, null);
CurrentSession_TimelinePropertiesChanged(_currentSession, null);
}
#endregion
}
}

View File

@@ -1,157 +1,85 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization;
using CommunityToolkit.WinUI.Helpers;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization;
using Microsoft.UI.Xaml;
using Windows.Storage;
using Windows.UI;
namespace BetterLyrics.WinUI3.Services
{
/// <summary>
/// Defines the <see cref="SettingsService" />
/// </summary>
public class SettingsService : ISettingsService
{
#region Constants
public const string LyricsCustomBgFontColorKey = "LyricsCustomBgFontColor";
public const string LyricsCustomFgFontColorKey = "LyricsCustomFgFontColor";
public const string LyricsCustomStrokeFontColorKey = "LyricsCustomStrokeFontColor";
// App behavior
/// <summary>
/// Defines the AutoStartWindowTypeKey
/// </summary>
private const string AutoStartWindowTypeKey = "AutoStartWindowType";
/// <summary>
/// Defines the BackdropTypeKey
/// </summary>
private const string BackdropTypeKey = "BackdropType";
/// <summary>
/// Defines the CoverImageRadiusKey
/// </summary>
private const string CoverImageRadiusKey = "CoverImageRadius";
/// <summary>
/// Defines the CoverOverlayBlurAmountKey
/// </summary>
private const string CoverImageRadiusKey = "AlbumArtCornerRadius";
private const string CoverOverlayBlurAmountKey = "CoverOverlayBlurAmount";
/// <summary>
/// Defines the CoverOverlayOpacityKey
/// </summary>
private const string CoverOverlayOpacityKey = "CoverOverlayOpacity";
// Album art
/// <summary>
/// Defines the IsCoverOverlayEnabledKey
/// </summary>
private const string IsCoverOverlayEnabledKey = "IsCoverOverlayEnabled";
/// <summary>
/// Defines the IsDynamicCoverOverlayEnabledKey
/// </summary>
private const string DesktopWindowLeftKey = "DesktopWindowLeft";
private const string DesktopWindowTopKey = "DesktopWindowTop";
private const string DesktopWindowWidthKey = "DesktopWindowWidth";
private const string DesktopWindowHeightKey = "DesktopWindowHeight";
private const string StandardWindowLeftKey = "StandardWindowLeft";
private const string StandardWindowTopKey = "StandardWindowTop";
private const string StandardWindowWidthKey = "StandardWindowWidth";
private const string StandardWindowHeightKey = "StandardWindowHeight";
private const string AutoLockOnDesktopModeKey = "AutoLockOnDesktopMode";
private const string IsDynamicCoverOverlayEnabledKey = "IsDynamicCoverOverlayEnabled";
/// <summary>
/// Defines the IsFirstRunKey
/// </summary>
private const string IsFanLyricsEnabledKey = "IsFanLyricsEnabled";
private const string IsFirstRunKey = "IsFirstRun";
/// <summary>
/// Defines the IsLyricsGlowEffectEnabledKey
/// </summary>
private const string IsLyricsGlowEffectEnabledKey = "IsLyricsGlowEffectEnabled";
/// <summary>
/// Defines the LanguageKey
/// </summary>
private const string LanguageKey = "Language";
// Lyrics lib
/// <summary>
/// Defines the LocalLyricsFoldersKey
/// </summary>
private const string LocalLyricsFoldersKey = "LocalLyricsFolders";
/// <summary>
/// Defines the LyricsAlignmentTypeKey
/// </summary>
private const string LyricsAlignmentTypeKey = "LyricsAlignmentType";
/// <summary>
/// Defines the LyricsBlurAmountKey
/// </summary>
private const string LyricsAlignmentTypeKey = "TextAlignmentType";
private const string SongInfoAlignmentTypeKey = "SongInfoAlignmentType";
private const string LyricsBlurAmountKey = "LyricsBlurAmount";
/// <summary>
/// Defines the LyricsFontColorTypeKey
/// </summary>
private const string LyricsFontColorTypeKey = "LyricsFontColorType";
private const string LyricsBgFontColorTypeKey = "_lyricsBgFontColorType";
private const string LyricsFgFontColorTypeKey = "LyricsFgFontColorType";
private const string LyricsStrokeFontColorTypeKey = "LyricsStrokeFontColorType";
private const string LyricsFontStrokeWidthKey = "LyricsFontStrokeWidth";
/// <summary>
/// Defines the LyricsFontSizeKey
/// </summary>
private const string LyricsFontSizeKey = "LyricsFontSize";
/// <summary>
/// Defines the LyricsFontWeightKey
/// </summary>
private const string LyricsFontWeightKey = "LyricsFontWeightKey";
/// <summary>
/// Defines the LyricsGlowEffectScopeKey
/// </summary>
private const string LyricsGlowEffectScopeKey = "LyricsGlowEffectScope";
/// <summary>
/// Defines the LyricsLineSpacingFactorKey
/// </summary>
private const string LyricsLineSpacingFactorKey = "LyricsLineSpacingFactor";
/// <summary>
/// Defines the LyricsSearchProvidersInfoKey
/// </summary>
private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo";
/// <summary>
/// Defines the LyricsVerticalEdgeOpacityKey
/// </summary>
private const string LyricsVerticalEdgeOpacityKey = "LyricsVerticalEdgeOpacity";
// App appearance
private const string MediaSourceProvidersInfoKey = "MediaSourceProvidersInfo";
/// <summary>
/// Defines the ThemeTypeKey
/// </summary>
private const string ThemeTypeKey = "ThemeType";
private const string IsTranslationEnabledKey = "IsTranslationEnabled";
private const string LibreTranslateServerKey = "LibreTranslateServer";
private const string SelectedTargetLanguageIndexKey = "SelectedTargetLanguageIndex";
/// <summary>
/// Defines the TitleBarTypeKey
/// </summary>
private const string TitleBarTypeKey = "TitleBarType";
private const string LyricsBackgroundThemeKey = "LyricsBackgroundTheme";
#endregion
private const string IgnoreFullscreenWindowKey = "IgnoreFullscreenWindow";
#region Fields
private const string PreferredDisplayTypeKey = "PreferredDisplayTypeKey";
/// <summary>
/// Defines the _localSettings
/// </summary>
private readonly ApplicationDataContainer _localSettings;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SettingsService"/> class.
/// </summary>
public SettingsService()
{
_localSettings = ApplicationData.Current.LocalSettings;
@@ -168,6 +96,7 @@ namespace BetterLyrics.WinUI3.Services
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
)
);
SetDefault(MediaSourceProvidersInfoKey, "[]");
if (LyricsSearchProvidersInfo.Count != Enum.GetValues<LyricsSearchProvider>().Length)
{
LyricsSearchProvidersInfo = Enum.GetValues<LyricsSearchProvider>()
@@ -181,127 +110,181 @@ namespace BetterLyrics.WinUI3.Services
.ToList();
}
// App appearance
SetDefault(ThemeTypeKey, (int)ElementTheme.Default);
SetDefault(LanguageKey, (int)Language.FollowSystem);
SetDefault(BackdropTypeKey, (int)BackdropType.DesktopAcrylic);
SetDefault(DesktopWindowHeightKey, 600);
SetDefault(DesktopWindowLeftKey, 200);
SetDefault(DesktopWindowTopKey, 200);
SetDefault(DesktopWindowWidthKey, 1200);
SetDefault(StandardWindowHeightKey, 800);
SetDefault(StandardWindowLeftKey, 200);
SetDefault(StandardWindowTopKey, 200);
SetDefault(StandardWindowWidthKey, 1600);
SetDefault(AutoLockOnDesktopModeKey, false);
// App behavior
SetDefault(AutoStartWindowTypeKey, (int)AutoStartWindowType.StandardMode);
// Album art
SetDefault(IsCoverOverlayEnabledKey, true);
SetDefault(IsDynamicCoverOverlayEnabledKey, true);
SetDefault(CoverOverlayOpacityKey, 75); // 100 % = 1.0
SetDefault(CoverOverlayOpacityKey, 100); // 100 % = 1.0
SetDefault(CoverOverlayBlurAmountKey, 200);
SetDefault(TitleBarTypeKey, (int)TitleBarType.Compact);
SetDefault(CoverImageRadiusKey, 24); // 24 %
SetDefault(CoverImageRadiusKey, 12); // 12 %
// Lyrics
SetDefault(LyricsAlignmentTypeKey, (int)LyricsAlignmentType.Center);
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Center);
SetDefault(SongInfoAlignmentTypeKey, (int)TextAlignmentType.Left);
SetDefault(LyricsFontWeightKey, (int)LyricsFontWeight.Bold);
SetDefault(LyricsBlurAmountKey, 5);
SetDefault(LyricsFontColorTypeKey, (int)LyricsFontColorType.Default);
SetDefault(LyricsBackgroundThemeKey, (int)ElementTheme.Default);
SetDefault(LyricsBgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
SetDefault(LyricsFgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
SetDefault(LyricsStrokeFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
SetDefault(LyricsCustomBgFontColorKey, Colors.White.ToInt());
SetDefault(LyricsCustomFgFontColorKey, Colors.White.ToInt());
SetDefault(LyricsCustomStrokeFontColorKey, Colors.White.ToInt());
SetDefault(LyricsFontSizeKey, 28);
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
SetDefault(IsLyricsGlowEffectEnabledKey, true);
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.UntilCurrentChar);
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.CurrentCharOnly);
SetDefault(IsFanLyricsEnabledKey, false);
SetDefault(LibreTranslateServerKey, "");
SetDefault(IsTranslationEnabledKey, false);
SetDefault(SelectedTargetLanguageIndexKey, 6);
SetDefault(LyricsFontStrokeWidthKey, 3);
SetDefault(IgnoreFullscreenWindowKey, false);
SetDefault(PreferredDisplayTypeKey, (int)LyricsDisplayType.SplitView);
}
#endregion
public LyricsDisplayType PreferredDisplayType
{
get => (LyricsDisplayType)GetValue<int>(PreferredDisplayTypeKey);
set => SetValue(PreferredDisplayTypeKey, (int)value);
}
#region Properties
public ElementTheme LyricsBackgroundTheme
{
get => (ElementTheme)GetValue<int>(LyricsBackgroundThemeKey);
set => SetValue(LyricsBackgroundThemeKey, (int)value);
}
/// <summary>
/// Gets or sets the AutoStartWindowType
/// </summary>
public AutoStartWindowType AutoStartWindowType
{
get => (AutoStartWindowType)GetValue<int>(AutoStartWindowTypeKey);
set => SetValue(AutoStartWindowTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the BackdropType
/// </summary>
public BackdropType BackdropType
public int DesktopWindowLeft
{
get => (BackdropType)GetValue<int>(BackdropTypeKey);
set => SetValue(BackdropTypeKey, (int)value);
get => GetValue<int>(DesktopWindowLeftKey);
set => SetValue(DesktopWindowLeftKey, value);
}
public int DesktopWindowTop
{
get => GetValue<int>(DesktopWindowTopKey);
set => SetValue(DesktopWindowTopKey, value);
}
public int DesktopWindowWidth
{
get => GetValue<int>(DesktopWindowWidthKey);
set => SetValue(DesktopWindowWidthKey, value);
}
public int DesktopWindowHeight
{
get => GetValue<int>(DesktopWindowHeightKey);
set => SetValue(DesktopWindowHeightKey, value);
}
public int StandardWindowLeft
{
get => GetValue<int>(StandardWindowLeftKey);
set => SetValue(StandardWindowLeftKey, value);
}
public int StandardWindowTop
{
get => GetValue<int>(StandardWindowTopKey);
set => SetValue(StandardWindowTopKey, value);
}
public int StandardWindowWidth
{
get => GetValue<int>(StandardWindowWidthKey);
set => SetValue(StandardWindowWidthKey, value);
}
public int StandardWindowHeight
{
get => GetValue<int>(StandardWindowHeightKey);
set => SetValue(StandardWindowHeightKey, value);
}
public bool AutoLockOnDesktopMode
{
get => GetValue<bool>(AutoLockOnDesktopModeKey);
set => SetValue(AutoLockOnDesktopModeKey, value);
}
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
public int CoverImageRadius
{
get => GetValue<int>(CoverImageRadiusKey);
set => SetValue(CoverImageRadiusKey, value);
}
/// <summary>
/// Gets or sets the CoverOverlayBlurAmount
/// </summary>
public int CoverOverlayBlurAmount
{
get => GetValue<int>(CoverOverlayBlurAmountKey);
set => SetValue(CoverOverlayBlurAmountKey, value);
}
/// <summary>
/// Gets or sets the CoverOverlayOpacity
/// </summary>
public int CoverOverlayOpacity
{
get => GetValue<int>(CoverOverlayOpacityKey);
set => SetValue(CoverOverlayOpacityKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
/// </summary>
public bool IsCoverOverlayEnabled
{
get => GetValue<bool>(IsCoverOverlayEnabledKey);
set => SetValue(IsCoverOverlayEnabledKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
public bool IsDynamicCoverOverlayEnabled
{
get => GetValue<bool>(IsDynamicCoverOverlayEnabledKey);
set => SetValue(IsDynamicCoverOverlayEnabledKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsFirstRun
/// </summary>
public bool IsFanLyricsEnabled
{
get => GetValue<bool>(IsFanLyricsEnabledKey);
set => SetValue(IsFanLyricsEnabledKey, value);
}
public bool IsFirstRun
{
get => GetValue<bool>(IsFirstRunKey);
set => SetValue(IsFirstRunKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
/// </summary>
public bool IsLyricsGlowEffectEnabled
{
get => GetValue<bool>(IsLyricsGlowEffectEnabledKey);
set => SetValue(IsLyricsGlowEffectEnabledKey, value);
}
/// <summary>
/// Gets or sets the Language
/// </summary>
public Language Language
{
get => (Language)GetValue<int>(LanguageKey);
set => SetValue(LanguageKey, (int)value);
}
/// <summary>
/// Gets or sets the LocalLyricsFolders
/// </summary>
public List<LocalLyricsFolder> LocalLyricsFolders
{
get =>
@@ -319,72 +302,90 @@ namespace BetterLyrics.WinUI3.Services
);
}
/// <summary>
/// Gets or sets the LyricsAlignmentType
/// </summary>
public LyricsAlignmentType LyricsAlignmentType
public TextAlignmentType LyricsAlignmentType
{
get => (LyricsAlignmentType)GetValue<int>(LyricsAlignmentTypeKey);
get => (TextAlignmentType)GetValue<int>(LyricsAlignmentTypeKey);
set => SetValue(LyricsAlignmentTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsBlurAmount
/// </summary>
public TextAlignmentType SongInfoAlignmentType
{
get => (TextAlignmentType)GetValue<int>(SongInfoAlignmentTypeKey);
set => SetValue(SongInfoAlignmentTypeKey, (int)value);
}
public int LyricsBlurAmount
{
get => GetValue<int>(LyricsBlurAmountKey);
set => SetValue(LyricsBlurAmountKey, value);
}
/// <summary>
/// Gets or sets the LyricsFontColorType
/// </summary>
public LyricsFontColorType LyricsFontColorType
public Color LyricsCustomBgFontColor
{
get => (LyricsFontColorType)GetValue<int>(LyricsFontColorTypeKey);
set => SetValue(LyricsFontColorTypeKey, (int)value);
get => GetValue<int>(LyricsCustomBgFontColorKey)!.ToColor();
set => SetValue(LyricsCustomBgFontColorKey, value.ToInt());
}
public Color LyricsCustomFgFontColor
{
get => GetValue<int>(LyricsCustomFgFontColorKey)!.ToColor();
set => SetValue(LyricsCustomFgFontColorKey, value.ToInt());
}
public Color LyricsCustomStrokeFontColor
{
get => GetValue<int>(LyricsCustomStrokeFontColorKey)!.ToColor();
set => SetValue(LyricsCustomStrokeFontColorKey, value.ToInt());
}
public LyricsFontColorType LyricsBgFontColorType
{
get => (LyricsFontColorType)GetValue<int>(LyricsBgFontColorTypeKey);
set => SetValue(LyricsBgFontColorTypeKey, (int)value);
}
public LyricsFontColorType LyricsFgFontColorType
{
get => (LyricsFontColorType)GetValue<int>(LyricsFgFontColorTypeKey);
set => SetValue(LyricsFgFontColorTypeKey, (int)value);
}
public LyricsFontColorType LyricsStrokeFontColorType
{
get => (LyricsFontColorType)GetValue<int>(LyricsStrokeFontColorTypeKey);
set => SetValue(LyricsStrokeFontColorTypeKey, (int)value);
}
public int LyricsFontStrokeWidth
{
get => GetValue<int>(LyricsFontStrokeWidthKey);
set => SetValue(LyricsFontStrokeWidthKey, value);
}
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
public int LyricsFontSize
{
get => GetValue<int>(LyricsFontSizeKey);
set => SetValue(LyricsFontSizeKey, value);
}
/// <summary>
/// Gets or sets the LyricsFontWeight
/// </summary>
public LyricsFontWeight LyricsFontWeight
{
get => (LyricsFontWeight)GetValue<int>(LyricsFontWeightKey);
set => SetValue(LyricsFontWeightKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsGlowEffectScope
/// </summary>
public LineRenderingType LyricsGlowEffectScope
{
get => (LineRenderingType)GetValue<int>(LyricsGlowEffectScopeKey);
set => SetValue(LyricsGlowEffectScopeKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsLineSpacingFactor
/// </summary>
public float LyricsLineSpacingFactor
{
get => GetValue<float>(LyricsLineSpacingFactorKey);
set => SetValue(LyricsLineSpacingFactorKey, value);
}
/// <summary>
/// Gets or sets the LyricsSearchProvidersInfo
/// </summary>
public List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo
{
get =>
@@ -402,43 +403,53 @@ namespace BetterLyrics.WinUI3.Services
);
}
/// <summary>
/// Gets or sets the LyricsVerticalEdgeOpacity
/// </summary>
public List<MediaSourceProviderInfo> MediaSourceProvidersInfo
{
get =>
System.Text.Json.JsonSerializer.Deserialize(
GetValue<string>(MediaSourceProvidersInfoKey) ?? "[]",
SourceGenerationContext.Default.ListMediaSourceProviderInfo
)!;
set =>
SetValue(
MediaSourceProvidersInfoKey,
System.Text.Json.JsonSerializer.Serialize(
value,
SourceGenerationContext.Default.ListMediaSourceProviderInfo
)
);
}
public int LyricsVerticalEdgeOpacity
{
get => GetValue<int>(LyricsVerticalEdgeOpacityKey);
set => SetValue(LyricsVerticalEdgeOpacityKey, value);
}
/// <summary>
/// Gets or sets the ThemeType
/// </summary>
public ElementTheme ThemeType
public string LibreTranslateServer
{
get => (ElementTheme)GetValue<int>(ThemeTypeKey);
set => SetValue(ThemeTypeKey, (int)value);
get => GetValue<string>(LibreTranslateServerKey)!;
set => SetValue(LibreTranslateServerKey, value);
}
/// <summary>
/// Gets or sets the TitleBarType
/// </summary>
public TitleBarType TitleBarType
public bool IsTranslationEnabled
{
get => (TitleBarType)GetValue<int>(TitleBarTypeKey);
set => SetValue(TitleBarTypeKey, (int)value);
get => GetValue<bool>(IsTranslationEnabledKey);
set => SetValue(IsTranslationEnabledKey, value);
}
#endregion
public int SelectedTargetLanguageIndex
{
get => GetValue<int>(SelectedTargetLanguageIndexKey);
set => SetValue(SelectedTargetLanguageIndexKey, value);
}
#region Methods
public bool IgnoreFullscreenWindow
{
get => GetValue<bool>(IgnoreFullscreenWindowKey);
set => SetValue(IgnoreFullscreenWindowKey, value);
}
/// <summary>
/// The GetValue
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key<see cref="string"/></param>
/// <returns>The <see cref="T?"/></returns>
private T? GetValue<T>(string key)
{
if (_localSettings.Values.TryGetValue(key, out object? value))
@@ -448,12 +459,6 @@ namespace BetterLyrics.WinUI3.Services
return default;
}
/// <summary>
/// The SetDefault
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key<see cref="string"/></param>
/// <param name="value">The value<see cref="T"/></param>
private void SetDefault<T>(string key, T value)
{
if (_localSettings.Values.ContainsKey(key) && _localSettings.Values[key] is T)
@@ -461,17 +466,9 @@ namespace BetterLyrics.WinUI3.Services
_localSettings.Values[key] = value;
}
/// <summary>
/// The SetValue
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key<see cref="string"/></param>
/// <param name="value">The value<see cref="T"/></param>
private void SetValue<T>(string key, T value)
{
_localSettings.Values[key] = value;
}
#endregion
}
}

View File

@@ -139,13 +139,16 @@
<value>Add a folder</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>Theme</value>
<value>Lyrics background theme</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>Language</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>Follow system</value>
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>Adaptive to lyrics background (Colored)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
<value>Adaptive to lyrics background (Colored)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>Light</value>
@@ -196,7 +199,7 @@
<value>Transparent</value>
</data>
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
<value>Backdrop</value>
<value>Lyrics backdrop</value>
</data>
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
<value>Default</value>
@@ -210,14 +213,14 @@
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
<value>The folder has been added. Please do not add it again.</value>
</data>
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
<value>Overlay album art background</value>
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
<value>Lyrics background</value>
</data>
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
<value>Dynamic album art background</value>
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
<value>Dynamic lyrics background</value>
</data>
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
<value>Album art background opacity</value>
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
<value>Lyrics background opacity</value>
</data>
<data name="SettingsPageTitle" xml:space="preserve">
<value>Settings - BetterLyrics</value>
@@ -237,8 +240,8 @@
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>Right</value>
</data>
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
<value>Album art background blur amount</value>
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
<value>Lyrics background blur amount</value>
</data>
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
<value>Blur amount</value>
@@ -252,9 +255,6 @@
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
<value>Significantly higher GPU usage when blur is enabled (&gt; 0)</value>
</data>
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
<value>Enabling this feature will slightly increase GPU utilization</value>
</data>
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>Top and bottom edge opacity</value>
</data>
@@ -273,8 +273,8 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>Immersive mode</value>
</data>
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
<value>Album background</value>
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
<value>Lyrics background</value>
</data>
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>About</value>
@@ -282,7 +282,7 @@
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>Lyrics library</value>
</data>
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>App appearance</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
@@ -304,7 +304,7 @@
<value>Welcome to BetterLyrics</value>
</data>
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
<value>Let's setup lyrics database now</value>
<value>Hover the mouse over the top or bottom area of the app to display more function options</value>
</data>
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>No music playing now</value>
@@ -316,7 +316,7 @@
<value>Play test music</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>Play using system player</value>
<value>Play "Cut To The Feeling" on "soundcloud.com"</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>Cache</value>
@@ -324,17 +324,20 @@
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>Including log files, network lyrics cache</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>Font color</value>
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
<value>Font color (Non-current playback area)</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>Default</value>
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
<value>Font color (Current playback area)</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>Album art accent color</value>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>Adaptive to lyrics background (Grayed)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>Adaptive to lyrics background (Grayed)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>Album art style</value>
<value>Album art area style</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>Corner radius</value>
@@ -384,19 +387,19 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>Exit picture-in-picture mode</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<data name="LyricsNotFound" xml:space="preserve">
<value>Lyrics not found</value>
</data>
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>Lyrics effect</value>
</data>
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>Lyrics style</value>
</data>
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
<value>This folder is already included in the existing folder and does not need to be added again</value>
</data>
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
<value>App behavior</value>
</data>
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
@@ -406,10 +409,10 @@
<value>Activate standard mode</value>
</data>
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
<value>Activate dock mode</value>
<value>Activate desktop mode</value>
</data>
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
<value>Lyrics not found</value>
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
<value>Activate dock mode</value>
</data>
<data name="SystemTrayPageTitle" xml:space="preserve">
<value>System tray - BetterLyrics</value>
@@ -468,7 +471,7 @@
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
<value>Settings</value>
</data>
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
<data name="LyricsLoading" xml:space="preserve">
<value>Loading lyrics...</value>
</data>
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
@@ -523,7 +526,7 @@
<value>Exit</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>Unlock the window</value>
<value>Unlock the window (Restart needed)</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>Lock</value>
@@ -531,4 +534,124 @@
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>To unlock after locking, go to the system tray to unlock</value>
</data>
<data name="SettingsPageFan.Header" xml:space="preserve">
<value>Fan lyrics</value>
</data>
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>Custom</value>
</data>
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
<value>Custom</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>Lyrics style and effect</value>
</data>
<data name="SettingsPageApp.Content" xml:space="preserve">
<value>App appearance and behavior</value>
</data>
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
<value>Auto-lock when activating desktop mode</value>
</data>
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
<value>Alignment</value>
</data>
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
<value>Center</value>
</data>
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
<value>Left</value>
</data>
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>Right</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>Album art</value>
</data>
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
<value>Song title &amp; artist</value>
</data>
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
<value>Easing animation type</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>Media sources</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
<value>Media source</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
<value>Enable or disable lyrics display for a specified media source</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>Log record</value>
</data>
<data name="SettingsPageTranslation.Content" xml:space="preserve">
<value>Lyrics translation</value>
</data>
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>Lyrics timeline offset (ms)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>Configure translation services</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>Server address</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>Test server</value>
</data>
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>Target language</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>Translation service powered by LibreTranslate</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>Visit https://github.com/LibreTranslate/LibreTranslate for installation instructions and more information (this software is not affiliated with this translation service in any way)</value>
</data>
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
<value>Server test successful</value>
</data>
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
<value>Server test failed</value>
</data>
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
<value>Lyrics stroke width (Desktop mode only)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>Follow system</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>Automatic startup</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>Lyrics stroke color (Desktop mode only)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
<value>Always stay on top of fullscreen applications</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
<value>Force this app to appear on top of full-screen apps when docked or desktop mode is enabled</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>More</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
<value>Lyrics timeline offset</value>
</data>
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
<value>Translate</value>
</data>
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
<value>Display type</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<value>Settings</value>
</data>
<data name="TranslateServerNotSet" xml:space="preserve">
<value>Translate server is not set, please configure it in settings first</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<value>Will automatically reset to 0 when switching songs</value>
</data>
</root>

View File

@@ -139,13 +139,16 @@
<value>フォルダーを追加します</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>テーマ</value>
<value>歌詞の背景テーマ</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>言語</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>システムをフォローします</value>
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>歌詞の背景に適応する(色付き)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
<value>歌詞の背景に適応する(色付き)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>ライト</value>
@@ -196,7 +199,7 @@
<value>透明</value>
</data>
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
<value>背景</value>
<value>歌詞の背景素材</value>
</data>
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
<value>デフォルト</value>
@@ -210,14 +213,14 @@
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
<value>フォルダーが追加されました。二度と追加しないでください。</value>
</data>
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
<value>オーバーレイアルバムアートの背景</value>
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
<value>歌詞の背景</value>
</data>
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
<value>ダイナミックアルバムアートの背景</value>
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
<value>ダイナミックな歌詞の背景</value>
</data>
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
<value>アルバムアートの背景不透明</value>
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
<value>歌詞の背景不透明</value>
</data>
<data name="SettingsPageTitle" xml:space="preserve">
<value>設定 - BetterLyrics</value>
@@ -237,8 +240,8 @@
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>右</value>
</data>
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
<value>アルバムアートバックグラウンドブラー量</value>
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
<value>歌詞の背景ぼやけ</value>
</data>
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
<value>ぼやけの量</value>
@@ -252,9 +255,6 @@
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
<value>ぼかしが有効になっている場合のGPU使用量が大幅に高くなります&gt; 0</value>
</data>
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
<value>この機能を有効にすると、GPUの使用率がわずかに増加します</value>
</data>
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>上端と下端の不透明度</value>
</data>
@@ -273,8 +273,8 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>没入モード</value>
</data>
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
<value>アルバムの背景</value>
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
<value>歌詞の背景</value>
</data>
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>について</value>
@@ -282,7 +282,7 @@
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>歌詞ライブラリ</value>
</data>
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>アプリの外観</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
@@ -304,7 +304,7 @@
<value>BetterLyrics へようこそ</value>
</data>
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
<value>今すぐ歌詞データベースをセットアップしましょう</value>
<value>マウスをアプリの上または下部の領域にホバリングして、より多くの機能オプションを表示します</value>
</data>
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>今は音楽が再生されていません</value>
@@ -316,7 +316,7 @@
<value>テスト音楽を再生します</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>システムプレーヤーを使用して再生します</value>
<value>「SoundCloud.com」で「Cut to the Feeling」を再生する</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>キャッシュ</value>
@@ -324,17 +324,20 @@
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>ログファイル、ネットワーク歌詞キャッシュを含む</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>フォントカラー</value>
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
<value>フォントカラー(非電流再生エリア)</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>フォルト</value>
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
<value>フォントカラー(現在の再生エリア)</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>アルバムアートアクセントカラー</value>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>歌詞の背景に適応する(灰色)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>歌詞の背景に適応する(灰色)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>アルバムアートスタイル</value>
<value>アルバムエリアスタイル</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>コーナー半径</value>
@@ -384,19 +387,19 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>ピクチャーインピクチャーモードを終了します</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<data name="LyricsNotFound" xml:space="preserve">
<value>歌詞が見つかりません</value>
</data>
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌詞効果</value>
</data>
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>歌詞スタイル</value>
</data>
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
<value>このフォルダーは既存のフォルダーに既に含まれており、再度追加する必要はありません</value>
</data>
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
<value>アプリの動作</value>
</data>
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
@@ -406,10 +409,10 @@
<value>標準モードをアクティブにします</value>
</data>
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
<value>ドックモードをアクティブにします</value>
<value>デスクトップモードを開始します</value>
</data>
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
<value>歌詞が見つかりません</value>
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
<value>ドックモードをアクティブにします</value>
</data>
<data name="SystemTrayPageTitle" xml:space="preserve">
<value>システムトレイ - BetterLyrics</value>
@@ -468,7 +471,7 @@
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
<value>設定</value>
</data>
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
<data name="LyricsLoading" xml:space="preserve">
<value>歌詞の読み込み...</value>
</data>
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
@@ -523,7 +526,7 @@
<value>プログラムを終了します</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>ウィンドウのロックを解除します</value>
<value>ウィンドウのロックを解除する(再起動が必要)</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>ロック</value>
@@ -531,4 +534,124 @@
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>ロック後にロックを解除するには、システムトレイに移動してロックを解除します</value>
</data>
<data name="SettingsPageFan.Header" xml:space="preserve">
<value>ファンの歌詞</value>
</data>
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>カスタマイズ</value>
</data>
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
<value>カスタマイズ</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>歌詞のスタイルと効果</value>
</data>
<data name="SettingsPageApp.Content" xml:space="preserve">
<value>アプリの外観と動作</value>
</data>
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
<value>デスクトップモードをアクティブにするときの自動ロック</value>
</data>
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
<value>アライメント</value>
</data>
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
<value>中心</value>
</data>
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
<value>左</value>
</data>
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>右</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>アルバムアート</value>
</data>
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
<value>曲のタイトル&アーティスト</value>
</data>
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
<value>アニメーションタイプを緩和します</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>メディアソース</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
<value>メディアソース</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
<value>指定されたメディアソースの歌詞ディスプレイを有効または無効にする</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>ログレコード</value>
</data>
<data name="SettingsPageTranslation.Content" xml:space="preserve">
<value>歌詞翻訳</value>
</data>
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>歌詞タイムラインオフセット(ミリ秒)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>翻訳サービスを構成します</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>サーバーアドレス</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>テストサーバー</value>
</data>
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>ターゲット言語</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>LibreTranslate を搭載した翻訳サービス</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>https://github.com/LibreTranslate/LibreTranslate にアクセスしてください。</value>
</data>
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
<value>サーバーテストが成功しました</value>
</data>
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
<value>サーバーテストに失敗しました</value>
</data>
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
<value>歌詞ストローク幅(デスクトップモードのみ)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>システムをフォローします</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>自動起動</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>歌詞ストロークカラー(デスクトップモードのみ)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
<value>常にフルスクリーンアプリケーションを常に把握してください</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
<value>このアプリは、ドッキングまたはデスクトップモードが有効になっているときにフルスクリーンアプリの上に表示されます</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>もっと</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
<value>歌詞タイムラインオフセット</value>
</data>
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
<value>翻訳する</value>
</data>
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
<value>表示タイプ</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<value>設定</value>
</data>
<data name="TranslateServerNotSet" xml:space="preserve">
<value>翻訳サーバーは設定されていません。最初に設定で構成してください</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<value>曲を切り替えると、0 に自動的にリセットされます</value>
</data>
</root>

View File

@@ -139,13 +139,16 @@
<value>폴더를 추가하십시오</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>주제</value>
<value>가사 배경 테마</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>언어</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>시스템을 따르십시오</value>
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>가사 배경 (색상)에 적응</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
<value>가사 배경 (색상)에 적응</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>빛</value>
@@ -196,7 +199,7 @@
<value>투명한</value>
</data>
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
<value>배경</value>
<value>가사 배경 자료</value>
</data>
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
<value>기본</value>
@@ -210,14 +213,14 @@
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
<value>폴더가 추가되었습니다. 다시 추가하지 마십시오.</value>
</data>
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
<value>오버레이 앨범 아트 배경</value>
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
<value>가사 배경</value>
</data>
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
<value>동적 앨범 아트 배경</value>
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
<value>동적 인 가사 배경</value>
</data>
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
<value>앨범 아트 배경 불투명도</value>
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
<value>가사 배경 불투명도</value>
</data>
<data name="SettingsPageTitle" xml:space="preserve">
<value>설정 - BetterLyrics</value>
@@ -237,8 +240,8 @@
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>오른쪽</value>
</data>
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
<value>앨범 아트 배경 흐림 금액</value>
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
<value>가사 배경 블러</value>
</data>
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
<value>흐림 금액</value>
@@ -252,9 +255,6 @@
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
<value>Blur가 활성화 될 때 상당히 높은 GPU 사용량 (&gt; 0)</value>
</data>
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
<value>이 기능을 활성화하면 GPU 사용률이 약간 증가합니다</value>
</data>
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>상단 및 하단 가장자리 불투명도</value>
</data>
@@ -273,8 +273,8 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>몰입 형 모드</value>
</data>
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
<value>앨범 배경</value>
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
<value>가사 배경</value>
</data>
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>에 대한</value>
@@ -282,7 +282,7 @@
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>가사 도서관</value>
</data>
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>앱 모양</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
@@ -304,7 +304,7 @@
<value>Betterlyrics에 오신 것을 환영합니다</value>
</data>
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
<value>지금 가사 데이터베이스를 설정합시다</value>
<value>더 많은 기능 옵션을 표시하려면 앱의 상단 또는 하단 영역 위로 마우스를 가져옵니다.</value>
</data>
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>지금 음악이 재생되지 않습니다</value>
@@ -316,7 +316,7 @@
<value>테스트 음악을 재생하십시오</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>시스템 플레이어를 사용하여 재생하십시오</value>
<value>"soundcloud.com"에서 "Fut to the Feeling"을 재생하십시오.</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>은닉처</value>
@@ -324,17 +324,20 @@
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>로그 파일, 네트워크 가사 캐시 포함</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>글꼴 색상</value>
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
<value>글꼴 색상 (비 전류 재생 영역)</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>기본</value>
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
<value>글꼴 색상 (현재 재생 영역)</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>앨범 아트 악센트 색상</value>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>가사 배경 (회색)에 적응</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>가사 배경 (회색)에 적응</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>앨범 아트 스타일</value>
<value>앨범 영역 스타일</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>코너 반경</value>
@@ -384,19 +387,19 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>Picture-in-Picture 모드 종료</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<data name="LyricsNotFound" xml:space="preserve">
<value>가사를 찾을 수 없습니다</value>
</data>
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>가사 효과</value>
</data>
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>가사 스타일</value>
</data>
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
<value>이 폴더는 이미 기존 폴더에 포함되어 있으며 다시 추가 할 필요가 없습니다.</value>
</data>
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
<value>앱 동작</value>
</data>
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
@@ -406,10 +409,10 @@
<value>표준 모드를 ​​활성화합니다</value>
</data>
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
<value>도크 모드를 활성화하십시오</value>
<value>데스크탑 모드를 시작하십시오</value>
</data>
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
<value>가사를 찾을 수 없습니다</value>
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
<value>도크 모드를 활성화하십시오</value>
</data>
<data name="SystemTrayPageTitle" xml:space="preserve">
<value>시스템 트레이 - BetterLyrics</value>
@@ -468,7 +471,7 @@
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
<value>설정</value>
</data>
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
<data name="LyricsLoading" xml:space="preserve">
<value>가사로드 ...</value>
</data>
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
@@ -523,7 +526,7 @@
<value>프로그램을 종료하십시오</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>창 잠금 해제하십시오</value>
<value>창 잠금 해제 (다시 시작)</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>잠금</value>
@@ -531,4 +534,124 @@
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>잠금 잠금을 해제하려면 시스템 트레이로 이동하여 잠금을 해제하십시오.</value>
</data>
<data name="SettingsPageFan.Header" xml:space="preserve">
<value>팬 가사</value>
</data>
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>사용자 정의하십시오</value>
</data>
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
<value>사용자 정의하십시오</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>가사 스타일과 효과</value>
</data>
<data name="SettingsPageApp.Content" xml:space="preserve">
<value>앱 외관과 행동</value>
</data>
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
<value>데스크탑 모드를 활성화 할 때 자동 잠금</value>
</data>
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
<value>조정</value>
</data>
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
<value>센터</value>
</data>
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
<value>왼쪽</value>
</data>
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>오른쪽</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>앨범 아트</value>
</data>
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
<value>노래 제목 및 아티스트</value>
</data>
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
<value>애니메이션 유형 완화</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>미디어 소스</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
<value>미디어 소스</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
<value>지정된 미디어 소스의 가사 디스플레이 활성화 또는 비활성화</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>로그 레코드</value>
</data>
<data name="SettingsPageTranslation.Content" xml:space="preserve">
<value>가사 번역</value>
</data>
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>가사 타임 라인 오프셋 (밀리초)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>번역 서비스를 구성하십시오</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>서버 주소</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>테스트 서버</value>
</data>
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>대상 언어</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>LibreTranslate 가 구동하는 번역 서비스</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>설치 지침 및 자세한 정보는 https://github.com/LibreTranslate/LibreTranslate 를 방문하십시오 (이 소프트웨어는이 번역 서비스와 제휴하지 않습니다).</value>
</data>
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
<value>서버 테스트 성공</value>
</data>
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
<value>서버 테스트가 실패했습니다</value>
</data>
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
<value>가사 뇌졸중 너비 (데스크탑 모드 만)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>시스템을 따르십시오</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>자동 시작</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>가사 스트로크 컬러 (데스크탑 모드 만)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
<value>항상 전체 화면 응용 프로그램 위에 머물러 있습니다</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
<value>도킹 또는 데스크탑 모드가 활성화 될 때이 앱이 전체 화면 앱 위에 나타나도록 강요</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>더</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
<value>가사 타임 라인 오프셋</value>
</data>
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
<value>번역하다</value>
</data>
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
<value>디스플레이 유형</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<value>설정</value>
</data>
<data name="TranslateServerNotSet" xml:space="preserve">
<value>번역 서버가 설정되지 않았습니다. 먼저 설정으로 구성하십시오.</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<value>노래를 전환 할 때 자동으로 0 으로 재설정됩니다</value>
</data>
</root>

View File

@@ -139,13 +139,16 @@
<value>添加文件夹</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>主题</value>
<value>歌词背景主题</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>语言</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>跟随系统</value>
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>适应歌词背景(彩色)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
<value>适应歌词背景(彩色)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>浅色</value>
@@ -196,7 +199,7 @@
<value>透明</value>
</data>
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
<value>背景材质</value>
<value>歌词背景材质</value>
</data>
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
<value>默认</value>
@@ -210,14 +213,14 @@
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
<value>已添加过该文件夹,请勿重复添加</value>
</data>
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
<value>叠加专辑图片背景</value>
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
<value>歌词背景</value>
</data>
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
<value>动态专辑图片背景</value>
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
<value>动态歌词背景</value>
</data>
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
<value>专辑图片背景不透明度</value>
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
<value>歌词背景不透明度</value>
</data>
<data name="SettingsPageTitle" xml:space="preserve">
<value>设置 - BetterLyrics</value>
@@ -237,8 +240,8 @@
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
<value>专辑图片背景模糊度</value>
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
<value>歌词背景模糊度</value>
</data>
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
<value>模糊度</value>
@@ -252,9 +255,6 @@
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
<value>启用模糊(&gt; 0时将显著提升 GPU 占用率</value>
</data>
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
<value>启用该功能将略微提升 GPU 占用率</value>
</data>
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>上下边缘不透明度</value>
</data>
@@ -273,8 +273,8 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>沉浸模式</value>
</data>
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
<value>专辑背景</value>
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
<value>歌词背景</value>
</data>
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>关于</value>
@@ -282,7 +282,7 @@
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>歌词库</value>
</data>
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>应用外观</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
@@ -304,7 +304,7 @@
<value>欢迎使用 BetterLyrics</value>
</data>
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
<value>现在就来初始化歌词数据库吧</value>
<value>将鼠标悬停在应用程序的顶部或底部区域以显示更多功能选项</value>
</data>
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>当前没有正在播放的音乐</value>
@@ -316,7 +316,7 @@
<value>播放测试音乐</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>使用系统播放器播放</value>
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>缓存</value>
@@ -324,17 +324,20 @@
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>包括日志文件,网络歌词缓存</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>字体颜色</value>
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
<value>字体颜色(非当前播放区域)</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>默认</value>
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
<value>字体颜色(当前播放区域)</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>专辑强调色</value>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>适应歌词背景(灰色)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>适应歌词背景(灰色)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>专辑封面样式</value>
<value>专辑区域样式</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>圆角半径</value>
@@ -384,19 +387,19 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>退出画中画模式</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<data name="LyricsNotFound" xml:space="preserve">
<value>未找到歌词</value>
</data>
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌词动效</value>
</data>
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>歌词样式</value>
</data>
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
<value>该文件夹已包含在已有文件夹中,无需再次添加</value>
</data>
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
<value>应用行为</value>
</data>
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
@@ -406,10 +409,10 @@
<value>启动标准模式</value>
</data>
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
<value>启动停靠模式</value>
<value>启动桌面模式</value>
</data>
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
<value>未找到歌词</value>
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
<value>启动停靠模式</value>
</data>
<data name="SystemTrayPageTitle" xml:space="preserve">
<value>系统托盘 - BetterLyrics</value>
@@ -468,7 +471,7 @@
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
<value>设置</value>
</data>
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
<data name="LyricsLoading" xml:space="preserve">
<value>加载歌词中...</value>
</data>
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
@@ -523,7 +526,7 @@
<value>退出程序</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>解锁窗口</value>
<value>解锁窗口(需要重新启动)</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>锁定</value>
@@ -531,4 +534,124 @@
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>锁定后解锁,请转到系统托盘解锁</value>
</data>
<data name="SettingsPageFan.Header" xml:space="preserve">
<value>扇形歌词</value>
</data>
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>自定义</value>
</data>
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
<value>自定义</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>歌词样式与动效</value>
</data>
<data name="SettingsPageApp.Content" xml:space="preserve">
<value>应用外观与行为</value>
</data>
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
<value>启动桌面模式时随即锁定窗口</value>
</data>
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
<value>对齐方式</value>
</data>
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
<value>居中</value>
</data>
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
<value>靠左</value>
</data>
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>专辑</value>
</data>
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
<value>歌曲标题和艺术家</value>
</data>
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
<value>缓动动画类型</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>媒体来源</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
<value>媒体来源</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
<value>为指定媒体源启用或禁用歌词显示</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>日志记录</value>
</data>
<data name="SettingsPageTranslation.Content" xml:space="preserve">
<value>歌词翻译</value>
</data>
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>歌词时间轴偏移(毫秒)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>配置翻译服务</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>服务器地址</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>测试服务器</value>
</data>
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>目标语言</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>翻译服务由 LibreTranslate 驱动</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>访问 https://github.com/LibreTranslate/LibreTranslate 获取安装教程及更多信息(本软件与该翻译服务无任何联系)</value>
</data>
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
<value>服务器测试成功</value>
</data>
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
<value>服务器测试失败</value>
</data>
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
<value>歌词描边宽度(仅桌面模式)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>跟随系统</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>自动启动</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>歌词描边颜色(仅桌面模式)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
<value>始终显示在全屏应用上方</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
<value>当启用停靠模式或桌面模式时强制将本应用显示在全屏应用的上方</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>更多</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
<value>歌词时间偏移</value>
</data>
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
<value>翻译</value>
</data>
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
<value>显示类型</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<value>设置</value>
</data>
<data name="TranslateServerNotSet" xml:space="preserve">
<value>未设置翻译服务器,请先在设置中进行配置</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<value>切换歌曲时将自动重置为 0</value>
</data>
</root>

View File

@@ -139,13 +139,16 @@
<value>新增資料夾</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>主題</value>
<value>歌詞背景主題</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>語言</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>跟隨系統</value>
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>適應歌詞背景(彩色)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
<value>適應歌詞背景(彩色)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>淺色</value>
@@ -196,7 +199,7 @@
<value>透明</value>
</data>
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
<value>背景材質</value>
<value>歌詞背景材質</value>
</data>
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
<value>預設</value>
@@ -210,14 +213,14 @@
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
<value>已新增過該資料夾,請勿重複新增</value>
</data>
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
<value>疊加專輯圖片背景</value>
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
<value>歌詞背景</value>
</data>
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
<value>動態專輯圖片背景</value>
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
<value>動態歌詞背景</value>
</data>
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
<value>專輯圖片背景不透明度</value>
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
<value>歌詞背景不透明度</value>
</data>
<data name="SettingsPageTitle" xml:space="preserve">
<value>設定 - BetterLyrics</value>
@@ -237,8 +240,8 @@
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
<value>專輯圖片背景模糊度</value>
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
<value>歌詞背景模糊度</value>
</data>
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
<value>模糊度</value>
@@ -252,9 +255,6 @@
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
<value>啟用模糊(&gt; 0時將顯著提升 GPU 佔用率</value>
</data>
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
<value>啟用此功能將略微提升 GPU 佔用率</value>
</data>
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>上下邊緣不透明度</value>
</data>
@@ -273,8 +273,8 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>沉浸模式</value>
</data>
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
<value>專輯背景</value>
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
<value>歌詞背景</value>
</data>
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>關於</value>
@@ -282,7 +282,7 @@
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>歌詞庫</value>
</data>
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>應用外觀</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
@@ -304,7 +304,7 @@
<value>歡迎使用 BetterLyrics</value>
</data>
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
<value>現在就來初始化歌詞資料庫吧</value>
<value>將鼠標懸停在應用程序的頂部或底部區域以顯示更多功能選項</value>
</data>
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>目前沒有正在播放的音樂</value>
@@ -316,7 +316,7 @@
<value>播放測試音樂</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>使用系統播放器播放</value>
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>快取</value>
@@ -324,17 +324,20 @@
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>包括日誌文件,網絡歌詞緩存</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>字體顏色</value>
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
<value>字體顏色(非當前播放區域)</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>預設</value>
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
<value>字體顏色(當前播放區域)</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>專輯強調色</value>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>適應歌詞背景(灰色)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>適應歌詞背景(灰色)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>專輯封面樣式</value>
<value>专辑区域样式</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>圓角半徑</value>
@@ -384,19 +387,19 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>退出畫中畫模式</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<data name="LyricsNotFound" xml:space="preserve">
<value>找不到歌詞</value>
</data>
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌詞動效</value>
</data>
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>歌詞樣式</value>
</data>
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
<value>該資料夾已包含在已有資料夾中,無需再次添加</value>
</data>
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
<value>應用行為</value>
</data>
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
@@ -406,10 +409,10 @@
<value>啟動標準模式</value>
</data>
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
<value>啟動停靠模式</value>
<value>啟動桌面模式</value>
</data>
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
<value>找不到歌詞</value>
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
<value>啟動停靠模式</value>
</data>
<data name="SystemTrayPageTitle" xml:space="preserve">
<value>系統托盤 - BetterLyrics</value>
@@ -468,7 +471,7 @@
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
<value>設定</value>
</data>
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
<data name="LyricsLoading" xml:space="preserve">
<value>載入歌詞中...</value>
</data>
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
@@ -523,7 +526,7 @@
<value>退出程序</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>解鎖窗口</value>
<value>解鎖窗口(需要重新啟動)</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>鎖定</value>
@@ -531,4 +534,124 @@
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>鎖定後解鎖,請轉到系統托盤解鎖</value>
</data>
<data name="SettingsPageFan.Header" xml:space="preserve">
<value>扇形歌詞</value>
</data>
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>自定義</value>
</data>
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
<value>自定義</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>歌詞樣式與動效</value>
</data>
<data name="SettingsPageApp.Content" xml:space="preserve">
<value>應用外觀與行為</value>
</data>
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
<value>啟動桌面模式時隨即鎖定窗口</value>
</data>
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
<value>對齊方式</value>
</data>
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
<value>居中</value>
</data>
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
<value>靠左</value>
</data>
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageAlbumArt.Text" xml:space="preserve">
<value>專輯</value>
</data>
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
<value>歌曲標題和藝術家</value>
</data>
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
<value>缓动动画类型</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>媒體來源</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
<value>媒體來源</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
<value>為指定媒體源啟用或禁用歌詞顯示</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>日誌記錄</value>
</data>
<data name="SettingsPageTranslation.Content" xml:space="preserve">
<value>歌詞翻譯</value>
</data>
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>歌詞時間軸偏移(毫秒)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>配置翻譯服務</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>服務器地址</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>測試服務器</value>
</data>
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>目標語言</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>翻译服务由 LibreTranslate 驱动</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>造訪 https://github.com/LibreTranslate/LibreTranslate 以取得安裝教學及更多資訊(本軟體與此翻譯服務無任何關聯)</value>
</data>
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
<value>服務器測試成功</value>
</data>
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
<value>服務器測試失敗</value>
</data>
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
<value>歌詞描邊寬度(僅桌面模式)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>跟随系统</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>自動啟動</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>歌词描边颜色(仅桌面模式)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
<value>始終顯示在全螢幕應用程式上方</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
<value>啟用停靠模式或桌面模式時強制將本應用程式顯示在全螢幕應用程式的上方</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>更多</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
<value>歌詞時間偏移</value>
</data>
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
<value>翻譯</value>
</data>
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
<value>顯示類型</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<value>設定</value>
</data>
<data name="TranslateServerNotSet" xml:space="preserve">
<value>未設定翻譯伺服器,請先在設定中進行配置</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<value>將在切換歌曲時自動重設為 0</value>
</data>
</root>

View File

@@ -1,57 +1,28 @@
// 2025/6/23 by Zhe Fang
using System;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Dispatching;
using System;
using System.Runtime.CompilerServices;
namespace BetterLyrics.WinUI3.ViewModels
{
/// <summary>
/// Defines the <see cref="BaseViewModel" />
/// </summary>
public partial class BaseViewModel : ObservableRecipient, IDisposable
{
#region Fields
/// <summary>
/// Defines the _dispatcherQueue
/// </summary>
private protected readonly DispatcherQueue _dispatcherQueue =
DispatcherQueue.GetForCurrentThread();
/// <summary>
/// Defines the _settingsService
/// </summary>
private protected readonly ISettingsService _settingsService;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="BaseViewModel"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
public BaseViewModel(ISettingsService settingsService)
{
IsActive = true;
_settingsService = settingsService;
}
#endregion
#region Methods
/// <summary>
/// The Dispose
/// </summary>
public void Dispose()
{
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class BaseWindowViewModel : BaseViewModel
{
public BaseWindowViewModel(ISettingsService settingsService) : base(settingsService) { }
}
}

View File

@@ -1,353 +0,0 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
using WinRT.Interop;
using WinUIEx;
using WinUIEx.Messaging;
namespace BetterLyrics.WinUI3
{
/// <summary>
/// Defines the <see cref="HostWindowViewModel" />
/// </summary>
public partial class HostWindowViewModel
: BaseViewModel,
IRecipient<PropertyChangedMessage<TitleBarType>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<BackdropType>>,
IRecipient<PropertyChangedMessage<int>>
{
#region Fields
/// <summary>
/// Defines the _watcherHelper
/// </summary>
private ForegroundWindowWatcherHelper? _watcherHelper = null;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="HostWindowViewModel"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
public HostWindowViewModel(ISettingsService settingsService)
: base(settingsService)
{
TitleBarType = _settingsService.TitleBarType;
ThemeType = _settingsService.ThemeType;
OnTitleBarTypeChanged(TitleBarType);
WeakReferenceMessenger.Default.Register<ShowNotificatonMessage>(
this,
async (r, m) =>
{
Notification = m.Value;
if (
!Notification.IsForeverDismissable
|| AlreadyForeverDismissedThisMessage() == false
)
{
Notification.Visibility = Notification.IsForeverDismissable
? Visibility.Visible
: Visibility.Collapsed;
ShowInfoBar = true;
await Task.Delay(AnimationHelper.StackedNotificationsShowingDuration);
ShowInfoBar = false;
}
}
);
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the ActivatedWindowAccentColor
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial Color ActivatedWindowAccentColor { get; set; }
/// <summary>
/// Gets or sets the AppLogoImageIconHeight
/// </summary>
[ObservableProperty]
public partial double AppLogoImageIconHeight { get; set; }
/// <summary>
/// Gets or sets the FramePageType
/// </summary>
[ObservableProperty]
public partial Type FramePageType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsDockMode
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDockMode { get; set; } = false;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDesktopMode { get; set; } = false;
/// <summary>
/// Gets or sets the Notification
/// </summary>
[ObservableProperty]
public partial Notification Notification { get; set; } = new();
/// <summary>
/// Gets or sets a value indicating whether ShowInfoBar
/// </summary>
[ObservableProperty]
public partial bool ShowInfoBar { get; set; } = false;
/// <summary>
/// Gets or sets the ThemeType
/// </summary>
[ObservableProperty]
public partial ElementTheme ThemeType { get; set; }
/// <summary>
/// Gets or sets the TitleBarFontSize
/// </summary>
[ObservableProperty]
public partial double TitleBarFontSize { get; set; }
/// <summary>
/// Gets or sets the TitleBarHeight
/// </summary>
[ObservableProperty]
public partial double TitleBarHeight { get; set; }
/// <summary>
/// Gets or sets the TitleBarType
/// </summary>
[ObservableProperty]
public partial TitleBarType TitleBarType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsLyricsWindowLocked { get; set; } = false;
#endregion
#region Methods
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{BackdropType}"/></param>
public void Receive(PropertyChangedMessage<BackdropType> message)
{
WindowHelper.GetWindowByFramePageType(FramePageType).SystemBackdrop =
SystemBackdropHelper.CreateSystemBackdrop(message.NewValue);
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{ElementTheme}"/></param>
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
ThemeType = message.NewValue;
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{int}"/></param>
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize))
{
if (IsDockMode)
{
DockModeHelper.UpdateAppBarHeight(
WindowNative.GetWindowHandle(
WindowHelper.GetWindowByFramePageType(FramePageType)
),
message.NewValue * 3
);
}
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{TitleBarType}"/></param>
public void Receive(PropertyChangedMessage<TitleBarType> message)
{
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.TitleBarType))
{
TitleBarType = message.NewValue;
}
}
}
/// <summary>
/// The UpdateAccentColor
/// </summary>
/// <param name="hwnd">The hwnd<see cref="nint"/></param>
public void UpdateAccentColor(nint hwnd)
{
ActivatedWindowAccentColor = WindowColorHelper
.GetDominantColorBelow(hwnd)
.ToWindowsUIColor();
}
/// <summary>
/// The AlreadyForeverDismissedThisMessage
/// </summary>
/// <returns>The <see cref="bool?"/></returns>
private bool? AlreadyForeverDismissedThisMessage()
{
//if (Notification.RelatedSettingsKeyName is string key)
// return _settingsService.Get(key, SettingsDefaultValues.NeverShowMessage);
//return null;
return null;
}
/// <summary>
/// The StartWatchWindowColorChange
/// </summary>
private void StartWatchWindowColorChange()
{
var hwnd = WindowNative.GetWindowHandle(
WindowHelper.GetWindowByFramePageType(FramePageType)
);
_watcherHelper = new ForegroundWindowWatcherHelper(
hwnd,
onWindowChanged =>
{
UpdateAccentColor(hwnd);
}
);
_watcherHelper.Start();
UpdateAccentColor(hwnd);
}
/// <summary>
/// The StopWatchWindowColorChange
/// </summary>
private void StopWatchWindowColorChange()
{
_watcherHelper?.Stop();
_watcherHelper = null;
}
/// <summary>
/// The ToggleDockMode
/// </summary>
[RelayCommand]
private void ToggleDockMode()
{
var window = WindowHelper.GetWindowByFramePageType(FramePageType);
IsDockMode = !IsDockMode;
if (IsDockMode)
{
DockModeHelper.Enable(window, _settingsService.LyricsFontSize * 3);
StartWatchWindowColorChange();
}
else
{
DockModeHelper.Disable(window);
StopWatchWindowColorChange();
}
}
[RelayCommand]
private void ToggleDesktopMode()
{
var window = WindowHelper.GetWindowByFramePageType(FramePageType);
IsDesktopMode = !IsDesktopMode;
if (IsDesktopMode)
{
DesktopModeHelper.Enable(window);
WindowHelper.GetWindowByFramePageType(typeof(LyricsPage)).SystemBackdrop =
SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
}
else
{
DesktopModeHelper.Disable(window);
WindowHelper.GetWindowByFramePageType(typeof(LyricsPage)).SystemBackdrop =
SystemBackdropHelper.CreateSystemBackdrop(_settingsService.BackdropType);
}
}
[RelayCommand]
private void LockWindow()
{
var window = WindowHelper.GetWindowByFramePageType(FramePageType);
DesktopModeHelper.Lock(window);
IsLyricsWindowLocked = true;
}
/// <summary>
/// The OnFramePageTypeChanged
/// </summary>
/// <param name="value">The value<see cref="Type"/></param>
partial void OnFramePageTypeChanged(Type value)
{
if (value != null)
{
var window = WindowHelper.GetWindowByFramePageType(FramePageType);
window.SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(
_settingsService.BackdropType
);
}
}
/// <summary>
/// The OnTitleBarTypeChanged
/// </summary>
/// <param name="value">The value<see cref="TitleBarType"/></param>
partial void OnTitleBarTypeChanged(TitleBarType value)
{
switch (value)
{
case TitleBarType.Compact:
AppLogoImageIconHeight = 18;
TitleBarFontSize = 11;
break;
case TitleBarType.Extended:
AppLogoImageIconHeight = 20;
TitleBarFontSize = 14;
break;
default:
break;
}
TitleBarHeight = value.GetHeight();
}
#endregion
}
}

View File

@@ -1,198 +1,96 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Imaging;
using WinUIEx.Messaging;
using System.Diagnostics;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.ViewModels
{
/// <summary>
/// Defines the <see cref="LyricsPageViewModel" />
/// </summary>
public partial class LyricsPageViewModel
: BaseViewModel,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<LyricsStatus>>
public partial class LyricsPageViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<int>>, IRecipient<PropertyChangedMessage<bool>>
{
#region Fields
/// <summary>
/// Defines the _playbackService
/// </summary>
private readonly IPlaybackService _playbackService;
/// <summary>
/// Defines the _preferredDisplayTypeBeforeSwitchToDockMode
/// </summary>
private LyricsDisplayType? _preferredDisplayTypeBeforeSwitchToNonStandardMode;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LyricsPageViewModel"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
/// <param name="playbackService">The playbackService<see cref="IPlaybackService"/></param>
public LyricsPageViewModel(
ISettingsService settingsService,
IPlaybackService playbackService
)
: base(settingsService)
public LyricsPageViewModel(ISettingsService settingsService, IPlaybackService playbackService) : base(settingsService)
{
LyricsFontSize = _settingsService.LyricsFontSize;
CoverImageRadius = _settingsService.CoverImageRadius;
IsTranslationEnabled = _settingsService.IsTranslationEnabled;
PreferredDisplayType = _settingsService.PreferredDisplayType;
_playbackService = playbackService;
_playbackService.SongInfoChanged += async (_, args) =>
await UpdateSongInfoUI(args.SongInfo).ConfigureAwait(true);
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
IsFirstRun = _settingsService.IsFirstRun;
UpdateSongInfoUI(_playbackService.SongInfo).ConfigureAwait(true);
}
#endregion
private void PlaybackService_SongInfoChanged(object? sender, Events.SongInfoChangedEventArgs e)
{
SongInfo = e.SongInfo;
PositionOffset = 0; // Reset position offset when song changes
TrySwitchToPreferredDisplayType(e.SongInfo);
}
#region Properties
/// <summary>
/// Gets or sets a value indicating whether AboutToUpdateUI
/// </summary>
[ObservableProperty]
public partial bool AboutToUpdateUI { get; set; }
/// <summary>
/// Gets or sets the CoverImage
/// </summary>
[ObservableProperty]
public partial BitmapImage? CoverImage { get; set; }
/// <summary>
/// Gets or sets the CoverImageGridActualHeight
/// </summary>
[ObservableProperty]
public partial double CoverImageGridActualHeight { get; set; }
/// <summary>
/// Gets or sets the CoverImageGridCornerRadius
/// </summary>
[ObservableProperty]
public partial CornerRadius CoverImageGridCornerRadius { get; set; }
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
[ObservableProperty]
public partial int CoverImageRadius { get; set; }
/// <summary>
/// Gets or sets the DisplayType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsDisplayType DisplayType { get; set; } =
LyricsDisplayType.PlaceholderOnly;
public partial LyricsDisplayType DisplayType { get; set; } = LyricsDisplayType.PlaceholderOnly;
/// <summary>
/// Gets or sets a value indicating whether IsFirstRun
/// </summary>
[ObservableProperty]
public partial bool IsFirstRun { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsNotMockMode
/// </summary>
[ObservableProperty]
public partial bool IsNotMockMode { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether IsWelcomeTeachingTipOpen
/// </summary>
[ObservableProperty]
public partial bool IsWelcomeTeachingTipOpen { get; set; }
/// <summary>
/// Gets or sets the LimitedLineWidth
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial double MaxLyricsWidth { get; set; } = 0.0;
public partial Visibility BottomCommandGridVisibility { get; set; } = Visibility.Visible;
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
[ObservableProperty]
public partial int LyricsFontSize { get; set; }
/// <summary>
/// Gets or sets the LyricsStatus
/// </summary>
[ObservableProperty]
public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading;
public partial LyricsDisplayType PreferredDisplayType { get; set; }
/// <summary>
/// Gets or sets the PreferredDisplayType
/// </summary>
[ObservableProperty]
public partial LyricsDisplayType? PreferredDisplayType { get; set; } =
LyricsDisplayType.SplitView;
/// <summary>
/// Gets or sets the SongInfo
/// </summary>
[ObservableProperty]
public partial SongInfo? SongInfo { get; set; } = null;
#endregion
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int PositionOffset { get; set; } = 0;
#region Methods
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsTranslationEnabled { get; set; } = false;
/// <summary>
/// The OpenMatchedFileFolderInFileExplorer
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
public void OpenMatchedFileFolderInFileExplorer(string path)
partial void OnIsTranslationEnabledChanged(bool value)
{
Process.Start(
new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"/select,\"{path}\"",
UseShellExecute = true,
}
);
_settingsService.IsTranslationEnabled = value;
}
partial void OnPreferredDisplayTypeChanged(LyricsDisplayType value)
{
_settingsService.PreferredDisplayType = value;
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{bool}"/></param>
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is HostWindowViewModel)
if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(HostWindowViewModel.IsDockMode))
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
{
IsNotMockMode = !message.NewValue;
SetNonStandardModePreferredDisplayType(message.NewValue);
TrySwitchToPreferredDisplayType(SongInfo);
}
else if (message.PropertyName == nameof(HostWindowViewModel.IsDesktopMode))
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
{
SetNonStandardModePreferredDisplayType(message.NewValue);
TrySwitchToPreferredDisplayType(SongInfo);
@@ -200,6 +98,23 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
{
LyricsFontSize = message.NewValue;
}
}
}
[RelayCommand]
private void OpenSettingsWindow()
{
WindowHelper.OpenOrShowWindow<SettingsWindow>();
}
private void SetNonStandardModePreferredDisplayType(bool isEnabled)
{
if (isEnabled)
@@ -209,94 +124,10 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode;
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode ?? LyricsDisplayType.SplitView;
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{int}"/></param>
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
{
CoverImageRadius = message.NewValue;
}
}
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize))
{
LyricsFontSize = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsStatus}"/></param>
public void Receive(PropertyChangedMessage<LyricsStatus> message)
{
if (message.Sender is LyricsRendererViewModel)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsStatus))
{
LyricsStatus = message.NewValue;
}
}
}
/// <summary>
/// The UpdateSongInfoUI
/// </summary>
/// <param name="songInfo">The songInfo<see cref="SongInfo?"/></param>
/// <returns>The <see cref="Task"/></returns>
public async Task UpdateSongInfoUI(SongInfo? songInfo)
{
AboutToUpdateUI = true;
await Task.Delay(AnimationHelper.StoryboardDefaultDuration);
SongInfo = songInfo;
CoverImage =
(songInfo?.AlbumArt == null)
? null
: await ImageHelper.GetBitmapImageFromBytesAsync(songInfo.AlbumArt);
TrySwitchToPreferredDisplayType(songInfo);
AboutToUpdateUI = false;
}
/// <summary>
/// The OnDisplayTypeChanged
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
[RelayCommand]
private void OnDisplayTypeChanged(object value)
{
int index = Convert.ToInt32(value);
PreferredDisplayType = (LyricsDisplayType)index;
DisplayType = (LyricsDisplayType)index;
}
/// <summary>
/// The OpenSettingsWindow
/// </summary>
[RelayCommand]
private void OpenSettingsWindow()
{
WindowHelper.OpenSettingsWindow();
}
/// <summary>
/// The TrySwitchToPreferredDisplayType
/// </summary>
/// <param name="songInfo">The songInfo<see cref="SongInfo?"/></param>
private void TrySwitchToPreferredDisplayType(SongInfo? songInfo)
{
LyricsDisplayType displayType;
@@ -315,44 +146,13 @@ namespace BetterLyrics.WinUI3.ViewModels
}
DisplayType = displayType;
}
/// <summary>
/// The OnCoverImageGridActualHeightChanged
/// </summary>
/// <param name="value">The value<see cref="double"/></param>
partial void OnCoverImageGridActualHeightChanged(double value)
{
if (double.IsNaN(value))
return;
CoverImageGridCornerRadius = new CornerRadius(CoverImageRadius / 100f * value / 2);
}
/// <summary>
/// The OnCoverImageRadiusChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverImageRadiusChanged(int value)
{
if (double.IsNaN(CoverImageGridActualHeight))
return;
CoverImageGridCornerRadius = new CornerRadius(
value / 100f * CoverImageGridActualHeight / 2
);
}
/// <summary>
/// The OnIsFirstRunChanged
/// </summary>
/// <param name="value">The value<see cref="bool"/></param>
partial void OnIsFirstRunChanged(bool value)
{
IsWelcomeTeachingTipOpen = value;
_settingsService.IsFirstRun = false;
}
#endregion
}
}

View File

@@ -1,129 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.UI;
using Windows.UI.Text;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
{
/// <summary>
/// The Draw
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
// Blurred lyrics layer
using var blurredLyrics = new CanvasCommandList(control);
using (var blurredLyricsDs = blurredLyrics.CreateDrawingSession())
{
switch (DisplayType)
{
case LyricsDisplayType.AlbumArtOnly:
case LyricsDisplayType.PlaceholderOnly:
break;
case LyricsDisplayType.LyricsOnly:
case LyricsDisplayType.SplitView:
DrawBlurredLyrics(control, blurredLyricsDs);
break;
default:
break;
}
DrawBlurredLyrics(control, blurredLyricsDs);
}
// Masked mock gradient blurred lyrics layer
using var maskedBlurredLyrics = new CanvasCommandList(control);
using (var maskedBlurredLyricsDs = maskedBlurredLyrics.CreateDrawingSession())
if (_lastAlbumArtSwBitmap != null && _lastAlbumArtCanvasBitmap == null)
{
if (LyricsVerticalEdgeOpacity == 100)
{
maskedBlurredLyricsDs.DrawImage(blurredLyrics);
}
else
{
using var mask = new CanvasCommandList(control);
using (var maskDs = mask.CreateDrawingSession())
{
DrawGradientOpacityMask(control, maskDs);
}
maskedBlurredLyricsDs.DrawImage(
new AlphaMaskEffect { Source = blurredLyrics, AlphaMask = mask }
);
}
_lastAlbumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _lastAlbumArtSwBitmap);
}
if (_albumArtSwBitmap != null && _albumArtCanvasBitmap == null)
{
_albumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _albumArtSwBitmap);
}
using var combined = new CanvasCommandList(control);
using var combinedDs = combined.CreateDrawingSession();
if (IsCoverOverlayEnabled)
{
DrawAlbumArtBackground(control, combinedDs);
}
DrawAlbumArtBackground(control, combinedDs);
if (_isDockMode)
{
DrawImmersiveBackground(control, combinedDs, IsCoverOverlayEnabled);
DrawImmersiveBackground(control, combinedDs);
}
combinedDs.DrawImage(maskedBlurredLyrics);
combinedDs.DrawImage(blurredLyrics);
if (_isDesktopMode)
{
float w = (float)control.Size.Width;
float h = (float)control.Size.Height;
float maskThickness = Math.Min(18f, Math.Min(w / 2, h / 2)); // 遮罩宽度
float cornerRadius = maskThickness / 2; // 圆角半径
float blurAmount = maskThickness / 2; // 高斯模糊强度
using var mask = new CanvasCommandList(control);
using (var maskDs = mask.CreateDrawingSession())
{
// 画一个比窗口小一圈的圆角矩形
var rect = new Rect(
maskThickness,
maskThickness,
w - maskThickness * 2,
h - maskThickness * 2
);
maskDs.FillRoundedRectangle(rect, cornerRadius, cornerRadius, Colors.White);
}
// 对圆角矩形做高斯模糊
var blurredMask = new GaussianBlurEffect
{
Source = mask,
BlurAmount = blurAmount,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
};
ds.DrawImage(new AlphaMaskEffect { Source = combined, AlphaMask = blurredMask });
ds.DrawImage(blurredLyrics);
}
else
{
ds.DrawImage(combined);
}
DrawAlbumArt(control, ds);
DrawTitleAndArtist(control, ds);
if (_isDebugOverlayEnabled)
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
var currentPlayingLine = _multiLangLyrics
.SafeGet(_langIndex)
?.SafeGet(currentPlayingLineIndex);
?.SafeGet(_playingLineIndex);
if (currentPlayingLine != null)
{
@@ -134,93 +78,117 @@ namespace BetterLyrics.WinUI3.ViewModels
out float charProgress
);
ds.DrawText(
$"DEBUG: "
+ $"播放行 {currentPlayingLineIndex}, 字符 {charStartIndex}, 长度 {charLength}, 进度 {charProgress}\n"
+ $"可见行 [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n"
+ $"当前时刻 {TotalTime}",
new Vector2(10, 10),
Colors.Red
);
//ds.DrawText(
// $"[DEBUG]\n" +
// $"Cur playing {_playingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
// $"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
// $"Cur time {_totalTime + _positionOffset}\n" +
// $"Lang size {_multiLangLyrics.Count}\n" +
// $"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
// new Vector2(10, 10),
// ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
//);
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
LyricsLine? line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
if (line != null)
{
ds.DrawText(
$"[{i}] {line.OriginalText} {line.HighlightOpacityTransition.Value}",
new Vector2(10, 30 + (i - _startVisibleLineIndex) * 20),
ThemeTypeSent == ElementTheme.Light ? Colors.Black : Colors.White
);
}
}
}
}
}
/// <summary>
/// The DrawImgae
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
/// <param name="softwareBitmap">The softwareBitmap<see cref="SoftwareBitmap"/></param>
/// <param name="opacity">The opacity<see cref="float"/></param>
private static void DrawImgae(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
SoftwareBitmap softwareBitmap,
float opacity
)
private void DrawBackgroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
{
using var canvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, softwareBitmap);
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
var scaleFactor =
(float)Math.Sqrt(Math.Pow(control.Size.Width, 2) + Math.Pow(control.Size.Height, 2))
/ Math.Min(imageWidth, imageHeight);
float scaleFactor = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2)) / MathF.Min(imageWidth, imageHeight);
ds.DrawImage(
new OpacityEffect
float x = _canvasWidth / 2 - imageWidth * scaleFactor / 2;
float y = _canvasHeight / 2 - imageHeight * scaleFactor / 2;
// Source: https://zhuanlan.zhihu.com/p/37178216
float bright = _lyricsBgBrightnessTransition.Value / 1f * 2f; // 明度参数范围在0.0f到2.0f之间
float whiteX = Math.Min(2 - bright, 1);
float whiteY = 1f;
float blackX = Math.Max(1 - bright, 0);
float blackY = 0f;
ds.DrawImage(new OpacityEffect
{
Source = new BrightnessEffect
{
Source = new ScaleEffect
{
InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
BorderMode = EffectBorderMode.Hard,
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
Opacity = opacity,
WhitePoint = new Vector2(whiteX, whiteY),
BlackPoint = new Vector2(blackX, blackY),
},
(float)control.Size.Width / 2 - imageWidth * scaleFactor / 2,
(float)control.Size.Height / 2 - imageHeight * scaleFactor / 2
Opacity = opacity,
}, new Vector2(x, y)
);
}
private void DrawForegroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
{
if (opacity == 0) return;
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
float scaleFactor = _albumArtSize / Math.Min(imageWidth, imageHeight);
if (scaleFactor < 0.01f) return;
float cornerRadius = _albumArtCornerRadius / 100f * _albumArtSize / 2;
using var cornerRadiusMask = new CanvasCommandList(control.Device);
using var cornerRadiusMaskDs = cornerRadiusMask.CreateDrawingSession();
cornerRadiusMaskDs.FillRoundedRectangle(
new Rect(0, 0, imageWidth * scaleFactor, imageHeight * scaleFactor),
cornerRadius, cornerRadius, Colors.White
);
ds.DrawImage(new OpacityEffect
{
Source = new AlphaMaskEffect
{
Source = new ScaleEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
AlphaMask = cornerRadiusMask,
},
Opacity = opacity,
}, new Vector2(_albumArtXTransition.Value, _albumArtY)
);
}
/// <summary>
/// The DrawAlbumArtBackground
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
var overlappedCovers = new CanvasCommandList(control.Device);
using var overlappedCovers = new CanvasCommandList(control.Device);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
if (_albumArtBgTransition.IsTransitioning)
if (_lastAlbumArtCanvasBitmap != null)
{
if (_lastAlbumArtBitmap != null)
{
DrawImgae(
control,
overlappedCoversDs,
_lastAlbumArtBitmap,
1 - _albumArtBgTransition.Value
);
}
if (_albumArtBitmap != null)
{
DrawImgae(
control,
overlappedCoversDs,
_albumArtBitmap,
_albumArtBgTransition.Value
);
}
DrawBackgroundImgae(control, overlappedCoversDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
}
else if (_albumArtBitmap != null)
if (_albumArtCanvasBitmap != null)
{
DrawImgae(control, overlappedCoversDs, _albumArtBitmap, 1f);
DrawBackgroundImgae(control, overlappedCoversDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
}
using var coverOverlayEffect = new OpacityEffect
@@ -237,84 +205,78 @@ namespace BetterLyrics.WinUI3.ViewModels
ds.Transform = Matrix3x2.Identity;
}
/// <summary>
/// The DrawGradientOpacityMask
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
private void DrawGradientOpacityMask(
ICanvasAnimatedControl control,
CanvasDrawingSession ds
)
private void DrawAlbumArt(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
byte verticalEdgeAlpha = (byte)(255 * LyricsVerticalEdgeOpacity / 100f);
using var maskBrush = new CanvasLinearGradientBrush(
control,
[
new() { Position = 0, Color = Color.FromArgb(verticalEdgeAlpha, 0, 0, 0) },
new() { Position = 0.5f, Color = Color.FromArgb(255, 0, 0, 0) },
new() { Position = 1, Color = Color.FromArgb(verticalEdgeAlpha, 0, 0, 0) },
]
)
using var albumArt = new CanvasCommandList(control.Device);
using var albumArtDs = albumArt.CreateDrawingSession();
if (_albumArtCanvasBitmap != null)
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(0, (float)control.Size.Height),
};
ds.FillRectangle(new Rect(0, 0, control.Size.Width, control.Size.Height), maskBrush);
DrawForegroundImgae(control, albumArtDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
}
if (_lastAlbumArtCanvasBitmap != null)
{
DrawForegroundImgae(control, albumArtDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
}
using var opacity = new CanvasCommandList(control.Device);
using var opacityDs = opacity.CreateDrawingSession();
opacityDs.DrawImage(new GaussianBlurEffect
{
Source = albumArt,
BlurAmount = 12f,
Optimization = EffectOptimization.Quality,
});
opacityDs.DrawImage(albumArt);
ds.DrawImage(new OpacityEffect
{
Source = opacity,
Opacity = _albumArtOpacityTransition.Value
});
}
/// <summary>
/// The DrawImmersiveBackground
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
/// <param name="withGradient">The withGradient<see cref="bool"/></param>
private void DrawImmersiveBackground(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
bool withGradient
)
private void DrawTitleAndArtist(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
ds.FillRectangle(
new Rect(0, 0, control.Size.Width, control.Size.Height),
new CanvasLinearGradientBrush(
control,
[
new CanvasGradientStop
{
Position = 0f,
Color = withGradient
? Color.FromArgb(
211,
_immersiveBgTransition.Value.R,
_immersiveBgTransition.Value.G,
_immersiveBgTransition.Value.B
)
: _immersiveBgTransition.Value,
},
new CanvasGradientStop
{
Position = 1,
Color = _immersiveBgTransition.Value,
},
]
)
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(0, (float)control.Size.Height),
}
);
if (_lastSongTitle != null || _lastSongArtist != null)
{
DrawSingleTitleAndArtist(control, ds, _lastSongTitle, _lastSongArtist, 1 - _songInfoOpacityTransition.Value);
}
if (_songTitle != null || _songArtist != null)
{
DrawSingleTitleAndArtist(control, ds, _songTitle, _songArtist, _songInfoOpacityTransition.Value);
}
}
private void DrawSingleTitleAndArtist(ICanvasAnimatedControl control, CanvasDrawingSession ds, string? title, string? artist, float opacity)
{
CanvasTextLayout titleLayout = new(
control, title ?? string.Empty,
_titleTextFormat, _albumArtSize, _canvasHeight
);
CanvasTextLayout artistLayout = new(
control, artist ?? string.Empty,
_artistTextFormat, _albumArtSize, _canvasHeight
);
ds.DrawTextLayout(
titleLayout,
new Vector2(_albumArtXTransition.Value, _titleY),
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 255 * opacity)));
ds.DrawTextLayout(
artistLayout,
new Vector2(_albumArtXTransition.Value, _titleY + (float)titleLayout.LayoutBounds.Height),
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 128 * opacity)));
}
/// <summary>
/// The DrawLyrics
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
/// <param name="currentLineHighlightType">The currentLineHighlightType<see cref="LyricsHighlightType"/></param>
private void DrawBlurredLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
var currentPlayingLine = _multiLangLyrics
.SafeGet(_langIndex)
?.SafeGet(_playingLineIndex);
if (currentPlayingLine == null)
{
return;
}
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
@@ -347,63 +309,71 @@ namespace BetterLyrics.WinUI3.ViewModels
switch (LyricsAlignmentType)
{
case LyricsAlignmentType.Left:
case TextAlignmentType.Left:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
break;
case LyricsAlignmentType.Center:
case TextAlignmentType.Center:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
centerX += (float)_maxLyricsWidthTransition.Value / 2;
centerX += _maxLyricsWidth / 2;
break;
case LyricsAlignmentType.Right:
case TextAlignmentType.Right:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
centerX += (float)_maxLyricsWidthTransition.Value;
centerX += _maxLyricsWidth;
break;
default:
break;
}
float offsetToLeft =
(float)control.Size.Width - _rightMargin - _maxLyricsWidthTransition.Value;
// Scale
// 组合变换:缩放 -> 旋转 -> 平移
ds.Transform =
Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
* Matrix3x2.CreateTranslation(
offsetToLeft,
_canvasYScrollTransition.Value + (float)(control.Size.Height / 2)
);
* Matrix3x2.CreateRotation(line.AngleTransition.Value, currentPlayingLine.Position)
* Matrix3x2.CreateTranslation(_lyricsXTransition.Value, _canvasYScrollTransition.Value + _canvasHeight / 2);
// Create the original lyrics line
using var lyrics = new CanvasCommandList(control.Device);
using var lyricsDs = lyrics.CreateDrawingSession();
lyricsDs.DrawTextLayout(textLayout, position, _fontColor);
// Create the background lyrics line with stroke and fill
using var bgLyrics = new CanvasCommandList(control.Device);
using var bgLyricsDs = bgLyrics.CreateDrawingSession();
// Create the foreground lyrics line with stroke and fill
using var fgLyrics = new CanvasCommandList(control.Device);
using var fgLyricsDs = fgLyrics.CreateDrawingSession();
// 创建文字几何体
using (var textGeometry = CanvasGeometry.CreateText(textLayout))
{
if (_isDesktopMode)
{
bgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 背景描边
fgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 前景描边
}
bgLyricsDs.FillGeometry(textGeometry, position, _bgFontColor); // 背景填充
fgLyricsDs.FillGeometry(textGeometry, position, _fgFontColor); // 前景填充
}
// Mock gradient blurred lyrics layer
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层(背景歌词层次)
// Current line will not be blurred
ds.DrawImage(
new GaussianBlurEffect
{
Source = new OpacityEffect { Source = lyrics, Opacity = _defaultOpacity },
Source = new OpacityEffect { Source = bgLyrics, Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
BlurAmount = line.BlurAmountTransition.Value,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
}
);
// 再叠加当前行歌词层
// Only draw the current line and the two lines around it
// This layer is to highlight the current line
// and for fade-in and fade-out effects, two lines around it is also drawn
if (Math.Abs(i - currentPlayingLineIndex) <= 1)
if (line.HighlightOpacityTransition.Value != 0)
{
// 再叠加高亮行歌词层(前景歌词层)
using var mask = new CanvasCommandList(control.Device);
using var maskDs = mask.CreateDrawingSession();
using var highlightMask = new CanvasCommandList(control.Device);
using var highlightMaskDs = highlightMask.CreateDrawingSession();
if (i == currentPlayingLineIndex)
if (i == _playingLineIndex)
{
GetLinePlayingProgress(
line,
@@ -480,12 +450,19 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
float height = 0f;
var regions = textLayout.GetCharacterRegions(0, string.Join("", line.CharTimings.Select(x => x.Text)).Length);
if (regions.Length > 0)
{
height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top;
}
maskDs.FillRectangle(
new Rect(
textLayout.LayoutBounds.X,
position.Y,
textLayout.LayoutBounds.Width,
textLayout.LayoutBounds.Height
height
),
Colors.White
);
@@ -501,7 +478,7 @@ namespace BetterLyrics.WinUI3.ViewModels
{
Source = new AlphaMaskEffect
{
Source = lyrics,
Source = fgLyrics,
AlphaMask = LyricsGlowEffectScope switch
{
LineRenderingType.UntilCurrentChar => mask,
@@ -511,16 +488,15 @@ namespace BetterLyrics.WinUI3.ViewModels
},
BlurAmount = _lyricsGlowEffectAmount,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
}
: new CanvasCommandList(control.Device),
Foreground = new AlphaMaskEffect
{
Source = lyrics,
Source = fgLyrics,
AlphaMask = mask,
},
},
Opacity = line.HighlightOpacityTransition.Value,
Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
}
);
}
@@ -530,15 +506,43 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The GetHorizontalFillBrush
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="stopPosition">The stopPosition<see cref="float[]"/></param>
/// <param name="stopOpacity">The stopOpacity<see cref="float[]"/></param>
/// <param name="startX">The startX<see cref="float"/></param>
/// <param name="endX">The endX<see cref="float"/></param>
/// <returns>The <see cref="CanvasLinearGradientBrush"/></returns>
private void DrawImmersiveBackground(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
bool withGradient = true
)
{
ds.FillRectangle(
new Rect(0, 0, _canvasWidth, _canvasHeight),
new CanvasLinearGradientBrush(
control,
[
new CanvasGradientStop
{
Position = 0f,
Color = withGradient
? Color.FromArgb(
211,
_immersiveBgTransition.Value.R,
_immersiveBgTransition.Value.G,
_immersiveBgTransition.Value.B
)
: _immersiveBgTransition.Value,
},
new CanvasGradientStop
{
Position = 1,
Color = _immersiveBgTransition.Value,
},
]
)
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(0, _canvasHeight),
}
);
}
private CanvasLinearGradientBrush GetHorizontalFillBrush(
ICanvasAnimatedControl control,
List<(float position, float opacity)> stops,

View File

@@ -1,322 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
{
/// <summary>
/// The Update
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="args">The args<see cref="CanvasAnimatedUpdateEventArgs"/></param>
public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
{
if (_isPlaying)
{
TotalTime += args.Timing.ElapsedTime;
}
ElapsedTime = args.Timing.ElapsedTime;
if (_immersiveBgTransition.IsTransitioning)
{
_immersiveBgTransition.Update(ElapsedTime);
}
if (_albumArtBgTransition.IsTransitioning)
{
_albumArtBgTransition.Update(ElapsedTime);
}
if (IsDynamicCoverOverlayEnabled)
{
_rotateAngle += _coverRotateSpeed;
_rotateAngle %= MathF.PI * 2;
}
if (_maxLyricsWidthTransition.IsTransitioning)
{
_maxLyricsWidthTransition.Update(ElapsedTime);
_isRelayoutNeeded = true;
}
if (_isRelayoutNeeded)
{
ReLayout(control);
_isRelayoutNeeded = false;
UpdateCanvasYScrollOffset(control, false);
}
else
{
UpdateCanvasYScrollOffset(control, true);
}
UpdateLinesProps();
}
/// <summary>
/// The UpdateCanvasYScrollOffset
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
private void UpdateCanvasYScrollOffset(ICanvasAnimatedControl control, bool withAnimation)
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
if (startLineIndex < 0 || endLineIndex < 0)
{
return;
}
// Set _scrollOffsetY
LyricsLine? currentPlayingLine = _multiLangLyrics
.SafeGet(_langIndex)
?.SafeGet(currentPlayingLineIndex);
var playingTextLayout = currentPlayingLine?.CanvasTextLayout;
if (currentPlayingLine == null || playingTextLayout == null)
{
return;
}
float targetYScrollOffset =
(float?)(
-currentPlayingLine.Position.Y
+ _multiLangLyrics.SafeGet(_langIndex)?[0].Position.Y
- playingTextLayout.LayoutBounds.Height / 2
) ?? 0f;
if (withAnimation && !_canvasYScrollTransition.IsTransitioning)
{
_canvasYScrollTransition.StartTransition(targetYScrollOffset);
}
else if (!withAnimation)
{
_canvasYScrollTransition.JumpTo(targetYScrollOffset);
}
if (_canvasYScrollTransition.IsTransitioning)
{
_canvasYScrollTransition.Update(ElapsedTime);
}
_startVisibleLineIndex = _endVisibleLineIndex = -1;
// Update visible line indices
for (int i = startLineIndex; i <= endLineIndex; i++)
{
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
if (line == null || line.CanvasTextLayout == null)
{
continue;
}
var textLayout = line.CanvasTextLayout;
if (
_canvasYScrollTransition.Value
+ (float)(control.Size.Height / 2)
+ line.Position.Y
+ textLayout.LayoutBounds.Height
>= 0
)
{
if (_startVisibleLineIndex == -1)
{
_startVisibleLineIndex = i;
}
}
if (
_canvasYScrollTransition.Value
+ (float)(control.Size.Height / 2)
+ line.Position.Y
+ textLayout.LayoutBounds.Height
>= control.Size.Height
)
{
if (_endVisibleLineIndex == -1)
{
_endVisibleLineIndex = i;
}
}
}
if (_startVisibleLineIndex != -1 && _endVisibleLineIndex == -1)
{
_endVisibleLineIndex = endLineIndex;
}
}
/// <summary>
/// The UpdateFontColor
/// </summary>
private protected void UpdateFontColor()
{
Color fallback = Colors.Transparent;
switch (Theme)
{
case ElementTheme.Default:
switch (Application.Current.RequestedTheme)
{
case ApplicationTheme.Light:
fallback = _darkFontColor;
break;
case ApplicationTheme.Dark:
fallback = _lightFontColor;
break;
default:
break;
}
break;
case ElementTheme.Light:
fallback = _darkFontColor;
break;
case ElementTheme.Dark:
fallback = _lightFontColor;
break;
default:
break;
}
switch (LyricsFontColorType)
{
case Enums.LyricsFontColorType.Default:
_fontColor = fallback;
break;
case Enums.LyricsFontColorType.Dominant:
_fontColor = _albumArtAccentColor ?? fallback;
break;
default:
break;
}
}
/// <summary>
/// The UpdateLinesProps
/// </summary>
/// <param name="source">The source<see cref="List{LyricsLine}?"/></param>
/// <param name="defaultOpacity">The defaultOpacity<see cref="float"/></param>
private void UpdateLinesProps()
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
int halfVisibleLineCount =
Math.Max(
currentPlayingLineIndex - _startVisibleLineIndex,
_endVisibleLineIndex - currentPlayingLineIndex
) + 1;
if (halfVisibleLineCount < 1)
{
return;
}
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
if (line == null)
{
return;
}
int distanceFromPlayingLine = Math.Abs(i - currentPlayingLineIndex);
if (distanceFromPlayingLine > halfVisibleLineCount)
{
return;
}
float distanceZoomFactor = distanceFromPlayingLine / (float)halfVisibleLineCount;
line.BlurAmountTransition.StartTransition(LyricsBlurAmount * distanceZoomFactor);
line.ScaleTransition.StartTransition(
_highlightedScale - distanceZoomFactor * (_highlightedScale - _defaultScale)
);
// Only calculate highlight opacity for the current line and the two lines around it
// to avoid unnecessary calculations
if (distanceFromPlayingLine <= 1)
{
line.HighlightOpacityTransition.StartTransition(
distanceFromPlayingLine == 0 ? 1 : 0
);
}
if (line.ScaleTransition.IsTransitioning)
{
line.ScaleTransition.Update(ElapsedTime);
}
if (line.BlurAmountTransition.IsTransitioning)
{
line.BlurAmountTransition.Update(ElapsedTime);
}
// Only update highlight opacity for the current line and the two lines around it
if (distanceFromPlayingLine <= 1)
{
if (line.HighlightOpacityTransition.IsTransitioning)
{
line.HighlightOpacityTransition.Update(ElapsedTime);
}
}
}
}
/// <summary>
/// Reassigns positions (x,y) to lyrics lines based on the current control size and font size
/// </summary>
/// <param name="control"></param>
private void ReLayout(ICanvasAnimatedControl control)
{
if (control == null)
return;
_textFormat.FontSize = LyricsFontSize;
float y = _topMargin;
// Init Positions
for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
{
var line = _multiLangLyrics[_langIndex].SafeGet(i);
if (line == null)
{
continue;
}
if (line.CanvasTextLayout != null)
{
line.CanvasTextLayout.Dispose();
line.CanvasTextLayout = null;
}
// Calculate layout bounds
line.CanvasTextLayout = new CanvasTextLayout(
control,
line.Text,
_textFormat,
(float)_maxLyricsWidthTransition.Value,
(float)control.Size.Height
);
line.Position = new Vector2(0, y);
y +=
(float)line.CanvasTextLayout.LayoutBounds.Height
/ line.CanvasTextLayout.LineCount
* (line.CanvasTextLayout.LineCount + LyricsLineSpacingFactor);
}
}
}
}

View File

@@ -1,17 +1,11 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using Windows.Graphics.Imaging;
using System;
using System.Collections.ObjectModel;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
@@ -19,362 +13,272 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial class LyricsRendererViewModel
: IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<float>>,
IRecipient<PropertyChangedMessage<double>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<Color>>,
IRecipient<PropertyChangedMessage<LyricsDisplayType>>,
IRecipient<PropertyChangedMessage<LyricsFontColorType>>,
IRecipient<PropertyChangedMessage<LyricsAlignmentType>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<TextAlignmentType>>,
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
IRecipient<PropertyChangedMessage<LineRenderingType>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
IRecipient<PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>>>
{
/// <summary>
/// The OnLyricsFontColorTypeChanged
/// </summary>
/// <param name="value">The value<see cref="LyricsFontColorType"/></param>
partial void OnLyricsFontColorTypeChanged(LyricsFontColorType value)
public void Receive(PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>> message)
{
UpdateFontColor();
}
/// <summary>
/// The OnLyricsFontSizeChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnLyricsFontSizeChanged(int value)
{
_isRelayoutNeeded = true;
}
/// <summary>
/// The OnLyricsFontWeightChanged
/// </summary>
/// <param name="value">The value<see cref="LyricsFontWeight"/></param>
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
{
_textFormat.FontWeight = value.ToFontWeight();
_isRelayoutNeeded = true;
}
/// <summary>
/// The OnLyricsLineSpacingFactorChanged
/// </summary>
/// <param name="value">The value<see cref="float"/></param>
partial void OnLyricsLineSpacingFactorChanged(float value)
{
_isRelayoutNeeded = true;
}
/// <summary>
/// The OnSongInfoChanged
/// </summary>
/// <param name="oldValue">The oldValue<see cref="SongInfo?"/></param>
/// <param name="newValue">The newValue<see cref="SongInfo?"/></param>
async partial void OnSongInfoChanged(SongInfo? oldValue, SongInfo? newValue)
{
TotalTime = TimeSpan.Zero;
_lastAlbumArtBitmap = _albumArtBitmap;
if (newValue?.AlbumArt is byte[] bytes)
if (message.Sender is SettingsPageViewModel)
{
_albumArtBitmap = await (
await ImageHelper.GetDecoderFromByte(bytes)
).GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
_albumArtAccentColor = (
await ImageHelper.GetAccentColorsFromByte(bytes)
).FirstOrDefault();
if (message.PropertyName == nameof(SettingsPageViewModel.LocalLyricsFolders))
{
// Music lib changed, re-fetch lyrics
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
RefreshLyricsAsync();
}
}
else
{
_albumArtBitmap = null;
_albumArtAccentColor = null;
}
UpdateFontColor();
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
await RefreshLyricsAsync();
}
/// <summary>
/// The OnThemeChanged
/// </summary>
/// <param name="value">The value<see cref="ElementTheme"/></param>
partial void OnThemeChanged(ElementTheme value)
public void Receive(PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>> message)
{
UpdateFontColor();
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsSearchProvidersInfo))
{
// Lyrics search providers info changed, re-fetch lyrics
_logger.LogInformation("Lyrics search providers info changed, refreshing lyrics.");
RefreshLyricsAsync();
}
}
}
// Receive methods for handling messages from other view models
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{bool}"/></param>
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is SettingsViewModel)
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.IsDynamicCoverOverlayEnabled))
if (message.PropertyName == nameof(SettingsPageViewModel.IsDynamicCoverOverlayEnabled))
{
IsDynamicCoverOverlayEnabled = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.IsCoverOverlayEnabled))
{
IsCoverOverlayEnabled = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.IsDebugOverlayEnabled))
else if (message.PropertyName == nameof(SettingsPageViewModel.IsDebugOverlayEnabled))
{
_isDebugOverlayEnabled = message.NewValue;
}
}
else if (message.Sender is LyricsSettingsControlViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.IsLyricsGlowEffectEnabled)
)
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLyricsGlowEffectEnabled))
{
IsLyricsGlowEffectEnabled = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.IsFanLyricsEnabled))
{
_isFanLyricsEnabled = message.NewValue;
_isLayoutChanged = true;
}
}
else if (message.Sender is HostWindowViewModel)
else if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(HostWindowViewModel.IsDockMode))
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
{
_isDockMode = message.NewValue;
}
else if (message.PropertyName == nameof(HostWindowViewModel.IsDesktopMode))
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
{
_isDesktopMode = message.NewValue;
}
}
else if (message.Sender is LyricsPageViewModel)
{
if (message.PropertyName == nameof(LyricsPageViewModel.IsTranslationEnabled))
{
_isTranslationEnabled = message.NewValue;
_logger.LogInformation("Translation enabled state changed: {IsEnabled}", _isTranslationEnabled);
UpdateTranslationsAsync();
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{Color}"/></param>
public void Receive(PropertyChangedMessage<Color> message)
{
if (message.Sender is HostWindowViewModel)
if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(HostWindowViewModel.ActivatedWindowAccentColor))
if (message.PropertyName == nameof(LyricsWindowViewModel.ActivatedWindowAccentColor))
{
_immersiveBgTransition.StartTransition(message.NewValue);
_environmentalColor = message.NewValue;
UpdateFontColor();
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{double}"/></param>
public void Receive(PropertyChangedMessage<double> message)
{
if (message.Sender is LyricsPageViewModel)
else if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(LyricsPageViewModel.MaxLyricsWidth))
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomBgFontColor))
{
_maxLyricsWidthTransition.StartTransition((float)message.NewValue);
_customBgFontColor = message.NewValue;
UpdateFontColor();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomFgFontColor))
{
_customFgFontColor = message.NewValue;
UpdateFontColor();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomStrokeFontColor))
{
_customStrokeFontColor = message.NewValue;
UpdateFontColor();
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{ElementTheme}"/></param>
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.ThemeType))
{
Theme = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{float}"/></param>
public void Receive(PropertyChangedMessage<float> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
if (message.Sender is SettingsPageViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsLineSpacingFactor)
)
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsLineSpacingFactor))
{
LyricsLineSpacingFactor = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{int}"/></param>
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is SettingsViewModel)
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
if (message.PropertyName == nameof(SettingsPageViewModel.CoverImageRadius))
{
CoverImageRadius = message.NewValue;
_albumArtCornerRadius = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.CoverOverlayOpacity))
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayOpacity))
{
CoverOverlayOpacity = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.CoverOverlayBlurAmount))
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayBlurAmount))
{
CoverOverlayBlurAmount = message.NewValue;
}
}
else if (message.Sender is LyricsSettingsControlViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsVerticalEdgeOpacity)
)
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsVerticalEdgeOpacity))
{
LyricsVerticalEdgeOpacity = message.NewValue;
_isLayoutChanged = true;
}
else if (
message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsBlurAmount)
)
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBlurAmount))
{
LyricsBlurAmount = message.NewValue;
_isLayoutChanged = true;
}
else if (
message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize)
)
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
{
LyricsFontSize = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsAlignmentType}"/></param>
public void Receive(PropertyChangedMessage<LyricsAlignmentType> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsAlignmentType)
)
else if (message.PropertyName == nameof(SettingsPageViewModel.SelectedTargetLanguageIndex))
{
LyricsAlignmentType = message.NewValue;
_targetLanguageIndex = message.NewValue;
_logger.LogInformation("Target language index changed: {Index}", _targetLanguageIndex);
UpdateTranslationsAsync();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontStrokeWidth))
{
_lyricsFontStrokeWidth = message.NewValue;
}
}
else if (message.Sender is LyricsPageViewModel)
{
if (message.PropertyName == nameof(LyricsPageViewModel.PositionOffset))
{
_positionOffset = TimeSpan.FromMilliseconds(message.NewValue);
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsDisplayType}"/></param>
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
{
DisplayType = message.NewValue;
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsFontColorType}"/></param>
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsFontColorType)
)
{
LyricsFontColorType = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsFontWeight}"/></param>
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontWeight))
{
LyricsFontWeight = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsGlowEffectScope}"/></param>
public void Receive(PropertyChangedMessage<LineRenderingType> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
if (message.Sender is SettingsPageViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsGlowEffectScope)
)
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsGlowEffectScope))
{
LyricsGlowEffectScope = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{ObservableCollection{LocalLyricsFolder}}"/></param>
public void Receive(PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>> message)
public void Receive(PropertyChangedMessage<TextAlignmentType> message)
{
if (message.Sender is SettingsViewModel)
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.LocalLyricsFolders))
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsAlignmentType))
{
// Music lib changed, re-fetch lyrics
RefreshLyricsAsync().ConfigureAwait(true);
LyricsAlignmentType = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.SongInfoAlignmentType))
{
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment =
message.NewValue.ToCanvasHorizontalAlignment();
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{ObservableCollection{LyricsSearchProviderInfo}}"/></param>
public void Receive(
PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>> message
)
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
{
if (message.Sender is SettingsViewModel)
_displayTypeReceived = message.NewValue;
}
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
{
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.LyricsSearchProvidersInfo))
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBgFontColorType))
{
// Lyrics search providers info changed, re-fetch lyrics
RefreshLyricsAsync().ConfigureAwait(true);
_lyricsBgFontColorType = message.NewValue;
UpdateFontColor();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFgFontColorType))
{
_lyricsFgFontColorType = message.NewValue;
UpdateFontColor();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsStrokeFontColorType))
{
_lyricsStrokeFontColorType = message.NewValue;
UpdateFontColor();
}
}
}
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
{
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontWeight))
{
LyricsFontWeight = message.NewValue;
}
}
}
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBackgroundTheme))
{
_lyricsBgTheme = message.NewValue;
UpdateFontColor();
}
}
}
partial void OnLyricsFontSizeChanged(int value)
{
_isLayoutChanged = true;
}
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
{
_lyricsTextFormat.FontWeight = value.ToFontWeight();
_isLayoutChanged = true;
}
partial void OnLyricsLineSpacingFactorChanged(float value)
{
_isLayoutChanged = true;
}
}
}

View File

@@ -0,0 +1,61 @@
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
{
private readonly ValueTransition<float> _canvasYScrollTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
);
private readonly ValueTransition<Color> _immersiveBgTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
private readonly ValueTransition<float> _lyricsXTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
);
private readonly ValueTransition<float> _lyricsOpacityTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
);
private readonly ValueTransition<float> _albumArtBgTransition = new(
initialValue: 0f,
durationSeconds: 1f
);
private readonly ValueTransition<float> _albumArtOpacityTransition = new(
initialValue: 0f,
durationSeconds: 1f
);
private readonly ValueTransition<float> _albumArtXTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
);
private readonly ValueTransition<float> _songInfoOpacityTransition = new(
initialValue: 0f,
durationSeconds: 1f
);
private readonly ValueTransition<float> _lyricsBgBrightnessTransition = new(
initialValue: 0f,
durationSeconds: 1f
);
}
}

View File

@@ -0,0 +1,401 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using System;
using System.Numerics;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
{
private bool _isCanvasWidthChanged = false;
private bool _isCanvasHeightChanged = false;
private bool _isDisplayTypeChanged = false;
private bool _isPlayingLineChanged = false;
private bool _isVisibleLinesBoundaryChanged = false;
public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
{
_elapsedTime = args.Timing.ElapsedTime;
if (_isPlaying)
{
_totalTime += _elapsedTime;
}
var playingLineIndex = GetCurrentPlayingLineIndex();
_isCanvasWidthChanged = _canvasWidth != control.Size.Width;
_isCanvasHeightChanged = _canvasHeight != control.Size.Height;
_isDisplayTypeChanged = _displayType != _displayTypeReceived;
_isPlayingLineChanged = _playingLineIndex != playingLineIndex;
_canvasWidth = (float)control.Size.Width;
_canvasHeight = (float)control.Size.Height;
_displayType = _displayTypeReceived;
_playingLineIndex = playingLineIndex;
_immersiveBgTransition.Update(_elapsedTime);
_albumArtBgTransition.Update(_elapsedTime);
_lyricsBgBrightnessTransition.Update(_elapsedTime);
_songInfoOpacityTransition.Update(_elapsedTime);
if (IsDynamicCoverOverlayEnabled)
{
_rotateAngle += _coverRotateSpeed;
_rotateAngle %= MathF.PI * 2;
}
if (_isCanvasHeightChanged)
{
_albumArtY = 36 + (_canvasHeight - 36 * 2) * 3 / 16f;
}
if (_isCanvasWidthChanged || _isCanvasHeightChanged)
{
_albumArtSize = MathF.Min(
(_canvasHeight - _topMargin - _bottomMargin) * 8.5f / 16,
(_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2);
_albumArtSize = MathF.Max(0, _albumArtSize);
_titleY = _albumArtY + _albumArtSize * 1.05f;
}
if (_isDisplayTypeChanged || _isCanvasWidthChanged)
{
bool jumpTo = !_isDisplayTypeChanged && _isCanvasWidthChanged;
switch (_displayType)
{
case LyricsDisplayType.AlbumArtOnly:
_lyricsOpacityTransition.StartTransition(0f, jumpTo);
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
_albumArtXTransition.StartTransition(_canvasWidth / 2 - _albumArtSize / 2, jumpTo);
break;
case LyricsDisplayType.LyricsOnly:
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
_albumArtOpacityTransition.StartTransition(0f, jumpTo);
_lyricsXTransition.StartTransition(_leftMargin, jumpTo);
break;
case LyricsDisplayType.SplitView:
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
_lyricsXTransition.StartTransition((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 + _leftMargin + _middleMargin, jumpTo);
_albumArtXTransition.StartTransition(_leftMargin + ((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 - _albumArtSize) / 2, jumpTo);
break;
case LyricsDisplayType.PlaceholderOnly:
break;
default:
break;
}
}
_lyricsXTransition.Update(_elapsedTime);
_albumArtXTransition.Update(_elapsedTime);
_lyricsOpacityTransition.Update(_elapsedTime);
_albumArtOpacityTransition.Update(_elapsedTime);
if (_isCanvasWidthChanged || _lyricsXTransition.IsTransitioning)
{
_maxLyricsWidth = _canvasWidth - _lyricsXTransition.Value - _rightMargin;
_maxLyricsWidth = Math.Max(_maxLyricsWidth, 0);
_isLayoutChanged = true;
}
if (_isLayoutChanged)
{
ReLayout(control);
UpdateCanvasYScrollOffset(control, true, false);
}
else
{
UpdateCanvasYScrollOffset(control, false, true);
}
UpdateLinesProps();
_isLayoutChanged = false;
}
private void ReLayout(ICanvasAnimatedControl control)
{
if (control == null)
return;
_lyricsTextFormat.FontSize = LyricsFontSize;
float y = 0;
// Init Positions
for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
{
var line = _multiLangLyrics[_langIndex].SafeGet(i);
if (line == null)
{
continue;
}
if (line.CanvasTextLayout != null)
{
line.CanvasTextLayout.Dispose();
line.CanvasTextLayout = null;
}
// Calculate layout bounds
line.CanvasTextLayout = new CanvasTextLayout(
control,
line.DisplayedText,
_lyricsTextFormat,
_maxLyricsWidth,
_canvasHeight
);
line.Position = new Vector2(0, y);
y +=
(float)line.CanvasTextLayout.LayoutBounds.Height
/ line.CanvasTextLayout.LineCount
* (line.CanvasTextLayout.LineCount + LyricsLineSpacingFactor);
}
}
private void UpdateCanvasYScrollOffset(ICanvasAnimatedControl control, bool forceScroll, bool withAnimation)
{
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
if (startLineIndex < 0 || endLineIndex < 0) return;
// Set _scrollOffsetY
if ((!_isPlayingLineChanged && forceScroll) || _isPlayingLineChanged)
{
LyricsLine? currentPlayingLine = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(_playingLineIndex);
if (currentPlayingLine == null) return;
var playingTextLayout = currentPlayingLine?.CanvasTextLayout;
if (playingTextLayout == null) return;
float? targetYScrollOffset = (float?)(-currentPlayingLine!.Position.Y + _multiLangLyrics.SafeGet(_langIndex)?[0].Position.Y - playingTextLayout.LayoutBounds.Height / 2);
if (!targetYScrollOffset.HasValue) return;
_canvasYScrollTransition.StartTransition(targetYScrollOffset.Value, !withAnimation);
}
_canvasYScrollTransition.Update(_elapsedTime);
int startVisibleLineIndex = -1;
int endVisibleLineIndex = -1;
// Update visible line indices
for (int i = startLineIndex; i <= endLineIndex; i++)
{
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
if (line == null || line.CanvasTextLayout == null)
{
continue;
}
var textLayout = line.CanvasTextLayout;
if (
_canvasYScrollTransition.Value
+ _canvasHeight / 2
+ line.Position.Y
+ textLayout.LayoutBounds.Height
>= 0
)
{
if (startVisibleLineIndex == -1)
{
startVisibleLineIndex = i;
}
}
if (
_canvasYScrollTransition.Value
+ _canvasHeight / 2
+ line.Position.Y
+ textLayout.LayoutBounds.Height
>= control.Size.Height
)
{
if (endVisibleLineIndex == -1)
{
endVisibleLineIndex = i;
}
}
}
if (startVisibleLineIndex != -1 && endVisibleLineIndex == -1)
{
endVisibleLineIndex = endLineIndex;
}
_isVisibleLinesBoundaryChanged = _startVisibleLineIndex != startVisibleLineIndex || _endVisibleLineIndex != endVisibleLineIndex;
_startVisibleLineIndex = startVisibleLineIndex;
_endVisibleLineIndex = endVisibleLineIndex;
}
private void UpdateFontColor()
{
if (_isDesktopMode || _isDockMode)
{
ThemeTypeSent = Helper.ColorHelper.GetElementThemeFromBackgroundColor(_environmentalColor);
}
else
{
ThemeTypeSent = _lyricsBgTheme;
}
float brightness = 0f;
Color grayedEnvironmentalColor = Colors.Transparent;
switch (ThemeTypeSent)
{
case ElementTheme.Default:
switch (Application.Current.RequestedTheme)
{
case ApplicationTheme.Light:
_adaptiveGrayedFontColor = _darkColor;
brightness = 0.7f;
break;
case ApplicationTheme.Dark:
_adaptiveGrayedFontColor = _lightColor;
brightness = 0.3f;
break;
default:
break;
}
break;
case ElementTheme.Light:
_adaptiveGrayedFontColor = _darkColor;
brightness = 0.7f;
break;
case ElementTheme.Dark:
_adaptiveGrayedFontColor = _lightColor;
brightness = 0.3f;
break;
default:
break;
}
if (_adaptiveGrayedFontColor == _lightColor)
{
grayedEnvironmentalColor = _darkColor;
} else if (_adaptiveGrayedFontColor == _darkColor)
{
grayedEnvironmentalColor = _lightColor;
}
_lyricsBgBrightnessTransition.StartTransition(brightness);
if (_isDesktopMode || _isDockMode)
{
_adaptiveColoredFontColor = Helper.ColorHelper.GetForegroundColor(_environmentalColor);
}
else
{
_adaptiveColoredFontColor = Helper.ColorHelper.GetForegroundColor(_albumArtAccentColor?.WithBrightness(brightness) ?? Colors.Transparent);
}
switch (_lyricsBgFontColorType)
{
case LyricsFontColorType.AdaptiveGrayed:
_bgFontColor = _adaptiveGrayedFontColor;
break;
case LyricsFontColorType.AdaptiveColored:
_bgFontColor = _adaptiveColoredFontColor ?? _adaptiveGrayedFontColor;
break;
case LyricsFontColorType.Custom:
_bgFontColor = _customBgFontColor ?? _adaptiveGrayedFontColor;
break;
default:
break;
}
switch (_lyricsFgFontColorType)
{
case LyricsFontColorType.AdaptiveGrayed:
_fgFontColor = _adaptiveGrayedFontColor;
break;
case LyricsFontColorType.AdaptiveColored:
_fgFontColor = _adaptiveColoredFontColor ?? _adaptiveGrayedFontColor;
break;
case LyricsFontColorType.Custom:
_fgFontColor = _customFgFontColor ?? _adaptiveGrayedFontColor;
break;
default:
break;
}
switch (_lyricsStrokeFontColorType)
{
case LyricsFontColorType.AdaptiveGrayed:
_strokeFontColor = grayedEnvironmentalColor.WithBrightness(0.7);
break;
case LyricsFontColorType.AdaptiveColored:
_strokeFontColor = _environmentalColor.WithBrightness(0.7);
break;
case LyricsFontColorType.Custom:
_strokeFontColor = _customStrokeFontColor ?? _environmentalColor;
break;
default:
break;
}
}
private void UpdateLinesProps()
{
var currentPlayingLine = _multiLangLyrics
.SafeGet(_langIndex)
?.SafeGet(_playingLineIndex);
if (currentPlayingLine == null) return;
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
if (line == null) continue;
if (_isLayoutChanged || _isVisibleLinesBoundaryChanged || _isPlayingLineChanged)
{
float distanceFromPlayingLine = Math.Abs(line.Position.Y - currentPlayingLine.Position.Y);
float distanceFactor = Math.Clamp(distanceFromPlayingLine / (_canvasHeight / 2), 0, 1);
line.AngleTransition.StartTransition(_isFanLyricsEnabled
? (float)Math.PI
* (30f / 180f)
* distanceFactor
* (i > _playingLineIndex ? 1 : -1)
: 0
);
line.BlurAmountTransition.StartTransition(LyricsBlurAmount * distanceFactor);
line.ScaleTransition.StartTransition(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale));
line.OpacityTransition.StartTransition(_defaultOpacity - distanceFactor * _defaultOpacity * (1 - LyricsVerticalEdgeOpacity / 100f));
line.HighlightOpacityTransition.StartTransition(i == _playingLineIndex ? 1f : 0f);
}
line.AngleTransition.Update(_elapsedTime);
line.ScaleTransition.Update(_elapsedTime);
line.BlurAmountTransition.Update(_elapsedTime);
line.OpacityTransition.Update(_elapsedTime);
line.HighlightOpacityTransition.Update(_elapsedTime);
}
}
}
}

View File

@@ -1,226 +1,177 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ABI.Microsoft.UI.Xaml;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
{
/// <summary>
/// Defines the <see cref="LyricsRendererViewModel" />
/// </summary>
public partial class LyricsRendererViewModel : BaseViewModel
{
#region Fields
private TimeSpan _elapsedTime = TimeSpan.Zero;
private TimeSpan _totalTime = TimeSpan.Zero;
private TimeSpan _positionOffset = TimeSpan.Zero;
/// <summary>
/// Defines the _albumArtBgTransition
/// </summary>
private readonly ValueTransition<float> _albumArtBgTransition = new(
initialValue: 0f,
durationSeconds: 1.0f
);
private SoftwareBitmap? _lastAlbumArtSwBitmap = null;
private SoftwareBitmap? _albumArtSwBitmap = null;
/// <summary>
/// Defines the _canvasYScrollTransition
/// </summary>
private readonly ValueTransition<float> _canvasYScrollTransition = new(
initialValue: 0f,
durationSeconds: 0.8f,
easingType: EasingType.SmootherStep
);
private CanvasBitmap? _lastAlbumArtCanvasBitmap = null;
private CanvasBitmap? _albumArtCanvasBitmap = null;
/// <summary>
/// Defines the _coverRotateSpeed
/// </summary>
private readonly float _coverRotateSpeed = 0.003f;
private float _albumArtSize = 0f;
private int _albumArtCornerRadius = 0;
private float _albumArtY = 0f;
private string? _lastSongTitle;
private string? _songTitle;
private float _titleY = 0f;
private string? _lastSongArtist;
private string? _songArtist;
private float _canvasWidth = 0f;
private float _canvasHeight = 0f;
/// <summary>
/// Defines the _defaultOpacity
/// </summary>
private readonly float _defaultOpacity = 0.3f;
/// <summary>
/// Defines the _defaultScale
/// </summary>
private readonly float _defaultScale = 0.75f;
/// <summary>
/// Defines the _highlightedOpacity
/// </summary>
private readonly float _highlightedOpacity = 1.0f;
/// <summary>
/// Defines the _highlightedScale
/// </summary>
private readonly float _defaultScale = 0.75f;
private readonly float _highlightedScale = 1.0f;
private bool _isDebugOverlayEnabled = false;
/// <summary>
/// Defines the _immersiveBgrTransition
/// </summary>
private readonly ValueTransition<Color> _immersiveBgTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) =>
Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
/// <summary>
/// Defines the _libWatcherService
/// </summary>
private readonly ILibWatcherService _libWatcherService;
/// <summary>
/// Defines the _limitedLineWidthTransition
/// </summary>
private readonly ValueTransition<float> _maxLyricsWidthTransition = new(
initialValue: 0f,
durationSeconds: 0.8f,
interpolator: (from, to, progress) => to
);
/// <summary>
/// Defines the _lyricsGlowEffectAmount
/// </summary>
private readonly float _lyricsGlowEffectAmount = 8f;
/// <summary>
/// Defines the _musicSearchService
/// </summary>
private protected readonly IMusicSearchService _musicSearchService;
/// <summary>
/// Defines the _playbackService
/// </summary>
private protected readonly IPlaybackService _playbackService;
/// <summary>
/// Defines the _rightMargin
/// </summary>
private readonly float _rightMargin = 36f;
/// <summary>
/// Defines the _topMargin
/// </summary>
private readonly float _topMargin = 0f;
/// <summary>
/// Defines the _albumArtAccentColor
/// </summary>
private Color? _albumArtAccentColor = null;
/// <summary>
/// Defines the _albumArtBitmap
/// </summary>
private SoftwareBitmap? _albumArtBitmap = null;
/// <summary>
/// Defines the _darkFontColor
/// </summary>
private Color _darkFontColor = Colors.Black;
/// <summary>
/// Defines the _endVisibleLineIndex
/// </summary>
private int _endVisibleLineIndex = -1;
/// <summary>
/// Defines the _fontColor
/// </summary>
private protected Color _fontColor;
/// <summary>
/// Defines the _isPlaying
/// </summary>
private bool _isPlaying = true;
/// <summary>
/// Defines the _isRelayoutNeeded
/// </summary>
private protected bool _isRelayoutNeeded = true;
/// <summary>
/// Defines the _langIndex
/// </summary>
private int _langIndex = 0;
/// <summary>
/// Defines the _lastAlbumArtBitmap
/// </summary>
private SoftwareBitmap? _lastAlbumArtBitmap = null;
/// <summary>
/// Defines the _lightFontColor
/// </summary>
private Color _lightFontColor = Colors.White;
/// <summary>
/// Defines the _multiLangLyrics
/// </summary>
private List<List<LyricsLine>> _multiLangLyrics = [];
/// <summary>
/// Defines the _rotateAngle
/// </summary>
private readonly float _coverRotateSpeed = 0.003f;
private float _rotateAngle = 0f;
/// <summary>
/// Defines the _startVisibleLineIndex
/// </summary>
private int _startVisibleLineIndex = -1;
private readonly float _lyricsGlowEffectAmount = 8f;
/// <summary>
/// Defines the _textFormat
/// </summary>
private protected CanvasTextFormat _textFormat = new()
private float _maxLyricsWidth = 0f;
private readonly IMusicSearchService _musicSearchService;
private readonly ILibWatcherService _libWatcherService;
private readonly IPlaybackService _playbackService;
private readonly ILibreTranslateService _libreTranslateService;
private readonly ILogger _logger;
private readonly float _leftMargin = 36f;
private readonly float _middleMargin = 36f;
private readonly float _rightMargin = 36f;
private readonly float _topMargin = 36f;
private readonly float _bottomMargin = 36f;
private Color _adaptiveGrayedFontColor = Colors.Transparent;
private Color? _adaptiveColoredFontColor = null;
private Color? _albumArtAccentColor = null;
private Color _environmentalColor = Colors.Transparent;
private Color _lightColor = Colors.White;
private Color _darkColor = Colors.Black;
private Color _bgFontColor;
private Color _fgFontColor;
private Color _strokeFontColor;
private Color? _customBgFontColor;
private Color? _customFgFontColor;
private Color? _customStrokeFontColor;
private LyricsFontColorType _lyricsBgFontColorType;
private LyricsFontColorType _lyricsFgFontColorType;
private LyricsFontColorType _lyricsStrokeFontColorType;
private ElementTheme _lyricsBgTheme;
private int _lyricsFontStrokeWidth;
private int _playingLineIndex = -1;
private int _startVisibleLineIndex = -1;
private int _endVisibleLineIndex = -1;
private bool _isDebugOverlayEnabled = false;
private bool _isDesktopMode = false;
private bool _isDockMode = false;
private bool _isFanLyricsEnabled = false;
private bool _isPlaying = true;
private bool _isLayoutChanged = true;
private int _langIndex = 0;
private List<List<LyricsLine>> _multiLangLyrics = [];
private List<string> _translations = [];
private bool _isTranslationEnabled = false;
private int _targetLanguageIndex = 6;
private CanvasTextFormat _lyricsTextFormat = new()
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
};
private CanvasTextFormat _titleTextFormat = new()
{
FontSize = 18,
FontWeight = FontWeights.Bold,
HorizontalAlignment = CanvasHorizontalAlignment.Left,
WordWrapping = CanvasWordWrapping.NoWrap,
TrimmingSign = CanvasTrimmingSign.Ellipsis,
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
};
private CanvasTextFormat _artistTextFormat = new()
{
FontSize = 16,
FontWeight = FontWeights.Bold,
HorizontalAlignment = CanvasHorizontalAlignment.Left,
WordWrapping = CanvasWordWrapping.NoWrap,
TrimmingSign = CanvasTrimmingSign.Ellipsis,
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
};
#endregion
private Task? _refreshLyricsTask;
private CancellationTokenSource? _refreshLyricsCts;
#region Constructors
private Task? _showTranslationsTask;
private CancellationTokenSource? _showTranslationsCts;
/// <summary>
/// Initializes a new instance of the <see cref="LyricsRendererViewModel"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
/// <param name="playbackService">The playbackService<see cref="IPlaybackService"/></param>
/// <param name="musicSearchService">The musicSearchService<see cref="IMusicSearchService"/></param>
/// <param name="libWatcherService">The libWatcherService<see cref="ILibWatcherService"/></param>
public LyricsRendererViewModel(
ISettingsService settingsService,
IPlaybackService playbackService,
IMusicSearchService musicSearchService,
ILibWatcherService libWatcherService
)
: base(settingsService)
public LyricsRendererViewModel(ISettingsService settingsService, IPlaybackService playbackService, IMusicSearchService musicSearchService, ILibWatcherService libWatcherService, ILibreTranslateService libreTranslateService) : base(settingsService)
{
_musicSearchService = musicSearchService;
_playbackService = playbackService;
_libWatcherService = libWatcherService;
_libreTranslateService = libreTranslateService;
CoverImageRadius = _settingsService.CoverImageRadius;
IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
_albumArtCornerRadius = _settingsService.CoverImageRadius;
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
LyricsFontColorType = _settingsService.LyricsFontColorType;
_lyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
_lyricsFgFontColorType = _settingsService.LyricsFgFontColorType;
LyricsFontWeight = _settingsService.LyricsFontWeight;
LyricsAlignmentType = _settingsService.LyricsAlignmentType;
LyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
@@ -230,6 +181,20 @@ namespace BetterLyrics.WinUI3.ViewModels
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
_customBgFontColor = _settingsService.LyricsCustomBgFontColor;
_customFgFontColor = _settingsService.LyricsCustomFgFontColor;
_lyricsBgTheme = _settingsService.LyricsBackgroundTheme;
_isFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
_lyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
_isTranslationEnabled = _settingsService.IsTranslationEnabled;
_targetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
_libWatcherService.MusicLibraryFilesChanged +=
LibWatcherService_MusicLibraryFilesChanged;
@@ -237,137 +202,47 @@ namespace BetterLyrics.WinUI3.ViewModels
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
_playbackService.PositionChanged += PlaybackService_PositionChanged;
_isPlaying = _playbackService.IsPlaying;
SongInfo = _playbackService.SongInfo;
TotalTime = _playbackService.Position;
UpdateFontColor();
}
#endregion
[ObservableProperty]
public partial bool IsTranslating { get; set; } = false;
#region Properties
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
public int CoverImageRadius { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayBlurAmount
/// </summary>
public int CoverOverlayBlurAmount { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayOpacity
/// </summary>
public int CoverOverlayOpacity { get; set; }
/// <summary>
/// Gets or sets the DisplayType
/// </summary>
public LyricsDisplayType DisplayType { get; set; }
private LyricsDisplayType _displayTypeReceived = LyricsDisplayType.PlaceholderOnly;
private LyricsDisplayType _displayType = LyricsDisplayType.PlaceholderOnly;
/// <summary>
/// Gets or sets the ElapsedTime
/// </summary>
public TimeSpan ElapsedTime { get; set; } = TimeSpan.Zero;
/// <summary>
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
/// </summary>
public bool IsCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
public bool IsDynamicCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
/// </summary>
public bool IsLyricsGlowEffectEnabled { get; set; }
/// <summary>
/// Gets or sets the LyricsAlignmentType
/// </summary>
public LyricsAlignmentType LyricsAlignmentType { get; set; }
public TextAlignmentType LyricsAlignmentType { get; set; }
/// <summary>
/// Gets or sets the LyricsBlurAmount
/// </summary>
public int LyricsBlurAmount { get; set; }
/// <summary>
/// Gets or sets the LyricsFontColorType
/// </summary>
[ObservableProperty]
public partial LyricsFontColorType LyricsFontColorType { get; set; }
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
[ObservableProperty]
public partial int LyricsFontSize { get; set; }
/// <summary>
/// Gets or sets the LyricsFontWeight
/// </summary>
[ObservableProperty]
public partial LyricsFontWeight LyricsFontWeight { get; set; }
/// <summary>
/// Gets or sets the LyricsGlowEffectScope
/// </summary>
public LineRenderingType LyricsGlowEffectScope { get; set; }
/// <summary>
/// Gets or sets the LyricsLineSpacingFactor
/// </summary>
[ObservableProperty]
public partial float LyricsLineSpacingFactor { get; set; }
/// <summary>
/// Gets or sets the LyricsStatus
/// </summary>
[NotifyPropertyChangedRecipients]
[ObservableProperty]
public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading;
/// <summary>
/// Gets or sets the LyricsVerticalEdgeOpacity
/// </summary>
public int LyricsVerticalEdgeOpacity { get; set; }
/// <summary>
/// Gets or sets the SongInfo
/// </summary>
[ObservableProperty]
public partial SongInfo? SongInfo { get; set; }
/// <summary>
/// Gets or sets the Theme
/// </summary>
[ObservableProperty]
public partial ElementTheme Theme { get; set; }
[NotifyPropertyChangedRecipients]
public partial ElementTheme ThemeTypeSent { get; set; }
/// <summary>
/// Gets or sets the TotalTime
/// </summary>
public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
private bool _isDockMode = false;
private bool _isDesktopMode = false;
#endregion
#region Methods
/// <summary>
/// The GetCurrentPlayingLineIndex
/// </summary>
/// <returns>The <see cref="int"/></returns>
private int GetCurrentPlayingLineIndex()
{
for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
@@ -378,8 +253,8 @@ namespace BetterLyrics.WinUI3.ViewModels
continue;
}
if (
line.StartMs <= TotalTime.TotalMilliseconds
&& TotalTime.TotalMilliseconds <= line.EndMs
line.StartMs <= _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds
&& _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds <= line.EndMs
)
{
return i;
@@ -389,23 +264,13 @@ namespace BetterLyrics.WinUI3.ViewModels
return -1;
}
/// <summary>
/// The GetLinePlayingProgress
/// </summary>
/// <param name="line">The line<see cref="LyricsLine"/></param>
/// <returns>The <see cref="float"/></returns>
private void GetLinePlayingProgress(
LyricsLine line,
out int charStartIndex,
out int charLength,
out float charProgress
)
private void GetLinePlayingProgress(LyricsLine line, out int charStartIndex, out int charLength, out float charProgress)
{
charStartIndex = 0;
charLength = 0;
charProgress = 0f;
float now = (float)TotalTime.TotalMilliseconds;
float now = (float)_totalTime.TotalMilliseconds + (float)_positionOffset.TotalMilliseconds;
// 1. 还没到本句
if (now < line.StartMs)
@@ -450,14 +315,10 @@ namespace BetterLyrics.WinUI3.ViewModels
charProgress = (now - line.StartMs) / line.DurationMs;
charProgress = Math.Clamp(charProgress, 0f, 1f);
charStartIndex = 0;
charLength = line.Text.Length;
charLength = line.OriginalText.Length;
}
}
/// <summary>
/// The GetMaxLyricsLineIndexBoundaries
/// </summary>
/// <returns>The <see cref="Tuple{int, int}"/></returns>
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
{
if (
@@ -472,93 +333,233 @@ namespace BetterLyrics.WinUI3.ViewModels
return new Tuple<int, int>(0, _multiLangLyrics[_langIndex].Count - 1);
}
/// <summary>
/// The LibWatcherService_MusicLibraryFilesChanged
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="Events.LibChangedEventArgs"/></param>
private void LibWatcherService_MusicLibraryFilesChanged(
object? sender,
LibChangedEventArgs e
)
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, LibChangedEventArgs e)
{
RefreshLyricsAsync().ConfigureAwait(true);
_logger.LogInformation("Music library files changed: {ChangeType} {FilePath}, refreshing lyrics...", e.ChangeType, e.FilePath);
RefreshLyricsAsync();
}
/// <summary>
/// The PlaybackService_IsPlayingChanged
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="IsPlayingChangedEventArgs"/></param>
private void PlaybackService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e)
{
_isPlaying = e.IsPlaying;
}
/// <summary>
/// The PlaybackService_PositionChanged
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="PositionChangedEventArgs"/></param>
private void PlaybackService_PositionChanged(object? sender, PositionChangedEventArgs e)
{
if (Math.Abs(TotalTime.TotalMilliseconds - e.Position.TotalMilliseconds) > 100)
TotalTime = e.Position;
if (Math.Abs(_totalTime.TotalMilliseconds - e.Position.TotalMilliseconds) > 300)
{
_totalTime = e.Position;
}
}
/// <summary>
/// The PlaybackService_SongInfoChanged
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="SongInfoChangedEventArgs"/></param>
private void PlaybackService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
private async void PlaybackService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
{
SongInfo = e.SongInfo;
if (SongInfo?.AlbumArtSwBitmap != _albumArtSwBitmap)
{
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
_lastAlbumArtCanvasBitmap = null;
_albumArtSwBitmap = SongInfo?.AlbumArtSwBitmap;
_albumArtCanvasBitmap = null;
_albumArtAccentColor = SongInfo?.AlbumArtAccentColor;
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
UpdateFontColor();
}
if (SongInfo?.Title != _songTitle || SongInfo?.Artist != _songArtist)
{
_lastSongTitle = _songTitle;
_songTitle = SongInfo?.Title;
_lastSongArtist = _songArtist;
_songArtist = SongInfo?.Artist;
_songInfoOpacityTransition.Reset(0f);
_songInfoOpacityTransition.StartTransition(1f);
_logger.LogInformation("Song info changed: Title={Title}, Artist={Artist}, refreshing lyrics...", _songTitle, _songArtist);
await RefreshLyricsAsync();
_totalTime = TimeSpan.Zero;
}
}
/// <summary>
/// Should invoke this function when:
/// 1. The song info is changed (new song is played).
/// 2. Lyrics search provider info is changed (change order, enable or disable any provider).
/// 3. Local music/lyrics files are changed (added, removed, renamed)
/// </summary>
/// <returns></returns>
private async Task RefreshLyricsAsync()
{
_multiLangLyrics = [];
_isRelayoutNeeded = true;
LyricsStatus = LyricsStatus.Loading;
string? lyricsRaw = null;
LyricsFormat? lyricsFormat = null;
if (SongInfo != null)
// 取消上一次
_refreshLyricsCts?.Cancel();
if (_refreshLyricsTask != null)
{
(lyricsRaw, lyricsFormat) = await _musicSearchService.SearchLyricsAsync(
SongInfo.Title,
SongInfo.Artist,
SongInfo.Album ?? "",
SongInfo.DurationMs ?? 0
);
await _refreshLyricsTask;
}
if (lyricsRaw == null)
var cts = new CancellationTokenSource();
_refreshLyricsCts = cts;
var token = cts.Token;
_refreshLyricsTask = RefreshLyricsCoreAsync(token);
await _refreshLyricsTask;
}
private async Task UpdateTranslationsAsync()
{
IsTranslating = true;
if (_isTranslationEnabled)
{
LyricsStatus = LyricsStatus.NotFound;
await ShowWithTranslationsAsync();
}
else if (SongInfo != null)
else
{
_multiLangLyrics = new LyricsParser().Parse(
lyricsRaw,
lyricsFormat,
SongInfo.Title,
SongInfo.Artist,
(int)(SongInfo.DurationMs ?? 0)
);
_isRelayoutNeeded = true;
LyricsStatus = LyricsStatus.Found;
ShowOriginalsOnly();
}
IsTranslating = false;
}
private async Task ShowWithTranslationsAsync()
{
_showTranslationsCts?.Cancel();
if (_showTranslationsTask != null)
{
await _showTranslationsTask;
}
var cts = new CancellationTokenSource();
_showTranslationsCts = cts;
var token = cts.Token;
_showTranslationsTask = ShowTranslationsCoreAsync(token);
await _showTranslationsTask;
}
private async Task ShowTranslationsCoreAsync(CancellationToken token)
{
_logger.LogInformation("Showing translations for lyrics...");
try
{
if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer))
{
_dispatcherQueue.TryEnqueue(() =>
{
App.Current.LyricsWindowNotificationPanel?.Notify(
App.ResourceLoader!.GetString("TranslateServerNotSet"),
Microsoft.UI.Xaml.Controls.InfoBarSeverity.Warning
);
});
ShowOriginalsOnly();
return;
}
var text = string.Join("\n", _multiLangLyrics.FirstOrDefault()?.Select(x => x.OriginalText) ?? []);
var translated = await _libreTranslateService.TranslateAsync(text, token);
token.ThrowIfCancellationRequested();
_translations = translated.Split('\n').ToList();
bool totallySame = true;
foreach (var langLyrics in _multiLangLyrics)
{
int i = 0;
foreach (var line in langLyrics)
{
if (line.OriginalText != _translations[i])
{
totallySame = false;
break;
}
i++;
}
break;
}
foreach (var langLyrics in _multiLangLyrics)
{
int i = 0;
foreach (var line in langLyrics)
{
line.DisplayedText = totallySame ? line.OriginalText : $"{line.OriginalText}\n{_translations[i]}";
i++;
}
break;
}
_isLayoutChanged = true;
}
catch (Exception)
{
IsTranslating = false;
}
}
#endregion
private void ShowOriginalsOnly()
{
_logger.LogInformation("Showing original lyrics only, translations disabled.");
foreach (var langLyrics in _multiLangLyrics)
{
foreach (var line in langLyrics)
{
line.DisplayedText = line.OriginalText;
}
}
_isLayoutChanged = true;
}
private async Task RefreshLyricsCoreAsync(CancellationToken token)
{
try
{
_logger.LogInformation("Refreshing lyrics...");
SetLyricsLoadingPlaceholder();
string? lyricsRaw = null;
if (SongInfo != null)
{
lyricsRaw = await _musicSearchService.SearchLyricsAsync(
SongInfo.Title,
SongInfo.Artist,
SongInfo.Album ?? "",
SongInfo.DurationMs ?? 0,
token
);
_logger.LogInformation("Lyrics search result: {LyricsRaw}", lyricsRaw ?? "null");
token.ThrowIfCancellationRequested();
}
else
{
_logger.LogWarning("SongInfo is null, cannot search lyrics.");
}
_multiLangLyrics = new LyricsParser().Parse(
lyricsRaw,
(int?)SongInfo?.DurationMs ?? (int)TimeSpan.FromMinutes(99).TotalMilliseconds
);
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _multiLangLyrics.Count);
await UpdateTranslationsAsync();
token.ThrowIfCancellationRequested();
}
catch (Exception) { }
}
private void SetLyricsLoadingPlaceholder()
{
_multiLangLyrics = [];
_multiLangLyrics.Add(
[
new LyricsLine
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = "● ● ●",
DisplayedText = "● ● ●",
CharTimings = [],
},
]
);
_isLayoutChanged = true;
}
}
}

View File

@@ -1,191 +0,0 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterInAppLyrics.WinUI3.ViewModels
{
/// <summary>
/// Defines the <see cref="LyricsSettingsControlViewModel" />
/// </summary>
public partial class LyricsSettingsControlViewModel : BaseViewModel
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LyricsSettingsControlViewModel"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
public LyricsSettingsControlViewModel(ISettingsService settingsService)
: base(settingsService)
{
IsActive = true;
LyricsAlignmentType = _settingsService.LyricsAlignmentType;
LyricsFontWeight = _settingsService.LyricsFontWeight;
LyricsBlurAmount = _settingsService.LyricsBlurAmount;
LyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
LyricsLineSpacingFactor = _settingsService.LyricsLineSpacingFactor;
LyricsFontSize = _settingsService.LyricsFontSize;
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
LyricsFontColorType = _settingsService.LyricsFontColorType;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsLyricsGlowEffectEnabled { get; set; }
/// <summary>
/// Gets or sets the LyricsAlignmentType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsAlignmentType LyricsAlignmentType { get; set; }
/// <summary>
/// Gets or sets the LyricsBlurAmount
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int LyricsBlurAmount { get; set; }
/// <summary>
/// Gets or sets the LyricsFontColorType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontColorType LyricsFontColorType { get; set; }
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int LyricsFontSize { get; set; }
/// <summary>
/// Gets or sets the LyricsFontWeight
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontWeight LyricsFontWeight { get; set; }
/// <summary>
/// Gets or sets the LyricsGlowEffectScope
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LineRenderingType LyricsGlowEffectScope { get; set; }
/// <summary>
/// Gets or sets the LyricsLineSpacingFactor
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial float LyricsLineSpacingFactor { get; set; }
/// <summary>
/// Gets or sets the LyricsVerticalEdgeOpacity
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int LyricsVerticalEdgeOpacity { get; set; }
#endregion
#region Methods
/// <summary>
/// The OnIsLyricsGlowEffectEnabledChanged
/// </summary>
/// <param name="value">The value<see cref="bool"/></param>
partial void OnIsLyricsGlowEffectEnabledChanged(bool value)
{
_settingsService.IsLyricsGlowEffectEnabled = value;
}
/// <summary>
/// The OnLyricsAlignmentTypeChanged
/// </summary>
/// <param name="value">The value<see cref="LyricsAlignmentType"/></param>
partial void OnLyricsAlignmentTypeChanged(LyricsAlignmentType value)
{
_settingsService.LyricsAlignmentType = value;
}
/// <summary>
/// The OnLyricsBlurAmountChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnLyricsBlurAmountChanged(int value)
{
_settingsService.LyricsBlurAmount = value;
}
/// <summary>
/// The OnLyricsFontColorTypeChanged
/// </summary>
/// <param name="value">The value<see cref="LyricsFontColorType"/></param>
partial void OnLyricsFontColorTypeChanged(LyricsFontColorType value)
{
_settingsService.LyricsFontColorType = value;
}
/// <summary>
/// The OnLyricsFontSizeChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnLyricsFontSizeChanged(int value)
{
_settingsService.LyricsFontSize = value;
}
/// <summary>
/// The OnLyricsFontWeightChanged
/// </summary>
/// <param name="value">The value<see cref="LyricsFontWeight"/></param>
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
{
_settingsService.LyricsFontWeight = value;
}
/// <summary>
/// The OnLyricsGlowEffectScopeChanged
/// </summary>
/// <param name="value">The value<see tef="LyricsGlowEffectScope"/></param>
partial void OnLyricsGlowEffectScopeChanged(LineRenderingType value)
{
_settingsService?.LyricsGlowEffectScope = value;
}
/// <summary>
/// The OnLyricsLineSpacingFactorChanged
/// </summary>
/// <param name="value">The value<see cref="float"/></param>
partial void OnLyricsLineSpacingFactorChanged(float value)
{
_settingsService.LyricsLineSpacingFactor = value;
}
/// <summary>
/// The OnLyricsVerticalEdgeOpacityChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnLyricsVerticalEdgeOpacityChanged(int value)
{
_settingsService.LyricsVerticalEdgeOpacity = value;
}
#endregion
}
}

View File

@@ -0,0 +1,199 @@
// 2025/6/23 by Zhe Fang
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Windows.UI;
using WinRT.Interop;
namespace BetterLyrics.WinUI3
{
public partial class LyricsWindowViewModel
: BaseWindowViewModel,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<bool>>
{
private ForegroundWindowWatcherHelper? _watcherHelper = null;
public LyricsWindowViewModel(ISettingsService settingsService) : base(settingsService)
{
_ignoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
}
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial Color ActivatedWindowAccentColor { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDesktopMode { get; set; } = false;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDockMode { get; set; } = false;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsLyricsWindowLocked { get; set; } = false;
[ObservableProperty]
public partial Notification Notification { get; set; } = new();
[ObservableProperty]
public partial bool ShowInfoBar { get; set; } = false;
[ObservableProperty]
public partial ElementTheme ThemeType { get; set; } = ElementTheme.Default;
[ObservableProperty]
public partial double TitleBarFontSize { get; set; } = 11;
[ObservableProperty]
public partial double TitleBarHeight { get; set; } = 36;
private bool _ignoreFullscreenWindow = false;
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is SystemTrayViewModel)
{
if (message.PropertyName == nameof(SystemTrayViewModel.IsLyricsWindowLocked))
{
if (IsLyricsWindowLocked != message.NewValue)
{
IsLyricsWindowLocked = message.NewValue;
}
}
}
else if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.IgnoreFullscreenWindow))
{
_ignoreFullscreenWindow = message.NewValue;
}
}
}
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
if (message.Sender is LyricsRendererViewModel)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.ThemeTypeSent))
{
ThemeType = message.NewValue;
}
}
}
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
{
if (IsDockMode)
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
DockModeHelper.UpdateAppBarHeight(WindowNative.GetWindowHandle(window), message.NewValue * 4);
}
}
}
}
public void StartWatchWindowColorChange(WindowColorSampleMode mode)
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
var hwnd = WindowNative.GetWindowHandle(window);
_watcherHelper = new ForegroundWindowWatcherHelper(
hwnd,
onWindowChanged =>
{
if (_ignoreFullscreenWindow && window.AppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.IsAlwaysOnTop = true;
}
UpdateAccentColor(hwnd, mode);
}
);
_watcherHelper.Start();
UpdateAccentColor(hwnd, mode);
}
public void UpdateAccentColor(nint hwnd, WindowColorSampleMode mode)
{
ActivatedWindowAccentColor = WindowColorHelper.GetDominantColor(hwnd, mode).ToColor();
}
[RelayCommand]
private void LockWindow()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
DesktopModeHelper.Lock(window);
IsLyricsWindowLocked = true;
}
private void StopWatchWindowColorChange()
{
_watcherHelper?.Stop();
_watcherHelper = null;
}
[RelayCommand]
private void ToggleDesktopMode()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
StopWatchWindowColorChange();
IsDesktopMode = !IsDesktopMode;
if (IsDesktopMode)
{
StartWatchWindowColorChange(WindowColorSampleMode.WindowEdge);
DesktopModeHelper.Enable(window);
}
else
{
DesktopModeHelper.Disable(window);
}
}
[RelayCommand]
private void ToggleDockMode()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
StopWatchWindowColorChange();
IsDockMode = !IsDockMode;
if (IsDockMode)
{
StartWatchWindowColorChange(WindowColorSampleMode.BelowWindow);
DockModeHelper.Enable(window, _settingsService.LyricsFontSize * 4);
}
else
{
DockModeHelper.Disable(window);
}
}
}
}

View File

@@ -0,0 +1,591 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using ShadowViewer.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Globalization;
using Windows.Media.Playback;
using Windows.System;
using Windows.UI;
using Windows.UI.Popups;
using WinRT.Interop;
using AppInfo = BetterLyrics.WinUI3.Helper.AppInfo;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class SettingsPageViewModel : BaseViewModel
{
private readonly ILibWatcherService _libWatcherService;
private readonly IPlaybackService _playbackService;
private readonly ILibreTranslateService _libreTranslateService;
private readonly string _autoStartupTaskId = "AutoStartup";
public SettingsPageViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService, IPlaybackService playbackService, ILibreTranslateService libreTranslateService) : base(settingsService)
{
_libWatcherService = libWatcherService;
_playbackService = playbackService;
_libreTranslateService = libreTranslateService;
LibreTranslateServer = _settingsService.LibreTranslateServer;
SelectedTargetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
Language = _settingsService.Language;
CoverImageRadius = _settingsService.CoverImageRadius;
AutoStartWindowType = _settingsService.AutoStartWindowType;
AutoLockOnDesktopMode = _settingsService.AutoLockOnDesktopMode;
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
LyricsAlignmentType = _settingsService.LyricsAlignmentType;
SongInfoAlignmentType = _settingsService.SongInfoAlignmentType;
LyricsFontWeight = _settingsService.LyricsFontWeight;
LyricsBlurAmount = _settingsService.LyricsBlurAmount;
LyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
LyricsLineSpacingFactor = _settingsService.LyricsLineSpacingFactor;
LyricsFontSize = _settingsService.LyricsFontSize;
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
IsFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
LyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
LyricsFgFontColorType = _settingsService.LyricsFgFontColorType;
LyricsStrokeFontColorType = _settingsService.LyricsStrokeFontColorType;
LyricsCustomBgFontColor = _settingsService.LyricsCustomBgFontColor;
LyricsCustomFgFontColor = _settingsService.LyricsCustomFgFontColor;
LyricsCustomStrokeFontColor = _settingsService.LyricsCustomStrokeFontColor;
LyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
LyricsBackgroundTheme = _settingsService.LyricsBackgroundTheme;
MediaSourceProvidersInfo = [.. _settingsService.MediaSourceProvidersInfo];
IgnoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
_playbackService.MediaSourceProvidersInfoChanged += PlaybackService_SessionIdsChanged;
Task.Run(async () =>
{
BuildDate = (await Helper.AppInfo.GetBuildDate()).ToString("(yyyy/MM/dd HH:mm:ss)");
});
}
private void PlaybackService_SessionIdsChanged(object? sender, Events.MediaSourceProvidersInfoEventArgs e)
{
MediaSourceProvidersInfo = [.. e.MediaSourceProviersInfo];
}
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ElementTheme LyricsBackgroundTheme { get; set; }
[ObservableProperty]
public partial AutoStartWindowType AutoStartWindowType { get; set; }
[ObservableProperty]
public partial bool AutoLockOnDesktopMode { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverImageRadius { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayBlurAmount { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayOpacity { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDebugOverlayEnabled { get; set; } = false;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsLogEnabled { get; set; } = false;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDynamicCoverOverlayEnabled { get; set; }
[ObservableProperty]
public partial Enums.Language Language { get; set; }
[ObservableProperty]
public partial ObservableCollection<LocalLyricsFolder> LocalLyricsFolders { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
[ObservableProperty]
public partial ObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsFanLyricsEnabled { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsLyricsGlowEffectEnabled { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial TextAlignmentType LyricsAlignmentType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial TextAlignmentType SongInfoAlignmentType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int LyricsBlurAmount { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial Color LyricsCustomBgFontColor { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial Color LyricsCustomFgFontColor { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial Color LyricsCustomStrokeFontColor { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontColorType LyricsBgFontColorType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontColorType LyricsFgFontColorType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontColorType LyricsStrokeFontColorType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int LyricsFontSize { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontWeight LyricsFontWeight { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LineRenderingType LyricsGlowEffectScope { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial float LyricsLineSpacingFactor { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int LyricsVerticalEdgeOpacity { get; set; }
[ObservableProperty]
public partial object NavViewSelectedItemTag { get; set; }
public string Version { get; set; } = Helper.AppInfo.AppVersion;
public string BuildDate { get; set; } = string.Empty;
[ObservableProperty]
public partial string LibreTranslateServer { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int SelectedTargetLanguageIndex { get; set; } = 0;
[ObservableProperty]
public partial bool IsLibreTranslateServerTesting { get; set; } = false;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int LyricsFontStrokeWidth { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IgnoreFullscreenWindow { get; set; }
partial void OnLyricsBackgroundThemeChanged(ElementTheme value)
{
_settingsService.LyricsBackgroundTheme = value;
}
partial void OnLyricsFontStrokeWidthChanged(int value)
{
_settingsService.LyricsFontStrokeWidth = value;
}
public void OnLyricsSearchProvidersReordered()
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
Broadcast(
LyricsSearchProvidersInfo,
LyricsSearchProvidersInfo,
nameof(LyricsSearchProvidersInfo)
);
}
public void OpenMusicFolder(LocalLyricsFolder folder)
{
OpenFolderInFileExplorer(folder.Path);
}
public void RemoveFolderAsync(LocalLyricsFolder folder)
{
LocalLyricsFolders.Remove(folder);
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
{
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
Broadcast(
LyricsSearchProvidersInfo,
LyricsSearchProvidersInfo,
nameof(LyricsSearchProvidersInfo)
);
}
public void ToggleMediaSourceProvider(MediaSourceProviderInfo providerInfo)
{
Broadcast(
MediaSourceProvidersInfo,
MediaSourceProvidersInfo,
nameof(MediaSourceProvidersInfo)
);
}
private void AddFolderAsync(string path)
{
var normalizedPath = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
if (LocalLyricsFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"));
}
else if (LocalLyricsFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
{
// 添加的文件夹是现有文件夹的子文件夹
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo"));
}
else if (LocalLyricsFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
)
{
// 添加的文件夹是现有文件夹的父文件夹
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo"));
}
else
{
LocalLyricsFolders.Add(new LocalLyricsFolder(path, true));
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
}
[RelayCommand]
private async Task LaunchProjectGitHubPageAsync()
{
await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl));
}
[RelayCommand]
private void OpenCacheFolder()
{
OpenFolderInFileExplorer(Helper.AppInfo.CacheFolder);
}
private void OpenFolderInFileExplorer(string path)
{
Process.Start(
new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = path,
UseShellExecute = true,
}
);
}
[RelayCommand]
private void PlayTestingMusicTask()
{
WindowHelper.OpenOrShowWindow<LyricsWindow>();
}
[RelayCommand]
private void RestartApp()
{
WindowHelper.RestartApp();
}
[RelayCommand]
private async Task SelectAndAddFolderAsync(UIElement sender)
{
var window = WindowHelper.GetWindowByWindowType<SettingsWindow>();
if (window == null) return;
var picker = new Windows.Storage.Pickers.FolderPicker();
picker.FileTypeFilter.Add("*");
var hwnd = WindowNative.GetWindowHandle(window);
InitializeWithWindow.Initialize(picker, hwnd);
var folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
AddFolderAsync(folder.Path);
}
}
[RelayCommand]
private void LibreTranslateServerTest()
{
IsLibreTranslateServerTesting = true;
Task.Run(async () =>
{
try
{
string result = await _libreTranslateService.TranslateAsync("Hello, world!", null);
_dispatcherQueue.TryEnqueue(() =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
IsLibreTranslateServerTesting = false;
});
}
catch (Exception)
{
_dispatcherQueue.TryEnqueue(() =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestFailedInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
IsLibreTranslateServerTesting = false;
});
}
});
}
public async Task<bool> ToggleAutoStartupAsync(bool target)
{
StartupTask startupTask = await StartupTask.GetAsync(_autoStartupTaskId);
if (target)
{
await startupTask.RequestEnableAsync();
}
else
{
startupTask.Disable();
}
return await DetectIsAutoStartupEnabledAsync();
}
public async Task<bool> DetectIsAutoStartupEnabledAsync()
{
bool result = false;
var startupTask = await StartupTask.GetAsync(_autoStartupTaskId);
switch (startupTask.State)
{
case StartupTaskState.Disabled:
case StartupTaskState.DisabledByUser:
case StartupTaskState.DisabledByPolicy:
result = false;
break;
case StartupTaskState.Enabled:
result = true;
break;
}
return result;
}
partial void OnIgnoreFullscreenWindowChanged(bool value)
{
_settingsService.IgnoreFullscreenWindow = value;
}
partial void OnSelectedTargetLanguageIndexChanged(int value)
{
_settingsService.SelectedTargetLanguageIndex = value;
}
partial void OnLibreTranslateServerChanged(string value)
{
_settingsService.LibreTranslateServer = value;
}
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
{
_settingsService.AutoStartWindowType = value;
}
partial void OnAutoLockOnDesktopModeChanged(bool value)
{
_settingsService.AutoLockOnDesktopMode = value;
}
partial void OnCoverImageRadiusChanged(int value)
{
_settingsService.CoverImageRadius = value;
}
partial void OnCoverOverlayBlurAmountChanged(int value)
{
_settingsService.CoverOverlayBlurAmount = value;
}
partial void OnCoverOverlayOpacityChanged(int value)
{
_settingsService.CoverOverlayOpacity = value;
}
partial void OnIsDynamicCoverOverlayEnabledChanged(bool value)
{
_settingsService.IsDynamicCoverOverlayEnabled = value;
}
partial void OnLanguageChanged(Enums.Language value)
{
switch (value)
{
case Enums.Language.FollowSystem:
ApplicationLanguages.PrimaryLanguageOverride = "";
break;
case Enums.Language.English:
ApplicationLanguages.PrimaryLanguageOverride = "en-US";
break;
case Enums.Language.SimplifiedChinese:
ApplicationLanguages.PrimaryLanguageOverride = "zh-CN";
break;
case Enums.Language.TraditionalChinese:
ApplicationLanguages.PrimaryLanguageOverride = "zh-TW";
break;
case Enums.Language.Japanese:
ApplicationLanguages.PrimaryLanguageOverride = "ja-JP";
break;
case Enums.Language.Korean:
ApplicationLanguages.PrimaryLanguageOverride = "ko-KR";
break;
default:
break;
}
_settingsService.Language = Language;
}
partial void OnIsFanLyricsEnabledChanged(bool value)
{
_settingsService.IsFanLyricsEnabled = value;
}
partial void OnIsLyricsGlowEffectEnabledChanged(bool value)
{
_settingsService.IsLyricsGlowEffectEnabled = value;
}
partial void OnLyricsAlignmentTypeChanged(TextAlignmentType value)
{
_settingsService.LyricsAlignmentType = value;
}
partial void OnSongInfoAlignmentTypeChanged(TextAlignmentType value)
{
_settingsService.SongInfoAlignmentType = value;
}
partial void OnLyricsBlurAmountChanged(int value)
{
_settingsService.LyricsBlurAmount = value;
}
partial void OnLyricsCustomBgFontColorChanged(Color value)
{
_settingsService.LyricsCustomBgFontColor = value;
}
partial void OnLyricsCustomFgFontColorChanged(Color value)
{
_settingsService.LyricsCustomFgFontColor = value;
}
partial void OnLyricsCustomStrokeFontColorChanged(Color value)
{
_settingsService.LyricsCustomStrokeFontColor = value;
}
partial void OnLyricsBgFontColorTypeChanged(LyricsFontColorType value)
{
_settingsService.LyricsBgFontColorType = value;
}
partial void OnLyricsFgFontColorTypeChanged(LyricsFontColorType value)
{
_settingsService.LyricsFgFontColorType = value;
}
partial void OnLyricsStrokeFontColorTypeChanged(LyricsFontColorType value)
{
_settingsService.LyricsStrokeFontColorType = value;
}
partial void OnLyricsFontSizeChanged(int value)
{
_settingsService.LyricsFontSize = value;
}
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
{
_settingsService.LyricsFontWeight = value;
}
partial void OnLyricsGlowEffectScopeChanged(LineRenderingType value)
{
_settingsService.LyricsGlowEffectScope = value;
}
partial void OnLyricsLineSpacingFactorChanged(float value)
{
_settingsService.LyricsLineSpacingFactor = value;
}
partial void OnLyricsVerticalEdgeOpacityChanged(int value)
{
_settingsService.LyricsVerticalEdgeOpacity = value;
}
}
}

View File

@@ -1,548 +0,0 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Core;
using Windows.Globalization;
using Windows.Media;
using Windows.Media.Playback;
using Windows.System;
using WinRT.Interop;
namespace BetterLyrics.WinUI3.ViewModels
{
/// <summary>
/// Defines the <see cref="SettingsViewModel" />
/// </summary>
public partial class SettingsViewModel : ObservableRecipient
{
#region Fields
/// <summary>
/// Defines the _libWatcherService
/// </summary>
private readonly ILibWatcherService _libWatcherService;
/// <summary>
/// Defines the _mediaPlayer
/// </summary>
private readonly MediaPlayer _mediaPlayer = new();
/// <summary>
/// Defines the _playbackService
/// </summary>
private readonly IPlaybackService _playbackService;
/// <summary>
/// Defines the _settingsService
/// </summary>
private readonly ISettingsService _settingsService;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SettingsViewModel"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
/// <param name="libWatcherService">The libWatcherService<see cref="ILibWatcherService"/></param>
/// <param name="playbackService">The playbackService<see cref="IPlaybackService"/></param>
public SettingsViewModel(
ISettingsService settingsService,
ILibWatcherService libWatcherService,
IPlaybackService playbackService
)
{
_settingsService = settingsService;
_libWatcherService = libWatcherService;
_playbackService = playbackService;
RootGridMargin = new Thickness(0, _settingsService.TitleBarType.GetHeight(), 0, 0);
LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
Language = _settingsService.Language;
CoverImageRadius = _settingsService.CoverImageRadius;
ThemeType = _settingsService.ThemeType;
BackdropType = _settingsService.BackdropType;
TitleBarType = _settingsService.TitleBarType;
AutoStartWindowType = _settingsService.AutoStartWindowType;
IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the AutoStartWindowType
/// </summary>
[ObservableProperty]
public partial AutoStartWindowType AutoStartWindowType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDebugOverlayEnabled { get; set; } = false;
/// <summary>
/// Gets or sets the BackdropType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial BackdropType BackdropType { get; set; }
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverImageRadius { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayBlurAmount
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayBlurAmount { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayOpacity
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayOpacity { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDynamicCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets the Language
/// </summary>
[ObservableProperty]
public partial Enums.Language Language { get; set; }
/// <summary>
/// Gets or sets the LocalLyricsFolders
/// </summary>
[ObservableProperty]
public partial ObservableCollection<LocalLyricsFolder> LocalLyricsFolders { get; set; }
/// <summary>
/// Gets or sets the LyricsSearchProvidersInfo
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
/// <summary>
/// Gets or sets the NavViewSelectedItemTag
/// </summary>
[ObservableProperty]
public partial object NavViewSelectedItemTag { get; set; } = "LyricsLib";
/// <summary>
/// Gets or sets the RootGridMargin
/// </summary>
[ObservableProperty]
public partial Thickness RootGridMargin { get; set; } = new(0, 0, 0, 0);
/// <summary>
/// Gets or sets the ThemeType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ElementTheme ThemeType { get; set; }
/// <summary>
/// Gets or sets the TitleBarType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial TitleBarType TitleBarType { get; set; }
/// <summary>
/// Gets or sets the Version
/// </summary>
public string Version { get; set; } = Helper.AppInfo.AppVersion;
#endregion
#region Methods
/// <summary>
/// The OnLyricsSearchProvidersReordered
/// </summary>
public void OnLyricsSearchProvidersReordered()
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
Broadcast(
LyricsSearchProvidersInfo,
LyricsSearchProvidersInfo,
nameof(LyricsSearchProvidersInfo)
);
}
/// <summary>
/// The OpenMusicFolder
/// </summary>
/// <param name="folder">The folder<see cref="LocalLyricsFolder"/></param>
public void OpenMusicFolder(LocalLyricsFolder folder)
{
OpenFolderInFileExplorer(folder.Path);
}
/// <summary>
/// The RemoveFolderAsync
/// </summary>
/// <param name="folder">The folder<see cref="LocalLyricsFolder"/></param>
public void RemoveFolderAsync(LocalLyricsFolder folder)
{
LocalLyricsFolders.Remove(folder);
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
/// <summary>
/// The ToggleLocalLyricsFolder
/// </summary>
/// <param name="folder">The folder<see cref="LocalLyricsFolder"/></param>
public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
{
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
/// <summary>
/// The ToggleLyricsSearchProvider
/// </summary>
/// <param name="providerInfo">The providerInfo<see cref="LyricsSearchProviderInfo"/></param>
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
Broadcast(
LyricsSearchProvidersInfo,
LyricsSearchProvidersInfo,
nameof(LyricsSearchProvidersInfo)
);
}
/// <summary>
/// The AddFolderAsync
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
private void AddFolderAsync(string path)
{
var normalizedPath =
Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
if (
LocalLyricsFolders.Any(x =>
Path.GetFullPath(x.Path)
.TrimEnd(Path.DirectorySeparatorChar)
.Equals(
normalizedPath.TrimEnd(Path.DirectorySeparatorChar),
StringComparison.OrdinalIgnoreCase
)
)
)
{
WeakReferenceMessenger.Default.Send(
new ShowNotificatonMessage(
new Notification(
App.ResourceLoader!.GetString("SettingsPagePathExistedInfo")
)
)
);
}
else if (
LocalLyricsFolders.Any(item =>
normalizedPath.StartsWith(
Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar)
+ Path.DirectorySeparatorChar,
StringComparison.OrdinalIgnoreCase
)
)
)
{
// 添加的文件夹是现有文件夹的子文件夹
WeakReferenceMessenger.Default.Send(
new ShowNotificatonMessage(
new Notification(
App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo")
)
)
);
}
else if (
LocalLyricsFolders.Any(item =>
Path.GetFullPath(item.Path)
.TrimEnd(Path.DirectorySeparatorChar)
.StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase)
)
)
{
// 添加的文件夹是现有文件夹的父文件夹
WeakReferenceMessenger.Default.Send(
new ShowNotificatonMessage(
new Notification(
App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo")
)
)
);
}
else
{
LocalLyricsFolders.Add(new LocalLyricsFolder(path, true));
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
}
/// <summary>
/// The LaunchProjectGitHubPageAsync
/// </summary>
/// <returns>The <see cref="Task"/></returns>
[RelayCommand]
private async Task LaunchProjectGitHubPageAsync()
{
await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl));
}
/// <summary>
/// The OpenFolderInFileExplorer
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
private void OpenFolderInFileExplorer(string path)
{
Process.Start(
new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = path,
UseShellExecute = true,
}
);
}
/// <summary>
/// The OpenLogFolder
/// </summary>
[RelayCommand]
private void OpenCacheFolder()
{
OpenFolderInFileExplorer(Helper.AppInfo.CacheFolder);
}
/// <summary>
/// The PlayTestingMusicTask
/// </summary>
[RelayCommand]
private void PlayTestingMusicTask()
{
AddFolderAsync(Helper.AppInfo.AssetsFolder);
_mediaPlayer.SetUriSource(new Uri(Helper.AppInfo.TestMusicPath));
_mediaPlayer.Play();
}
/// <summary>
/// The RestartApp
/// </summary>
[RelayCommand]
private void RestartApp()
{
// The restart will be executed immediately.
AppRestartFailureReason failureReason =
Microsoft.Windows.AppLifecycle.AppInstance.Restart("");
// If the restart fails, handle it here.
switch (failureReason)
{
case AppRestartFailureReason.RestartPending:
break;
case AppRestartFailureReason.NotInForeground:
break;
case AppRestartFailureReason.InvalidUser:
break;
default: //AppRestartFailureReason.Other
break;
}
}
/// <summary>
/// The SelectAndAddFolderAsync
/// </summary>
/// <param name="sender">The sender<see cref="UIElement"/></param>
/// <returns>The <see cref="Task"/></returns>
[RelayCommand]
private async Task SelectAndAddFolderAsync(UIElement sender)
{
var picker = new Windows.Storage.Pickers.FolderPicker();
picker.FileTypeFilter.Add("*");
var hwnd = WindowNative.GetWindowHandle(WindowHelper.GetWindowForElement(sender));
InitializeWithWindow.Initialize(picker, hwnd);
var folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
AddFolderAsync(folder.Path);
}
}
/// <summary>
/// The OnAutoStartWindowTypeChanged
/// </summary>
/// <param name="value">The value<see cref="AutoStartWindowType"/></param>
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
{
_settingsService.AutoStartWindowType = value;
}
/// <summary>
/// The OnBackdropTypeChanged
/// </summary>
/// <param name="value">The value<see cref="BackdropType"/></param>
partial void OnBackdropTypeChanged(BackdropType value)
{
_settingsService.BackdropType = value;
}
/// <summary>
/// The OnCoverImageRadiusChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverImageRadiusChanged(int value)
{
_settingsService.CoverImageRadius = value;
}
/// <summary>
/// The OnCoverOverlayBlurAmountChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverOverlayBlurAmountChanged(int value)
{
_settingsService.CoverOverlayBlurAmount = value;
}
/// <summary>
/// The OnCoverOverlayOpacityChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverOverlayOpacityChanged(int value)
{
_settingsService.CoverOverlayOpacity = value;
}
/// <summary>
/// The OnIsCoverOverlayEnabledChanged
/// </summary>
/// <param name="value">The value<see cref="bool"/></param>
partial void OnIsCoverOverlayEnabledChanged(bool value)
{
_settingsService.IsCoverOverlayEnabled = value;
}
/// <summary>
/// The OnIsDynamicCoverOverlayEnabledChanged
/// </summary>
/// <param name="value">The value<see cref="bool"/></param>
partial void OnIsDynamicCoverOverlayEnabledChanged(bool value)
{
_settingsService.IsDynamicCoverOverlayEnabled = value;
}
/// <summary>
/// The OnLanguageChanged
/// </summary>
/// <param name="value">The value<see cref="Enums.Language"/></param>
partial void OnLanguageChanged(Enums.Language value)
{
switch (value)
{
case Enums.Language.FollowSystem:
ApplicationLanguages.PrimaryLanguageOverride = "";
break;
case Enums.Language.English:
ApplicationLanguages.PrimaryLanguageOverride = "en-US";
break;
case Enums.Language.SimplifiedChinese:
ApplicationLanguages.PrimaryLanguageOverride = "zh-CN";
break;
case Enums.Language.TraditionalChinese:
ApplicationLanguages.PrimaryLanguageOverride = "zh-TW";
break;
case Enums.Language.Japanese:
ApplicationLanguages.PrimaryLanguageOverride = "ja-JP";
break;
case Enums.Language.Korean:
ApplicationLanguages.PrimaryLanguageOverride = "ko-KR";
break;
default:
break;
}
_settingsService.Language = Language;
}
/// <summary>
/// The OnThemeTypeChanged
/// </summary>
/// <param name="value">The value<see cref="ElementTheme"/></param>
partial void OnThemeTypeChanged(ElementTheme value)
{
_settingsService.ThemeType = value;
}
/// <summary>
/// The OnTitleBarTypeChanged
/// </summary>
/// <param name="value">The value<see cref="TitleBarType"/></param>
partial void OnTitleBarTypeChanged(TitleBarType value)
{
_settingsService.TitleBarType = value;
RootGridMargin = new Thickness(0, value.GetHeight(), 0, 0);
}
#endregion
}
}

View File

@@ -0,0 +1,9 @@
using BetterLyrics.WinUI3.Services;
namespace BetterLyrics.WinUI3.ViewModels
{
public class SettingsWindowViewModel : BaseWindowViewModel
{
public SettingsWindowViewModel(ISettingsService settingsService) : base(settingsService) { }
}
}

View File

@@ -1,63 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class SystemTrayViewModel
: BaseViewModel,
IRecipient<PropertyChangedMessage<bool>>
public partial class SystemTrayViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<bool>>
{
public SystemTrayViewModel(ISettingsService settingsService) : base(settingsService) { }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsLyricsWindowLocked { get; set; } = false;
[ObservableProperty]
public partial string ToolTipText { get; set; } = AppInfo.AppName;
[ObservableProperty]
public partial bool IsLyricsWindowLocked { get; set; } = false;
public SystemTrayViewModel(ISettingsService settingsService)
: base(settingsService) { }
[RelayCommand]
private void OpenSettings()
public void Receive(PropertyChangedMessage<bool> message)
{
// 打开设置窗口
WindowHelper.OpenSettingsWindow();
if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(LyricsWindowViewModel.IsLyricsWindowLocked))
{
if (IsLyricsWindowLocked != message.NewValue)
{
IsLyricsWindowLocked = message.NewValue;
}
}
}
}
[RelayCommand]
private void ExitApp()
{
// 退出应用程序
App.Current.Exit();
WindowHelper.ExitAllWindows();
}
[RelayCommand]
private void OpenSettings()
{
// 打开设置窗口
WindowHelper.OpenOrShowWindow<SettingsWindow>();
}
[RelayCommand]
private void UnlockWindow()
{
var window = WindowHelper.GetWindowByFramePageType(typeof(LyricsPage));
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
DesktopModeHelper.Unlock(window);
IsLyricsWindowLocked = false;
}
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is HostWindowViewModel)
{
if (message.PropertyName == nameof(HostWindowViewModel.IsLyricsWindowLocked))
{
IsLyricsWindowLocked = message.NewValue;
}
}
}
}
}

View File

@@ -1,219 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Window
x:Class="BetterLyrics.WinUI3.Views.HostWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:media="using:CommunityToolkit.WinUI.Media"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid
x:Name="RootGrid"
PointerMoved="RootGrid_PointerMoved"
RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
<Frame
x:Name="RootFrame"
Navigated="RootFrame_Navigated"
NavigationFailed="RootFrame_NavigationFailed" />
<Grid
x:Name="TopCommandGrid"
Height="{x:Bind ViewModel.TitleBarHeight, Mode=OneWay}"
VerticalAlignment="Top"
Background="Transparent"
Opacity="0"
PointerMoved="TopCommandGrid_PointerMoved">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
<ImageIcon
x:Name="AppLogoImageIcon"
Height="{x:Bind ViewModel.AppLogoImageIconHeight, Mode=OneWay}"
Margin="16,0"
Source="ms-appx:///Assets/Logo.png" />
<TextBlock
x:Name="AppTitleTextBlock"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
FontWeight="SemiBold"
Text="{x:Bind Title, Mode=OneWay}" />
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button
x:Name="ClickThroughButton"
x:Uid="HostWindowClickThroughButton"
Command="{x:Bind ViewModel.LockWindowCommand}"
Style="{StaticResource TitleBarButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="LockToolTip" x:Uid="HostWindowLockToolTip" />
</ToolTipService.ToolTip>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction
PropertyName="IsOpen"
TargetObject="{x:Bind LockToolTip}"
Value="True" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction
PropertyName="IsOpen"
TargetObject="{x:Bind LockToolTip}"
Value="False" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button x:Name="MoreButton" Style="{StaticResource TitleBarButtonStyle}">
<Grid>
<FontIcon
Margin="0,0,0,8"
FontFamily="Segoe Fluent Icons"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
FontWeight="ExtraBold"
Glyph="&#xEF2D;" />
<FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
FontWeight="ExtraBold"
Glyph="&#xEF2D;" />
<FontIcon
Margin="0,8,0,0"
FontFamily="Segoe Fluent Icons"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
FontWeight="ExtraBold"
Glyph="&#xEF2D;" />
</Grid>
<Button.Flyout>
<MenuFlyout>
<ToggleMenuFlyoutItem
x:Name="AOTFlyoutItem"
x:Uid="BaseWindowAOTFlyoutItem"
Click="AOTFlyoutItem_Click" />
<ToggleMenuFlyoutItem
x:Name="FullScreenFlyoutItem"
x:Uid="BaseWindowFullScreenFlyoutItem"
Click="FullScreenFlyoutItem_Click" />
<ToggleMenuFlyoutItem
x:Name="DockFlyoutItem"
x:Uid="HostWindowDockFlyoutItem"
Command="{x:Bind ViewModel.ToggleDockModeCommand}"
IsChecked="{x:Bind ViewModel.IsDockMode, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="DesktopFlyoutItem"
x:Uid="HostWindowDesktopFlyoutItem"
Command="{x:Bind ViewModel.ToggleDesktopModeCommand}"
IsChecked="{x:Bind ViewModel.IsDesktopMode, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="MiniFlyoutItem"
x:Uid="BaseWindowMiniFlyoutItem"
Click="MiniFlyoutItem_Click" />
<MenuFlyoutItem
x:Name="SettingsFlyoutItem"
x:Uid="HostWindowSettingsFlyoutItem"
Click="SettingsMenuFlyoutItem_Click" />
</MenuFlyout>
</Button.Flyout>
</Button>
<!-- Window Minimise -->
<Button
x:Name="MinimiseButton"
Click="MinimiseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xEF2D;" />
</Button>
<!-- Window Maximise -->
<Button
x:Name="MaximiseButton"
Click="MaximiseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xEF2E;" />
</Button>
<!-- Window Restore -->
<Button
x:Name="RestoreButton"
Click="RestoreButton_Click"
Style="{StaticResource TitleBarButtonStyle}"
Visibility="Collapsed">
<FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xEF2F;" />
</Button>
<!-- Window Close -->
<Button
x:Name="CloseButton"
Click="CloseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="Segoe Fluent Icons"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xEF2C;" />
</Button>
</StackPanel>
</Grid>
<InfoBar
x:Name="HostInfoBar"
Margin="36"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Background="{ThemeResource SystemFillColorSolidAttentionBackgroundBrush}"
IsClosable="False"
IsOpen="{x:Bind ViewModel.ShowInfoBar, Mode=OneWay}"
Message="{x:Bind ViewModel.Notification.Message, Mode=OneWay}"
Opacity="0"
Severity="{x:Bind ViewModel.Notification.Severity, Mode=OneWay}">
<InfoBar.RenderTransform>
<TranslateTransform x:Name="HostInfoBarTransform" Y="20" />
</InfoBar.RenderTransform>
<InfoBar.Resources>
<Storyboard x:Key="InfoBarShowAndHideStoryboard">
<!-- Opacity -->
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="HostInfoBar" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.6" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.9" Value="0" />
</DoubleAnimationUsingKeyFrames>
<!-- Y -->
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="HostInfoBarTransform" Storyboard.TargetProperty="Y">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="20" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:3.6" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:3.9" Value="20" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</InfoBar.Resources>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{Binding ElementName=HostInfoBar, Path=IsOpen, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource InfoBarShowAndHideStoryboard}" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</InfoBar>
</Grid>
</Window>

Some files were not shown because too many files have changed in this diff Show More