Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
db6847b74f | ||
|
|
d510892650 | ||
|
|
a0e51d976e | ||
|
|
80ef86478b | ||
|
|
9736a4c9cc | ||
|
|
d01be4b883 | ||
|
|
b470b91d0e | ||
|
|
61f4e5706b | ||
|
|
6f2d3a3505 | ||
|
|
58b7cd2520 | ||
|
|
b2319e7983 | ||
|
|
a3b250dd46 |
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
|
||||
|
||||
@@ -40,15 +40,16 @@
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
|
||||
<DefaultLanguage>zh-CN</DefaultLanguage>
|
||||
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
|
||||
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
|
||||
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
|
||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||
<AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>
|
||||
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
||||
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
||||
<AppxBundlePlatforms>x86|x64|arm64</AppxBundlePlatforms>
|
||||
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
|
||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
@@ -80,6 +81,7 @@
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
|
||||
<Content Include="Images\LargeTile.scale-100.png" />
|
||||
<Content Include="Images\LargeTile.scale-125.png" />
|
||||
<Content Include="Images\LargeTile.scale-150.png" />
|
||||
|
||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 824 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 824 B |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 531 B After Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 703 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 46 KiB |
@@ -5,12 +5,13 @@
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
xmlns:uap18="http://schemas.microsoft.com/appx/manifest/uap/windows10/18"
|
||||
IgnorableNamespaces="uap rescap uap18">
|
||||
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.1.0" />
|
||||
Publisher="CN=Zhe"
|
||||
Version="1.0.5.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
@@ -26,7 +27,11 @@
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
<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>
|
||||
@@ -16,12 +17,8 @@
|
||||
|
||||
<!-- Theme -->
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<Color x:Key="SemiTransparentSystemBaseHighColor">#80000000</Color>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Color x:Key="SemiTransparentSystemBaseHighColor">#80FFFFFF</Color>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light" />
|
||||
<ResourceDictionary x:Key="Dark" />
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<!-- Brush -->
|
||||
@@ -44,10 +41,14 @@
|
||||
<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:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
|
||||
<x:Double x:Key="SettingsCardSpacing">4</x:Double>
|
||||
|
||||
@@ -60,6 +61,34 @@
|
||||
<Setter Property="Margin" Value="1,30,0,6" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style x:Key="TitleBarButtonStyle" TargetType="Button">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="16,0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="GhostButtonStyle" TargetType="Button">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="TitleBarToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="16,0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<!-- Dimensions -->
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
@@ -1,63 +1,132 @@
|
||||
using BetterLyrics.WinUI3.Services.Database;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using System;
|
||||
using System.Text;
|
||||
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 System.Text;
|
||||
using Serilog;
|
||||
|
||||
// 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 {
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : Application {
|
||||
public static App Current => (App)Application.Current;
|
||||
public MainWindow? MainWindow { get; private set; }
|
||||
public MainWindow? SettingsWindow { get; set; }
|
||||
public partial class App : Application
|
||||
{
|
||||
private readonly ILogger<App> _logger;
|
||||
|
||||
public static ResourceLoader ResourceLoader = new();
|
||||
public static new App Current => (App)Application.Current;
|
||||
|
||||
public static DispatcherQueue DispatcherQueue => DispatcherQueue.GetForCurrentThread();
|
||||
public static ResourceLoader? ResourceLoader { get; private set; }
|
||||
|
||||
public static DispatcherQueue? DispatcherQueue { get; private set; }
|
||||
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
|
||||
|
||||
/// <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().
|
||||
/// </summary>
|
||||
public App() {
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
DispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
DispatcherQueueTimer = DispatcherQueue.CreateTimer();
|
||||
ResourceLoader = new ResourceLoader();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void CurrentDomain_FirstChanceException(
|
||||
object? sender,
|
||||
System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e
|
||||
)
|
||||
{
|
||||
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
}
|
||||
|
||||
private void TaskScheduler_UnobservedTaskException(
|
||||
object? sender,
|
||||
UnobservedTaskExceptionEventArgs e
|
||||
)
|
||||
{
|
||||
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
}
|
||||
|
||||
private void CurrentDomain_UnhandledException(
|
||||
object sender,
|
||||
System.UnhandledExceptionEventArgs e
|
||||
)
|
||||
{
|
||||
_logger.LogError(e.ExceptionObject.ToString(), "CurrentDomain_UnhandledException");
|
||||
}
|
||||
|
||||
private static void ConfigureServices()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.File(AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
// Register services
|
||||
Ioc.Default.ConfigureServices(
|
||||
new ServiceCollection()
|
||||
.AddLogging(loggingBuilder =>
|
||||
{
|
||||
loggingBuilder.ClearProviders();
|
||||
loggingBuilder.AddSerilog();
|
||||
})
|
||||
// Services
|
||||
.AddSingleton<ISettingsService, SettingsService>()
|
||||
.AddSingleton<IPlaybackService, PlaybackService>()
|
||||
.AddSingleton<IMusicSearchService, MusicSearchService>()
|
||||
.AddSingleton<ILibWatcherService, LibWatcherService>()
|
||||
// ViewModels
|
||||
.AddTransient<HostWindowViewModel>()
|
||||
.AddSingleton<SettingsViewModel>()
|
||||
.AddSingleton<LyricsPageViewModel>()
|
||||
.AddSingleton<LyricsRendererViewModel>()
|
||||
.AddSingleton<LyricsSettingsControlViewModel>()
|
||||
.BuildServiceProvider()
|
||||
);
|
||||
}
|
||||
|
||||
private void App_UnhandledException(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.UnhandledExceptionEventArgs e
|
||||
)
|
||||
{
|
||||
_logger.LogError(e.Exception, "App_UnhandledException");
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) {
|
||||
|
||||
// Register services
|
||||
Ioc.Default.ConfigureServices(
|
||||
new ServiceCollection()
|
||||
.AddSingleton(DispatcherQueue.GetForCurrentThread())
|
||||
// Services
|
||||
.AddSingleton<SettingsService>()
|
||||
.AddSingleton<DatabaseService>()
|
||||
// ViewModels
|
||||
.AddSingleton<MainViewModel>()
|
||||
.AddSingleton<SettingsViewModel>()
|
||||
.BuildServiceProvider());
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
// Activate the window
|
||||
MainWindow = new MainWindow();
|
||||
MainWindow!.Navigate(typeof(MainPage));
|
||||
MainWindow.Activate();
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
WindowHelper.OpenLyricsWindow();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/AI - 甜度爆表.mp3
Normal file
|
Before Width: | Height: | Size: 334 B |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.ico
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
@@ -2,42 +2,77 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Logo.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</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.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.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="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.5" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<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.5.1" />
|
||||
<PackageReference Include="z440.atl.core" Version="6.26.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Publish Properties -->
|
||||
<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>
|
||||
<ItemGroup>
|
||||
<Folder Include="Controls\" />
|
||||
<Folder Include="ViewModels\Lyrics\" />
|
||||
</ItemGroup>
|
||||
<!--Disable Trimming for Specific Packages-->
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
@@ -45,4 +80,22 @@
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
<PropertyGroup>
|
||||
<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>
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter {
|
||||
public class ColorToBrushConverter : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, string language) {
|
||||
if (value is Color color) {
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class ColorToBrushConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Color color)
|
||||
{
|
||||
return new SolidColorBrush(color);
|
||||
}
|
||||
return new SolidColorBrush();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) {
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
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 EnumToIntConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Enum)
|
||||
{
|
||||
return System.Convert.ToInt32(value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class IntToCornerRadius : IValueConverter
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class LyricsSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is LyricsSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLocalLrcFile"
|
||||
),
|
||||
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLocalMusicFile"
|
||||
),
|
||||
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLrcLib"
|
||||
),
|
||||
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderEslrcFile"
|
||||
),
|
||||
LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderTtmlFile"
|
||||
),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null),
|
||||
};
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class MatchedLocalFilesPathToVisibilityConverter : IValueConverter
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter {
|
||||
internal class ThemeTypeToElementThemeConverter : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, string language) {
|
||||
if (value is int themeType) {
|
||||
return (ElementTheme)themeType;
|
||||
}
|
||||
return ElementTheme.Default;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum AutoStartWindowType
|
||||
{
|
||||
StandardMode,
|
||||
DockMode,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum BackdropType
|
||||
{
|
||||
None = 0,
|
||||
Mica = 1,
|
||||
MicaAlt = 2,
|
||||
DesktopAcrylic = 3,
|
||||
Transparent = 4,
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,15 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models {
|
||||
public enum Language {
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum Language
|
||||
{
|
||||
FollowSystem,
|
||||
English,
|
||||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
Japanese,
|
||||
Korean,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LocalSearchTargetProps
|
||||
{
|
||||
LyricsOnly,
|
||||
LyricsAndAlbumArt,
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models {
|
||||
public enum LyricsAlignmentType {
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsAlignmentType
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsDisplayType
|
||||
{
|
||||
AlbumArtOnly,
|
||||
LyricsOnly,
|
||||
SplitView,
|
||||
PlaceholderOnly,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsFontColorType
|
||||
{
|
||||
Default,
|
||||
Dominant,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Text;
|
||||
using Windows.UI.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsFontWeight
|
||||
{
|
||||
Thin,
|
||||
ExtraLight,
|
||||
Light,
|
||||
SemiLight,
|
||||
Normal,
|
||||
Medium,
|
||||
SemiBold,
|
||||
Bold,
|
||||
ExtraBold,
|
||||
Black,
|
||||
ExtraBlack,
|
||||
}
|
||||
|
||||
public static class LyricsFontWeightExtensions
|
||||
{
|
||||
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
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsFormat
|
||||
{
|
||||
Lrc,
|
||||
Eslrc,
|
||||
Ttml,
|
||||
}
|
||||
|
||||
public static class LyricsFormatExtensions
|
||||
{
|
||||
public static string ToFileExtension(this LyricsFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
LyricsFormat.Lrc => ".lrc",
|
||||
LyricsFormat.Eslrc => ".eslrc",
|
||||
LyricsFormat.Ttml => ".ttml",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null),
|
||||
};
|
||||
}
|
||||
|
||||
public static LyricsFormat? Detect(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsGlowEffectScope
|
||||
{
|
||||
WholeLyrics,
|
||||
CurrentLine,
|
||||
CurrentChar,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsHighlightType
|
||||
{
|
||||
LineByLine,
|
||||
CharByChar,
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,23 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models {
|
||||
public enum LyricsPlayingState {
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsPlayingState
|
||||
{
|
||||
/// <summary>
|
||||
/// Not played yet, will be playing in the future
|
||||
/// </summary>
|
||||
NotPlayed,
|
||||
|
||||
/// <summary>
|
||||
/// Playing
|
||||
/// </summary>
|
||||
Playing,
|
||||
|
||||
/// <summary>
|
||||
/// Has already played
|
||||
/// </summary>
|
||||
Played
|
||||
Played,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsSearchProvider
|
||||
{
|
||||
LrcLib,
|
||||
LocalMusicFile,
|
||||
LocalLrcFile,
|
||||
LocalEslrcFile,
|
||||
LocalTtmlFile,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsStatus
|
||||
{
|
||||
NotFound,
|
||||
Found,
|
||||
Loading,
|
||||
}
|
||||
}
|
||||
14
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/LyricsType.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsType
|
||||
{
|
||||
InAppLyrics,
|
||||
DesktopLyrics,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum MusicSearchMatchMode
|
||||
{
|
||||
TitleAndArtist,
|
||||
TitleArtistAlbumAndDuration,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum TitleBarType
|
||||
{
|
||||
Compact,
|
||||
Extended,
|
||||
}
|
||||
|
||||
public static class TitleBarTypeExtensions
|
||||
{
|
||||
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
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class IsPlayingChangedEventArgs(bool isPlaying) : EventArgs
|
||||
{
|
||||
public bool IsPlaying { get; set; } = isPlaying;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class LibChangedEventArgs : EventArgs
|
||||
{
|
||||
public string Folder { get; }
|
||||
public string FilePath { get; }
|
||||
public WatcherChangeTypes ChangeType { get; }
|
||||
|
||||
public LibChangedEventArgs(string folder, string filePath, WatcherChangeTypes changeType)
|
||||
{
|
||||
Folder = folder;
|
||||
FilePath = filePath;
|
||||
ChangeType = changeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
|
||||
{
|
||||
public TimeSpan Position { get; set; } = position;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class SongInfoChangedEventArgs(SongInfo? songInfo) : EventArgs
|
||||
{
|
||||
public SongInfo? SongInfo { get; set; } = songInfo;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,79 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
|
||||
/// <summary>
|
||||
/// Edited based on: https://stackoverflow.com/a/25236507/11048731
|
||||
/// </summary>
|
||||
public class AnimationHelper : DependencyObject {
|
||||
public static int GetAnimationDuration(DependencyObject obj) {
|
||||
return (int)obj.GetValue(AnimationDurationProperty);
|
||||
}
|
||||
|
||||
public static void SetAnimationDuration(DependencyObject obj, int value) {
|
||||
obj.SetValue(AnimationDurationProperty, value);
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for AnimationDuration.
|
||||
// This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty AnimationDurationProperty =
|
||||
DependencyProperty.RegisterAttached("AnimationDuration", typeof(int),
|
||||
typeof(AnimationHelper), new PropertyMetadata(0,
|
||||
OnAnimationDurationChanged));
|
||||
|
||||
private static void OnAnimationDurationChanged(DependencyObject d,
|
||||
DependencyPropertyChangedEventArgs e) {
|
||||
FrameworkElement element = d as FrameworkElement;
|
||||
|
||||
var ms = (int)e.NewValue;
|
||||
|
||||
if (ms < 0) return;
|
||||
|
||||
var key = "LyricsLineCharGradientInTextBlock";
|
||||
foreach (var timeline in (element.Resources[key] as Storyboard).Children) {
|
||||
foreach (var keyFrame in (timeline as DoubleAnimationUsingKeyFrames).KeyFrames) {
|
||||
(keyFrame as LinearDoubleKeyFrame).KeyTime =
|
||||
KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(ms));
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class AnimationHelper
|
||||
{
|
||||
public const int StackedNotificationsShowingDuration = 3900;
|
||||
public const int StoryboardDefaultDuration = 200;
|
||||
public const int DebounceDefaultDuration = 200;
|
||||
}
|
||||
|
||||
public class ValueTransition<T>
|
||||
where T : struct
|
||||
{
|
||||
private T _currentValue;
|
||||
private T _startValue;
|
||||
private T _targetValue;
|
||||
private float _progress;
|
||||
private float _durationSeconds;
|
||||
private bool _isTransitioning;
|
||||
private Func<T, T, float, T> _interpolator;
|
||||
|
||||
public ValueTransition(
|
||||
T initialValue,
|
||||
float durationSeconds,
|
||||
Func<T, T, float, T> interpolator
|
||||
)
|
||||
{
|
||||
_currentValue = initialValue;
|
||||
_startValue = initialValue;
|
||||
_targetValue = initialValue;
|
||||
_durationSeconds = durationSeconds;
|
||||
_progress = 1f;
|
||||
_isTransitioning = false;
|
||||
_interpolator = interpolator;
|
||||
}
|
||||
|
||||
public T Value => _currentValue;
|
||||
public bool IsTransitioning => _isTransitioning;
|
||||
|
||||
public void StartTransition(T targetValue)
|
||||
{
|
||||
if (!targetValue.Equals(_currentValue))
|
||||
{
|
||||
_startValue = _currentValue;
|
||||
_targetValue = targetValue;
|
||||
_progress = 0f;
|
||||
_isTransitioning = true;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 0f;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
61
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/AppInfo.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
|
||||
public static class AppInfo
|
||||
{
|
||||
// App Metadata
|
||||
public const string AppName = "BetterLyrics";
|
||||
public const string AppDisplayName = "Better Lyrics";
|
||||
public const string AppAuthor = "Zhe Fang";
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
public static string AppVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = Package.Current.Id.Version;
|
||||
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
|
||||
|
||||
// Data Files
|
||||
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
public static string OnlineLyricsCacheDirectory =>
|
||||
Path.Combine(CacheFolder, "online-lyrics");
|
||||
|
||||
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
|
||||
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
|
||||
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(LocalFolder);
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
Directory.CreateDirectory(OnlineLyricsCacheDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,12 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
public static class CollectionHelper {
|
||||
public static T? SafeGet<T>(this IList<T> list, int index) {
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class CollectionHelper
|
||||
{
|
||||
public static T? SafeGet<T>(this IList<T> list, int index)
|
||||
{
|
||||
if (list == null || index < 0 || index >= list.Count)
|
||||
return default;
|
||||
return list[index];
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
public class ColorHelper {
|
||||
public static Windows.UI.Color LerpColor(Windows.UI.Color a, Windows.UI.Color b, double t) {
|
||||
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);
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class ColorHelper
|
||||
{
|
||||
public static Windows.UI.Color ToWindowsUIColor(this System.Drawing.Color color)
|
||||
{
|
||||
return Windows.UI.Color.FromArgb(color.A, color.R, color.G, color.B);
|
||||
}
|
||||
|
||||
public static Color GetInterpolatedColor(
|
||||
float progress,
|
||||
Color startColor,
|
||||
Color targetColor
|
||||
)
|
||||
{
|
||||
byte Lerp(byte a, byte b) => (byte)(a + (progress * (b - a)));
|
||||
return Color.FromArgb(
|
||||
Lerp(startColor.A, targetColor.A),
|
||||
Lerp(startColor.R, targetColor.R),
|
||||
Lerp(startColor.G, targetColor.G),
|
||||
Lerp(startColor.B, targetColor.B)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
|
||||
// This file is a fork of https://github.com/KSemenenko/ColorThief
|
||||
// and is used to extract dominant colors from images in WinUI3 application
|
||||
// I have modified it to fit the WinUI3 environment and removed unnecessary parts
|
||||
// Credits to KSemenenko for the original work.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
@@ -12,7 +17,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
internal class CMap
|
||||
{
|
||||
private readonly List<VBox> vboxes = new List<VBox>();
|
||||
private List<QuantizedColor> palette;
|
||||
private List<QuantizedColor>? palette;
|
||||
|
||||
public void Push(VBox box)
|
||||
{
|
||||
@@ -24,10 +29,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
if (palette == null)
|
||||
{
|
||||
palette = (from vBox in vboxes
|
||||
let rgb = vBox.Avg(false)
|
||||
let color = FromRgb(rgb[0], rgb[1], rgb[2])
|
||||
select new QuantizedColor(color, vBox.Count(false))).ToList();
|
||||
palette = (
|
||||
from vBox in vboxes
|
||||
let rgb = vBox.Avg(false)
|
||||
let color = FromRgb(rgb[0], rgb[1], rgb[2])
|
||||
select new QuantizedColor(color, vBox.Count(false))
|
||||
).ToList();
|
||||
}
|
||||
|
||||
return palette;
|
||||
@@ -38,7 +45,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return vboxes.Count;
|
||||
}
|
||||
|
||||
public int[] Map(int[] color)
|
||||
public int[]? Map(int[] color)
|
||||
{
|
||||
foreach (var vbox in vboxes.Where(vbox => vbox.Contains(color)))
|
||||
{
|
||||
@@ -47,17 +54,19 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return Nearest(color);
|
||||
}
|
||||
|
||||
public int[] Nearest(int[] color)
|
||||
public int[]? Nearest(int[] color)
|
||||
{
|
||||
var d1 = double.MaxValue;
|
||||
int[] pColor = null;
|
||||
int[]? pColor = null;
|
||||
|
||||
foreach (var t in vboxes)
|
||||
{
|
||||
var vbColor = t.Avg(false);
|
||||
var d2 = Math.Sqrt(Math.Pow(color[0] - vbColor[0], 2)
|
||||
+ Math.Pow(color[1] - vbColor[1], 2)
|
||||
+ Math.Pow(color[2] - vbColor[2], 2));
|
||||
var d2 = Math.Sqrt(
|
||||
Math.Pow(color[0] - vbColor[0], 2)
|
||||
+ Math.Pow(color[1] - vbColor[1], 2)
|
||||
+ Math.Pow(color[2] - vbColor[2], 2)
|
||||
);
|
||||
if (d2 < d1)
|
||||
{
|
||||
d1 = d2;
|
||||
@@ -67,9 +76,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return pColor;
|
||||
}
|
||||
|
||||
public VBox FindColor(double targetLuma, double minLuma, double maxLuma, double targetSaturation, double minSaturation, double maxSaturation)
|
||||
public VBox FindColor(
|
||||
double targetLuma,
|
||||
double minLuma,
|
||||
double maxLuma,
|
||||
double targetSaturation,
|
||||
double minSaturation,
|
||||
double maxSaturation
|
||||
)
|
||||
{
|
||||
VBox max = null;
|
||||
VBox? max = null;
|
||||
double maxValue = 0;
|
||||
var highestPopulation = vboxes.Select(p => p.Count(false)).Max();
|
||||
|
||||
@@ -80,11 +96,21 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
var sat = hsl.S;
|
||||
var luma = hsl.L;
|
||||
|
||||
if (sat >= minSaturation && sat <= maxSaturation &&
|
||||
luma >= minLuma && luma <= maxLuma)
|
||||
if (
|
||||
sat >= minSaturation
|
||||
&& sat <= maxSaturation
|
||||
&& luma >= minLuma
|
||||
&& luma <= maxLuma
|
||||
)
|
||||
{
|
||||
var thisValue = Mmcq.CreateComparisonValue(sat, targetSaturation, luma, targetLuma,
|
||||
swatch.Count(false), highestPopulation);
|
||||
var thisValue = Mmcq.CreateComparisonValue(
|
||||
sat,
|
||||
targetSaturation,
|
||||
luma,
|
||||
targetLuma,
|
||||
swatch.Count(false),
|
||||
highestPopulation
|
||||
);
|
||||
|
||||
if (max == null || thisValue > maxValue)
|
||||
{
|
||||
@@ -97,14 +123,14 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return max;
|
||||
}
|
||||
|
||||
public Color FromRgb(int red, int green, int blue)
|
||||
public RGB FromRgb(int red, int green, int blue)
|
||||
{
|
||||
var color = new Color
|
||||
var color = new RGB
|
||||
{
|
||||
A = 255,
|
||||
R = (byte)red,
|
||||
G = (byte)green,
|
||||
B = (byte)blue
|
||||
B = (byte)blue,
|
||||
};
|
||||
|
||||
return color;
|
||||
@@ -114,7 +140,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
/// <summary>
|
||||
/// Defines a color in RGB space.
|
||||
/// </summary>
|
||||
public struct Color
|
||||
public struct RGB
|
||||
{
|
||||
/// <summary>
|
||||
/// Get or Set the Alpha component value for sRGB.
|
||||
@@ -269,9 +295,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
private static VBox VboxFromPixels(IList<byte[]> pixels, int[] histo)
|
||||
{
|
||||
int rmin = 1000000, rmax = 0;
|
||||
int gmin = 1000000, gmax = 0;
|
||||
int bmin = 1000000, bmax = 0;
|
||||
int rmin = 1000000,
|
||||
rmax = 0;
|
||||
int gmin = 1000000,
|
||||
gmax = 0;
|
||||
int bmin = 1000000,
|
||||
bmax = 0;
|
||||
|
||||
// find min/max
|
||||
var numPixels = pixels.Count;
|
||||
@@ -313,7 +342,13 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
|
||||
}
|
||||
|
||||
private static VBox[] DoCut(char color, VBox vbox, IList<int> partialsum, IList<int> lookaheadsum, int total)
|
||||
private static VBox[] DoCut(
|
||||
char color,
|
||||
VBox vbox,
|
||||
IList<int> partialsum,
|
||||
IList<int> lookaheadsum,
|
||||
int total
|
||||
)
|
||||
{
|
||||
int vboxDim1;
|
||||
int vboxDim2;
|
||||
@@ -344,9 +379,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
var left = i - vboxDim1;
|
||||
var right = vboxDim2 - i;
|
||||
|
||||
var d2 = left <= right
|
||||
? Math.Min(vboxDim2 - 1, Math.Abs(i + right / 2))
|
||||
: Math.Max(vboxDim1, Math.Abs(Convert.ToInt32(i - 1 - left / 2.0)));
|
||||
var d2 =
|
||||
left <= right
|
||||
? Math.Min(vboxDim2 - 1, Math.Abs(i + right / 2))
|
||||
: Math.Max(vboxDim1, Math.Abs(Convert.ToInt32(i - 1 - left / 2.0)));
|
||||
|
||||
// avoid 0-count boxes
|
||||
while (d2 < 0 || partialsum[d2] <= 0)
|
||||
@@ -383,7 +419,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
throw new Exception("VBox can't be cut");
|
||||
}
|
||||
|
||||
private static VBox[] MedianCutApply(IList<int> histo, VBox vbox)
|
||||
private static VBox?[]? MedianCutApply(IList<int> histo, VBox vbox)
|
||||
{
|
||||
if (vbox.Count(false) == 0)
|
||||
{
|
||||
@@ -391,7 +427,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
if (vbox.Count(false) == 1)
|
||||
{
|
||||
return new[] { vbox.Clone(), null };
|
||||
return [vbox.Clone(), null];
|
||||
}
|
||||
|
||||
// only one pixel, no split
|
||||
@@ -417,7 +453,11 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
lookaheadsum[l] = -1;
|
||||
}
|
||||
|
||||
int i, j, k, sum, index;
|
||||
int i,
|
||||
j,
|
||||
k,
|
||||
sum,
|
||||
index;
|
||||
|
||||
if (maxw == rw)
|
||||
{
|
||||
@@ -480,8 +520,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
|
||||
// determine the cut planes
|
||||
return maxw == rw ? DoCut('r', vbox, partialsum, lookaheadsum, total) : maxw == gw
|
||||
? DoCut('g', vbox, partialsum, lookaheadsum, total) : DoCut('b', vbox, partialsum, lookaheadsum, total);
|
||||
return maxw == rw ? DoCut('r', vbox, partialsum, lookaheadsum, total)
|
||||
: maxw == gw ? DoCut('g', vbox, partialsum, lookaheadsum, total)
|
||||
: DoCut('b', vbox, partialsum, lookaheadsum, total);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -492,7 +533,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="histo">The histo.</param>
|
||||
/// <exception cref="System.Exception">vbox1 not defined; shouldn't happen!</exception>
|
||||
private static void Iter(List<VBox> lh, IComparer<VBox> comparator, int target, IList<int> histo)
|
||||
private static void Iter(
|
||||
List<VBox> lh,
|
||||
IComparer<VBox> comparator,
|
||||
int target,
|
||||
IList<int> histo
|
||||
)
|
||||
{
|
||||
var ncolors = 1;
|
||||
var niters = 0;
|
||||
@@ -511,13 +557,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
// do the cut
|
||||
var vboxes = MedianCutApply(histo, vbox);
|
||||
var vbox1 = vboxes[0];
|
||||
var vbox2 = vboxes[1];
|
||||
var vbox1 = vboxes?[0];
|
||||
var vbox2 = vboxes?[1];
|
||||
|
||||
if (vbox1 == null)
|
||||
{
|
||||
throw new Exception(
|
||||
"vbox1 not defined; shouldn't happen!");
|
||||
throw new Exception("vbox1 not defined; shouldn't happen!");
|
||||
}
|
||||
|
||||
lh.Add(vbox1);
|
||||
@@ -579,11 +624,23 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return cmap;
|
||||
}
|
||||
|
||||
public static double CreateComparisonValue(double saturation, double targetSaturation, double luma, double targetLuma, int population, int highestPopulation)
|
||||
public static double CreateComparisonValue(
|
||||
double saturation,
|
||||
double targetSaturation,
|
||||
double luma,
|
||||
double targetLuma,
|
||||
int population,
|
||||
int highestPopulation
|
||||
)
|
||||
{
|
||||
return WeightedMean(InvertDiff(saturation, targetSaturation), WeightSaturation,
|
||||
InvertDiff(luma, targetLuma), WeightLuma,
|
||||
population / (double)highestPopulation, WeightPopulation);
|
||||
return WeightedMean(
|
||||
InvertDiff(saturation, targetSaturation),
|
||||
WeightSaturation,
|
||||
InvertDiff(luma, targetLuma),
|
||||
WeightLuma,
|
||||
population / (double)highestPopulation,
|
||||
WeightPopulation
|
||||
);
|
||||
}
|
||||
|
||||
private static double WeightedMean(params double[] values)
|
||||
@@ -611,20 +668,22 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public class QuantizedColor
|
||||
{
|
||||
public QuantizedColor(Color color, int population)
|
||||
public QuantizedColor(RGB color, int population)
|
||||
{
|
||||
Color = color;
|
||||
Population = population;
|
||||
IsDark = CalculateYiqLuma(color) < 128;
|
||||
}
|
||||
|
||||
public Color Color { get; private set; }
|
||||
public RGB Color { get; private set; }
|
||||
public int Population { get; private set; }
|
||||
public bool IsDark { get; private set; }
|
||||
|
||||
public int CalculateYiqLuma(Color color)
|
||||
public int CalculateYiqLuma(RGB color)
|
||||
{
|
||||
return Convert.ToInt32(Math.Round((299 * color.R + 587 * color.G + 114 * color.B) / 1000f));
|
||||
return Convert.ToInt32(
|
||||
Math.Round((299 * color.R + 587 * color.G + 114 * color.B) / 1000f)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,8 +791,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
avg = new[]
|
||||
{
|
||||
Math.Abs(rsum / ntot), Math.Abs(gsum / ntot),
|
||||
Math.Abs(bsum / ntot)
|
||||
Math.Abs(rsum / ntot),
|
||||
Math.Abs(gsum / ntot),
|
||||
Math.Abs(bsum / ntot),
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -742,7 +802,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
Math.Abs(Mmcq.Mult * (R1 + R2 + 1) / 2),
|
||||
Math.Abs(Mmcq.Mult * (G1 + G2 + 1) / 2),
|
||||
Math.Abs(Mmcq.Mult * (B1 + B2 + 1) / 2)
|
||||
Math.Abs(Mmcq.Mult * (B1 + B2 + 1) / 2),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -809,14 +869,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
private byte[][] ConvertPixels(byte[] pixels, int pixelCount, int quality, bool ignoreWhite)
|
||||
{
|
||||
|
||||
|
||||
var expectedDataLength = pixelCount * ColorDepth;
|
||||
if (expectedDataLength != pixels.Length)
|
||||
{
|
||||
throw new ArgumentException("(expectedDataLength = "
|
||||
+ expectedDataLength + ") != (pixels.length = "
|
||||
+ pixels.Length + ")");
|
||||
throw new ArgumentException(
|
||||
"(expectedDataLength = "
|
||||
+ expectedDataLength
|
||||
+ ") != (pixels.length = "
|
||||
+ pixels.Length
|
||||
+ ")"
|
||||
);
|
||||
}
|
||||
|
||||
// Store the RGB values in an array format suitable for quantize
|
||||
@@ -864,17 +926,24 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
/// </param>
|
||||
/// <param name="ignoreWhite">if set to <c>true</c> [ignore white].</param>
|
||||
/// <returns></returns>
|
||||
public async Task<QuantizedColor> GetColor(BitmapDecoder sourceImage, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite)
|
||||
public async Task<QuantizedColor> GetColor(
|
||||
BitmapDecoder sourceImage,
|
||||
int quality = DefaultQuality,
|
||||
bool ignoreWhite = DefaultIgnoreWhite
|
||||
)
|
||||
{
|
||||
var palette = await GetPalette(sourceImage, 3, quality, ignoreWhite);
|
||||
|
||||
var dominantColor = new QuantizedColor(new Color
|
||||
{
|
||||
A = Convert.ToByte(palette.Average(a => a.Color.A)),
|
||||
R = Convert.ToByte(palette.Average(a => a.Color.R)),
|
||||
G = Convert.ToByte(palette.Average(a => a.Color.G)),
|
||||
B = Convert.ToByte(palette.Average(a => a.Color.B))
|
||||
}, Convert.ToInt32(palette.Average(a => a.Population)));
|
||||
var dominantColor = new QuantizedColor(
|
||||
new RGB
|
||||
{
|
||||
A = Convert.ToByte(palette.Average(a => a.Color.A)),
|
||||
R = Convert.ToByte(palette.Average(a => a.Color.R)),
|
||||
G = Convert.ToByte(palette.Average(a => a.Color.G)),
|
||||
B = Convert.ToByte(palette.Average(a => a.Color.B)),
|
||||
},
|
||||
Convert.ToInt32(palette.Average(a => a.Population))
|
||||
);
|
||||
|
||||
return dominantColor;
|
||||
}
|
||||
@@ -893,7 +962,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
/// <param name="ignoreWhite">if set to <c>true</c> [ignore white].</param>
|
||||
/// <returns></returns>
|
||||
/// <code>true</code>
|
||||
public async Task<List<QuantizedColor>> GetPalette(BitmapDecoder sourceImage, int colorCount = DefaultColorCount, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite)
|
||||
public async Task<List<QuantizedColor>> GetPalette(
|
||||
BitmapDecoder sourceImage,
|
||||
int colorCount = DefaultColorCount,
|
||||
int quality = DefaultQuality,
|
||||
bool ignoreWhite = DefaultIgnoreWhite
|
||||
)
|
||||
{
|
||||
var pixelArray = await GetPixelsFast(sourceImage, quality, ignoreWhite);
|
||||
var cmap = GetColorMap(pixelArray, colorCount);
|
||||
@@ -912,7 +986,11 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private async Task<byte[][]> GetPixelsFast(BitmapDecoder sourceImage, int quality, bool ignoreWhite)
|
||||
private async Task<byte[][]> GetPixelsFast(
|
||||
BitmapDecoder sourceImage,
|
||||
int quality,
|
||||
bool ignoreWhite
|
||||
)
|
||||
{
|
||||
if (quality < 1)
|
||||
{
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using SQLite;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
public class DatabaseHelper {
|
||||
private static SQLiteConnection _database;
|
||||
|
||||
public static SQLiteConnection InitializeDatabase() {
|
||||
string dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MusicMetadataIndex.db");
|
||||
_database = new SQLiteConnection(dbPath);
|
||||
_database.CreateTable<MetadataIndex>(); // Create table if it doesn't exist
|
||||
return _database;
|
||||
}
|
||||
}
|
||||
}
|
||||
218
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockHelper.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 DockHelper
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
public class EasingHelper {
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class EasingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// No easing
|
||||
/// </summary>
|
||||
@@ -25,23 +26,24 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
/// <summary>
|
||||
/// Acceleration until halfway then deceleration
|
||||
/// </summary>
|
||||
public static float EaseInOutQuad(float t) {
|
||||
return t < 0.5f
|
||||
? 2 * t * t
|
||||
: -1 + (4 - 2 * t) * t;
|
||||
public static float EaseInOutQuad(float t)
|
||||
{
|
||||
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smoother transition than linear
|
||||
/// </summary>
|
||||
public static float SmoothStep(float t) {
|
||||
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) {
|
||||
public static float SmootherStep(float t)
|
||||
{
|
||||
return t * t * t * (t * (6 * t - 15) + 10);
|
||||
}
|
||||
}
|
||||
|
||||
27
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FileHelper.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
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
|
||||
{
|
||||
public class FileHelper
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,26 +1,156 @@
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
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 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 Windows.Graphics.Imaging;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ImageHelper
|
||||
{
|
||||
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
|
||||
private static readonly ColorThief _colorThief = new();
|
||||
public const int AccentColorCount = 3;
|
||||
|
||||
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(
|
||||
byte[] imageBytes
|
||||
)
|
||||
{
|
||||
if (imageBytes == null || imageBytes.Length == 0)
|
||||
return null;
|
||||
|
||||
InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
|
||||
{
|
||||
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
|
||||
using var memoryStream = new MemoryStream();
|
||||
await stream.AsStreamForRead().CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
public static async Task<List<Color>> GetAccentColorsFromByte(byte[] bytes) =>
|
||||
[
|
||||
.. (
|
||||
await _colorThief.GetPalette(await GetDecoderFromByte(bytes), AccentColorCount)
|
||||
).Select(color =>
|
||||
Color.FromArgb(color.Color.A, color.Color.R, color.Color.G, color.Color.B)
|
||||
),
|
||||
];
|
||||
|
||||
public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
|
||||
await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
314
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LyricsParser
|
||||
{
|
||||
private List<List<LyricsLine>> _multiLangLyricsLines = [];
|
||||
|
||||
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.Ttml:
|
||||
ParseTtml(raw, durationMs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return _multiLangLyricsLines;
|
||||
}
|
||||
|
||||
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 = [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseLrc(string raw, int durationMs)
|
||||
{
|
||||
var lines = raw.Split(new[] { "\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, "").Trim();
|
||||
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)
|
||||
{
|
||||
for (int j = 0; j < syllables.Count; j++)
|
||||
{
|
||||
var (charStart, charText) = syllables[j];
|
||||
int charEnd = (j + 1 < syllables.Count) ? syllables[j + 1].Item1 : 0;
|
||||
line.CharTimings.Add(
|
||||
new CharTiming { StartMs = charStart, EndMs = charEnd }
|
||||
);
|
||||
}
|
||||
}
|
||||
_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 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.Value));
|
||||
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.Trim();
|
||||
|
||||
singleLangLyricsLine.Add(
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = pEndMs,
|
||||
Text = text,
|
||||
CharTimings = charTimings,
|
||||
}
|
||||
);
|
||||
}
|
||||
PostProcessLyricsLines(singleLangLyricsLine);
|
||||
_multiLangLyricsLines.Add(singleLangLyricsLine);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 解析失败,忽略
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +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];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||