mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e74468194 | ||
|
|
ff65429b16 | ||
|
|
ab03870b6a | ||
|
|
23bafc4d75 | ||
|
|
3bdce0d975 | ||
|
|
454edbeaba | ||
|
|
1e7e63032a | ||
|
|
a93b535667 | ||
|
|
0eca011054 | ||
|
|
68b7601b0f | ||
|
|
894fe935a5 | ||
|
|
0befdf48dd | ||
|
|
827602766d | ||
|
|
11c3002b77 | ||
|
|
9d193b7b71 | ||
|
|
811cd760d4 | ||
|
|
fe5039db78 | ||
|
|
a42a3cdb88 | ||
|
|
ee003e1764 | ||
|
|
749ab2ca1a | ||
|
|
4bbde71bfa | ||
|
|
7f3fbda237 | ||
|
|
06d2e19ee2 | ||
|
|
cbcb140bec | ||
|
|
bc8fd4de31 | ||
|
|
9e8bc3b7df | ||
|
|
f9070eed5d | ||
|
|
447533db12 | ||
|
|
1fe8675743 | ||
|
|
6775f9af57 | ||
|
|
bf9107754d | ||
|
|
906d8d7d49 | ||
|
|
517e026ca9 | ||
|
|
9535306a92 | ||
|
|
222ac42357 | ||
|
|
2c55b11e70 | ||
|
|
703fc26ceb | ||
|
|
52ae59154a | ||
|
|
82f22ac5d1 | ||
|
|
f0f74ba195 | ||
|
|
7bca1d1205 | ||
|
|
8e5e35ec23 | ||
|
|
0a63b6d8cd | ||
|
|
8b01e47c8e | ||
|
|
66d8705066 | ||
|
|
aa1f9f071f | ||
|
|
81c2f45f95 | ||
|
|
d5ae75e5a4 | ||
|
|
3ca2b90eff | ||
|
|
d1e48e95b7 | ||
|
|
12b78b374c | ||
|
|
cd857a2807 | ||
|
|
def2c9820a | ||
|
|
63c0577e73 | ||
|
|
e028ec2f0f | ||
|
|
4258ab6957 | ||
|
|
894081b097 | ||
|
|
772f41b236 | ||
|
|
e6db08c593 | ||
|
|
ea4a4ad072 | ||
|
|
b4bd479d3c | ||
|
|
9745b7e558 | ||
|
|
eb666fd8f2 | ||
|
|
2404c54bb6 | ||
|
|
221cd67c39 | ||
|
|
7c311972b5 | ||
|
|
9b42aebb56 | ||
|
|
e96320c9ad | ||
|
|
fdafb96852 | ||
|
|
cf5e4a7e8c | ||
|
|
4b4651bf6e | ||
|
|
a3e7503537 | ||
|
|
30fab426df | ||
|
|
7746a04bd9 | ||
|
|
a437f382cb | ||
|
|
852741bbfc | ||
|
|
7d839c655f | ||
|
|
ba8aad9831 | ||
|
|
2970b5e246 | ||
|
|
80a68b2612 | ||
|
|
088d2fa78b | ||
|
|
a4bc63d352 | ||
|
|
e8c428614a | ||
|
|
4f336282bb | ||
|
|
74daa48536 | ||
|
|
3dc14e52d8 | ||
|
|
1a736c13d5 | ||
|
|
377d68d83c | ||
|
|
f512e686b0 | ||
|
|
004dcbb4f4 | ||
|
|
1bfad8740c | ||
|
|
5b71f44bf3 | ||
|
|
81651abfec |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -404,4 +404,5 @@ FodyWeavers.xsd
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
*.sln.iml
|
||||
/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package)_TemporaryKey.pfx
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.2.0" />
|
||||
Version="1.0.6.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
<Resource Language="en-US"/>
|
||||
<Resource Language="zh-CN"/>
|
||||
<Resource Language="zh-TW"/>
|
||||
<Resource Language="ja-JP"/>
|
||||
<Resource Language="ko-KR"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converter="using:BetterLyrics.WinUI3.Converter"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:local="using:BetterLyrics.WinUI3">
|
||||
xmlns:local="using:BetterLyrics.WinUI3"
|
||||
xmlns:media="using:CommunityToolkit.WinUI.Media">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@@ -40,8 +41,12 @@
|
||||
<ExponentialEase x:Key="EaseIn" EasingMode="EaseIn" />
|
||||
|
||||
<!-- Converter -->
|
||||
<converter:ThemeTypeToElementThemeConverter x:Key="ThemeTypeToElementThemeConverter" />
|
||||
<converter:EnumToIntConverter x:Key="EnumToIntConverter" />
|
||||
<converter:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
||||
<converter:MatchedLocalFilesPathToVisibilityConverter x:Key="MatchedLocalFilesPathToVisibilityConverter" />
|
||||
<converter:IntToCornerRadius x:Key="IntToCornerRadius" />
|
||||
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
|
||||
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Database;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using System.Threading.Tasks;
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
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;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Windows.ApplicationModel.Core;
|
||||
|
||||
// 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.
|
||||
@@ -25,24 +25,23 @@ using Windows.ApplicationModel.Core;
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// Provides application-specific behavior to supplement the default Application class
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
private readonly ILogger<App> _logger;
|
||||
|
||||
public static new App Current => (App)Application.Current;
|
||||
public BaseWindow? MainWindow { get; private set; }
|
||||
public BaseWindow? SettingsWindow { get; set; }
|
||||
|
||||
public static ResourceLoader? ResourceLoader { get; private set; }
|
||||
|
||||
public static DispatcherQueue? DispatcherQueue { get; private set; }
|
||||
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// Defines the _logger
|
||||
/// </summary>
|
||||
private readonly ILogger<App> _logger;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="App"/> class.
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
@@ -52,19 +51,63 @@ namespace BetterLyrics.WinUI3
|
||||
DispatcherQueueTimer = DispatcherQueue.CreateTimer();
|
||||
ResourceLoader = new ResourceLoader();
|
||||
|
||||
Helper.AppInfo.EnsureDirectories();
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
AppInfo.EnsureDirectories();
|
||||
ConfigureServices();
|
||||
|
||||
_logger = Ioc.Default.GetService<ILogger<App>>()!;
|
||||
|
||||
UnhandledException += App_UnhandledException;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ConfigureServices
|
||||
/// </summary>
|
||||
private static void ConfigureServices()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.File(Helper.AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.WriteTo.File(AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
// Register services
|
||||
@@ -76,16 +119,26 @@ namespace BetterLyrics.WinUI3
|
||||
loggingBuilder.AddSerilog();
|
||||
})
|
||||
// Services
|
||||
.AddSingleton<SettingsService>()
|
||||
.AddSingleton<DatabaseService>()
|
||||
.AddSingleton<ISettingsService, SettingsService>()
|
||||
.AddSingleton<IPlaybackService, PlaybackService>()
|
||||
.AddSingleton<IMusicSearchService, MusicSearchService>()
|
||||
.AddSingleton<ILibWatcherService, LibWatcherService>()
|
||||
// ViewModels
|
||||
.AddSingleton<BaseWindowModel>()
|
||||
.AddSingleton<MainViewModel>()
|
||||
.AddTransient<HostWindowViewModel>()
|
||||
.AddSingleton<SystemTrayViewModel>()
|
||||
.AddSingleton<SettingsViewModel>()
|
||||
.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
|
||||
@@ -96,17 +149,44 @@ namespace BetterLyrics.WinUI3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched.
|
||||
/// The CurrentDomain_FirstChanceException
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
/// <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
|
||||
)
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
// Activate the window
|
||||
MainWindow = new BaseWindow();
|
||||
MainWindow!.Navigate(typeof(MainPage));
|
||||
MainWindow.Activate();
|
||||
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
}
|
||||
|
||||
/// <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
|
||||
)
|
||||
{
|
||||
_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
|
||||
)
|
||||
{
|
||||
//_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.ico
Normal file
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 201 KiB |
@@ -8,7 +8,20 @@
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="ViewModels\Lyrics\**" />
|
||||
<Content Remove="ViewModels\Lyrics\**" />
|
||||
<EmbeddedResource Remove="ViewModels\Lyrics\**" />
|
||||
<None Remove="ViewModels\Lyrics\**" />
|
||||
<Page Remove="ViewModels\Lyrics\**" />
|
||||
<PRIResource Remove="ViewModels\Lyrics\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Controls\DependenciesSettingsExpander.xaml" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Logo.ico" />
|
||||
</ItemGroup>
|
||||
@@ -17,41 +30,62 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference
|
||||
Include="CommunityToolkit.Labs.WinUI.OpacityMaskView"
|
||||
Version="0.1.250513-build.2126"
|
||||
/>
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference
|
||||
Include="CommunityToolkit.WinUI.Controls.SettingsControls"
|
||||
Version="8.2.250402"
|
||||
/>
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<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="DevWinUI" Version="8.3.0" />
|
||||
<PackageReference Include="DevWinUI.Controls" Version="8.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.5" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
|
||||
<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.250513003" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.5" />
|
||||
<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="z440.atl.core" Version="6.24.0" />
|
||||
<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>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\InAppLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\DesktopLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!--Disable Trimming for Specific Packages-->
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\SystemTray.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\DependenciesSettingsExpander.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
@@ -61,8 +95,21 @@
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Logo.ico</ApplicationIcon>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ShouldCreateLogs>True</ShouldCreateLogs>
|
||||
<AdvancedSettingsExpanded>True</AdvancedSettingsExpanded>
|
||||
<UpdateAssemblyVersion>False</UpdateAssemblyVersion>
|
||||
<UpdateAssemblyFileVersion>False</UpdateAssemblyFileVersion>
|
||||
<UpdateAssemblyInfoVersion>False</UpdateAssemblyInfoVersion>
|
||||
<UpdatePackageVersion>True</UpdatePackageVersion>
|
||||
<AssemblyInfoVersionType>SettingsVersion</AssemblyInfoVersionType>
|
||||
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
|
||||
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
|
||||
<Version>2025.6.0</Version>
|
||||
<AssemblyVersion>2025.6.18.0110</AssemblyVersion>
|
||||
<FileVersion>2025.6.18.0110</FileVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<?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=}"
|
||||
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>
|
||||
@@ -0,0 +1,28 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.SystemTray"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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:tb="using:H.NotifyIcon"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<tb:TaskbarIcon
|
||||
x:Name="TrayIcon"
|
||||
x:FieldModifier="public"
|
||||
ContextMenuMode="SecondWindow"
|
||||
IconSource="ms-appx:///Assets/Logo.ico"
|
||||
NoLeftClickDelay="True"
|
||||
ToolTipText="{x:Bind ViewModel.ToolTipText, Mode=OneWay}">
|
||||
<tb:TaskbarIcon.ContextFlyout>
|
||||
<MenuFlyout
|
||||
AreOpenCloseAnimationsEnabled="True"
|
||||
LightDismissOverlayMode="On"
|
||||
ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||
<MenuFlyoutItem x:Uid="SystemTraySettings" Command="{x:Bind ViewModel.OpenSettingsCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTrayExit" Command="{x:Bind ViewModel.ExitAppCommand}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SystemTrayUnlock"
|
||||
Command="{x:Bind ViewModel.UnlockWindowCommand}"
|
||||
Visibility="{x:Bind ViewModel.IsLyricsWindowLocked, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</MenuFlyout>
|
||||
</tb:TaskbarIcon.ContextFlyout>
|
||||
</tb:TaskbarIcon>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,33 @@
|
||||
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
|
||||
{
|
||||
public sealed partial class SystemTray : UserControl
|
||||
{
|
||||
public SystemTrayViewModel ViewModel => (SystemTrayViewModel)DataContext;
|
||||
|
||||
public SystemTray()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetService<SystemTrayViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class ColorToBrushConverter : IValueConverter
|
||||
/// <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)
|
||||
@@ -20,9 +31,19 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,25 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
internal class ThemeTypeToElementThemeConverter : IValueConverter
|
||||
internal partial class CornerRadiusToDoubleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is int themeType)
|
||||
if (value is Microsoft.UI.Xaml.CornerRadius cornerRadius)
|
||||
{
|
||||
return (ElementTheme)themeType;
|
||||
// Convert CornerRadius to an integer value, e.g., using the top-left radius
|
||||
return (double)cornerRadius.TopLeft;
|
||||
}
|
||||
return ElementTheme.Default;
|
||||
return .0; // or handle the case where value is not a CornerRadius
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
return 0;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
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)
|
||||
{
|
||||
return System.Convert.ToInt32(value);
|
||||
}
|
||||
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)
|
||||
{
|
||||
return Enum.ToObject(targetType, value);
|
||||
}
|
||||
return Enum.ToObject(targetType, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
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)
|
||||
{
|
||||
return new Microsoft.UI.Xaml.CornerRadius(intValue / 100f * controlHeight / 2);
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
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"
|
||||
),
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
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)
|
||||
{
|
||||
if (path == App.ResourceLoader!.GetString("MainPageNoLocalFilesMatched"))
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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
|
||||
}
|
||||
39
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/EasingType.cs
Normal file
39
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/EasingType.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
50
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/Language.cs
Normal file
50
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/Language.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// 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 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
|
||||
}
|
||||
@@ -4,11 +4,11 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum TitleBarType
|
||||
public enum LineMaskType
|
||||
{
|
||||
Compact,
|
||||
Extended,
|
||||
Glow,
|
||||
Highlight,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LineMaskType
|
||||
/// </summary>
|
||||
public enum LineRenderingType
|
||||
{
|
||||
UntilCurrentChar,
|
||||
|
||||
/// <summary>
|
||||
/// Current char only
|
||||
/// </summary>
|
||||
CurrentCharOnly,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// 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 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
|
||||
{
|
||||
LyricsFontWeight.Thin => FontWeights.Thin,
|
||||
LyricsFontWeight.ExtraLight => FontWeights.ExtraLight,
|
||||
LyricsFontWeight.Light => FontWeights.Light,
|
||||
LyricsFontWeight.SemiLight => FontWeights.SemiLight,
|
||||
LyricsFontWeight.Normal => FontWeights.Normal,
|
||||
LyricsFontWeight.Medium => FontWeights.Medium,
|
||||
LyricsFontWeight.SemiBold => FontWeights.SemiBold,
|
||||
LyricsFontWeight.Bold => FontWeights.Bold,
|
||||
LyricsFontWeight.ExtraBold => FontWeights.ExtraBold,
|
||||
LyricsFontWeight.Black => FontWeights.Black,
|
||||
LyricsFontWeight.ExtraBlack => FontWeights.ExtraBlack,
|
||||
LyricsFontWeight _ => throw new ArgumentOutOfRangeException(
|
||||
nameof(weight),
|
||||
weight,
|
||||
null
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// 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")
|
||||
)
|
||||
{
|
||||
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}>"
|
||||
)
|
||||
)
|
||||
{
|
||||
return LyricsFormat.Lrc;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
LyricsFormat.Lrc => ".lrc",
|
||||
LyricsFormat.Qrc => ".qrc",
|
||||
LyricsFormat.Krc => ".krc",
|
||||
LyricsFormat.Eslrc => ".eslrc",
|
||||
LyricsFormat.Ttml => ".ttml",
|
||||
_ => ".*",
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,35 @@
|
||||
using System;
|
||||
// 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
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsPlayingState
|
||||
/// </summary>
|
||||
public enum LyricsPlayingState
|
||||
{
|
||||
/// <summary>
|
||||
/// Not played yet, will be playing in the future
|
||||
/// Defines the NotPlayed
|
||||
/// </summary>
|
||||
NotPlayed,
|
||||
|
||||
/// <summary>
|
||||
/// Playing
|
||||
/// Defines the Playing
|
||||
/// </summary>
|
||||
Playing,
|
||||
|
||||
/// <summary>
|
||||
/// Has already played
|
||||
/// Defines the Played
|
||||
/// </summary>
|
||||
Played,
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => AppInfo.LrcLibLyricsCacheDirectory,
|
||||
LyricsSearchProvider.QQ => AppInfo.QQLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Netease => AppInfo.NeteaseLyricsCacheDirectory,
|
||||
LyricsSearchProvider.Kugou => AppInfo.KugouLyricsCacheDirectory,
|
||||
LyricsSearchProvider.AmllTtmlDb => AppInfo.AmllTtmlDbLyricsCacheDirectory,
|
||||
_ => throw new System.ArgumentOutOfRangeException(nameof(provider)),
|
||||
};
|
||||
}
|
||||
|
||||
public static LyricsFormat GetLyricsFormat(this LyricsSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.QQ => LyricsFormat.Qrc,
|
||||
LyricsSearchProvider.Kugou => LyricsFormat.Krc,
|
||||
LyricsSearchProvider.Netease => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.AmllTtmlDb => LyricsFormat.Ttml,
|
||||
LyricsSearchProvider.LocalLrcFile => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.LocalEslrcFile => LyricsFormat.Eslrc,
|
||||
LyricsSearchProvider.LocalTtmlFile => LyricsFormat.Ttml,
|
||||
_ => LyricsFormat.NotSpecified,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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
|
||||
}
|
||||
30
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsType.cs
Normal file
30
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsType.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LibChangedEventArgs" />
|
||||
/// </summary>
|
||||
public class LibChangedEventArgs : 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,239 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class AnimationHelper
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AnimationHelper" />
|
||||
/// </summary>
|
||||
public class AnimationHelper
|
||||
{
|
||||
public const int StackedNotificationsShowingDuration = 3900;
|
||||
public const int StoryboardDefaultDuration = 200;
|
||||
#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 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;
|
||||
|
||||
#endregion
|
||||
|
||||
#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
|
||||
)
|
||||
{
|
||||
_currentValue = initialValue;
|
||||
_startValue = initialValue;
|
||||
_targetValue = initialValue;
|
||||
_durationSeconds = durationSeconds;
|
||||
_progress = 1f;
|
||||
_isTransitioning = false;
|
||||
|
||||
if (interpolator != null)
|
||||
{
|
||||
_interpolator = interpolator;
|
||||
_easingType = null;
|
||||
}
|
||||
else if (easingType.HasValue)
|
||||
{
|
||||
_easingType = easingType;
|
||||
_interpolator = GetInterpolatorByEasingType(easingType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_interpolator = GetInterpolatorByEasingType(EasingType.Linear);
|
||||
_easingType = EasingType.Linear;
|
||||
}
|
||||
}
|
||||
|
||||
#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)
|
||||
{
|
||||
// 这里只以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("当前类型未实现默认缓动插值");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Reset
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="T"/></param>
|
||||
public void Reset(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 0f;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The StartTransition
|
||||
/// </summary>
|
||||
/// <param name="targetValue">The targetValue<see cref="T"/></param>
|
||||
public void StartTransition(T targetValue)
|
||||
{
|
||||
if (!targetValue.Equals(_currentValue))
|
||||
{
|
||||
_startValue = _currentValue;
|
||||
_targetValue = targetValue;
|
||||
_progress = 0f;
|
||||
_isTransitioning = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 立即跳转到指定值,无动画
|
||||
/// </summary>
|
||||
/// <param name="value">目标值</param>
|
||||
public void JumpTo(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 1f;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Update
|
||||
/// </summary>
|
||||
/// <param name="elapsedTime">The elapsedTime<see cref="TimeSpan"/></param>
|
||||
public void Update(TimeSpan elapsedTime)
|
||||
{
|
||||
if (!_isTransitioning)
|
||||
return;
|
||||
|
||||
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
|
||||
if (_progress >= 1f)
|
||||
{
|
||||
_progress = 1f;
|
||||
_currentValue = _targetValue;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentValue = _interpolator(_startValue, _targetValue, _progress);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="AppInfo" />
|
||||
/// </summary>
|
||||
public static class AppInfo
|
||||
{
|
||||
// App Metadata
|
||||
public const string AppName = "BetterLyrics";
|
||||
public const string AppDisplayName = "Better Lyrics";
|
||||
#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
|
||||
@@ -27,33 +51,81 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
// Environment Info
|
||||
public static bool IsDebug =>
|
||||
#if DEBUG
|
||||
true;
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
|
||||
// Base Folders
|
||||
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
|
||||
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
|
||||
/// <summary>
|
||||
/// Gets the AssetsFolder
|
||||
/// </summary>
|
||||
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
|
||||
|
||||
// Data Files
|
||||
private static string DatabaseFileName => "database.db";
|
||||
public static string DatabasePath => Path.Combine(LocalFolder, DatabaseFileName);
|
||||
/// <summary>
|
||||
/// Gets the CacheFolder
|
||||
/// </summary>
|
||||
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
|
||||
|
||||
// Environment Info
|
||||
|
||||
// Data Files
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LogDirectory
|
||||
/// </summary>
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LogFilePattern
|
||||
/// </summary>
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
|
||||
/// <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 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 AmllTtmlDbIndexPath =>
|
||||
Path.Combine(CacheFolder, "amll-ttml-db-index.json");
|
||||
|
||||
/// <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(LogDirectory);
|
||||
Directory.CreateDirectory(LocalFolder);
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(QQLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(KugouLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -6,13 +8,27 @@ 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;
|
||||
return list[index];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,48 @@
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ColorHelper
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ColorHelper" />
|
||||
/// </summary>
|
||||
public static class ColorHelper
|
||||
{
|
||||
public static Windows.UI.Color LerpColor(Windows.UI.Color a, Windows.UI.Color b, double t)
|
||||
#region Methods
|
||||
|
||||
/// <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
|
||||
)
|
||||
{
|
||||
byte A = (byte)(a.A + (b.A - a.A) * t);
|
||||
byte R = (byte)(a.R + (b.R - a.R) * t);
|
||||
byte G = (byte)(a.G + (b.G - a.G) * t);
|
||||
byte B = (byte)(a.B + (b.B - a.B) * t);
|
||||
return Windows.UI.Color.FromArgb(A, R, G, B);
|
||||
byte Lerp(byte a, byte b) => (byte)(a + (progress * (b - a)));
|
||||
return Color.FromArgb(
|
||||
Lerp(startColor.A, targetColor.A),
|
||||
Lerp(startColor.R, targetColor.R),
|
||||
Lerp(startColor.G, targetColor.G),
|
||||
Lerp(startColor.B, targetColor.B)
|
||||
);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return Windows.UI.Color.FromArgb(color.A, color.R, color.G, color.B);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.UI.Xaml;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class DesktopModeHelper
|
||||
{
|
||||
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
|
||||
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 = [];
|
||||
|
||||
// <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);
|
||||
|
||||
// <20>ָ<EFBFBD>TopMost״̬
|
||||
if (_originalTopmostStates.TryGetValue(hwnd, out var wasTopMost))
|
||||
{
|
||||
window.SetIsAlwaysOnTop(wasTopMost);
|
||||
_originalTopmostStates.Remove(hwnd);
|
||||
}
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
|
||||
var windowManager = WindowManager.Get(window);
|
||||
if (_originalWindowBounds.TryGetValue(hwnd, out var bounds))
|
||||
{
|
||||
windowManager.AppWindow.MoveAndResize(
|
||||
new Windows.Graphics.RectInt32(
|
||||
(int)bounds.X,
|
||||
(int)bounds.Y,
|
||||
(int)bounds.Width,
|
||||
(int)bounds.Height
|
||||
)
|
||||
);
|
||||
_originalWindowBounds.Remove(hwnd);
|
||||
}
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
_originalWindowStyles.Remove(hwnd);
|
||||
}
|
||||
|
||||
window.SetIsShownInSwitchers(true);
|
||||
}
|
||||
|
||||
public static void Lock(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ޱ߿<DEB1><DFBF><EFBFBD><EFBFBD><CDB8>
|
||||
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
|
||||
SetClickThrough(window, true);
|
||||
}
|
||||
|
||||
public static void Unlock(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD>ֻ<EFBFBD><D6BB><EFBFBD><EFBFBD> Disable ʱ<><CAB1><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD>
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
}
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
SetClickThrough(window, false);
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
218
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockModeHelper.cs
Normal file
218
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockModeHelper.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
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 WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
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)
|
||||
{
|
||||
window.SetIsShownInSwitchers(true);
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
window.SetIsAlwaysOnTop(false);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
window.SetWindowStyle(_originalWindowStyle[hwnd]);
|
||||
_originalWindowStyle.Remove(hwnd);
|
||||
|
||||
if (_originalPositions.TryGetValue(hwnd, out var rect))
|
||||
{
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
|
||||
);
|
||||
_originalPositions.Remove(hwnd);
|
||||
}
|
||||
|
||||
UnregisterAppBar(hwnd);
|
||||
}
|
||||
|
||||
public static void Enable(Window window, int appBarHeight)
|
||||
{
|
||||
window.SetIsShownInSwitchers(false);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
window.SetIsAlwaysOnTop(true);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
if (!_originalWindowStyle.ContainsKey(hwnd))
|
||||
{
|
||||
_originalWindowStyle[hwnd] = window.GetWindowStyle();
|
||||
}
|
||||
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
|
||||
|
||||
if (!_originalPositions.ContainsKey(hwnd))
|
||||
{
|
||||
if (GetWindowRect(hwnd, out var rect))
|
||||
{
|
||||
_originalPositions[hwnd] = rect;
|
||||
}
|
||||
}
|
||||
|
||||
RegisterAppBar(hwnd, appBarHeight);
|
||||
|
||||
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
|
||||
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
screenWidth,
|
||||
appBarHeight,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | 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;
|
||||
|
||||
APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = Marshal.SizeOf<APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = ABE_TOP,
|
||||
rc = new RECT
|
||||
{
|
||||
left = 0,
|
||||
top = 0,
|
||||
right = GetSystemMetrics(SM_CXSCREEN),
|
||||
bottom = height,
|
||||
},
|
||||
};
|
||||
|
||||
SHAppBarMessage(ABM_NEW, ref abd);
|
||||
SHAppBarMessage(ABM_SETPOS, ref abd);
|
||||
|
||||
_registered.Add(hwnd);
|
||||
}
|
||||
|
||||
private static void UnregisterAppBar(IntPtr hwnd)
|
||||
{
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
APPBARDATA abd = new() { cbSize = Marshal.SizeOf<APPBARDATA>(), hWnd = hwnd };
|
||||
|
||||
SHAppBarMessage(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()
|
||||
{
|
||||
cbSize = Marshal.SizeOf<APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = ABE_TOP,
|
||||
rc = new RECT
|
||||
{
|
||||
left = 0,
|
||||
top = 0,
|
||||
right = GetSystemMetrics(SM_CXSCREEN),
|
||||
bottom = newHeight,
|
||||
},
|
||||
};
|
||||
|
||||
SHAppBarMessage(ABM_SETPOS, ref abd);
|
||||
|
||||
// 同步窗口实际高度
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
GetSystemMetrics(SM_CXSCREEN),
|
||||
newHeight,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -6,45 +8,64 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="EasingHelper" />
|
||||
/// </summary>
|
||||
public class EasingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// No easing
|
||||
/// </summary>
|
||||
public static float Linear(float t) => t;
|
||||
|
||||
/// <summary>
|
||||
/// Accelerating from 0
|
||||
/// </summary>
|
||||
public static float EaseInQuad(float t) => t * t;
|
||||
|
||||
/// <summary>
|
||||
/// Decelerating to 0
|
||||
/// </summary>
|
||||
public static float EaseOutQuad(float t) => t * (2 - t);
|
||||
#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 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)
|
||||
{
|
||||
return t * t * t * (t * (6 * t - 15) + 10);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Even smoother transition with continuous first and second derivatives
|
||||
/// </summary>
|
||||
public static float SmootherStep(float t)
|
||||
{
|
||||
return t * t * t * (t * (6 * t - 15) + 10);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
41
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FileHelper.cs
Normal file
41
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FileHelper.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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);
|
||||
var cdet = new CharsetDetector();
|
||||
cdet.Feed(bytes, 0, bytes.Length);
|
||||
cdet.DataEnd();
|
||||
var encoding = cdet.Charset;
|
||||
if (encoding == null)
|
||||
{
|
||||
return Encoding.UTF8;
|
||||
}
|
||||
return Encoding.GetEncoding(encoding);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ForegroundWindowWatcherHelper
|
||||
{
|
||||
private readonly WinEventDelegate _winEventDelegate;
|
||||
private readonly List<IntPtr> _hooks = new();
|
||||
private IntPtr _currentForeground = IntPtr.Zero;
|
||||
private readonly IntPtr _selfHwnd;
|
||||
private readonly DispatcherTimer _pollingTimer;
|
||||
private DateTime _lastEventTime = DateTime.MinValue;
|
||||
private const int ThrottleIntervalMs = 100;
|
||||
|
||||
public delegate void WindowChangedHandler(IntPtr 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);
|
||||
|
||||
_pollingTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
|
||||
_pollingTimer.Tick += (_, _) =>
|
||||
{
|
||||
if (_currentForeground != IntPtr.Zero && _currentForeground != _selfHwnd)
|
||||
_onWindowChanged?.Invoke(_currentForeground);
|
||||
};
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// Hook: foreground changes and minimize end
|
||||
_hooks.Add(
|
||||
SetWinEventHook(
|
||||
EVENT_SYSTEM_FOREGROUND,
|
||||
EVENT_SYSTEM_MINIMIZEEND,
|
||||
IntPtr.Zero,
|
||||
_winEventDelegate,
|
||||
0,
|
||||
0,
|
||||
WINEVENT_OUTOFCONTEXT
|
||||
)
|
||||
);
|
||||
|
||||
// Hook: window move/resize (location change)
|
||||
_hooks.Add(
|
||||
SetWinEventHook(
|
||||
EVENT_OBJECT_LOCATIONCHANGE,
|
||||
EVENT_OBJECT_LOCATIONCHANGE,
|
||||
IntPtr.Zero,
|
||||
_winEventDelegate,
|
||||
0,
|
||||
0,
|
||||
WINEVENT_OUTOFCONTEXT
|
||||
)
|
||||
);
|
||||
|
||||
_pollingTimer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
foreach (var hook in _hooks)
|
||||
UnhookWinEvent(hook);
|
||||
|
||||
_hooks.Clear();
|
||||
_pollingTimer.Stop();
|
||||
}
|
||||
|
||||
private void WinEventProc(
|
||||
IntPtr hWinEventHook,
|
||||
uint eventType,
|
||||
IntPtr hwnd,
|
||||
int idObject,
|
||||
int idChild,
|
||||
uint dwEventThread,
|
||||
uint dwmsEventTime
|
||||
)
|
||||
{
|
||||
if (hwnd == IntPtr.Zero || hwnd == _selfHwnd)
|
||||
return;
|
||||
|
||||
var now = DateTime.Now;
|
||||
if ((now - _lastEventTime).TotalMilliseconds < ThrottleIntervalMs)
|
||||
return;
|
||||
|
||||
_lastEventTime = now;
|
||||
|
||||
if (eventType == EVENT_SYSTEM_FOREGROUND)
|
||||
{
|
||||
_currentForeground = hwnd;
|
||||
_onWindowChanged?.Invoke(hwnd);
|
||||
}
|
||||
else if (
|
||||
(eventType == EVENT_OBJECT_LOCATIONCHANGE || eventType == 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
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,191 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
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();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
|
||||
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
|
||||
)
|
||||
{
|
||||
var device = CanvasDevice.GetSharedDevice();
|
||||
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
|
||||
|
||||
// 居中绘制文字
|
||||
using (var ds = renderTarget.CreateDrawingSession())
|
||||
{
|
||||
// 背景色
|
||||
ds.Clear(Colors.LightGray);
|
||||
|
||||
// 文字格式
|
||||
var format = new CanvasTextFormat
|
||||
{
|
||||
FontSize = Math.Min(width, height) / 6f,
|
||||
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Center,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Center,
|
||||
WordWrapping = CanvasWordWrapping.Wrap,
|
||||
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
|
||||
Options = CanvasDrawTextOptions.Default,
|
||||
};
|
||||
|
||||
// 设定边距
|
||||
float margin = Math.Min(width, height) / 12f;
|
||||
float availableWidth = width - 2 * margin;
|
||||
float availableHeight = height - 2 * margin;
|
||||
|
||||
// 计算合适的字体大小以适应内容区域
|
||||
float fontSize = format.FontSize;
|
||||
float minFontSize = 8f;
|
||||
float maxFontSize = format.FontSize;
|
||||
CanvasTextLayout layout;
|
||||
do
|
||||
{
|
||||
format.FontSize = fontSize;
|
||||
layout = new CanvasTextLayout(
|
||||
ds,
|
||||
text,
|
||||
format,
|
||||
availableWidth,
|
||||
availableHeight
|
||||
);
|
||||
if (
|
||||
layout.LayoutBounds.Width <= availableWidth
|
||||
&& layout.LayoutBounds.Height <= availableHeight
|
||||
)
|
||||
break;
|
||||
fontSize -= 1f;
|
||||
} while (fontSize >= minFontSize);
|
||||
|
||||
// 居中绘制文字(在内容区域内居中)
|
||||
var bounds = layout.LayoutBounds;
|
||||
var x = margin + (availableWidth - (float)bounds.Width) / 2f - (float)bounds.X;
|
||||
var y = margin + (availableHeight - (float)bounds.Height) / 2f - (float)bounds.Y;
|
||||
ds.DrawTextLayout(layout, new Vector2(x, y), Colors.DarkGray);
|
||||
}
|
||||
|
||||
// 保存为 PNG 并转为 byte[]
|
||||
using (var stream = new InMemoryRandomAccessStream())
|
||||
{
|
||||
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
|
||||
var buffer = new byte[stream.Size];
|
||||
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
|
||||
{
|
||||
await reader.LoadAsync((uint)stream.Size);
|
||||
reader.ReadBytes(buffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
),
|
||||
];
|
||||
|
||||
/// <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();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
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
|
||||
)
|
||||
@@ -21,11 +195,15 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
|
||||
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();
|
||||
@@ -33,5 +211,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
await stream.AsStreamForRead().CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
450
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs
Normal file
450
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs
Normal file
@@ -0,0 +1,450 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
)
|
||||
{
|
||||
_multiLangLyricsLines = [];
|
||||
switch (lyricsFormat)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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)
|
||||
{
|
||||
var lines = raw.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
|
||||
var lrcLines =
|
||||
new List<(int time, string text, List<(int time, string text)> syllables)>();
|
||||
|
||||
// 支持 [mm:ss.xx]字、<mm:ss.xx>字,毫秒两位或三位
|
||||
var syllableRegex = new Regex(
|
||||
@"(\[|\<)(\d{2}):(\d{2})\.(\d{2,3})(\]|\>)([^\[\]\<\>]*)"
|
||||
);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var matches = syllableRegex.Matches(line);
|
||||
var syllables = new List<(int, string)>();
|
||||
for (int i = 0; i < matches.Count; i++)
|
||||
{
|
||||
var m = matches[i];
|
||||
int min = int.Parse(m.Groups[2].Value);
|
||||
int sec = int.Parse(m.Groups[3].Value);
|
||||
int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
|
||||
int totalMs = min * 60_000 + sec * 1000 + ms;
|
||||
string text = m.Groups[6].Value;
|
||||
|
||||
syllables.Add((totalMs, text));
|
||||
}
|
||||
if (syllables.Count > 0)
|
||||
{
|
||||
lrcLines.Add(
|
||||
(
|
||||
syllables[0].Item1,
|
||||
string.Concat(syllables.Select(s => s.Item2)),
|
||||
syllables
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 普通LRC行
|
||||
var bracketRegex = new Regex(@"\[(\d{2}):(\d{2})\.(\d{2,3})\]");
|
||||
var bracketMatches = bracketRegex.Matches(line);
|
||||
string content = line;
|
||||
int? lineStartTime = null;
|
||||
if (bracketMatches.Count > 0)
|
||||
{
|
||||
var m = bracketMatches[0];
|
||||
int min = int.Parse(m.Groups[1].Value);
|
||||
int sec = int.Parse(m.Groups[2].Value);
|
||||
int ms = int.Parse(m.Groups[3].Value.PadRight(3, '0'));
|
||||
lineStartTime = min * 60_000 + sec * 1000 + ms;
|
||||
content = bracketRegex.Replace(line, "");
|
||||
lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按时间分组
|
||||
var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList();
|
||||
int languageCount = grouped.Max(g => g.Count());
|
||||
|
||||
// 初始化每种语言的歌词列表
|
||||
_multiLangLyricsLines.Clear();
|
||||
for (int i = 0; i < languageCount; i++)
|
||||
_multiLangLyricsLines.Add(new List<LyricsLine>());
|
||||
|
||||
// 遍历每个时间分组
|
||||
foreach (var group in grouped)
|
||||
{
|
||||
var linesInGroup = group.ToList();
|
||||
for (int langIdx = 0; langIdx < languageCount; langIdx++)
|
||||
{
|
||||
// 如果该语言有翻译,取对应行,否则用原文(第一行)
|
||||
var (start, text, syllables) =
|
||||
langIdx < linesInGroup.Count ? linesInGroup[langIdx] : linesInGroup[0];
|
||||
var line = new LyricsLine
|
||||
{
|
||||
StartMs = start,
|
||||
EndMs = 0, // 稍后统一修正
|
||||
Text = text,
|
||||
CharTimings = [],
|
||||
};
|
||||
if (syllables != null && syllables.Count > 0)
|
||||
{
|
||||
int currentIndex = 0;
|
||||
for (int j = 0; j < syllables.Count; j++)
|
||||
{
|
||||
var (charStart, charText) = syllables[j];
|
||||
int startIndex = currentIndex;
|
||||
line.CharTimings.Add(
|
||||
new CharTiming
|
||||
{
|
||||
StartMs = charStart,
|
||||
EndMs = 0, // Fixed later
|
||||
Text = charText ?? "",
|
||||
StartIndex = startIndex,
|
||||
}
|
||||
);
|
||||
currentIndex += charText?.Length ?? 0;
|
||||
}
|
||||
}
|
||||
_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)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<LyricsLine> singleLangLyricsLine = [];
|
||||
var xdoc = XDocument.Parse(raw);
|
||||
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
|
||||
if (body == null)
|
||||
return;
|
||||
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
|
||||
foreach (var p in ps)
|
||||
{
|
||||
// 句级时间
|
||||
string? pBegin = p.Attribute("begin")?.Value;
|
||||
string? pEnd = p.Attribute("end")?.Value;
|
||||
int pStartMs = ParseTtmlTime(pBegin);
|
||||
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();
|
||||
|
||||
string text = string.Concat(spans.Select(s => s));
|
||||
var charTimings = new List<CharTiming>();
|
||||
|
||||
for (int i = 0; i < spans.Count; i++)
|
||||
{
|
||||
var span = spans[i];
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
string? sEnd = span.Attribute("end")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
int sEndMs = ParseTtmlTime(sEnd);
|
||||
|
||||
if (sStartMs == 0 && sEndMs == 0)
|
||||
continue;
|
||||
|
||||
if (sEndMs == 0)
|
||||
sEndMs =
|
||||
(i + 1 < spans.Count)
|
||||
? ParseTtmlTime(spans[i + 1].Attribute("begin")?.Value)
|
||||
: pEndMs;
|
||||
|
||||
charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = sEndMs });
|
||||
}
|
||||
|
||||
if (spans.Count == 0)
|
||||
text = p.Value;
|
||||
|
||||
singleLangLyricsLine.Add(
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = pEndMs,
|
||||
Text = text,
|
||||
CharTimings = charTimings,
|
||||
}
|
||||
);
|
||||
}
|
||||
PostProcessLyricsLines(singleLangLyricsLine);
|
||||
_multiLangLyricsLines.Add(singleLangLyricsLine);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 解析失败,忽略
|
||||
}
|
||||
}
|
||||
|
||||
/// <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))
|
||||
return 0;
|
||||
|
||||
t = t.Trim();
|
||||
|
||||
// 支持 "1.000s"
|
||||
if (t.EndsWith("s"))
|
||||
{
|
||||
if (
|
||||
double.TryParse(
|
||||
t.TrimEnd('s'),
|
||||
System.Globalization.NumberStyles.Float,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out double seconds
|
||||
)
|
||||
)
|
||||
return (int)(seconds * 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
var parts = t.Split(':');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
// hh:mm:ss.xxx
|
||||
int h = int.Parse(parts[0]);
|
||||
int m = int.Parse(parts[1]);
|
||||
double s = double.Parse(
|
||||
parts[2],
|
||||
System.Globalization.CultureInfo.InvariantCulture
|
||||
);
|
||||
return (int)((h * 3600 + m * 60 + s) * 1000);
|
||||
}
|
||||
else if (parts.Length == 2)
|
||||
{
|
||||
// mm:ss.xxx
|
||||
int m = int.Parse(parts[0]);
|
||||
double s = double.Parse(
|
||||
parts[1],
|
||||
System.Globalization.CultureInfo.InvariantCulture
|
||||
);
|
||||
return (int)((m * 60 + s) * 1000);
|
||||
}
|
||||
else if (parts.Length == 1)
|
||||
{
|
||||
// ss.xxx
|
||||
if (
|
||||
double.TryParse(
|
||||
parts[0],
|
||||
System.Globalization.NumberStyles.Float,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out double s
|
||||
)
|
||||
)
|
||||
return (int)(s * 1000);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The PostProcessLyricsLines
|
||||
/// </summary>
|
||||
/// <param name="lines">The lines<see cref="List{LyricsLine}"/></param>
|
||||
private void PostProcessLyricsLines(List<LyricsLine> lines)
|
||||
{
|
||||
if (lines.Count > 0 && lines[0].StartMs > 0)
|
||||
{
|
||||
lines.Insert(
|
||||
0,
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = lines[0].StartMs,
|
||||
Text = "● ● ●",
|
||||
CharTimings = [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class MathHelper
|
||||
{
|
||||
public static List<int> GetAllFactors(int n)
|
||||
{
|
||||
var result = new SortedSet<int>();
|
||||
|
||||
for (int i = 1; i <= Math.Sqrt(n); i++)
|
||||
{
|
||||
if (n % i == 0)
|
||||
{
|
||||
result.Add(i);
|
||||
result.Add(n / i);
|
||||
}
|
||||
}
|
||||
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,36 @@
|
||||
using DevWinUI;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
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
|
||||
{
|
||||
BackdropType.None => null,
|
||||
BackdropType.Mica => new MicaSystemBackdrop(MicaKind.Base),
|
||||
BackdropType.MicaAlt => new MicaSystemBackdrop(MicaKind.BaseAlt),
|
||||
BackdropType.Mica => new MicaBackdrop { Kind = MicaKind.Base },
|
||||
BackdropType.MicaAlt => new MicaBackdrop { Kind = MicaKind.BaseAlt },
|
||||
BackdropType.DesktopAcrylic => new DesktopAcrylicBackdrop(),
|
||||
BackdropType.AcrylicThin => new AcrylicSystemBackdrop(DesktopAcrylicKind.Thin),
|
||||
BackdropType.AcrylicBase => new AcrylicSystemBackdrop(DesktopAcrylicKind.Base),
|
||||
BackdropType.Transparent => new TransparentBackdrop(),
|
||||
BackdropType.Transparent => new WinUIEx.TransparentTintBackdrop(),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class VisualHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Source: https://stackoverflow.com/a/61626933/11048731
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="depObj"></param>
|
||||
/// <returns></returns>
|
||||
public static List<T> FindVisualChildren<T>(DependencyObject depObj)
|
||||
where T : DependencyObject
|
||||
{
|
||||
List<T> list = [];
|
||||
if (depObj != null)
|
||||
{
|
||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
|
||||
{
|
||||
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
|
||||
if (child != null && child is T t)
|
||||
{
|
||||
list.Add(t);
|
||||
}
|
||||
|
||||
List<T> childItems = FindVisualChildren<T>(child);
|
||||
if (childItems != null && childItems.Count > 0)
|
||||
{
|
||||
foreach (var item in childItems)
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class WindowColorHelper
|
||||
{
|
||||
public static Color GetDominantColorBelow(IntPtr myHwnd)
|
||||
{
|
||||
if (!GetWindowRect(myHwnd, out RECT myRect))
|
||||
return Color.Transparent;
|
||||
|
||||
int screenWidth = GetSystemMetrics(SystemMetric.SM_CXSCREEN);
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
|
||||
private static Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
|
||||
{
|
||||
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
|
||||
using Graphics gDest = Graphics.FromImage(bmp);
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = GetDC(IntPtr.Zero); // Entire screen
|
||||
|
||||
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, SRCCOPY);
|
||||
|
||||
gDest.ReleaseHdc(hdcDest);
|
||||
ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
private static Color ComputeAverageColor(Bitmap bmp)
|
||||
{
|
||||
long r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int y = 0; y < bmp.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bmp.Width; x++)
|
||||
{
|
||||
Color pixel = bmp.GetPixel(x, y);
|
||||
r += pixel.R;
|
||||
g += pixel.G;
|
||||
b += pixel.B;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
228
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/WindowHelper.cs
Normal file
228
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/WindowHelper.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="WindowHelper" />
|
||||
/// </summary>
|
||||
public static class WindowHelper
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <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
|
||||
{
|
||||
get { return _activeWindows; }
|
||||
}
|
||||
|
||||
#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)
|
||||
{
|
||||
foreach (var cachedWindow in _windowCache)
|
||||
{
|
||||
if (cachedWindow.Key == type)
|
||||
{
|
||||
return cachedWindow.Value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (element.XamlRoot != 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();
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (element.XamlRoot == window.Content.XamlRoot)
|
||||
{
|
||||
return element.XamlRoot.RasterizationScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
// 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) { }
|
||||
: ValueChangedMessage<Notification>(value)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using DevWinUI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Messages
|
||||
{
|
||||
public class SystemBackdropChangedMessage(BackdropType value)
|
||||
: ValueChangedMessage<BackdropType>(value) { }
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Messages
|
||||
{
|
||||
public class ThemeChangedMessage(ElementTheme value)
|
||||
: ValueChangedMessage<ElementTheme>(value) { }
|
||||
}
|
||||
34
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/CharTiming.cs
Normal file
34
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/CharTiming.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public enum Language
|
||||
{
|
||||
FollowSystem,
|
||||
English,
|
||||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LocalLyricsFolder" />
|
||||
/// </summary>
|
||||
public partial class LocalLyricsFolder : ObservableObject
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalLyricsFolder"/> class.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public enum LyricsAlignmentType
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
}
|
||||
31
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsData.cs
Normal file
31
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsData.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public enum LyricsFontColorType
|
||||
{
|
||||
Default,
|
||||
Dominant,
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsLine" />
|
||||
/// </summary>
|
||||
public class LyricsLine
|
||||
{
|
||||
public List<string> Texts { get; set; } = [];
|
||||
#region Properties
|
||||
|
||||
public int LanguageIndex { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Gets or sets the BlurAmountTransition
|
||||
/// </summary>
|
||||
public ValueTransition<float> BlurAmountTransition { get; set; } =
|
||||
new(initialValue: 0f, durationSeconds: 0.3f);
|
||||
|
||||
public string Text => Texts[LanguageIndex];
|
||||
|
||||
public int StartPlayingTimestampMs { get; set; }
|
||||
public int EndPlayingTimestampMs { get; set; }
|
||||
|
||||
public LyricsPlayingState PlayingState { get; set; }
|
||||
|
||||
public int DurationMs => EndPlayingTimestampMs - StartPlayingTimestampMs;
|
||||
|
||||
public float EnteringProgress { get; set; }
|
||||
|
||||
public float ExitingProgress { get; set; }
|
||||
|
||||
public float PlayingProgress { get; set; }
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the CanvasTextLayout
|
||||
/// </summary>
|
||||
public CanvasTextLayout? CanvasTextLayout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CenterPosition
|
||||
/// </summary>
|
||||
public Vector2 CenterPosition { get; set; }
|
||||
|
||||
public float Scale { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the CharTimings
|
||||
/// </summary>
|
||||
public List<CharTiming> CharTimings { get; set; } = [];
|
||||
|
||||
public float Opacity { get; set; }
|
||||
/// <summary>
|
||||
/// Gets the DurationMs
|
||||
/// </summary>
|
||||
public int DurationMs => EndMs - StartMs;
|
||||
|
||||
public CanvasTextLayout TextLayout { get; set; }
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the StartMs
|
||||
/// </summary>
|
||||
public int StartMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Text
|
||||
/// </summary>
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsSearchProviderInfo" />
|
||||
/// </summary>
|
||||
public partial class LyricsSearchProviderInfo : ObservableObject
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LyricsSearchProviderInfo"/> class.
|
||||
/// </summary>
|
||||
public LyricsSearchProviderInfo()
|
||||
{
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using SQLite;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class MetadataIndex
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string? Path { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Artist { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,30 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Notification" />
|
||||
/// </summary>
|
||||
public partial class Notification : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private InfoBarSeverity _severity;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _message;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isForeverDismissable;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _visibility;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _relatedSettingsKeyName;
|
||||
#region Constructors
|
||||
|
||||
/// <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,
|
||||
@@ -39,5 +38,41 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
63
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/SongInfo.cs
Normal file
63
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/SongInfo.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
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;
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
[STAThread]
|
||||
static int Main(string[] args)
|
||||
{
|
||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||
bool isRedirect = DecideRedirection();
|
||||
|
||||
if (!isRedirect)
|
||||
{
|
||||
Application.Start(
|
||||
(p) =>
|
||||
{
|
||||
var context = new DispatcherQueueSynchronizationContext(
|
||||
DispatcherQueue.GetForCurrentThread()
|
||||
);
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
_ = new App();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool DecideRedirection()
|
||||
{
|
||||
bool isRedirect = false;
|
||||
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||
ExtendedActivationKind kind = args.Kind;
|
||||
AppInstance keyInstance = AppInstance.FindOrRegisterForKey(Helper.AppInfo.AppName);
|
||||
|
||||
if (keyInstance.IsCurrent)
|
||||
{
|
||||
keyInstance.Activated += OnActivated;
|
||||
}
|
||||
else
|
||||
{
|
||||
isRedirect = true;
|
||||
RedirectActivationTo(args, keyInstance);
|
||||
}
|
||||
|
||||
return isRedirect;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr CreateEvent(
|
||||
IntPtr lpEventAttributes,
|
||||
bool bManualReset,
|
||||
bool bInitialState,
|
||||
string lpName
|
||||
);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool SetEvent(IntPtr hEvent);
|
||||
|
||||
[DllImport("ole32.dll")]
|
||||
private static extern uint CoWaitForMultipleObjects(
|
||||
uint dwFlags,
|
||||
uint dwMilliseconds,
|
||||
ulong nHandles,
|
||||
IntPtr[] pHandles,
|
||||
out uint dwIndex
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
private static IntPtr redirectEventHandle = IntPtr.Zero;
|
||||
|
||||
// Do the redirection on another thread, and use a non-blocking
|
||||
// wait method to wait for the redirection to complete.
|
||||
public static void RedirectActivationTo(
|
||||
AppActivationArguments args,
|
||||
AppInstance keyInstance
|
||||
)
|
||||
{
|
||||
redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
|
||||
Task.Run(() =>
|
||||
{
|
||||
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
|
||||
SetEvent(redirectEventHandle);
|
||||
});
|
||||
|
||||
uint CWMO_DEFAULT = 0;
|
||||
uint INFINITE = 0xFFFFFFFF;
|
||||
_ = CoWaitForMultipleObjects(
|
||||
CWMO_DEFAULT,
|
||||
INFINITE,
|
||||
1,
|
||||
[redirectEventHandle],
|
||||
out uint handleIndex
|
||||
);
|
||||
|
||||
// Bring the window to the foreground
|
||||
Process process = Process.GetProcessById((int)keyInstance.ProcessId);
|
||||
SetForegroundWindow(process.MainWindowHandle);
|
||||
}
|
||||
|
||||
private static void OnActivated(object sender, AppActivationArguments args)
|
||||
{
|
||||
ExtendedActivationKind kind = args.Kind;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>ARM64</Platform>
|
||||
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
|
||||
<PublishDir>bin\\\win-arm64\publish\</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
<Configuration>Release</Configuration>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>x64</Platform>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishDir>bin\\\win-x64\publish\</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
<Configuration>Release</Configuration>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>x86</Platform>
|
||||
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
||||
<PublishDir>bin\\\win-x86\publish\</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
<Configuration>Release</Configuration>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Renderer.LyricsRenderer"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Renderer"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<canvas:CanvasAnimatedControl
|
||||
x:Name="LyricsCanvas"
|
||||
Draw="LyricsCanvas_Draw"
|
||||
Update="LyricsCanvas_Update" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LyricsRenderer" />
|
||||
/// </summary>
|
||||
public sealed partial class LyricsRenderer : UserControl
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <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
|
||||
)
|
||||
{
|
||||
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
|
||||
)
|
||||
{
|
||||
ViewModel.Update(sender, args);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.Graphics.Imaging;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Rendering
|
||||
{
|
||||
public class CoverBackgroundRenderer
|
||||
{
|
||||
private readonly SettingsService _settingsService;
|
||||
|
||||
public float RotateAngle { get; set; } = 0f;
|
||||
|
||||
private SoftwareBitmap? _lastSoftwareBitmap = null;
|
||||
private SoftwareBitmap? _softwareBitmap = null;
|
||||
public SoftwareBitmap? SoftwareBitmap
|
||||
{
|
||||
get => _softwareBitmap;
|
||||
set
|
||||
{
|
||||
if (_softwareBitmap != null)
|
||||
{
|
||||
_lastSoftwareBitmap = _softwareBitmap;
|
||||
_transitionStartTime = DateTimeOffset.Now;
|
||||
_isTransitioning = true;
|
||||
_transitionAlpha = 0f;
|
||||
}
|
||||
|
||||
_softwareBitmap = value;
|
||||
}
|
||||
}
|
||||
|
||||
private float _transitionAlpha = 1f;
|
||||
private TimeSpan _transitionDuration = TimeSpan.FromMilliseconds(1000);
|
||||
private DateTimeOffset _transitionStartTime;
|
||||
private bool _isTransitioning = false;
|
||||
|
||||
public CoverBackgroundRenderer()
|
||||
{
|
||||
_settingsService = Ioc.Default.GetService<SettingsService>()!;
|
||||
}
|
||||
|
||||
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
if (!_settingsService.IsCoverOverlayEnabled || SoftwareBitmap == null)
|
||||
return;
|
||||
|
||||
ds.Transform = Matrix3x2.CreateRotation(RotateAngle, control.Size.ToVector2() * 0.5f);
|
||||
|
||||
var overlappedCovers = new CanvasCommandList(control);
|
||||
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
|
||||
|
||||
if (_isTransitioning && _lastSoftwareBitmap != null)
|
||||
{
|
||||
DrawImgae(control, overlappedCoversDs, _lastSoftwareBitmap, 1 - _transitionAlpha);
|
||||
DrawImgae(control, overlappedCoversDs, SoftwareBitmap, _transitionAlpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawImgae(control, overlappedCoversDs, SoftwareBitmap, 1);
|
||||
}
|
||||
|
||||
using var coverOverlayEffect = new OpacityEffect
|
||||
{
|
||||
Opacity = _settingsService.CoverOverlayOpacity / 100f,
|
||||
Source = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = _settingsService.CoverOverlayBlurAmount,
|
||||
Source = overlappedCovers,
|
||||
},
|
||||
};
|
||||
ds.DrawImage(coverOverlayEffect);
|
||||
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
private void DrawImgae(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
SoftwareBitmap softwareBitmap,
|
||||
float opacity
|
||||
)
|
||||
{
|
||||
float imageWidth = (float)(softwareBitmap.PixelWidth * 96f / softwareBitmap.DpiX);
|
||||
float imageHeight = (float)(softwareBitmap.PixelHeight * 96f / softwareBitmap.DpiY);
|
||||
var scaleFactor =
|
||||
(float)Math.Sqrt(Math.Pow(control.Size.Width, 2) + Math.Pow(control.Size.Height, 2))
|
||||
/ Math.Min(imageWidth, imageHeight);
|
||||
|
||||
ds.DrawImage(
|
||||
new OpacityEffect
|
||||
{
|
||||
Source = new ScaleEffect
|
||||
{
|
||||
InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
|
||||
BorderMode = EffectBorderMode.Hard,
|
||||
Scale = new Vector2(scaleFactor),
|
||||
Source = CanvasBitmap.CreateFromSoftwareBitmap(control, softwareBitmap),
|
||||
},
|
||||
Opacity = opacity,
|
||||
},
|
||||
(float)control.Size.Width / 2 - imageWidth * scaleFactor / 2,
|
||||
(float)control.Size.Height / 2 - imageHeight * scaleFactor / 2
|
||||
);
|
||||
}
|
||||
|
||||
public void Calculate(ICanvasAnimatedControl control)
|
||||
{
|
||||
if (_isTransitioning)
|
||||
{
|
||||
var elapsed = DateTimeOffset.Now - _transitionStartTime;
|
||||
float progress = (float)(
|
||||
elapsed.TotalMilliseconds / _transitionDuration.TotalMilliseconds
|
||||
);
|
||||
_transitionAlpha = Math.Clamp(progress, 0f, 1f);
|
||||
|
||||
if (_transitionAlpha >= 1f)
|
||||
{
|
||||
_isTransitioning = false;
|
||||
_lastSoftwareBitmap?.Dispose();
|
||||
_lastSoftwareBitmap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Brushes;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Text;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Rendering
|
||||
{
|
||||
public class PureLyricsRenderer
|
||||
{
|
||||
private readonly SettingsService _settingsService;
|
||||
|
||||
private readonly float _defaultOpacity = 0.3f;
|
||||
private readonly float _highlightedOpacity = 1.0f;
|
||||
|
||||
private readonly float _defaultScale = 0.95f;
|
||||
private readonly float _highlightedScale = 1.0f;
|
||||
|
||||
private readonly int _lineEnteringDurationMs = 800;
|
||||
private readonly int _lineExitingDurationMs = 800;
|
||||
private readonly int _lineScrollDurationMs = 800;
|
||||
|
||||
private float _lastTotalYScroll = 0.0f;
|
||||
private float _totalYScroll = 0.0f;
|
||||
|
||||
private int _startVisibleLineIndex = -1;
|
||||
private int _endVisibleLineIndex = -1;
|
||||
|
||||
private bool _forceToScroll = false;
|
||||
|
||||
private readonly double _rightMargin = 36;
|
||||
|
||||
public double LimitedLineWidth { get; set; } = 0;
|
||||
public double CanvasWidth { get; set; } = 0;
|
||||
public double CanvasHeight { get; set; } = 0;
|
||||
|
||||
public TimeSpan CurrentTime { get; set; }
|
||||
|
||||
public List<LyricsLine> LyricsLines { get; set; } = [];
|
||||
|
||||
public PureLyricsRenderer()
|
||||
{
|
||||
_settingsService = Ioc.Default.GetService<SettingsService>()!;
|
||||
}
|
||||
|
||||
private Tuple<int, int> GetVisibleLyricsLineIndexBoundaries()
|
||||
{
|
||||
// _logger.LogDebug($"{_startVisibleLineIndex} {_endVisibleLineIndex}");
|
||||
return new Tuple<int, int>(_startVisibleLineIndex, _endVisibleLineIndex);
|
||||
}
|
||||
|
||||
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
|
||||
{
|
||||
if (LyricsLines.Count == 0)
|
||||
{
|
||||
return new Tuple<int, int>(-1, -1);
|
||||
}
|
||||
|
||||
return new Tuple<int, int>(0, LyricsLines.Count - 1);
|
||||
}
|
||||
|
||||
public void Draw(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
byte r,
|
||||
byte g,
|
||||
byte b
|
||||
)
|
||||
{
|
||||
var (displayStartLineIndex, displayEndLineIndex) =
|
||||
GetVisibleLyricsLineIndexBoundaries();
|
||||
|
||||
for (
|
||||
int i = displayStartLineIndex;
|
||||
LyricsLines.Count > 0
|
||||
&& i >= 0
|
||||
&& i < LyricsLines.Count
|
||||
&& i <= displayEndLineIndex;
|
||||
i++
|
||||
)
|
||||
{
|
||||
var line = LyricsLines[i];
|
||||
|
||||
if (line.TextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float progressPerChar = 1f / line.Text.Length;
|
||||
|
||||
var position = line.Position;
|
||||
|
||||
float centerX = position.X;
|
||||
float centerY = position.Y + (float)line.TextLayout.LayoutBounds.Height / 2;
|
||||
|
||||
switch ((LyricsAlignmentType)_settingsService.LyricsAlignmentType)
|
||||
{
|
||||
case LyricsAlignmentType.Left:
|
||||
line.TextLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
|
||||
break;
|
||||
case LyricsAlignmentType.Center:
|
||||
line.TextLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
|
||||
centerX += (float)LimitedLineWidth / 2;
|
||||
break;
|
||||
case LyricsAlignmentType.Right:
|
||||
line.TextLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
|
||||
centerX += (float)LimitedLineWidth;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
int startIndex = 0;
|
||||
|
||||
// Set brush
|
||||
for (int j = 0; j < line.TextLayout.LineCount; j++)
|
||||
{
|
||||
int count = line.TextLayout.LineMetrics[j].CharacterCount;
|
||||
var regions = line.TextLayout.GetCharacterRegions(startIndex, count);
|
||||
float subLinePlayingProgress = Math.Clamp(
|
||||
(line.PlayingProgress * line.Text.Length - startIndex) / count,
|
||||
0,
|
||||
1
|
||||
);
|
||||
|
||||
using var horizontalFillBrush = new CanvasLinearGradientBrush(
|
||||
control,
|
||||
[
|
||||
new()
|
||||
{
|
||||
Position = 0,
|
||||
Color = Color.FromArgb((byte)(255 * line.Opacity), r, g, b),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Position =
|
||||
subLinePlayingProgress * (1 + progressPerChar)
|
||||
- progressPerChar,
|
||||
Color = Color.FromArgb((byte)(255 * line.Opacity), r, g, b),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Position = subLinePlayingProgress * (1 + progressPerChar),
|
||||
Color = Color.FromArgb((byte)(255 * _defaultOpacity), r, g, b),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Position = 1.5f,
|
||||
Color = Color.FromArgb((byte)(255 * _defaultOpacity), r, g, b),
|
||||
},
|
||||
]
|
||||
)
|
||||
{
|
||||
StartPoint = new Vector2(
|
||||
(float)(regions[0].LayoutBounds.Left + position.X),
|
||||
0
|
||||
),
|
||||
EndPoint = new Vector2(
|
||||
(float)(regions[^1].LayoutBounds.Right + position.X),
|
||||
0
|
||||
),
|
||||
};
|
||||
|
||||
line.TextLayout.SetBrush(startIndex, count, horizontalFillBrush);
|
||||
startIndex += count;
|
||||
}
|
||||
|
||||
// Scale
|
||||
ds.Transform =
|
||||
Matrix3x2.CreateScale(line.Scale, new Vector2(centerX, centerY))
|
||||
* Matrix3x2.CreateTranslation(0, _totalYScroll);
|
||||
// _logger.LogDebug(_totalYScroll);
|
||||
|
||||
ds.DrawTextLayout(line.TextLayout, position, Colors.Transparent);
|
||||
|
||||
// Reset scale
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ForceToScrollToCurrentPlayingLineAsync()
|
||||
{
|
||||
_forceToScroll = true;
|
||||
await Task.Delay(1);
|
||||
_forceToScroll = false;
|
||||
}
|
||||
|
||||
public async Task ReLayoutAsync(ICanvasAnimatedControl control)
|
||||
{
|
||||
if (control == null)
|
||||
return;
|
||||
|
||||
float leftMargin = (float)(CanvasWidth - LimitedLineWidth - _rightMargin);
|
||||
|
||||
using CanvasTextFormat textFormat = new()
|
||||
{
|
||||
FontSize = _settingsService.LyricsFontSize,
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Top,
|
||||
FontWeight = FontWeights.Bold,
|
||||
//FontFamily = "Segoe UI Mono",
|
||||
};
|
||||
float y = (float)CanvasHeight / 2;
|
||||
|
||||
// Init Positions
|
||||
for (int i = 0; i < LyricsLines.Count; i++)
|
||||
{
|
||||
var line = LyricsLines[i];
|
||||
|
||||
// Calculate layout bounds
|
||||
line.TextLayout = new CanvasTextLayout(
|
||||
control.Device,
|
||||
line.Text,
|
||||
textFormat,
|
||||
(float)LimitedLineWidth,
|
||||
(float)CanvasHeight
|
||||
);
|
||||
line.Position = new Vector2(leftMargin, y);
|
||||
|
||||
y +=
|
||||
(float)line.TextLayout.LayoutBounds.Height
|
||||
/ line.TextLayout.LineCount
|
||||
* (line.TextLayout.LineCount + _settingsService.LyricsLineSpacingFactor);
|
||||
}
|
||||
|
||||
await ForceToScrollToCurrentPlayingLineAsync();
|
||||
}
|
||||
|
||||
public void CalculateScaleAndOpacity(int currentPlayingLineIndex)
|
||||
{
|
||||
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
|
||||
|
||||
for (int i = startLineIndex; LyricsLines.Count > 0 && i <= endLineIndex; i++)
|
||||
{
|
||||
var line = LyricsLines[i];
|
||||
|
||||
bool linePlaying = i == currentPlayingLineIndex;
|
||||
|
||||
var lineEnteringDurationMs = Math.Min(line.DurationMs, _lineEnteringDurationMs);
|
||||
var lineExitingDurationMs = _lineExitingDurationMs;
|
||||
if (i + 1 <= endLineIndex)
|
||||
{
|
||||
lineExitingDurationMs = Math.Min(
|
||||
LyricsLines[i + 1].DurationMs,
|
||||
lineExitingDurationMs
|
||||
);
|
||||
}
|
||||
|
||||
float lineEnteringProgress = 0.0f;
|
||||
float lineExitingProgress = 0.0f;
|
||||
|
||||
bool lineEntering = false;
|
||||
bool lineExiting = false;
|
||||
|
||||
float scale = _defaultScale;
|
||||
float opacity = _defaultOpacity;
|
||||
|
||||
float playProgress = 0;
|
||||
|
||||
if (linePlaying)
|
||||
{
|
||||
line.PlayingState = LyricsPlayingState.Playing;
|
||||
|
||||
scale = _highlightedScale;
|
||||
opacity = _highlightedOpacity;
|
||||
|
||||
playProgress =
|
||||
((float)CurrentTime.TotalMilliseconds - line.StartPlayingTimestampMs)
|
||||
/ line.DurationMs;
|
||||
|
||||
var durationFromStartMs =
|
||||
CurrentTime.TotalMilliseconds - line.StartPlayingTimestampMs;
|
||||
lineEntering = durationFromStartMs <= lineEnteringDurationMs;
|
||||
if (lineEntering)
|
||||
{
|
||||
lineEnteringProgress = (float)durationFromStartMs / lineEnteringDurationMs;
|
||||
scale =
|
||||
_defaultScale
|
||||
+ (_highlightedScale - _defaultScale) * (float)lineEnteringProgress;
|
||||
opacity =
|
||||
_defaultOpacity
|
||||
+ (_highlightedOpacity - _defaultOpacity) * (float)lineEnteringProgress;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < currentPlayingLineIndex)
|
||||
{
|
||||
line.PlayingState = LyricsPlayingState.Played;
|
||||
playProgress = 1;
|
||||
|
||||
var durationToEndMs =
|
||||
CurrentTime.TotalMilliseconds - line.EndPlayingTimestampMs;
|
||||
lineExiting = durationToEndMs <= lineExitingDurationMs;
|
||||
if (lineExiting)
|
||||
{
|
||||
lineExitingProgress = (float)durationToEndMs / lineExitingDurationMs;
|
||||
scale =
|
||||
_highlightedScale
|
||||
- (_highlightedScale - _defaultScale) * (float)lineExitingProgress;
|
||||
opacity =
|
||||
_highlightedOpacity
|
||||
- (_highlightedOpacity - _defaultOpacity)
|
||||
* (float)lineExitingProgress;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
line.PlayingState = LyricsPlayingState.NotPlayed;
|
||||
}
|
||||
}
|
||||
|
||||
line.EnteringProgress = lineEnteringProgress;
|
||||
line.ExitingProgress = lineExitingProgress;
|
||||
|
||||
line.Scale = scale;
|
||||
line.Opacity = opacity;
|
||||
|
||||
line.PlayingProgress = playProgress;
|
||||
}
|
||||
}
|
||||
|
||||
public void CalculatePosition(ICanvasAnimatedControl control, int currentPlayingLineIndex)
|
||||
{
|
||||
if (currentPlayingLineIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
|
||||
|
||||
if (startLineIndex < 0 || endLineIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set _scrollOffsetY
|
||||
LyricsLine? currentPlayingLine = LyricsLines?[currentPlayingLineIndex];
|
||||
|
||||
if (currentPlayingLine == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPlayingLine.TextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lineScrollingProgress =
|
||||
(CurrentTime.TotalMilliseconds - currentPlayingLine.StartPlayingTimestampMs)
|
||||
/ Math.Min(_lineScrollDurationMs, currentPlayingLine.DurationMs);
|
||||
|
||||
var targetYScrollOffset = (float)(
|
||||
-currentPlayingLine.Position.Y
|
||||
+ LyricsLines![0].Position.Y
|
||||
- currentPlayingLine.TextLayout.LayoutBounds.Height / 2
|
||||
- _lastTotalYScroll
|
||||
);
|
||||
|
||||
var yScrollOffset =
|
||||
targetYScrollOffset
|
||||
* EasingHelper.SmootherStep((float)Math.Min(1, lineScrollingProgress));
|
||||
|
||||
bool isScrollingNow = lineScrollingProgress <= 1;
|
||||
|
||||
if (isScrollingNow)
|
||||
{
|
||||
_totalYScroll = _lastTotalYScroll + yScrollOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_forceToScroll && Math.Abs(targetYScrollOffset) >= 1)
|
||||
{
|
||||
_totalYScroll = _lastTotalYScroll + targetYScrollOffset;
|
||||
}
|
||||
_lastTotalYScroll = _totalYScroll;
|
||||
}
|
||||
|
||||
_startVisibleLineIndex = _endVisibleLineIndex = -1;
|
||||
|
||||
// Update Positions
|
||||
for (int i = startLineIndex; i >= 0 && i <= endLineIndex; i++)
|
||||
{
|
||||
var line = LyricsLines[i];
|
||||
|
||||
if (_totalYScroll + line.Position.Y + line.TextLayout.LayoutBounds.Height >= 0)
|
||||
{
|
||||
if (_startVisibleLineIndex == -1)
|
||||
{
|
||||
_startVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
if (
|
||||
_totalYScroll + line.Position.Y + line.TextLayout.LayoutBounds.Height
|
||||
>= control.Size.Height
|
||||
)
|
||||
{
|
||||
if (_endVisibleLineIndex == -1)
|
||||
{
|
||||
_endVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_startVisibleLineIndex != -1 && _endVisibleLineIndex == -1)
|
||||
{
|
||||
_endVisibleLineIndex = endLineIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Serialization
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="SourceGenerationContext" />
|
||||
/// </summary>
|
||||
[JsonSerializable(typeof(List<LyricsSearchProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<LocalLyricsFolder>))]
|
||||
[JsonSerializable(typeof(List<string>))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
internal partial class SourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using SQLite;
|
||||
using Ude;
|
||||
using Windows.Media.Control;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.Database
|
||||
{
|
||||
public class DatabaseService
|
||||
{
|
||||
private readonly SQLiteConnection _connection;
|
||||
|
||||
private readonly CharsetDetector _charsetDetector = new();
|
||||
|
||||
public DatabaseService()
|
||||
{
|
||||
_connection = new SQLiteConnection(Helper.AppInfo.DatabasePath);
|
||||
if (_connection.GetTableInfo("MetadataIndex").Count == 0)
|
||||
{
|
||||
_connection.CreateTable<MetadataIndex>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RebuildMusicMetadataIndexDatabaseAsync(IList<string> paths)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
_connection.DeleteAll<MetadataIndex>();
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(path))
|
||||
{
|
||||
var fileExtension = Path.GetExtension(file);
|
||||
var track = new Track(file);
|
||||
_connection.Insert(
|
||||
new MetadataIndex
|
||||
{
|
||||
Path = file,
|
||||
Title = track.Title,
|
||||
Artist = track.Artist,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Track? GetMusicMetadata(
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps
|
||||
)
|
||||
{
|
||||
if (mediaProps == null || mediaProps.Title == null || mediaProps.Artist == null)
|
||||
return null;
|
||||
|
||||
var founds = _connection
|
||||
.Table<MetadataIndex>()
|
||||
// Look up by Title and Artist (these two props were fetched by reading metadata in music file befoe) first
|
||||
// then by Path (music file name usually contains song name and artist so this can be a second way to look up for)
|
||||
// Please note for .lrc file, only the second way works for it
|
||||
.Where(m =>
|
||||
(
|
||||
m.Title != null
|
||||
&& m.Artist != null
|
||||
&& m.Title.Contains(mediaProps.Title)
|
||||
&& m.Artist.Contains(mediaProps.Artist)
|
||||
)
|
||||
|| (
|
||||
m.Path != null
|
||||
&& m.Path.Contains(mediaProps.Title)
|
||||
&& m.Path.Contains(mediaProps.Artist)
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
if (founds == null || founds.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var first = new Track(founds[0].Path);
|
||||
if (founds.Count == 1)
|
||||
{
|
||||
return first;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first.Lyrics.Exists())
|
||||
{
|
||||
return first;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var found in founds)
|
||||
{
|
||||
if (found.Path.EndsWith(".lrc"))
|
||||
{
|
||||
using (FileStream fs = File.OpenRead(found.Path))
|
||||
{
|
||||
_charsetDetector.Feed(fs);
|
||||
_charsetDetector.DataEnd();
|
||||
}
|
||||
|
||||
string content;
|
||||
if (_charsetDetector.Charset != null)
|
||||
{
|
||||
Encoding encoding = Encoding.GetEncoding(
|
||||
_charsetDetector.Charset
|
||||
);
|
||||
content = File.ReadAllText(found.Path, encoding);
|
||||
}
|
||||
else
|
||||
{
|
||||
content = File.ReadAllText(found.Path, Encoding.UTF8);
|
||||
}
|
||||
first.Lyrics.ParseLRC(content);
|
||||
|
||||
return first;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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;
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
#region Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="IMusicSearchService" />
|
||||
/// </summary>
|
||||
public interface IMusicSearchService
|
||||
{
|
||||
#region Methods
|
||||
|
||||
/// <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(
|
||||
string title,
|
||||
string artist,
|
||||
string album = "",
|
||||
double durationMs = 0.0,
|
||||
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
|
||||
);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Collections.Generic;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using 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 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; }
|
||||
|
||||
// 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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsBlurAmount
|
||||
/// </summary>
|
||||
int LyricsBlurAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsFontColorType
|
||||
/// </summary>
|
||||
LyricsFontColorType LyricsFontColorType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsFontSize
|
||||
/// </summary>
|
||||
int LyricsFontSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsFontWeight
|
||||
/// </summary>
|
||||
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>
|
||||
int LyricsVerticalEdgeOpacity { get; set; }
|
||||
|
||||
// App appearance
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ThemeType
|
||||
/// </summary>
|
||||
ElementTheme ThemeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TitleBarType
|
||||
/// </summary>
|
||||
TitleBarType TitleBarType { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// 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.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="LibWatcherService" />
|
||||
/// </summary>
|
||||
public class LibWatcherService : IDisposable, ILibWatcherService
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _settingsService
|
||||
/// </summary>
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
UpdateWatchers(_settingsService.LocalLyricsFolders);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Defines the MusicLibraryFilesChanged
|
||||
/// </summary>
|
||||
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The Dispose
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var watcher in _watchers.Values)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
App.DispatcherQueue!.TryEnqueue(
|
||||
Microsoft.UI.Dispatching.DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
MusicLibraryFilesChanged?.Invoke(
|
||||
this,
|
||||
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,671 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _settingsService
|
||||
/// </summary>
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
#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();
|
||||
_lrcLibHttpClient.DefaultRequestHeaders.Add(
|
||||
"User-Agent",
|
||||
$"{AppInfo.AppName} {AppInfo.AppVersion} ({AppInfo.GithubUrl})"
|
||||
);
|
||||
_amllTtmlDbHttpClient = new HttpClient();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <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)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (
|
||||
var file in Directory.GetFiles(
|
||||
folder.Path,
|
||||
$"*.*",
|
||||
SearchOption.AllDirectories
|
||||
)
|
||||
)
|
||||
{
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
Track track = new(file);
|
||||
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
if (bytes != null)
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
{
|
||||
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string? cachedLyrics;
|
||||
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
|
||||
|
||||
// Check cache first
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
cachedLyrics = ReadCache(
|
||||
title,
|
||||
artist,
|
||||
lyricsFormat,
|
||||
provider.Provider.GetCacheDirectory()
|
||||
);
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
{
|
||||
return (cachedLyrics, lyricsFormat);
|
||||
}
|
||||
}
|
||||
|
||||
string? searchedLyrics = null;
|
||||
|
||||
if (provider.Provider.IsLocal())
|
||||
{
|
||||
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
|
||||
{
|
||||
searchedLyrics = LocalLyricsSearchInMusicFiles(title, artist);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchedLyrics = await LocalLyricsSearchInLyricsFiles(
|
||||
title,
|
||||
artist,
|
||||
lyricsFormat
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
searchedLyrics = await SearchLrcLibAsync(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
(int)(durationMs / 1000),
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
searchedLyrics = await SearchQQAsync(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
(int)durationMs,
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
searchedLyrics = await SearchKugouAsync(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
(int)durationMs,
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
searchedLyrics = await SearchNeteaseAsync(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
(int)durationMs,
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchedLyrics))
|
||||
{
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
WriteCache(
|
||||
title,
|
||||
artist,
|
||||
searchedLyrics,
|
||||
lyricsFormat,
|
||||
provider.Provider.GetCacheDirectory()
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
searchedLyrics,
|
||||
lyricsFormat == LyricsFormat.NotSpecified
|
||||
? searchedLyrics.DetectFormat()
|
||||
: lyricsFormat
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private static bool MusicMatch(string fileName, string title, string artist)
|
||||
{
|
||||
return fileName.Contains(title) && fileName.Contains(artist);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
private static string SanitizeFileName(string fileName, char replacement = '_')
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
var sb = new StringBuilder(fileName.Length);
|
||||
foreach (var c in fileName)
|
||||
{
|
||||
sb.Append(Array.IndexOf(invalidChars, c) >= 0 ? replacement : c);
|
||||
}
|
||||
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
|
||||
)
|
||||
{
|
||||
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
|
||||
)
|
||||
)
|
||||
{
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
string? raw = await File.ReadAllTextAsync(
|
||||
file,
|
||||
FileHelper.GetEncoding(file)
|
||||
);
|
||||
if (raw != null)
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
)
|
||||
)
|
||||
{
|
||||
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;
|
||||
if (plain != null && plain != string.Empty)
|
||||
{
|
||||
return plain;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(
|
||||
cacheFolderPath,
|
||||
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
|
||||
);
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllText(cacheFilePath);
|
||||
}
|
||||
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
|
||||
)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
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 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
|
||||
if (!File.Exists(AppInfo.AmllTtmlDbIndexPath))
|
||||
{
|
||||
var downloadOk = await DownloadAmllTtmlDbIndexAsync();
|
||||
if (!downloadOk || !File.Exists(AppInfo.AmllTtmlDbIndexPath))
|
||||
return null;
|
||||
}
|
||||
|
||||
string? rawLyricFile = null;
|
||||
foreach (var line in File.ReadLines(AppInfo.AmllTtmlDbIndexPath))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(line);
|
||||
var root = doc.RootElement;
|
||||
if (!root.TryGetProperty("metadata", out var metadataArr))
|
||||
continue;
|
||||
string? musicName = null;
|
||||
string? artists = null;
|
||||
foreach (var meta in metadataArr.EnumerateArray())
|
||||
{
|
||||
if (meta.GetArrayLength() != 2)
|
||||
continue;
|
||||
var key = meta[0].GetString();
|
||||
var valueArr = meta[1];
|
||||
if (key == "musicName" && valueArr.GetArrayLength() > 0)
|
||||
musicName = valueArr[0].GetString();
|
||||
if (key == "artists" && valueArr.GetArrayLength() > 0)
|
||||
artists = valueArr[0].GetString();
|
||||
}
|
||||
if (musicName == null || artists == null)
|
||||
continue;
|
||||
|
||||
if (MusicMatch($"{artists} - {musicName}", title, artist))
|
||||
{
|
||||
if (root.TryGetProperty("rawLyricFile", out var rawLyricFileProp))
|
||||
{
|
||||
rawLyricFile = rawLyricFileProp.GetString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawLyricFile))
|
||||
return null;
|
||||
|
||||
// 下载歌词内容
|
||||
var url =
|
||||
$"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
|
||||
try
|
||||
{
|
||||
var response = await _amllTtmlDbHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载 amll-ttml-db 的 JSONL 索引文件到本地缓存目录
|
||||
/// </summary>
|
||||
/// <returns>下载成功返回 true,否则 false</returns>
|
||||
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
|
||||
{
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(
|
||||
string title,
|
||||
string artist,
|
||||
string lyrics,
|
||||
LyricsFormat format,
|
||||
string cacheFolderPath
|
||||
)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(
|
||||
cacheFolderPath,
|
||||
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
|
||||
);
|
||||
File.WriteAllText(cacheFilePath, lyrics);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
// 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 Microsoft.UI.Dispatching;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Media.Control;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="PlaybackService" />
|
||||
/// </summary>
|
||||
public partial class PlaybackService : IPlaybackService
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _dispatcherQueue
|
||||
/// </summary>
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _musicSearchService
|
||||
/// </summary>
|
||||
private readonly IMusicSearchService _musicSearchService;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _currentSession
|
||||
/// </summary>
|
||||
private GlobalSystemMediaTransportControlsSession? _currentSession = null;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _sessionManager
|
||||
/// </summary>
|
||||
private GlobalSystemMediaTransportControlsSessionManager? _sessionManager = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <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
|
||||
)
|
||||
{
|
||||
_musicSearchService = musicSearchService;
|
||||
InitMediaManager().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
#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
|
||||
)
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
|
||||
if (sender == null)
|
||||
{
|
||||
SongInfo = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
mediaProps = await sender.TryGetMediaPropertiesAsync();
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
if (mediaProps == null)
|
||||
{
|
||||
SongInfo = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SongInfo = new SongInfo
|
||||
{
|
||||
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";
|
||||
}
|
||||
|
||||
if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
SongInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(streamReference);
|
||||
}
|
||||
else
|
||||
{
|
||||
SongInfo.AlbumArt = _musicSearchService.SearchAlbumArtAsync(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist
|
||||
);
|
||||
|
||||
if (SongInfo.AlbumArt == null)
|
||||
{
|
||||
SongInfo.AlbumArt = await ImageHelper.CreateTextPlaceholderBytesAsync(
|
||||
$"{SongInfo.Artist} - {SongInfo.Title}",
|
||||
400,
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: Non-UI thread
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
private void CurrentSession_PlaybackInfoChanged(
|
||||
GlobalSystemMediaTransportControlsSession? sender,
|
||||
PlaybackInfoChangedEventArgs? args
|
||||
)
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
IsPlaying = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var playbackState = sender.GetPlaybackInfo().PlaybackStatus;
|
||||
// _logger.LogDebug(playbackState.ToString());
|
||||
|
||||
switch (playbackState)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
_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
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.Settings
|
||||
{
|
||||
public static class SettingsDefaultValues
|
||||
{
|
||||
public const bool IsFirstRun = true;
|
||||
|
||||
// Theme
|
||||
public const int ThemeType = 0; // Follow system
|
||||
|
||||
// Language
|
||||
public const int Language = 0; // Default
|
||||
|
||||
// Music
|
||||
public const string MusicLibraries = "[]";
|
||||
|
||||
// Backdrop
|
||||
public const int BackdropType = 5; // Acrylic Base
|
||||
public const bool IsCoverOverlayEnabled = true;
|
||||
public const bool IsDynamicCoverOverlay = true;
|
||||
public const int CoverOverlayOpacity = 100; // 1.0
|
||||
public const int CoverOverlayBlurAmount = 200;
|
||||
|
||||
// Title bar
|
||||
public const int TitleBarType = 0;
|
||||
|
||||
// Album art
|
||||
public const int CoverImageRadius = 24;
|
||||
|
||||
// Lyrics
|
||||
public const int LyricsAlignmentType = 1; // Center
|
||||
public const int LyricsBlurAmount = 0;
|
||||
public const int LyricsVerticalEdgeOpacity = 0; // 0.0
|
||||
public const float LyricsLineSpacingFactor = 0.5f;
|
||||
public const int LyricsFontSize = 28;
|
||||
public const bool IsLyricsGlowEffectEnabled = false;
|
||||
public const bool IsLyricsDynamicGlowEffectEnabled = false;
|
||||
public const int LyricsFontColorType = 0; // Default
|
||||
public const int LyricsFontSelectedAccentColorIndex = 0;
|
||||
|
||||
// Notification
|
||||
public const bool NeverShowEnterFullScreenMessage = false;
|
||||
public const bool NeverShowEnterImmersiveModeMessage = false;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.Settings
|
||||
{
|
||||
public static class SettingsKeys
|
||||
{
|
||||
public const string IsFirstRun = "IsFirstRun";
|
||||
|
||||
// Theme
|
||||
public const string ThemeType = "ThemeType";
|
||||
|
||||
// Language
|
||||
public const string Language = "Language";
|
||||
|
||||
// Music
|
||||
public const string MusicLibraries = "MusicLibraries";
|
||||
|
||||
// Backdrop
|
||||
public const string BackdropType = "BackdropType";
|
||||
public const string IsCoverOverlayEnabled = "IsCoverOverlayEnabled";
|
||||
public const string IsDynamicCoverOverlay = "IsDynamicCoverOverlay";
|
||||
public const string CoverOverlayOpacity = "CoverOverlayOpacity";
|
||||
public const string CoverOverlayBlurAmount = "CoverOverlayBlurAmount";
|
||||
|
||||
// Title bar
|
||||
public const string TitleBarType = "TitleBarType";
|
||||
|
||||
// Album art
|
||||
public const string CoverImageRadius = "CoverImageRadius";
|
||||
|
||||
// Lyrics
|
||||
public const string LyricsAlignmentType = "LyricsAlignmentType";
|
||||
public const string LyricsBlurAmount = "LyricsBlurAmount";
|
||||
public const string LyricsVerticalEdgeOpacity = "LyricsVerticalEdgeOpacity";
|
||||
public const string LyricsLineSpacingFactor = "LyricsLineSpacingFactor";
|
||||
public const string LyricsFontSize = "LyricsFontSize";
|
||||
public const string IsLyricsGlowEffectEnabled = "IsLyricsGlowEffectEnabled";
|
||||
public const string IsLyricsDynamicGlowEffectEnabled = "IsLyricsDynamicGlowEffectEnabled";
|
||||
public const string LyricsFontColorType = "LyricsFontColorType";
|
||||
public const string LyricsFontSelectedAccentColorIndex =
|
||||
"LyricsFontSelectedAccentColorIndex";
|
||||
|
||||
// Notification
|
||||
public const string NeverShowEnterFullScreenMessage = "NeverShowEnterFullScreenMessage";
|
||||
public const string NeverShowEnterImmersiveModeMessage =
|
||||
"NeverShowEnterImmersiveModeMessage";
|
||||
}
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using DevWinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Newtonsoft.Json;
|
||||
using Windows.Globalization;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.Settings
|
||||
{
|
||||
public partial class SettingsService : ObservableObject
|
||||
{
|
||||
private readonly ApplicationDataContainer _localSettings;
|
||||
|
||||
public SettingsService()
|
||||
{
|
||||
_localSettings = ApplicationData.Current.LocalSettings;
|
||||
|
||||
_musicLibraries =
|
||||
[
|
||||
.. JsonConvert.DeserializeObject<List<string>>(
|
||||
Get(SettingsKeys.MusicLibraries, SettingsDefaultValues.MusicLibraries)!
|
||||
)!,
|
||||
];
|
||||
|
||||
_musicLibraries.CollectionChanged += (_, _) => SaveMusicLibraries();
|
||||
}
|
||||
|
||||
public bool IsFirstRun
|
||||
{
|
||||
get => Get(SettingsKeys.IsFirstRun, SettingsDefaultValues.IsFirstRun);
|
||||
set => Set(SettingsKeys.IsFirstRun, value);
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isRebuildingLyricsIndexDatabase = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isImmersiveMode = false;
|
||||
|
||||
// Theme
|
||||
public int Theme
|
||||
{
|
||||
get => Get(SettingsKeys.ThemeType, SettingsDefaultValues.ThemeType);
|
||||
set
|
||||
{
|
||||
Set(SettingsKeys.ThemeType, value);
|
||||
WeakReferenceMessenger.Default.Send(new ThemeChangedMessage((ElementTheme)value));
|
||||
}
|
||||
}
|
||||
|
||||
// Music
|
||||
private ObservableCollection<string> _musicLibraries;
|
||||
|
||||
public ObservableCollection<string> MusicLibraries
|
||||
{
|
||||
get { return _musicLibraries; }
|
||||
set
|
||||
{
|
||||
if (_musicLibraries != null)
|
||||
{
|
||||
_musicLibraries.CollectionChanged -= (_, _) => SaveMusicLibraries();
|
||||
}
|
||||
|
||||
_musicLibraries = value;
|
||||
_musicLibraries.CollectionChanged += (_, _) => SaveMusicLibraries();
|
||||
SaveMusicLibraries();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveMusicLibraries()
|
||||
{
|
||||
Set(SettingsKeys.MusicLibraries, JsonConvert.SerializeObject(MusicLibraries.ToList()));
|
||||
}
|
||||
|
||||
// Language
|
||||
public int Language
|
||||
{
|
||||
get => Get(SettingsKeys.Language, SettingsDefaultValues.Language);
|
||||
set
|
||||
{
|
||||
Set(SettingsKeys.Language, value);
|
||||
switch ((Models.Language)Language)
|
||||
{
|
||||
case Models.Language.FollowSystem:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "";
|
||||
break;
|
||||
case Models.Language.English:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "en-US";
|
||||
break;
|
||||
case Models.Language.SimplifiedChinese:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "zh-CN";
|
||||
break;
|
||||
case Models.Language.TraditionalChinese:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "zh-TW";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backdrop
|
||||
public int BackdropType
|
||||
{
|
||||
get => Get(SettingsKeys.BackdropType, SettingsDefaultValues.BackdropType);
|
||||
set
|
||||
{
|
||||
Set(SettingsKeys.BackdropType, value);
|
||||
WeakReferenceMessenger.Default.Send(
|
||||
new SystemBackdropChangedMessage((BackdropType)value)
|
||||
);
|
||||
}
|
||||
}
|
||||
public bool IsCoverOverlayEnabled
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.IsCoverOverlayEnabled,
|
||||
SettingsDefaultValues.IsCoverOverlayEnabled
|
||||
);
|
||||
set => Set(SettingsKeys.IsCoverOverlayEnabled, value);
|
||||
}
|
||||
public bool IsDynamicCoverOverlay
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.IsDynamicCoverOverlay,
|
||||
SettingsDefaultValues.IsDynamicCoverOverlay
|
||||
);
|
||||
set => Set(SettingsKeys.IsDynamicCoverOverlay, value);
|
||||
}
|
||||
public int CoverOverlayOpacity
|
||||
{
|
||||
get => Get(SettingsKeys.CoverOverlayOpacity, SettingsDefaultValues.CoverOverlayOpacity);
|
||||
set => Set(SettingsKeys.CoverOverlayOpacity, value);
|
||||
}
|
||||
public int CoverOverlayBlurAmount
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.CoverOverlayBlurAmount,
|
||||
SettingsDefaultValues.CoverOverlayBlurAmount
|
||||
);
|
||||
set => Set(SettingsKeys.CoverOverlayBlurAmount, value);
|
||||
}
|
||||
|
||||
// Title bar
|
||||
public int TitleBarType
|
||||
{
|
||||
get => Get(SettingsKeys.TitleBarType, SettingsDefaultValues.TitleBarType);
|
||||
set => Set(SettingsKeys.TitleBarType, value);
|
||||
}
|
||||
|
||||
// Album art
|
||||
public int CoverImageRadius
|
||||
{
|
||||
get => Get(SettingsKeys.CoverImageRadius, SettingsDefaultValues.CoverImageRadius);
|
||||
set => Set(SettingsKeys.CoverImageRadius, value);
|
||||
}
|
||||
|
||||
// Lyrics
|
||||
public int LyricsAlignmentType
|
||||
{
|
||||
get => Get(SettingsKeys.LyricsAlignmentType, SettingsDefaultValues.LyricsAlignmentType);
|
||||
set => Set(SettingsKeys.LyricsAlignmentType, value);
|
||||
}
|
||||
public int LyricsBlurAmount
|
||||
{
|
||||
get => Get(SettingsKeys.LyricsBlurAmount, SettingsDefaultValues.LyricsBlurAmount);
|
||||
set => Set(SettingsKeys.LyricsBlurAmount, value);
|
||||
}
|
||||
public int LyricsVerticalEdgeOpacity
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.LyricsVerticalEdgeOpacity,
|
||||
SettingsDefaultValues.LyricsVerticalEdgeOpacity
|
||||
);
|
||||
set => Set(SettingsKeys.LyricsVerticalEdgeOpacity, value);
|
||||
}
|
||||
public float LyricsLineSpacingFactor
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.LyricsLineSpacingFactor,
|
||||
SettingsDefaultValues.LyricsLineSpacingFactor
|
||||
);
|
||||
set => Set(SettingsKeys.LyricsLineSpacingFactor, value);
|
||||
}
|
||||
public int LyricsFontSize
|
||||
{
|
||||
get => Get(SettingsKeys.LyricsFontSize, SettingsDefaultValues.LyricsFontSize);
|
||||
set => Set(SettingsKeys.LyricsFontSize, value);
|
||||
}
|
||||
public bool IsLyricsGlowEffectEnabled
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.IsLyricsGlowEffectEnabled,
|
||||
SettingsDefaultValues.IsLyricsGlowEffectEnabled
|
||||
);
|
||||
set => Set(SettingsKeys.IsLyricsGlowEffectEnabled, value);
|
||||
}
|
||||
public bool IsLyricsDynamicGlowEffectEnabled
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.IsLyricsDynamicGlowEffectEnabled,
|
||||
SettingsDefaultValues.IsLyricsDynamicGlowEffectEnabled
|
||||
);
|
||||
set => Set(SettingsKeys.IsLyricsDynamicGlowEffectEnabled, value);
|
||||
}
|
||||
public int LyricsFontColorType
|
||||
{
|
||||
get => Get(SettingsKeys.LyricsFontColorType, SettingsDefaultValues.LyricsFontColorType);
|
||||
set => Set(SettingsKeys.LyricsFontColorType, value);
|
||||
}
|
||||
public int LyricsFontSelectedAccentColorIndex
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.LyricsFontSelectedAccentColorIndex,
|
||||
SettingsDefaultValues.LyricsFontSelectedAccentColorIndex
|
||||
);
|
||||
set
|
||||
{
|
||||
if (value >= 0)
|
||||
Set(SettingsKeys.LyricsFontSelectedAccentColorIndex, value);
|
||||
}
|
||||
}
|
||||
|
||||
//Notification
|
||||
public bool NeverShowEnterFullScreenMessage
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.NeverShowEnterFullScreenMessage,
|
||||
SettingsDefaultValues.NeverShowEnterFullScreenMessage
|
||||
);
|
||||
set => Set(SettingsKeys.NeverShowEnterFullScreenMessage, value);
|
||||
}
|
||||
public bool NeverShowEnterImmersiveModeMessage
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.NeverShowEnterImmersiveModeMessage,
|
||||
SettingsDefaultValues.NeverShowEnterImmersiveModeMessage
|
||||
);
|
||||
set => Set(SettingsKeys.NeverShowEnterImmersiveModeMessage, value);
|
||||
}
|
||||
|
||||
// Utils
|
||||
|
||||
private T? Get<T>(string key, T? defaultValue = default)
|
||||
{
|
||||
if (_localSettings.Values.TryGetValue(key, out object? value))
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private void Set<T>(string key, T value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
_localSettings.Values[key] = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="SettingsService" />
|
||||
/// </summary>
|
||||
public class SettingsService : ISettingsService
|
||||
{
|
||||
#region Constants
|
||||
|
||||
// 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 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 IsDynamicCoverOverlayEnabledKey = "IsDynamicCoverOverlayEnabled";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the IsFirstRunKey
|
||||
/// </summary>
|
||||
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 LyricsBlurAmountKey = "LyricsBlurAmount";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LyricsFontColorTypeKey
|
||||
/// </summary>
|
||||
private const string LyricsFontColorTypeKey = "LyricsFontColorType";
|
||||
|
||||
/// <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
|
||||
|
||||
/// <summary>
|
||||
/// Defines the ThemeTypeKey
|
||||
/// </summary>
|
||||
private const string ThemeTypeKey = "ThemeType";
|
||||
|
||||
/// <summary>
|
||||
/// Defines the TitleBarTypeKey
|
||||
/// </summary>
|
||||
private const string TitleBarTypeKey = "TitleBarType";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
/// <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;
|
||||
|
||||
SetDefault(IsFirstRunKey, true);
|
||||
// Lyrics lib
|
||||
SetDefault(LocalLyricsFoldersKey, "[]");
|
||||
SetDefault(
|
||||
LyricsSearchProvidersInfoKey,
|
||||
System.Text.Json.JsonSerializer.Serialize(
|
||||
Enum.GetValues<LyricsSearchProvider>()
|
||||
.Select(p => new LyricsSearchProviderInfo(p, true))
|
||||
.ToList(),
|
||||
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
|
||||
)
|
||||
);
|
||||
if (LyricsSearchProvidersInfo.Count != Enum.GetValues<LyricsSearchProvider>().Length)
|
||||
{
|
||||
LyricsSearchProvidersInfo = Enum.GetValues<LyricsSearchProvider>()
|
||||
.Select(p => new LyricsSearchProviderInfo(
|
||||
p,
|
||||
LyricsSearchProvidersInfo
|
||||
.Where(x => x.Provider == p)
|
||||
.FirstOrDefault()
|
||||
?.IsEnabled ?? true
|
||||
))
|
||||
.ToList();
|
||||
}
|
||||
// App appearance
|
||||
SetDefault(ThemeTypeKey, (int)ElementTheme.Default);
|
||||
SetDefault(LanguageKey, (int)Language.FollowSystem);
|
||||
SetDefault(BackdropTypeKey, (int)BackdropType.DesktopAcrylic);
|
||||
// App behavior
|
||||
SetDefault(AutoStartWindowTypeKey, (int)AutoStartWindowType.StandardMode);
|
||||
// Album art
|
||||
SetDefault(IsCoverOverlayEnabledKey, true);
|
||||
SetDefault(IsDynamicCoverOverlayEnabledKey, true);
|
||||
SetDefault(CoverOverlayOpacityKey, 75); // 100 % = 1.0
|
||||
SetDefault(CoverOverlayBlurAmountKey, 200);
|
||||
SetDefault(TitleBarTypeKey, (int)TitleBarType.Compact);
|
||||
SetDefault(CoverImageRadiusKey, 24); // 24 %
|
||||
// Lyrics
|
||||
SetDefault(LyricsAlignmentTypeKey, (int)LyricsAlignmentType.Center);
|
||||
SetDefault(LyricsFontWeightKey, (int)LyricsFontWeight.Bold);
|
||||
SetDefault(LyricsBlurAmountKey, 5);
|
||||
SetDefault(LyricsFontColorTypeKey, (int)LyricsFontColorType.Default);
|
||||
SetDefault(LyricsFontSizeKey, 28);
|
||||
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
|
||||
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
|
||||
SetDefault(IsLyricsGlowEffectEnabledKey, true);
|
||||
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.UntilCurrentChar);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <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
|
||||
{
|
||||
get => (BackdropType)GetValue<int>(BackdropTypeKey);
|
||||
set => SetValue(BackdropTypeKey, (int)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 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 =>
|
||||
System.Text.Json.JsonSerializer.Deserialize(
|
||||
GetValue<string>(LocalLyricsFoldersKey) ?? "[]",
|
||||
SourceGenerationContext.Default.ListLocalLyricsFolder
|
||||
)!;
|
||||
set =>
|
||||
SetValue(
|
||||
LocalLyricsFoldersKey,
|
||||
System.Text.Json.JsonSerializer.Serialize(
|
||||
value,
|
||||
SourceGenerationContext.Default.ListLocalLyricsFolder
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsAlignmentType
|
||||
/// </summary>
|
||||
public LyricsAlignmentType LyricsAlignmentType
|
||||
{
|
||||
get => (LyricsAlignmentType)GetValue<int>(LyricsAlignmentTypeKey);
|
||||
set => SetValue(LyricsAlignmentTypeKey, (int)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsBlurAmount
|
||||
/// </summary>
|
||||
public int LyricsBlurAmount
|
||||
{
|
||||
get => GetValue<int>(LyricsBlurAmountKey);
|
||||
set => SetValue(LyricsBlurAmountKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsFontColorType
|
||||
/// </summary>
|
||||
public LyricsFontColorType LyricsFontColorType
|
||||
{
|
||||
get => (LyricsFontColorType)GetValue<int>(LyricsFontColorTypeKey);
|
||||
set => SetValue(LyricsFontColorTypeKey, (int)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 =>
|
||||
System.Text.Json.JsonSerializer.Deserialize(
|
||||
GetValue<string>(LyricsSearchProvidersInfoKey) ?? "[]",
|
||||
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
|
||||
)!;
|
||||
set =>
|
||||
SetValue(
|
||||
LyricsSearchProvidersInfoKey,
|
||||
System.Text.Json.JsonSerializer.Serialize(
|
||||
value,
|
||||
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LyricsVerticalEdgeOpacity
|
||||
/// </summary>
|
||||
public int LyricsVerticalEdgeOpacity
|
||||
{
|
||||
get => GetValue<int>(LyricsVerticalEdgeOpacityKey);
|
||||
set => SetValue(LyricsVerticalEdgeOpacityKey, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ThemeType
|
||||
/// </summary>
|
||||
public ElementTheme ThemeType
|
||||
{
|
||||
get => (ElementTheme)GetValue<int>(ThemeTypeKey);
|
||||
set => SetValue(ThemeTypeKey, (int)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TitleBarType
|
||||
/// </summary>
|
||||
public TitleBarType TitleBarType
|
||||
{
|
||||
get => (TitleBarType)GetValue<int>(TitleBarTypeKey);
|
||||
set => SetValue(TitleBarTypeKey, (int)value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <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))
|
||||
{
|
||||
return (T)value;
|
||||
}
|
||||
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)
|
||||
return;
|
||||
_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
|
||||
}
|
||||
}
|
||||
@@ -121,10 +121,7 @@
|
||||
<value>Local music libraries</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>Add folders storing music or lyrics to build lyrics index database</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpenPath.Content" xml:space="preserve">
|
||||
<value>Open in file explorer</value>
|
||||
<value>Add folders storing music or lyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpenLogFolderButton.Content" xml:space="preserve">
|
||||
<value>Open in file explorer</value>
|
||||
@@ -223,14 +220,11 @@
|
||||
<value>Album art background opacity</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
<value>Settings - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageTitle" xml:space="preserve">
|
||||
<data name="LyricsPageTitle" xml:space="preserve">
|
||||
<value>BetterLyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>Lyrics style</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
|
||||
<value>Alignment</value>
|
||||
</data>
|
||||
@@ -273,45 +267,39 @@
|
||||
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>Font size</value>
|
||||
</data>
|
||||
<data name="MainWindowLyricsOnly.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Show lyrics only</value>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>Lyrics only</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Immersive mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>Lyrics effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>Album background</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Text" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>Lyrics library</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<value>App appearance</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>Glow effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDynamicGlowEffect.Header" xml:space="preserve">
|
||||
<value>Dynamic glow effect</value>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>Glow effect scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageRebuildDatabase.Header" xml:space="preserve">
|
||||
<value>Rebuild lyrics index database</value>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Configure lyrics search providers</value>
|
||||
</data>
|
||||
<data name="SettingsPageRebuildDatabaseButton.Content" xml:space="preserve">
|
||||
<value>Rebuild</value>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>Drag to sort, the lyrics search order will be in the following order</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>Add</value>
|
||||
</data>
|
||||
<data name="SettingsPageRebuildDatabaseDesc.Text" xml:space="preserve">
|
||||
<value>Rebuilding the database, please wait...</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>Welcome to BetterLyrics</value>
|
||||
</data>
|
||||
@@ -321,7 +309,7 @@
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>No music playing now</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Text" xml:space="preserve">
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>Developer options</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
@@ -330,8 +318,11 @@
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>Play using system player</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>Log</value>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>Cache</value>
|
||||
</data>
|
||||
<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>
|
||||
@@ -342,7 +333,7 @@
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>Album art accent color</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>Album art style</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
@@ -372,4 +363,172 @@
|
||||
<data name="BaseWindowHostInfoBarCheckBox.Content" xml:space="preserve">
|
||||
<value>Do not show this message again</value>
|
||||
</data>
|
||||
<data name="MainPageNoLocalFilesMatched.Text" xml:space="preserve">
|
||||
<value>No local files matched</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>Album art only</value>
|
||||
</data>
|
||||
<data name="MainPageSplitView.Content" xml:space="preserve">
|
||||
<value>Split view</value>
|
||||
</data>
|
||||
<data name="MainPageDisplayTypeSwitcher.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Change display type</value>
|
||||
</data>
|
||||
<data name="MainPageDesktopLyricsToggler.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Switch to desktop lyrics mode</value>
|
||||
</data>
|
||||
<data name="BaseWindowMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Picture-in-picture mode</value>
|
||||
</data>
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Exit picture-in-picture mode</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>Lyrics not found</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<value>Lyrics effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" 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">
|
||||
<value>App behavior</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
<value>When starting the app</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartInAppLyrics.Content" xml:space="preserve">
|
||||
<value>Activate standard mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>Activate dock mode</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>Lyrics not found</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>System tray - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Dock mode</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Desktop mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>Font weight</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>Thin</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraLight.Content" xml:space="preserve">
|
||||
<value>Extra Light</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLight.Content" xml:space="preserve">
|
||||
<value>Light</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiLight.Content" xml:space="preserve">
|
||||
<value>Semi Light</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsNormal.Content" xml:space="preserve">
|
||||
<value>Normal</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsMedium.Content" xml:space="preserve">
|
||||
<value>Medium</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiBold.Content" xml:space="preserve">
|
||||
<value>Semi Bold</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBold.Content" xml:space="preserve">
|
||||
<value>Bold</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBold.Content" xml:space="preserve">
|
||||
<value>Extra Bold</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlack.Content" xml:space="preserve">
|
||||
<value>Black</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>Extra Black</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>Whole lyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>Current line</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>Current char</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<value>Loading lyrics...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
<value>Local .LRC files</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>Local music files</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LRCLIB</value>
|
||||
</data>
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
<data name="SettingsPageKO.Content" xml:space="preserve">
|
||||
<value>한국어</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderEslrcFile" xml:space="preserve">
|
||||
<value>Local .ESLRC files</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderTtmlFile" xml:space="preserve">
|
||||
<value>Local .TTML files</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>This folder contains added folders, please delete these folders to add the folder</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQ" xml:space="preserve">
|
||||
<value>QQ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderNetease" xml:space="preserve">
|
||||
<value>Netease</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugou" xml:space="preserve">
|
||||
<value>Kugou</value>
|
||||
</data>
|
||||
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
|
||||
<value>Show debug overlay</value>
|
||||
</data>
|
||||
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
|
||||
<value>Dependencies</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Lock</value>
|
||||
</data>
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||
<value>Exit</value>
|
||||
</data>
|
||||
<data name="SystemTrayUnlock.Text" xml:space="preserve">
|
||||
<value>Unlock the window</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>Lock</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>To unlock after locking, go to the system tray to unlock</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,534 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>地元の音楽図書館</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>音楽や歌詞を保存するフォルダーを追加します</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpenLogFolderButton.Content" xml:space="preserve">
|
||||
<value>ファイルエクスプローラーで開きます</value>
|
||||
</data>
|
||||
<data name="SettingsPageRemovePath.Content" xml:space="preserve">
|
||||
<value>アプリから削除します</value>
|
||||
</data>
|
||||
<data name="SettingsPageRemoveInfo.Title" xml:space="preserve">
|
||||
<value>次のアイテムを削除しても安全です</value>
|
||||
</data>
|
||||
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
|
||||
<value>このパスの元のファイルとフォルダーは、このアプリから削除するときに削除されません</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolder.Header" xml:space="preserve">
|
||||
<value>フォルダーを追加します</value>
|
||||
</data>
|
||||
<data name="SettingsPageTheme.Header" xml:space="preserve">
|
||||
<value>テーマ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLanguage.Header" xml:space="preserve">
|
||||
<value>言語</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>システムをフォローします</value>
|
||||
</data>
|
||||
<data name="SettingsPageLight.Content" xml:space="preserve">
|
||||
<value>ライト</value>
|
||||
</data>
|
||||
<data name="SettingsPageDark.Content" xml:space="preserve">
|
||||
<value>暗い</value>
|
||||
</data>
|
||||
<data name="SettingsPageSC.Content" xml:space="preserve">
|
||||
<value>简体中文</value>
|
||||
</data>
|
||||
<data name="SettingsPageTC.Content" xml:space="preserve">
|
||||
<value>繁體中文</value>
|
||||
</data>
|
||||
<data name="SettingsPageEN.Content" xml:space="preserve">
|
||||
<value>English</value>
|
||||
</data>
|
||||
<data name="SettingsPageGitHub.Header" xml:space="preserve">
|
||||
<value>これはオープンソースアプリです</value>
|
||||
</data>
|
||||
<data name="SettingsPageGitHub.ActionIconToolTip" xml:space="preserve">
|
||||
<value>新しいウィンドウで開きます</value>
|
||||
</data>
|
||||
<data name="SettingsPageGitHub.Description" xml:space="preserve">
|
||||
<value>GitHubのソースコードを参照してください</value>
|
||||
</data>
|
||||
<data name="SettingsPageVersion.Text" xml:space="preserve">
|
||||
<value>バージョン</value>
|
||||
</data>
|
||||
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
|
||||
<value>なし</value>
|
||||
</data>
|
||||
<data name="SettingsPageMica.Content" xml:space="preserve">
|
||||
<value>雲母</value>
|
||||
</data>
|
||||
<data name="SettingsPageMicaAlt.Content" xml:space="preserve">
|
||||
<value>マイカル</value>
|
||||
</data>
|
||||
<data name="SettingsPageDesktopAcrylic.Content" xml:space="preserve">
|
||||
<value>デスクトップアクリル</value>
|
||||
</data>
|
||||
<data name="SettingsPageAcrylicBase.Content" xml:space="preserve">
|
||||
<value>アクリルベース</value>
|
||||
</data>
|
||||
<data name="SettingsPageAcrylicThin.Content" xml:space="preserve">
|
||||
<value>アクリル薄い</value>
|
||||
</data>
|
||||
<data name="SettingsPageTransparent.Content" xml:space="preserve">
|
||||
<value>透明</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
|
||||
<value>背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
|
||||
<value>デフォルト</value>
|
||||
</data>
|
||||
<data name="SettingsPageRestart.Content" xml:space="preserve">
|
||||
<value>変更を適用するためのアプリを再起動します</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathNotFound.Text" xml:space="preserve">
|
||||
<value>パスはコンピューターでは見つかりません</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
|
||||
<value>フォルダーが追加されました。二度と追加しないでください。</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
|
||||
<value>オーバーレイアルバムアートの背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
|
||||
<value>ダイナミックアルバムアートの背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
|
||||
<value>アルバムアートの背景不透明</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>設定 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LyricsPageTitle" xml:space="preserve">
|
||||
<value>BetterLyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
|
||||
<value>アライメント</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsCenter.Content" xml:space="preserve">
|
||||
<value>中心</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLeft.Content" xml:space="preserve">
|
||||
<value>左</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
|
||||
<value>右</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
|
||||
<value>アルバムアートバックグラウンドブラー量</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
|
||||
<value>ぼやけの量</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmountSideEffect.Text" xml:space="preserve">
|
||||
<value>この値を調整すると、アルバム画像のバックグラウンドブラー強度も増加します。</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>現在の値:</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>ぼかしが有効になっている場合のGPU使用量が大幅に高くなります(> 0)</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
|
||||
<value>この機能を有効にすると、GPUの使用率がわずかに増加します</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
|
||||
<value>上端と下端の不透明度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLineSpacingFactor.Header" xml:space="preserve">
|
||||
<value>ライン間隔</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLineSpacingFactorUnit.Text" xml:space="preserve">
|
||||
<value>x線の高さ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>フォントサイズ</value>
|
||||
</data>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>歌詞のみ</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>没入モード</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>アルバムの背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>について</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>歌詞ライブラリ</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<value>アプリの外観</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>グロー効果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>グローエフェクトスコープ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>歌詞検索プロバイダーを構成します</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>ドラッグしてソートすると、歌詞の検索注文は次の順序で行われます</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>追加</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>BetterLyrics へようこそ</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>今すぐ歌詞データベースをセットアップしましょう</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>今は音楽が再生されていません</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>開発者オプション</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>テスト音楽を再生します</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>システムプレーヤーを使用して再生します</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>キャッシュ</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||
<value>ログファイル、ネットワーク歌詞キャッシュを含む</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
|
||||
<value>フォントカラー</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
|
||||
<value>デフォルト</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>アルバムアートアクセントカラー</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>アルバムアートスタイル</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
<value>コーナー半径</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitleBarType.Header" xml:space="preserve">
|
||||
<value>タイトルバーサイズ</value>
|
||||
</data>
|
||||
<data name="SettingsPageCompactTitleBar.Content" xml:space="preserve">
|
||||
<value>コンパクト</value>
|
||||
</data>
|
||||
<data name="SettingsPageExtendedTitleBar.Content" xml:space="preserve">
|
||||
<value>拡張</value>
|
||||
</data>
|
||||
<data name="BaseWindowAOTFlyoutItem.Text" xml:space="preserve">
|
||||
<value>常にトップ</value>
|
||||
</data>
|
||||
<data name="BaseWindowFullScreenFlyoutItem.Text" xml:space="preserve">
|
||||
<value>全画面表示</value>
|
||||
</data>
|
||||
<data name="BaseWindowEnterFullScreenHint" xml:space="preserve">
|
||||
<value>ESCを押して、フルスクリーンモードを終了します</value>
|
||||
</data>
|
||||
<data name="MainPageEnterImmersiveModeHint" xml:space="preserve">
|
||||
<value>再びホバリングして、トグルボタンを表示します</value>
|
||||
</data>
|
||||
<data name="BaseWindowHostInfoBarCheckBox.Content" xml:space="preserve">
|
||||
<value>このメッセージを二度と見せないでください</value>
|
||||
</data>
|
||||
<data name="MainPageNoLocalFilesMatched.Text" xml:space="preserve">
|
||||
<value>一致するローカルファイルはありません</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>アルバムアートのみ</value>
|
||||
</data>
|
||||
<data name="MainPageSplitView.Content" xml:space="preserve">
|
||||
<value>分割ビュー</value>
|
||||
</data>
|
||||
<data name="MainPageDisplayTypeSwitcher.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>表示タイプを変更します</value>
|
||||
</data>
|
||||
<data name="MainPageDesktopLyricsToggler.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>デスクトップ歌詞モードに切り替えます</value>
|
||||
</data>
|
||||
<data name="BaseWindowMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ピクチャーインピクチャーモード</value>
|
||||
</data>
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ピクチャーインピクチャーモードを終了します</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>歌詞が見つかりません</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<value>歌詞効果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<value>歌詞スタイル</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>このフォルダーは既存のフォルダーに既に含まれており、再度追加する必要はありません</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<value>アプリの動作</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
<value>アプリを起動するとき</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartInAppLyrics.Content" xml:space="preserve">
|
||||
<value>標準モードをアクティブにします</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>ドックモードをアクティブにします</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>歌詞が見つかりません</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>システムトレイ - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ドックモード</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>デスクトップモード</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>フォント重量</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>薄い</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraLight.Content" xml:space="preserve">
|
||||
<value>余分な光</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLight.Content" xml:space="preserve">
|
||||
<value>ライト</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiLight.Content" xml:space="preserve">
|
||||
<value>半光</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsNormal.Content" xml:space="preserve">
|
||||
<value>普通</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsMedium.Content" xml:space="preserve">
|
||||
<value>中くらい</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiBold.Content" xml:space="preserve">
|
||||
<value>セミボールド</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBold.Content" xml:space="preserve">
|
||||
<value>大胆な</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBold.Content" xml:space="preserve">
|
||||
<value>余分な太字</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlack.Content" xml:space="preserve">
|
||||
<value>黒</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>余分な黒</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>歌詞全体</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>現在の行</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>現在の文字</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<value>歌詞の読み込み...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
<value>ローカル.LRCファイル</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>ローカル音楽ファイル</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LRCLIB</value>
|
||||
</data>
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
<data name="SettingsPageKO.Content" xml:space="preserve">
|
||||
<value>한국어</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderEslrcFile" xml:space="preserve">
|
||||
<value>ローカル.ESLRCファイル</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderTtmlFile" xml:space="preserve">
|
||||
<value>ローカル.TTMLファイル</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>このフォルダーには追加されたフォルダーが含まれています。これらのフォルダを削除してフォルダーを追加してください</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQ" xml:space="preserve">
|
||||
<value>QQ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderNetease" xml:space="preserve">
|
||||
<value>Netease</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugou" xml:space="preserve">
|
||||
<value>Kugou</value>
|
||||
</data>
|
||||
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
|
||||
<value>デバッグオーバーレイを表示します</value>
|
||||
</data>
|
||||
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
|
||||
<value>依存関係</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ロック</value>
|
||||
</data>
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>設定を開く</value>
|
||||
</data>
|
||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||
<value>プログラムを終了します</value>
|
||||
</data>
|
||||
<data name="SystemTrayUnlock.Text" xml:space="preserve">
|
||||
<value>ウィンドウのロックを解除します</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>ロック</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>ロック後にロックを解除するには、システムトレイに移動してロックを解除します</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,534 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>로컬 음악 도서관</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>음악이나 가사를 저장하는 폴더 추가</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpenLogFolderButton.Content" xml:space="preserve">
|
||||
<value>파일 탐색기에서 열립니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageRemovePath.Content" xml:space="preserve">
|
||||
<value>앱에서 제거하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageRemoveInfo.Title" xml:space="preserve">
|
||||
<value>다음 항목을 제거하는 것이 안전합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageRemoveInfo.Message" xml:space="preserve">
|
||||
<value>이 경로의 원본 파일과 폴더는이 앱에서 제거 할 때 삭제되지 않습니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolder.Header" xml:space="preserve">
|
||||
<value>폴더를 추가하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageTheme.Header" xml:space="preserve">
|
||||
<value>주제</value>
|
||||
</data>
|
||||
<data name="SettingsPageLanguage.Header" xml:space="preserve">
|
||||
<value>언어</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>시스템을 따르십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLight.Content" xml:space="preserve">
|
||||
<value>빛</value>
|
||||
</data>
|
||||
<data name="SettingsPageDark.Content" xml:space="preserve">
|
||||
<value>어두운</value>
|
||||
</data>
|
||||
<data name="SettingsPageSC.Content" xml:space="preserve">
|
||||
<value>简体中文</value>
|
||||
</data>
|
||||
<data name="SettingsPageTC.Content" xml:space="preserve">
|
||||
<value>繁體中文</value>
|
||||
</data>
|
||||
<data name="SettingsPageEN.Content" xml:space="preserve">
|
||||
<value>English</value>
|
||||
</data>
|
||||
<data name="SettingsPageGitHub.Header" xml:space="preserve">
|
||||
<value>이것은 오픈 소스 앱입니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageGitHub.ActionIconToolTip" xml:space="preserve">
|
||||
<value>새 창에서 열립니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageGitHub.Description" xml:space="preserve">
|
||||
<value>GitHub의 소스 코드를 참조하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageVersion.Text" xml:space="preserve">
|
||||
<value>버전</value>
|
||||
</data>
|
||||
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
|
||||
<value>없음</value>
|
||||
</data>
|
||||
<data name="SettingsPageMica.Content" xml:space="preserve">
|
||||
<value>운모</value>
|
||||
</data>
|
||||
<data name="SettingsPageMicaAlt.Content" xml:space="preserve">
|
||||
<value>운모 대체</value>
|
||||
</data>
|
||||
<data name="SettingsPageDesktopAcrylic.Content" xml:space="preserve">
|
||||
<value>데스크탑 아크릴</value>
|
||||
</data>
|
||||
<data name="SettingsPageAcrylicBase.Content" xml:space="preserve">
|
||||
<value>아크릴베이스</value>
|
||||
</data>
|
||||
<data name="SettingsPageAcrylicThin.Content" xml:space="preserve">
|
||||
<value>아크릴 얇은</value>
|
||||
</data>
|
||||
<data name="SettingsPageTransparent.Content" xml:space="preserve">
|
||||
<value>투명한</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
|
||||
<value>배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
|
||||
<value>기본</value>
|
||||
</data>
|
||||
<data name="SettingsPageRestart.Content" xml:space="preserve">
|
||||
<value>변경 사항을 적용하려면 앱을 다시 시작하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathNotFound.Text" xml:space="preserve">
|
||||
<value>경로는 컴퓨터에서 찾을 수 없습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
|
||||
<value>폴더가 추가되었습니다. 다시 추가하지 마십시오.</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
|
||||
<value>오버레이 앨범 아트 배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
|
||||
<value>동적 앨범 아트 배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
|
||||
<value>앨범 아트 배경 불투명도</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>설정 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LyricsPageTitle" xml:space="preserve">
|
||||
<value>BetterLyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
|
||||
<value>조정</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsCenter.Content" xml:space="preserve">
|
||||
<value>센터</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLeft.Content" xml:space="preserve">
|
||||
<value>왼쪽</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
|
||||
<value>오른쪽</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
|
||||
<value>앨범 아트 배경 흐림 금액</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
|
||||
<value>흐림 금액</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmountSideEffect.Text" xml:space="preserve">
|
||||
<value>이 값을 조정하면 앨범 이미지의 배경 흐림 강도가 증가합니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>현재 가치 :</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>Blur가 활성화 될 때 상당히 높은 GPU 사용량 (> 0)</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
|
||||
<value>이 기능을 활성화하면 GPU 사용률이 약간 증가합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
|
||||
<value>상단 및 하단 가장자리 불투명도</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLineSpacingFactor.Header" xml:space="preserve">
|
||||
<value>라인 간격</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLineSpacingFactorUnit.Text" xml:space="preserve">
|
||||
<value>X 라인 높이</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>글꼴 크기</value>
|
||||
</data>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>가사 만</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>몰입 형 모드</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>앨범 배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>에 대한</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>가사 도서관</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<value>앱 모양</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>글로우 효과</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>글로우 효과 범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>가사 검색 제공 업체를 구성하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>정렬하기 위해 드래그하면 가사 검색 순서는 다음 순서로됩니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>추가하다</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>Betterlyrics에 오신 것을 환영합니다</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>지금 가사 데이터베이스를 설정합시다</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>지금 음악이 재생되지 않습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>개발자 옵션</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>테스트 음악을 재생하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>시스템 플레이어를 사용하여 재생하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>은닉처</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||
<value>로그 파일, 네트워크 가사 캐시 포함</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
|
||||
<value>글꼴 색상</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
|
||||
<value>기본</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>앨범 아트 악센트 색상</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>앨범 아트 스타일</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
<value>코너 반경</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitleBarType.Header" xml:space="preserve">
|
||||
<value>제목 바 크기</value>
|
||||
</data>
|
||||
<data name="SettingsPageCompactTitleBar.Content" xml:space="preserve">
|
||||
<value>콤팩트</value>
|
||||
</data>
|
||||
<data name="SettingsPageExtendedTitleBar.Content" xml:space="preserve">
|
||||
<value>펼친</value>
|
||||
</data>
|
||||
<data name="BaseWindowAOTFlyoutItem.Text" xml:space="preserve">
|
||||
<value>항상 위에</value>
|
||||
</data>
|
||||
<data name="BaseWindowFullScreenFlyoutItem.Text" xml:space="preserve">
|
||||
<value>전체 화면</value>
|
||||
</data>
|
||||
<data name="BaseWindowEnterFullScreenHint" xml:space="preserve">
|
||||
<value>ESC를 눌러 전체 화면 모드를 종료하십시오</value>
|
||||
</data>
|
||||
<data name="MainPageEnterImmersiveModeHint" xml:space="preserve">
|
||||
<value>토글 버튼을 표시하려면 다시 다시 가져옵니다</value>
|
||||
</data>
|
||||
<data name="BaseWindowHostInfoBarCheckBox.Content" xml:space="preserve">
|
||||
<value>이 메시지를 다시 표시하지 마십시오</value>
|
||||
</data>
|
||||
<data name="MainPageNoLocalFilesMatched.Text" xml:space="preserve">
|
||||
<value>로컬 파일이 일치하지 않습니다</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>앨범 아트 만</value>
|
||||
</data>
|
||||
<data name="MainPageSplitView.Content" xml:space="preserve">
|
||||
<value>분할보기</value>
|
||||
</data>
|
||||
<data name="MainPageDisplayTypeSwitcher.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>디스플레이 유형을 변경하십시오</value>
|
||||
</data>
|
||||
<data name="MainPageDesktopLyricsToggler.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>데스크탑 가사 모드로 전환하십시오</value>
|
||||
</data>
|
||||
<data name="BaseWindowMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>사진 인당 모드</value>
|
||||
</data>
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Picture-in-Picture 모드 종료</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>가사를 찾을 수 없습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<value>가사 효과</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<value>가사 스타일</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>이 폴더는 이미 기존 폴더에 포함되어 있으며 다시 추가 할 필요가 없습니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<value>앱 동작</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
<value>앱을 시작할 때</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartInAppLyrics.Content" xml:space="preserve">
|
||||
<value>표준 모드를 활성화합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>도크 모드를 활성화하십시오</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>가사를 찾을 수 없습니다</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>시스템 트레이 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>도크 모드</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>데스크탑 모드</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>글꼴 무게</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>얇은</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraLight.Content" xml:space="preserve">
|
||||
<value>여분의 빛</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLight.Content" xml:space="preserve">
|
||||
<value>빛</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiLight.Content" xml:space="preserve">
|
||||
<value>반 빛</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsNormal.Content" xml:space="preserve">
|
||||
<value>정상</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsMedium.Content" xml:space="preserve">
|
||||
<value>중간</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiBold.Content" xml:space="preserve">
|
||||
<value>반 대담한</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBold.Content" xml:space="preserve">
|
||||
<value>용감한</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBold.Content" xml:space="preserve">
|
||||
<value>추가 대담한</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlack.Content" xml:space="preserve">
|
||||
<value>검은색</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>여분의 검은 색</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>전체 가사</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>현재 라인</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>현재 숯</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>설정</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<value>가사로드 ...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
<value>로컬 .LRC 파일</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>로컬 음악 파일</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LRCLIB</value>
|
||||
</data>
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
<data name="SettingsPageKO.Content" xml:space="preserve">
|
||||
<value>한국어</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderEslrcFile" xml:space="preserve">
|
||||
<value>로컬 .ESLRC 파일</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderTtmlFile" xml:space="preserve">
|
||||
<value>로컬 .TTML 파일</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>이 폴더에는 추가 된 폴더가 포함되어 있습니다. 폴더를 추가하려면이 폴더를 삭제하십시오.</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQ" xml:space="preserve">
|
||||
<value>QQ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderNetease" xml:space="preserve">
|
||||
<value>Netease</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugou" xml:space="preserve">
|
||||
<value>Kugou</value>
|
||||
</data>
|
||||
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
|
||||
<value>디버그 오버레이를 표시하십시오</value>
|
||||
</data>
|
||||
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
|
||||
<value>의존성</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughFlyoutItem.Text" xml:space="preserve">
|
||||
<value>잠금</value>
|
||||
</data>
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>열기 설정</value>
|
||||
</data>
|
||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||
<value>프로그램을 종료하십시오</value>
|
||||
</data>
|
||||
<data name="SystemTrayUnlock.Text" xml:space="preserve">
|
||||
<value>창을 잠금 해제하십시오</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>잠금</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>잠금 잠금을 해제하려면 시스템 트레이로 이동하여 잠금을 해제하십시오.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -121,10 +121,7 @@
|
||||
<value>本地音乐媒体库</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>添加存放音乐或歌词的文件夹以构建歌词索引数据库</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpenPath.Content" xml:space="preserve">
|
||||
<value>在文件资源管理器中打开</value>
|
||||
<value>添加存放音乐或歌词的文件夹</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpenLogFolderButton.Content" xml:space="preserve">
|
||||
<value>在文件资源管理器中打开</value>
|
||||
@@ -223,14 +220,11 @@
|
||||
<value>专辑图片背景不透明度</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>设置</value>
|
||||
<value>设置 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageTitle" xml:space="preserve">
|
||||
<data name="LyricsPageTitle" xml:space="preserve">
|
||||
<value>BetterLyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>歌词样式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
|
||||
<value>对齐方式</value>
|
||||
</data>
|
||||
@@ -273,45 +267,39 @@
|
||||
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>字体大小</value>
|
||||
</data>
|
||||
<data name="MainWindowLyricsOnly.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>仅展示歌词</value>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>仅显示歌词</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>沉浸模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>歌词效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>专辑背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>关于</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Text" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>歌词库</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<value>应用外观</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>辉光效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDynamicGlowEffect.Header" xml:space="preserve">
|
||||
<value>动态辉光效果</value>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>辉光效果作用范围</value>
|
||||
</data>
|
||||
<data name="SettingsPageRebuildDatabase.Header" xml:space="preserve">
|
||||
<value>重构歌词索引数据库</value>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置歌词搜索服务</value>
|
||||
</data>
|
||||
<data name="SettingsPageRebuildDatabaseButton.Content" xml:space="preserve">
|
||||
<value>重构</value>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>拖动排序,歌词搜索顺序将按以下顺序</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>添加</value>
|
||||
</data>
|
||||
<data name="SettingsPageRebuildDatabaseDesc.Text" xml:space="preserve">
|
||||
<value>重构数据库中,请稍候...</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>欢迎使用 BetterLyrics</value>
|
||||
</data>
|
||||
@@ -321,7 +309,7 @@
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>当前没有正在播放的音乐</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Text" xml:space="preserve">
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>开发者选项</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
@@ -330,8 +318,11 @@
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>使用系统播放器播放</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>日志</value>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>缓存</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||
<value>包括日志文件,网络歌词缓存</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
|
||||
<value>字体颜色</value>
|
||||
@@ -342,7 +333,7 @@
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>专辑强调色</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>专辑封面样式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
@@ -370,6 +361,174 @@
|
||||
<value>再次悬停以显示切换按钮</value>
|
||||
</data>
|
||||
<data name="BaseWindowHostInfoBarCheckBox.Content" xml:space="preserve">
|
||||
<value> 不再显示此消息</value>
|
||||
<value>不再显示此消息</value>
|
||||
</data>
|
||||
<data name="MainPageNoLocalFilesMatched.Text" xml:space="preserve">
|
||||
<value>找不到匹配的本地文件</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>仅显示专辑封面</value>
|
||||
</data>
|
||||
<data name="MainPageSplitView.Content" xml:space="preserve">
|
||||
<value>分屏视图</value>
|
||||
</data>
|
||||
<data name="MainPageDisplayTypeSwitcher.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>切换显示模式</value>
|
||||
</data>
|
||||
<data name="MainPageDesktopLyricsToggler.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>切换到桌面歌词模式</value>
|
||||
</data>
|
||||
<data name="BaseWindowMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>画中画模式</value>
|
||||
</data>
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>退出画中画模式</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>未找到歌词</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<value>歌词动效</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<value>歌词样式</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>该文件夹已包含在已有文件夹中,无需再次添加</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<value>应用行为</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
<value>启动应用时</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartInAppLyrics.Content" xml:space="preserve">
|
||||
<value>启动标准模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>启动停靠模式</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>未找到歌词</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>系统托盘 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>字体粗细</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>极细</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraLight.Content" xml:space="preserve">
|
||||
<value>超细</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLight.Content" xml:space="preserve">
|
||||
<value>细体</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiLight.Content" xml:space="preserve">
|
||||
<value>次细</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsNormal.Content" xml:space="preserve">
|
||||
<value>常规</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsMedium.Content" xml:space="preserve">
|
||||
<value>中等</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiBold.Content" xml:space="preserve">
|
||||
<value>次粗</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBold.Content" xml:space="preserve">
|
||||
<value>粗体</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBold.Content" xml:space="preserve">
|
||||
<value>特粗</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlack.Content" xml:space="preserve">
|
||||
<value>黑体</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>超黑</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>全部歌词</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>当前行</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>当前字符</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>设置</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<value>加载歌词中...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
<value>本地 .LRC 文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>本地音乐文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LRCLIB</value>
|
||||
</data>
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
<data name="SettingsPageKO.Content" xml:space="preserve">
|
||||
<value>한국어</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderEslrcFile" xml:space="preserve">
|
||||
<value>本地 .ESLRC 文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderTtmlFile" xml:space="preserve">
|
||||
<value>本地 .TTML 文件</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>该文件夹包含已添加文件夹,请删除这些文件夹以添加该文件夹</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQ" xml:space="preserve">
|
||||
<value>QQ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderNetease" xml:space="preserve">
|
||||
<value>Netease</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugou" xml:space="preserve">
|
||||
<value>Kugou</value>
|
||||
</data>
|
||||
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
|
||||
<value>显示调试覆盖层</value>
|
||||
</data>
|
||||
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
|
||||
<value>依赖</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughFlyoutItem.Text" xml:space="preserve">
|
||||
<value>锁定</value>
|
||||
</data>
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>打开设置</value>
|
||||
</data>
|
||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||
<value>退出程序</value>
|
||||
</data>
|
||||
<data name="SystemTrayUnlock.Text" xml:space="preserve">
|
||||
<value>解锁窗口</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>锁定</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>锁定后解锁,请转到系统托盘解锁</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -121,10 +121,7 @@
|
||||
<value>本地音樂媒體庫</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>新增存放音樂或歌詞的資料夾以建立歌詞索引資料庫</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpenPath.Content" xml:space="preserve">
|
||||
<value>在檔案總管中開啟</value>
|
||||
<value>新增存放音樂或歌詞的資料夾</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpenLogFolderButton.Content" xml:space="preserve">
|
||||
<value>在檔案總管中開啟</value>
|
||||
@@ -223,14 +220,11 @@
|
||||
<value>專輯圖片背景不透明度</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
<value>設定 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageTitle" xml:space="preserve">
|
||||
<data name="LyricsPageTitle" xml:space="preserve">
|
||||
<value>BetterLyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>歌詞樣式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
|
||||
<value>對齊方式</value>
|
||||
</data>
|
||||
@@ -273,45 +267,39 @@
|
||||
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>字體大小</value>
|
||||
</data>
|
||||
<data name="MainWindowLyricsOnly.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>僅展示歌詞</value>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>僅顯示歌詞</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>沉浸模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>歌詞效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>專輯背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>關於</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLib.Text" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>歌詞庫</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<value>應用外觀</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>輝光效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDynamicGlowEffect.Header" xml:space="preserve">
|
||||
<value>動態輝光效果</value>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>輝光效果作用範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageRebuildDatabase.Header" xml:space="preserve">
|
||||
<value>重構歌詞索引資料庫</value>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置歌詞搜尋服務</value>
|
||||
</data>
|
||||
<data name="SettingsPageRebuildDatabaseButton.Content" xml:space="preserve">
|
||||
<value>重構</value>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
|
||||
<value>拖動排序,歌詞搜索順序將按以下順序</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>添加</value>
|
||||
</data>
|
||||
<data name="SettingsPageRebuildDatabaseDesc.Text" xml:space="preserve">
|
||||
<value>重構資料庫中,請稍候...</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>歡迎使用 BetterLyrics</value>
|
||||
</data>
|
||||
@@ -321,7 +309,7 @@
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>目前沒有正在播放的音樂</value>
|
||||
</data>
|
||||
<data name="SettingsPageDev.Text" xml:space="preserve">
|
||||
<data name="SettingsPageDev.Content" xml:space="preserve">
|
||||
<value>開發者選項</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
@@ -330,8 +318,11 @@
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>使用系統播放器播放</value>
|
||||
</data>
|
||||
<data name="SettingsPageLog.Header" xml:space="preserve">
|
||||
<value>紀錄</value>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>快取</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||
<value>包括日誌文件,網絡歌詞緩存</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
|
||||
<value>字體顏色</value>
|
||||
@@ -342,7 +333,7 @@
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>專輯強調色</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>專輯封面樣式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
@@ -372,4 +363,172 @@
|
||||
<data name="BaseWindowHostInfoBarCheckBox.Content" xml:space="preserve">
|
||||
<value>不再顯示此訊息</value>
|
||||
</data>
|
||||
<data name="MainPageNoLocalFilesMatched.Text" xml:space="preserve">
|
||||
<value>找不到匹配的本地文件</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>僅顯示專輯封面</value>
|
||||
</data>
|
||||
<data name="MainPageSplitView.Content" xml:space="preserve">
|
||||
<value>分割畫面視圖</value>
|
||||
</data>
|
||||
<data name="MainPageDisplayTypeSwitcher.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>切換顯示模式</value>
|
||||
</data>
|
||||
<data name="MainPageDesktopLyricsToggler.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>切換到桌面歌詞模式</value>
|
||||
</data>
|
||||
<data name="BaseWindowMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>畫中畫模式</value>
|
||||
</data>
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>退出畫中畫模式</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>找不到歌詞</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<value>歌詞動效</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<value>歌詞樣式</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>該資料夾已包含在已有資料夾中,無需再次添加</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<value>應用行為</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
<value>啟動應用程式時</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartInAppLyrics.Content" xml:space="preserve">
|
||||
<value>啟動標準模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>啟動停靠模式</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>找不到歌詞</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>系統托盤 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>字體粗細</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>極細</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraLight.Content" xml:space="preserve">
|
||||
<value>超細</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLight.Content" xml:space="preserve">
|
||||
<value>細體</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiLight.Content" xml:space="preserve">
|
||||
<value>次細</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsNormal.Content" xml:space="preserve">
|
||||
<value>常規</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsMedium.Content" xml:space="preserve">
|
||||
<value>中等</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiBold.Content" xml:space="preserve">
|
||||
<value>次粗</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBold.Content" xml:space="preserve">
|
||||
<value>粗體</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBold.Content" xml:space="preserve">
|
||||
<value>特粗</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlack.Content" xml:space="preserve">
|
||||
<value>黑體</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>超黑</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeWholeLyrics.Content" xml:space="preserve">
|
||||
<value>全部歌詞</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentLine.Content" xml:space="preserve">
|
||||
<value>目前行</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScopeCurrentChar.Content" xml:space="preserve">
|
||||
<value>目前字元</value>
|
||||
</data>
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<value>載入歌詞中...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
<value>本地 .lRC 文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>本地音樂文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LRCLIB</value>
|
||||
</data>
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
<data name="SettingsPageKO.Content" xml:space="preserve">
|
||||
<value>한국어</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderEslrcFile" xml:space="preserve">
|
||||
<value>本地 .ESLRC 文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderTtmlFile" xml:space="preserve">
|
||||
<value>本地 .TTML 文件</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>該文件夾包含已添加文件夾,請刪除這些文件夾以添加該文件夾</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQ" xml:space="preserve">
|
||||
<value>QQ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderNetease" xml:space="preserve">
|
||||
<value>Netease</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugou" xml:space="preserve">
|
||||
<value>Kugou</value>
|
||||
</data>
|
||||
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
|
||||
<value>顯示調試覆蓋層</value>
|
||||
</data>
|
||||
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
|
||||
<value>依賴</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughFlyoutItem.Text" xml:space="preserve">
|
||||
<value>鎖定</value>
|
||||
</data>
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>打開設置</value>
|
||||
</data>
|
||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||
<value>退出程序</value>
|
||||
</data>
|
||||
<data name="SystemTrayUnlock.Text" xml:space="preserve">
|
||||
<value>解鎖窗口</value>
|
||||
</data>
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>鎖定</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>鎖定後解鎖,請轉到系統托盤解鎖</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,57 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class BaseWindowModel : ObservableObject
|
||||
{
|
||||
public SettingsService SettingsService { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private int _titleBarFontSize = 11;
|
||||
|
||||
[ObservableProperty]
|
||||
private Notification _notification = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _showInfoBar = false;
|
||||
|
||||
public BaseWindowModel(SettingsService settingsService)
|
||||
{
|
||||
SettingsService = settingsService;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SwitchInfoBarNeverShowItAgainCheckBox(bool value)
|
||||
{
|
||||
switch (Notification.RelatedSettingsKeyName)
|
||||
{
|
||||
case SettingsKeys.NeverShowEnterFullScreenMessage:
|
||||
SettingsService.NeverShowEnterFullScreenMessage = value;
|
||||
break;
|
||||
case SettingsKeys.NeverShowEnterImmersiveModeMessage:
|
||||
SettingsService.NeverShowEnterImmersiveModeMessage = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool? AlreadyForeverDismissedThisMessage() =>
|
||||
Notification.RelatedSettingsKeyName switch
|
||||
{
|
||||
SettingsKeys.NeverShowEnterFullScreenMessage =>
|
||||
SettingsService.NeverShowEnterFullScreenMessage,
|
||||
SettingsKeys.NeverShowEnterImmersiveModeMessage =>
|
||||
SettingsService.NeverShowEnterImmersiveModeMessage,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
// 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 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;
|
||||
|
||||
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>>
|
||||
{
|
||||
#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)
|
||||
{
|
||||
LyricsFontSize = _settingsService.LyricsFontSize;
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
|
||||
_playbackService = playbackService;
|
||||
_playbackService.SongInfoChanged += async (_, args) =>
|
||||
await UpdateSongInfoUI(args.SongInfo).ConfigureAwait(true);
|
||||
|
||||
IsFirstRun = _settingsService.IsFirstRun;
|
||||
|
||||
UpdateSongInfoUI(_playbackService.SongInfo).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#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;
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <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
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The OpenMatchedFileFolderInFileExplorer
|
||||
/// </summary>
|
||||
/// <param name="path">The path<see cref="string"/></param>
|
||||
public void OpenMatchedFileFolderInFileExplorer(string path)
|
||||
{
|
||||
Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "explorer.exe",
|
||||
Arguments = $"/select,\"{path}\"",
|
||||
UseShellExecute = true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <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.PropertyName == nameof(HostWindowViewModel.IsDockMode))
|
||||
{
|
||||
IsNotMockMode = !message.NewValue;
|
||||
SetNonStandardModePreferredDisplayType(message.NewValue);
|
||||
TrySwitchToPreferredDisplayType(SongInfo);
|
||||
}
|
||||
else if (message.PropertyName == nameof(HostWindowViewModel.IsDesktopMode))
|
||||
{
|
||||
SetNonStandardModePreferredDisplayType(message.NewValue);
|
||||
TrySwitchToPreferredDisplayType(SongInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetNonStandardModePreferredDisplayType(bool isEnabled)
|
||||
{
|
||||
if (isEnabled)
|
||||
{
|
||||
_preferredDisplayTypeBeforeSwitchToNonStandardMode = PreferredDisplayType;
|
||||
PreferredDisplayType = LyricsDisplayType.LyricsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
if (songInfo == null)
|
||||
{
|
||||
displayType = LyricsDisplayType.PlaceholderOnly;
|
||||
}
|
||||
else if (PreferredDisplayType is LyricsDisplayType preferredDisplayType)
|
||||
{
|
||||
displayType = preferredDisplayType;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayType = LyricsDisplayType.SplitView;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user