Merge pull request #2 from jayfunc/dev

Dev
This commit is contained in:
Zhe Fang
2025-06-05 22:02:40 -04:00
committed by GitHub
93 changed files with 1877 additions and 921 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 531 B

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 703 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -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.0.0" />
Version="1.0.1.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
@@ -26,7 +27,9 @@
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
<Resource Language="en-US"/>
<Resource Language="zh-CN"/>
<Resource Language="zh-TW"/>
</Resources>
<Applications>

View File

@@ -44,6 +44,7 @@
<converter:ColorToBrushConverter x:Key="ColorToBrushConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<x:Double x:Key="SettingsCardSpacing">4</x:Double>

View File

@@ -1,33 +1,34 @@
using BetterLyrics.WinUI3.Services.Database;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Database;
using BetterLyrics.WinUI3.Services.Settings;
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.IO;
using System.Text;
using System;
using Microsoft.Extensions.Logging;
using Serilog;
using Windows.ApplicationModel.Core;
using Serilog.Core;
using BetterLyrics.WinUI3.Models;
using Newtonsoft.Json;
using System.Collections.Generic;
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.
namespace BetterLyrics.WinUI3 {
namespace BetterLyrics.WinUI3
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application {
public partial class App : Application
{
private readonly ILogger<App> _logger;
public static new App Current => (App)Application.Current;
@@ -42,7 +43,8 @@ namespace BetterLyrics.WinUI3 {
/// 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();
App.ResourceLoader = new ResourceLoader();
@@ -55,8 +57,8 @@ namespace BetterLyrics.WinUI3 {
UnhandledException += App_UnhandledException;
}
private static void ConfigureServices() {
private static void ConfigureServices()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.File(Helper.AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
@@ -65,21 +67,27 @@ namespace BetterLyrics.WinUI3 {
// Register services
Ioc.Default.ConfigureServices(
new ServiceCollection()
.AddSingleton(DispatcherQueue.GetForCurrentThread())
.AddLogging(loggingBuilder => {
loggingBuilder.ClearProviders();
loggingBuilder.AddSerilog();
})
// Services
.AddSingleton<SettingsService>()
.AddSingleton<DatabaseService>()
// ViewModels
.AddSingleton<MainViewModel>()
.AddSingleton<SettingsViewModel>()
.BuildServiceProvider());
.AddSingleton(DispatcherQueue.GetForCurrentThread())
.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddSerilog();
})
// Services
.AddSingleton<SettingsService>()
.AddSingleton<DatabaseService>()
// ViewModels
.AddSingleton<MainViewModel>()
.AddSingleton<SettingsViewModel>()
.BuildServiceProvider()
);
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) {
private void App_UnhandledException(
object sender,
Microsoft.UI.Xaml.UnhandledExceptionEventArgs e
)
{
_logger.LogError(e.Exception, "App_UnhandledException");
e.Handled = true;
}
@@ -88,8 +96,8 @@ namespace BetterLyrics.WinUI3 {
/// 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) {
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// Activate the window
@@ -97,6 +105,5 @@ namespace BetterLyrics.WinUI3 {
MainWindow!.Navigate(typeof(MainPage));
MainWindow.Activate();
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -1,68 +1,67 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
<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>
</PropertyGroup>
<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.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.Converters" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" 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.Logging" Version="9.0.5" />
<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.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="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="z440.atl.core" Version="6.24.0" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\Icon.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Update="Assets\AI - 甜度爆表.mp3">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<!-- Publish Properties -->
<PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
</PropertyGroup>
</Project>
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
</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.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.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.Logging" Version="9.0.5" />
<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.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="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="z440.atl.core" Version="6.24.0" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\AI - 甜度爆表.mp3">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<!-- Publish Properties -->
<PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Logo.ico</ApplicationIcon>
</PropertyGroup>
</Project>

View File

@@ -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();
}
}

View File

@@ -1,21 +1,26 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
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 ThemeTypeToElementThemeConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, string language) {
if (value is int themeType) {
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) {
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return 0;
}
}

View File

@@ -1,13 +1,13 @@
using Microsoft.UI.Xaml;
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Animation;
using System;
namespace BetterLyrics.WinUI3.Helper {
public static class AnimationHelper {
namespace BetterLyrics.WinUI3.Helper
{
public static class AnimationHelper
{
public const int StackedNotificationsShowingDuration = 3900;
public const int StoryboardDefaultDuration = 200;
public const int DebounceDefaultDuration = 200;
}
}

View File

@@ -4,20 +4,24 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper {
namespace BetterLyrics.WinUI3.Helper
{
using System;
using System.IO;
using Windows.ApplicationModel;
using Windows.Storage;
public static class AppInfo {
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 {
public static string AppVersion
{
get
{
var version = Package.Current.Id.Version;
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
@@ -28,7 +32,7 @@ namespace BetterLyrics.WinUI3.Helper {
#if DEBUG
true;
#else
false;
false;
#endif
// Base Folders
@@ -46,10 +50,10 @@ namespace BetterLyrics.WinUI3.Helper {
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
public static void EnsureDirectories() {
public static void EnsureDirectories()
{
Directory.CreateDirectory(LogDirectory);
Directory.CreateDirectory(LocalFolder);
}
}
}

View File

@@ -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];

View File

@@ -1,12 +1,14 @@
namespace BetterLyrics.WinUI3.Helper {
public class ColorHelper {
public static Windows.UI.Color LerpColor(Windows.UI.Color a, Windows.UI.Color b, double t) {
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);
}
}
}

View File

@@ -4,51 +4,66 @@ using System.Linq;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
namespace BetterLyrics.WinUI3.Helper {
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Color map
/// </summary>
internal class CMap {
internal class CMap
{
private readonly List<VBox> vboxes = new List<VBox>();
private List<QuantizedColor>? palette;
public void Push(VBox box) {
public void Push(VBox box)
{
palette = null;
vboxes.Add(box);
}
public List<QuantizedColor> GeneratePalette() {
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();
public List<QuantizedColor> GeneratePalette()
{
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();
}
return palette;
}
public int Size() {
public int Size()
{
return vboxes.Count;
}
public int[]? Map(int[] color) {
foreach (var vbox in vboxes.Where(vbox => vbox.Contains(color))) {
public int[]? Map(int[] color)
{
foreach (var vbox in vboxes.Where(vbox => vbox.Contains(color)))
{
return vbox.Avg(false);
}
return Nearest(color);
}
public int[]? Nearest(int[] color) {
public int[]? Nearest(int[] color)
{
var d1 = double.MaxValue;
int[]? pColor = null;
foreach (var t in vboxes) {
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));
if (d2 < d1) {
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;
pColor = vbColor;
}
@@ -56,23 +71,44 @@ 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;
double maxValue = 0;
var highestPopulation = vboxes.Select(p => p.Count(false)).Max();
foreach (var swatch in vboxes) {
foreach (var swatch in vboxes)
{
var avg = swatch.Avg(false);
var hsl = FromRgb(avg[0], avg[1], avg[2]).ToHsl();
var sat = hsl.S;
var luma = hsl.L;
if (sat >= minSaturation && sat <= maxSaturation &&
luma >= minLuma && luma <= maxLuma) {
var thisValue = Mmcq.CreateComparisonValue(sat, targetSaturation, luma, targetLuma,
swatch.Count(false), highestPopulation);
if (
sat >= minSaturation
&& sat <= maxSaturation
&& luma >= minLuma
&& luma <= maxLuma
)
{
var thisValue = Mmcq.CreateComparisonValue(
sat,
targetSaturation,
luma,
targetLuma,
swatch.Count(false),
highestPopulation
);
if (max == null || thisValue > maxValue) {
if (max == null || thisValue > maxValue)
{
max = swatch;
maxValue = thisValue;
}
@@ -82,12 +118,14 @@ namespace BetterLyrics.WinUI3.Helper {
return max;
}
public Color FromRgb(int red, int green, int blue) {
var color = new Color {
public RGB FromRgb(int red, int green, int blue)
{
var color = new RGB
{
A = 255,
R = (byte)red,
G = (byte)green,
B = (byte)blue
B = (byte)blue,
};
return color;
@@ -97,7 +135,8 @@ 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.
/// </summary>
@@ -122,7 +161,8 @@ namespace BetterLyrics.WinUI3.Helper {
/// Get HSL color.
/// </summary>
/// <returns></returns>
public HslColor ToHsl() {
public HslColor ToHsl()
{
const double toDouble = 1.0 / 255;
var r = toDouble * R;
var g = toDouble * G;
@@ -133,14 +173,20 @@ namespace BetterLyrics.WinUI3.Helper {
double h1;
// ReSharper disable CompareOfFloatsByEqualityOperator
if (chroma == 0) {
if (chroma == 0)
{
h1 = 0;
} else if (max == r) {
}
else if (max == r)
{
h1 = (g - b) / chroma % 6;
} else if (max == g) {
}
else if (max == g)
{
h1 = 2 + (b - r) / chroma;
} else //if (max == b)
{
}
else //if (max == b)
{
h1 = 4 + (r - g) / chroma;
}
@@ -155,16 +201,20 @@ namespace BetterLyrics.WinUI3.Helper {
// ReSharper restore CompareOfFloatsByEqualityOperator
}
public string ToHexString() {
public string ToHexString()
{
return "#" + R.ToString("X2") + G.ToString("X2") + B.ToString("X2");
}
public string ToHexAlphaString() {
public string ToHexAlphaString()
{
return "#" + A.ToString("X2") + R.ToString("X2") + G.ToString("X2") + B.ToString("X2");
}
public override string ToString() {
if (A == 255) {
public override string ToString()
{
if (A == 255)
{
return ToHexString();
}
@@ -175,7 +225,8 @@ namespace BetterLyrics.WinUI3.Helper {
/// <summary>
/// Defines a color in Hue/Saturation/Lightness (HSL) space.
/// </summary>
public struct HslColor {
public struct HslColor
{
/// <summary>
/// The Alpha/opacity in 0..1 range.
/// </summary>
@@ -197,7 +248,8 @@ namespace BetterLyrics.WinUI3.Helper {
public double S;
}
internal static class Mmcq {
internal static class Mmcq
{
public const int Sigbits = 5;
public const int Rshift = 8 - Sigbits;
public const int Mult = 1 << Rshift;
@@ -211,7 +263,8 @@ namespace BetterLyrics.WinUI3.Helper {
private static readonly VBoxComparer ComparatorProduct = new VBoxComparer();
private static readonly VBoxCountComparer ComparatorCount = new VBoxCountComparer();
public static int GetColorIndex(int r, int g, int b) {
public static int GetColorIndex(int r, int g, int b)
{
return (r << (2 * Sigbits)) + (g << Sigbits) + b;
}
@@ -220,10 +273,12 @@ namespace BetterLyrics.WinUI3.Helper {
/// </summary>
/// <param name="pixels">The pixels.</param>
/// <returns>Histo (1-d array, giving the number of pixels in each quantized region of color space), or null on error.</returns>
private static int[] GetHisto(IEnumerable<byte[]> pixels) {
private static int[] GetHisto(IEnumerable<byte[]> pixels)
{
var histo = new int[Histosize];
foreach (var pixel in pixels) {
foreach (var pixel in pixels)
{
var rval = pixel[0] >> Rshift;
var gval = pixel[1] >> Rshift;
var bval = pixel[2] >> Rshift;
@@ -233,34 +288,48 @@ namespace BetterLyrics.WinUI3.Helper {
return histo;
}
private static VBox VboxFromPixels(IList<byte[]> pixels, int[] histo) {
int rmin = 1000000, rmax = 0;
int gmin = 1000000, gmax = 0;
int bmin = 1000000, bmax = 0;
private static VBox VboxFromPixels(IList<byte[]> pixels, int[] histo)
{
int rmin = 1000000,
rmax = 0;
int gmin = 1000000,
gmax = 0;
int bmin = 1000000,
bmax = 0;
// find min/max
var numPixels = pixels.Count;
for (var i = 0; i < numPixels; i++) {
for (var i = 0; i < numPixels; i++)
{
var pixel = pixels[i];
var rval = pixel[0] >> Rshift;
var gval = pixel[1] >> Rshift;
var bval = pixel[2] >> Rshift;
if (rval < rmin) {
if (rval < rmin)
{
rmin = rval;
} else if (rval > rmax) {
}
else if (rval > rmax)
{
rmax = rval;
}
if (gval < gmin) {
if (gval < gmin)
{
gmin = gval;
} else if (gval > gmax) {
}
else if (gval > gmax)
{
gmax = gval;
}
if (bval < bmin) {
if (bval < bmin)
{
bmin = bval;
} else if (bval > bmax) {
}
else if (bval > bmax)
{
bmax = bval;
}
}
@@ -268,11 +337,19 @@ 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;
switch (color) {
switch (color)
{
case 'r':
vboxDim1 = vbox.R1;
vboxDim2 = vbox.R2;
@@ -287,29 +364,35 @@ namespace BetterLyrics.WinUI3.Helper {
break;
}
for (var i = vboxDim1; i <= vboxDim2; i++) {
if (partialsum[i] > total / 2) {
for (var i = vboxDim1; i <= vboxDim2; i++)
{
if (partialsum[i] > total / 2)
{
var vbox1 = vbox.Clone();
var vbox2 = vbox.Clone();
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) {
while (d2 < 0 || partialsum[d2] <= 0)
{
d2++;
}
var count2 = lookaheadsum[d2];
while (count2 == 0 && d2 > 0 && partialsum[d2 - 1] > 0) {
while (count2 == 0 && d2 > 0 && partialsum[d2 - 1] > 0)
{
count2 = lookaheadsum[--d2];
}
// set dimensions
switch (color) {
switch (color)
{
case 'r':
vbox1.R2 = d2;
vbox2.R1 = d2 + 1;
@@ -331,11 +414,14 @@ namespace BetterLyrics.WinUI3.Helper {
throw new Exception("VBox can't be cut");
}
private static VBox?[]? MedianCutApply(IList<int> histo, VBox vbox) {
if (vbox.Count(false) == 0) {
private static VBox?[]? MedianCutApply(IList<int> histo, VBox vbox)
{
if (vbox.Count(false) == 0)
{
return null;
}
if (vbox.Count(false) == 1) {
if (vbox.Count(false) == 1)
{
return [vbox.Clone(), null];
}
@@ -350,23 +436,33 @@ namespace BetterLyrics.WinUI3.Helper {
var total = 0;
var partialsum = new int[VboxLength];
// -1 = not set / 0 = 0
for (var l = 0; l < partialsum.Length; l++) {
for (var l = 0; l < partialsum.Length; l++)
{
partialsum[l] = -1;
}
// -1 = not set / 0 = 0
var lookaheadsum = new int[VboxLength];
for (var l = 0; l < lookaheadsum.Length; l++) {
for (var l = 0; l < lookaheadsum.Length; l++)
{
lookaheadsum[l] = -1;
}
int i, j, k, sum, index;
int i,
j,
k,
sum,
index;
if (maxw == rw) {
for (i = vbox.R1; i <= vbox.R2; i++) {
if (maxw == rw)
{
for (i = vbox.R1; i <= vbox.R2; i++)
{
sum = 0;
for (j = vbox.G1; j <= vbox.G2; j++) {
for (k = vbox.B1; k <= vbox.B2; k++) {
for (j = vbox.G1; j <= vbox.G2; j++)
{
for (k = vbox.B1; k <= vbox.B2; k++)
{
index = GetColorIndex(i, j, k);
sum += histo[index];
}
@@ -374,11 +470,16 @@ namespace BetterLyrics.WinUI3.Helper {
total += sum;
partialsum[i] = total;
}
} else if (maxw == gw) {
for (i = vbox.G1; i <= vbox.G2; i++) {
}
else if (maxw == gw)
{
for (i = vbox.G1; i <= vbox.G2; i++)
{
sum = 0;
for (j = vbox.R1; j <= vbox.R2; j++) {
for (k = vbox.B1; k <= vbox.B2; k++) {
for (j = vbox.R1; j <= vbox.R2; j++)
{
for (k = vbox.B1; k <= vbox.B2; k++)
{
index = GetColorIndex(j, i, k);
sum += histo[index];
}
@@ -386,12 +487,16 @@ namespace BetterLyrics.WinUI3.Helper {
total += sum;
partialsum[i] = total;
}
} else /* maxw == bw */
{
for (i = vbox.B1; i <= vbox.B2; i++) {
}
else /* maxw == bw */
{
for (i = vbox.B1; i <= vbox.B2; i++)
{
sum = 0;
for (j = vbox.R1; j <= vbox.R2; j++) {
for (k = vbox.G1; k <= vbox.G2; k++) {
for (j = vbox.R1; j <= vbox.R2; j++)
{
for (k = vbox.G1; k <= vbox.G2; k++)
{
index = GetColorIndex(j, k, i);
sum += histo[index];
}
@@ -401,15 +506,18 @@ namespace BetterLyrics.WinUI3.Helper {
}
}
for (i = 0; i < VboxLength; i++) {
if (partialsum[i] != -1) {
for (i = 0; i < VboxLength; i++)
{
if (partialsum[i] != -1)
{
lookaheadsum[i] = total - partialsum[i];
}
}
// 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>
@@ -420,13 +528,21 @@ 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;
while (niters < MaxIterations) {
while (niters < MaxIterations)
{
var vbox = lh[lh.Count - 1];
if (vbox.Count(false) == 0) {
if (vbox.Count(false) == 0)
{
lh.Sort(comparator);
niters++;
continue;
@@ -439,30 +555,35 @@ namespace BetterLyrics.WinUI3.Helper {
var vbox1 = vboxes?[0];
var vbox2 = vboxes?[1];
if (vbox1 == null) {
throw new Exception(
"vbox1 not defined; shouldn't happen!");
if (vbox1 == null)
{
throw new Exception("vbox1 not defined; shouldn't happen!");
}
lh.Add(vbox1);
if (vbox2 != null) {
if (vbox2 != null)
{
lh.Add(vbox2);
ncolors++;
}
lh.Sort(comparator);
if (ncolors >= target) {
if (ncolors >= target)
{
return;
}
if (niters++ > MaxIterations) {
if (niters++ > MaxIterations)
{
return;
}
}
}
public static CMap Quantize(byte[][] pixels, int maxcolors) {
public static CMap Quantize(byte[][] pixels, int maxcolors)
{
// short-circuit
if (pixels.Length == 0 || maxcolors < 2 || maxcolors > 256) {
if (pixels.Length == 0 || maxcolors < 2 || maxcolors > 256)
{
return null;
}
@@ -490,24 +611,40 @@ namespace BetterLyrics.WinUI3.Helper {
// calculate the actual colors
var cmap = new CMap();
foreach (var vb in pq) {
foreach (var vb in pq)
{
cmap.Push(vb);
}
return cmap;
}
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);
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
);
}
private static double WeightedMean(params double[] values) {
private static double WeightedMean(params double[] values)
{
double sum = 0;
double sumWeight = 0;
for (var i = 0; i < values.Length; i += 2) {
for (var i = 0; i < values.Length; i += 2)
{
var value = values[i];
var weight = values[i + 1];
@@ -518,31 +655,38 @@ namespace BetterLyrics.WinUI3.Helper {
return sum / sumWeight;
}
private static double InvertDiff(double value, double targetValue) {
private static double InvertDiff(double value, double targetValue)
{
return 1 - Math.Abs(value - targetValue);
}
}
public class QuantizedColor {
public QuantizedColor(Color color, int population) {
public class QuantizedColor
{
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) {
return Convert.ToInt32(Math.Round((299 * color.R + 587 * color.G + 114 * color.B) / 1000f));
public int CalculateYiqLuma(RGB color)
{
return Convert.ToInt32(
Math.Round((299 * color.R + 587 * color.G + 114 * color.B) / 1000f)
);
}
}
/// <summary>
/// 3D color space box.
/// </summary>
internal class VBox {
internal class VBox
{
private readonly int[] histo;
private int[] avg;
public int B1;
@@ -554,7 +698,8 @@ namespace BetterLyrics.WinUI3.Helper {
public int R2;
private int? volume;
public VBox(int r1, int r2, int g1, int g2, int b1, int b2, int[] histo) {
public VBox(int r1, int r2, int g1, int g2, int b1, int b2, int[] histo)
{
R1 = r1;
R2 = r2;
G1 = g1;
@@ -565,24 +710,31 @@ namespace BetterLyrics.WinUI3.Helper {
this.histo = histo;
}
public int Volume(bool force) {
if (volume == null || force) {
public int Volume(bool force)
{
if (volume == null || force)
{
volume = (R2 - R1 + 1) * (G2 - G1 + 1) * (B2 - B1 + 1);
}
return volume.Value;
}
public int Count(bool force) {
if (count == null || force) {
public int Count(bool force)
{
if (count == null || force)
{
var npix = 0;
int i;
for (i = R1; i <= R2; i++) {
for (i = R1; i <= R2; i++)
{
int j;
for (j = G1; j <= G2; j++) {
for (j = G1; j <= G2; j++)
{
int k;
for (k = B1; k <= B2; k++) {
for (k = B1; k <= B2; k++)
{
var index = Mmcq.GetColorIndex(i, j, k);
npix += histo[index];
}
@@ -595,12 +747,15 @@ namespace BetterLyrics.WinUI3.Helper {
return count.Value;
}
public VBox Clone() {
public VBox Clone()
{
return new VBox(R1, R2, G1, G2, B1, B2, histo);
}
public int[] Avg(bool force) {
if (avg == null || force) {
public int[] Avg(bool force)
{
if (avg == null || force)
{
var ntot = 0;
var rsum = 0;
@@ -609,11 +764,14 @@ namespace BetterLyrics.WinUI3.Helper {
int i;
for (i = R1; i <= R2; i++) {
for (i = R1; i <= R2; i++)
{
int j;
for (j = G1; j <= G2; j++) {
for (j = G1; j <= G2; j++)
{
int k;
for (k = B1; k <= B2; k++) {
for (k = B1; k <= B2; k++)
{
var histoindex = Mmcq.GetColorIndex(i, j, k);
var hval = histo[histoindex];
ntot += hval;
@@ -624,18 +782,22 @@ namespace BetterLyrics.WinUI3.Helper {
}
}
if (ntot > 0) {
if (ntot > 0)
{
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 {
}
else
{
avg = new[]
{
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),
};
}
}
@@ -643,7 +805,8 @@ namespace BetterLyrics.WinUI3.Helper {
return avg;
}
public bool Contains(int[] pixel) {
public bool Contains(int[] pixel)
{
var rval = pixel[0] >> Mmcq.Rshift;
var gval = pixel[1] >> Mmcq.Rshift;
var bval = pixel[2] >> Mmcq.Rshift;
@@ -652,16 +815,20 @@ namespace BetterLyrics.WinUI3.Helper {
}
}
internal class VBoxCountComparer : IComparer<VBox> {
public int Compare(VBox x, VBox y) {
internal class VBoxCountComparer : IComparer<VBox>
{
public int Compare(VBox x, VBox y)
{
var a = x.Count(false);
var b = y.Count(false);
return a < b ? -1 : (a > b ? 1 : 0);
}
}
internal class VBoxComparer : IComparer<VBox> {
public int Compare(VBox x, VBox y) {
internal class VBoxComparer : IComparer<VBox>
{
public int Compare(VBox x, VBox y)
{
var aCount = x.Count(false);
var bCount = y.Count(false);
var aVolume = x.Volume(false);
@@ -674,17 +841,20 @@ namespace BetterLyrics.WinUI3.Helper {
}
}
public class ColorThief {
public class ColorThief
{
public const int DefaultColorCount = 5;
public const int DefaultQuality = 10;
public const bool DefaultIgnoreWhite = true;
public const int ColorDepth = 4;
private CMap GetColorMap(byte[][] pixelArray, int colorCount) {
private CMap GetColorMap(byte[][] pixelArray, int colorCount)
{
// Send array to quantize function which clusters values using median
// cut algorithm
if (colorCount > 0) {
if (colorCount > 0)
{
--colorCount;
}
@@ -692,14 +862,18 @@ namespace BetterLyrics.WinUI3.Helper {
return cmap;
}
private byte[][] ConvertPixels(byte[] pixels, int pixelCount, int quality, bool ignoreWhite) {
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 + ")");
if (expectedDataLength != pixels.Length)
{
throw new ArgumentException(
"(expectedDataLength = "
+ expectedDataLength
+ ") != (pixels.length = "
+ pixels.Length
+ ")"
);
}
// Store the RGB values in an array format suitable for quantize
@@ -713,7 +887,8 @@ namespace BetterLyrics.WinUI3.Helper {
var numUsedPixels = 0;
var pixelArray = new byte[numRegardedPixels][];
for (var i = 0; i < pixelCount; i += quality) {
for (var i = 0; i < pixelCount; i += quality)
{
var offset = i * ColorDepth;
var b = pixels[offset];
var g = pixels[offset + 1];
@@ -721,7 +896,8 @@ namespace BetterLyrics.WinUI3.Helper {
var a = pixels[offset + 3];
// If pixel is mostly opaque and not white
if (a >= 125 && !(ignoreWhite && r > 250 && g > 250 && b > 250)) {
if (a >= 125 && !(ignoreWhite && r > 250 && g > 250 && b > 250))
{
pixelArray[numUsedPixels] = new[] { r, g, b };
numUsedPixels++;
}
@@ -745,15 +921,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;
}
@@ -772,24 +957,38 @@ 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);
if (cmap != null) {
if (cmap != null)
{
var colors = cmap.GeneratePalette();
return colors;
}
return new List<QuantizedColor>();
}
private async Task<byte[]> GetIntFromPixel(BitmapDecoder decoder) {
private async Task<byte[]> GetIntFromPixel(BitmapDecoder decoder)
{
var pixelsData = await decoder.GetPixelDataAsync();
var pixels = pixelsData.DetachPixelData();
return pixels;
}
private async Task<byte[][]> GetPixelsFast(BitmapDecoder sourceImage, int quality, bool ignoreWhite) {
if (quality < 1) {
private async Task<byte[][]> GetPixelsFast(
BitmapDecoder sourceImage,
int quality,
bool ignoreWhite
)
{
if (quality < 1)
{
quality = DefaultQuality;
}

View File

@@ -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);
}
}

View File

@@ -1,8 +1,9 @@
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
@@ -11,7 +12,9 @@ namespace BetterLyrics.WinUI3.Helper
{
public class ImageHelper
{
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(
byte[] imageBytes
)
{
if (imageBytes == null || imageBytes.Length == 0)
return null;
@@ -22,5 +25,13 @@ namespace BetterLyrics.WinUI3.Helper
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();
}
}
}

View File

@@ -4,13 +4,18 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper {
public class MathHelper {
public static List<int> GetAllFactors(int n) {
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) {
for (int i = 1; i <= Math.Sqrt(n); i++)
{
if (n % i == 0)
{
result.Add(i);
result.Add(n / i);
}
@@ -18,6 +23,5 @@ namespace BetterLyrics.WinUI3.Helper {
return [.. result];
}
}
}
}

View File

@@ -6,8 +6,10 @@ namespace BetterLyrics.WinUI3.Helper
{
public class SystemBackdropHelper
{
public static SystemBackdrop? CreateSystemBackdrop(BackdropType backdropType) {
return backdropType switch {
public static SystemBackdrop? CreateSystemBackdrop(BackdropType backdropType)
{
return backdropType switch
{
BackdropType.None => null,
BackdropType.Mica => new MicaSystemBackdrop(MicaKind.Base),
BackdropType.MicaAlt => new MicaSystemBackdrop(MicaKind.BaseAlt),
@@ -18,6 +20,5 @@ namespace BetterLyrics.WinUI3.Helper
_ => null,
};
}
}
}

View File

@@ -1,32 +1,40 @@
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml;
using System;
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 {
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 {
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++) {
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) {
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) {
if (childItems != null && childItems.Count > 0)
{
foreach (var item in childItems)
{
list.Add(item);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@@ -57,6 +57,21 @@
</interactivity:Interaction.Behaviors>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<ImageIcon
Height="24"
Margin="16,0"
Source="ms-appx:///Assets/Logo.png" />
<TextBlock
x:Name="AppTitleTextBlock"
Margin="0,-4,0,0"
VerticalAlignment="Center"
Text="{x:Bind Title}" />
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<!-- Always On Top -->
<AppBarButton
x:Name="AOTButton"
@@ -89,15 +104,6 @@
</Grid>
</AppBarButton>
<TextBlock
x:Name="AppTitleTextBlock"
Margin="0,-4,0,0"
VerticalAlignment="Center"
Text="{x:Bind Title}" />
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<!-- Window Mini -->
<AppBarButton
x:Name="MiniButton"

View File

@@ -1,3 +1,4 @@
using System;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Services.Settings;
@@ -12,39 +13,54 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Navigation;
using System;
// 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>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window {
public sealed partial class MainWindow : Window
{
private readonly OverlappedPresenter _presenter;
private readonly SettingsService _settingsService;
public static StackedNotificationsBehavior? StackedNotificationsBehavior { get; private set; }
public static StackedNotificationsBehavior? StackedNotificationsBehavior
{
get;
private set;
}
public MainWindow() {
public MainWindow()
{
this.InitializeComponent();
_settingsService = Ioc.Default.GetService<SettingsService>()!;
RootGrid.RequestedTheme = (ElementTheme)_settingsService.Theme;
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop((BackdropType)_settingsService.BackdropType);
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(
(BackdropType)_settingsService.BackdropType
);
WeakReferenceMessenger.Default.Register<ThemeChangedMessage>(this, (r, m) => {
RootGrid.RequestedTheme = m.Value;
});
WeakReferenceMessenger.Default.Register<ThemeChangedMessage>(
this,
(r, m) =>
{
RootGrid.RequestedTheme = m.Value;
}
);
WeakReferenceMessenger.Default.Register<SystemBackdropChangedMessage>(this, (r, m) => {
SystemBackdrop = null;
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(m.Value);
});
WeakReferenceMessenger.Default.Register<SystemBackdropChangedMessage>(
this,
(r, m) =>
{
SystemBackdrop = null;
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(m.Value);
}
);
// AppWindow.SetIcon("white_round.ico");
StackedNotificationsBehavior = NotificationQueue;
@@ -56,50 +72,67 @@ namespace BetterLyrics.WinUI3 {
SetTitleBar(TopCommandGrid);
}
public void Navigate(Type type) {
public void Navigate(Type type)
{
RootFrame.Navigate(type);
}
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) {
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
private void CloseButton_Click(object sender, RoutedEventArgs e) {
if (RootFrame.CurrentSourcePageType == typeof(MainPage)) {
((RootFrame.Content as MainPage)!.FindChild("LyricsCanvas") as CanvasAnimatedControl)!.Paused = true;
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
if (RootFrame.CurrentSourcePageType == typeof(MainPage))
{
(
(RootFrame.Content as MainPage)!.FindChild("LyricsCanvas")
as CanvasAnimatedControl
)!.Paused = true;
App.Current.Exit();
} else if (RootFrame.CurrentSourcePageType == typeof(SettingsPage)) {
}
else if (RootFrame.CurrentSourcePageType == typeof(SettingsPage))
{
App.Current.SettingsWindow!.AppWindow.Hide();
}
}
private void MaximiseButton_Click(object sender, RoutedEventArgs e) {
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
{
_presenter.Maximize();
//MaximiseButton.Visibility = Visibility.Collapsed;
//RestoreButton.Visibility = Visibility.Visible;
}
private void MinimiseButton_Click(object sender, RoutedEventArgs e) {
private void MinimiseButton_Click(object sender, RoutedEventArgs e)
{
_presenter.Minimize();
}
private void RestoreButton_Click(object sender, RoutedEventArgs e) {
private void RestoreButton_Click(object sender, RoutedEventArgs e)
{
_presenter.Restore();
//MaximiseButton.Visibility = Visibility.Visible;
//RestoreButton.Visibility = Visibility.Collapsed;
}
private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args) {
if (_presenter.State == OverlappedPresenterState.Maximized) {
private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args)
{
if (_presenter.State == OverlappedPresenterState.Maximized)
{
MaximiseButton.Visibility = Visibility.Collapsed;
RestoreButton.Visibility = Visibility.Visible;
} else if (_presenter.State == OverlappedPresenterState.Restored) {
}
else if (_presenter.State == OverlappedPresenterState.Restored)
{
MaximiseButton.Visibility = Visibility.Visible;
RestoreButton.Visibility = Visibility.Collapsed;
}
}
private void MiniButton_Click(object sender, RoutedEventArgs e) {
private void MiniButton_Click(object sender, RoutedEventArgs e)
{
AppWindow.Resize(new Windows.Graphics.SizeInt32(144, 48));
MiniButton.Visibility = Visibility.Collapsed;
UnminiButton.Visibility = Visibility.Visible;
@@ -109,7 +142,8 @@ namespace BetterLyrics.WinUI3 {
CloseButton.Visibility = Visibility.Collapsed;
}
private void UnminiButton_Click(object sender, RoutedEventArgs e) {
private void UnminiButton_Click(object sender, RoutedEventArgs e)
{
AppWindow.Resize(new Windows.Graphics.SizeInt32(800, 600));
MiniButton.Visibility = Visibility.Visible;
UnminiButton.Visibility = Visibility.Collapsed;
@@ -119,21 +153,26 @@ namespace BetterLyrics.WinUI3 {
CloseButton.Visibility = Visibility.Visible;
}
private void RootFrame_Navigated(object sender, NavigationEventArgs e) {
AppWindow.Title = Title = App.ResourceLoader!.GetString($"{e.SourcePageType.Name}Title");
private void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
AppWindow.Title = Title = App.ResourceLoader!.GetString(
$"{e.SourcePageType.Name}Title"
);
}
private void AOTButton_Click(object sender, RoutedEventArgs e) {
private void AOTButton_Click(object sender, RoutedEventArgs e)
{
_presenter.IsAlwaysOnTop = !_presenter.IsAlwaysOnTop;
string prefix;
if (_presenter.IsAlwaysOnTop) {
if (_presenter.IsAlwaysOnTop)
{
prefix = "Show";
} else {
}
else
{
prefix = "Hide";
}
(PinnedFontIcon.Resources[$"{prefix}PinnedFontIconStoryboard"] as Storyboard)!.Begin();
}
}
}

View File

@@ -1,12 +1,13 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
using DevWinUI;
using System;
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) {
}
namespace BetterLyrics.WinUI3.Messages
{
public class SystemBackdropChangedMessage(BackdropType value)
: ValueChangedMessage<BackdropType>(value) { }
}

View File

@@ -1,12 +1,13 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using System;
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) {
}
namespace BetterLyrics.WinUI3.Messages
{
public class ThemeChangedMessage(ElementTheme value)
: ValueChangedMessage<ElementTheme>(value) { }
}

View File

@@ -4,8 +4,10 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models {
public enum Language {
namespace BetterLyrics.WinUI3.Models
{
public enum Language
{
FollowSystem,
English,
SimplifiedChinese,

View File

@@ -4,8 +4,10 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models {
public enum LyricsAlignmentType {
namespace BetterLyrics.WinUI3.Models
{
public enum LyricsAlignmentType
{
Left,
Center,
Right,

View File

@@ -4,9 +4,11 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models {
public enum LyricsFontColorType {
namespace BetterLyrics.WinUI3.Models
{
public enum LyricsFontColorType
{
Default,
Dominant
Dominant,
}
}

View File

@@ -1,14 +1,15 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Graphics.Canvas.Text;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Numerics;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Graphics.Canvas.Text;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Models {
public class LyricsLine {
namespace BetterLyrics.WinUI3.Models
{
public class LyricsLine
{
public List<string> Texts { get; set; } = [];
public int LanguageIndex { get; set; } = 0;
@@ -37,6 +38,5 @@ namespace BetterLyrics.WinUI3.Models {
public float Opacity { get; set; }
public CanvasTextLayout TextLayout { get; set; }
}
}

View File

@@ -4,19 +4,23 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models {
public enum LyricsPlayingState {
namespace BetterLyrics.WinUI3.Models
{
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,
}
}

View File

@@ -1,7 +1,9 @@
using SQLite;
namespace BetterLyrics.WinUI3.Models {
public class MetadataIndex {
namespace BetterLyrics.WinUI3.Models
{
public class MetadataIndex
{
[PrimaryKey]
public string? Path { get; set; }
public string? Title { get; set; }

View File

@@ -7,34 +7,46 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
namespace BetterLyrics.WinUI3 {
public class Program {
namespace BetterLyrics.WinUI3
{
public class Program
{
[STAThread]
static int Main(string[] args) {
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();
});
if (!isRedirect)
{
Application.Start(
(p) =>
{
var context = new DispatcherQueueSynchronizationContext(
DispatcherQueue.GetForCurrentThread()
);
SynchronizationContext.SetSynchronizationContext(context);
_ = new App();
}
);
}
return 0;
}
private static bool DecideRedirection() {
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) {
if (keyInstance.IsCurrent)
{
keyInstance.Activated += OnActivated;
} else {
}
else
{
isRedirect = true;
RedirectActivationTo(args, keyInstance);
}
@@ -44,16 +56,23 @@ namespace BetterLyrics.WinUI3 {
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr CreateEvent(
IntPtr lpEventAttributes, bool bManualReset,
bool bInitialState, string lpName);
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);
uint dwFlags,
uint dwMilliseconds,
ulong nHandles,
IntPtr[] pHandles,
out uint dwIndex
);
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
@@ -62,10 +81,14 @@ namespace BetterLyrics.WinUI3 {
// 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) {
public static void RedirectActivationTo(
AppActivationArguments args,
AppInstance keyInstance
)
{
redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
Task.Run(() => {
Task.Run(() =>
{
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
SetEvent(redirectEventHandle);
});
@@ -73,17 +96,21 @@ namespace BetterLyrics.WinUI3 {
uint CWMO_DEFAULT = 0;
uint INFINITE = 0xFFFFFFFF;
_ = CoWaitForMultipleObjects(
CWMO_DEFAULT, INFINITE, 1,
[redirectEventHandle], out uint handleIndex);
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) {
private static void OnActivated(object sender, AppActivationArguments args)
{
ExtendedActivationKind kind = args.Kind;
}
}
}
}

View File

@@ -1,77 +1,127 @@
using ATL;
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 System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Ude;
using Windows.Media.Control;
namespace BetterLyrics.WinUI3.Services.Database {
public class DatabaseService {
namespace BetterLyrics.WinUI3.Services.Database
{
public class DatabaseService
{
private readonly SQLiteConnection _connection;
private readonly CharsetDetector _charsetDetector = new();
public DatabaseService() {
public DatabaseService()
{
_connection = new SQLiteConnection(Helper.AppInfo.DatabasePath);
if (_connection.GetTableInfo("MetadataIndex").Count == 0) {
if (_connection.GetTableInfo("MetadataIndex").Count == 0)
{
_connection.CreateTable<MetadataIndex>();
}
}
public async Task RebuildMusicMetadataIndexDatabaseAsync(IList<string> paths) {
await Task.Run(() => {
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)) {
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,
});
_connection.Insert(
new MetadataIndex
{
Path = file,
Title = track.Title,
Artist = track.Artist,
}
);
}
}
}
});
}
public Track? GetMusicMetadata(string? title, string? artist) {
var founds = _connection.Table<MetadataIndex>()
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.Contains(title) && m.Artist.Contains(artist)) || (m.Path.Contains(title) && m.Path.Contains(artist))).ToList();
if (founds == null || founds.Count == 0) {
.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 {
}
else
{
var first = new Track(founds[0].Path);
if (founds.Count == 1) {
if (founds.Count == 1)
{
return first;
} else {
if (first.Lyrics.Exists()) {
}
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)) {
}
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);
if (_charsetDetector.Charset != null)
{
Encoding encoding = Encoding.GetEncoding(
_charsetDetector.Charset
);
content = File.ReadAllText(found.Path, encoding);
} else {
}
else
{
content = File.ReadAllText(found.Path, Encoding.UTF8);
}
first.Lyrics.ParseLRC(content);
@@ -84,7 +134,5 @@ namespace BetterLyrics.WinUI3.Services.Database {
}
}
}
}
}

View File

@@ -1,26 +1,35 @@
using BetterLyrics.WinUI3.Models;
using System;
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 {
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;
// Album art
public const int CoverImageRadius = 24;
// Lyrics
public const int LyricsAlignmentType = 1; // Center
public const int LyricsBlurAmount = 0;
@@ -30,5 +39,6 @@ namespace BetterLyrics.WinUI3.Services.Settings {
public const bool IsLyricsGlowEffectEnabled = false;
public const bool IsLyricsDynamicGlowEffectEnabled = false;
public const int LyricsFontColorType = 0; // Default
public const int LyricsFontSelectedAccentColorIndex = 0;
}
}

View File

@@ -4,22 +4,31 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.Settings {
public static class SettingsKeys {
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";
// Album art
public const string CoverImageRadius = "CoverImageRadius";
// Lyrics
public const string LyricsAlignmentType = "LyricsAlignmentType";
public const string LyricsBlurAmount = "LyricsBlurAmount";
@@ -29,6 +38,7 @@ namespace BetterLyrics.WinUI3.Services.Settings {
public const string IsLyricsGlowEffectEnabled = "IsLyricsGlowEffectEnabled";
public const string IsLyricsDynamicGlowEffectEnabled = "IsLyricsDynamicGlowEffectEnabled";
public const string LyricsFontColorType = "LyricsFontColorType";
public const string LyricsFontSelectedAccentColorIndex =
"LyricsFontSelectedAccentColorIndex";
}
}

View File

@@ -1,21 +1,62 @@
using BetterLyrics.WinUI3.Messages;
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 System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Windows.Globalization;
using Windows.Storage;
namespace BetterLyrics.WinUI3.Services.Settings {
public partial class SettingsService : ObservableObject {
namespace BetterLyrics.WinUI3.Services.Settings
{
public partial class SettingsService : ObservableObject
{
private readonly ApplicationDataContainer _localSettings;
public bool IsFirstRun {
public SettingsService()
{
_localSettings = ApplicationData.Current.LocalSettings;
_musicLibraries =
[
.. JsonConvert.DeserializeObject<List<string>>(
Get(SettingsKeys.MusicLibraries, SettingsDefaultValues.MusicLibraries)!
)!,
];
_musicLibraries.CollectionChanged += (_, _) => SaveMusicLibraries();
}
private void WatchMultipleDirectories(IEnumerable<string> directories)
{
foreach (var dir in directories)
{
if (!Directory.Exists(dir))
continue;
var watcher = new FileSystemWatcher
{
Path = dir,
Filter = "*.*",
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
EnableRaisingEvents = true,
};
}
}
private void OnFileCreated(object sender, FileSystemEventArgs e)
{
App.DispatcherQueue.TryEnqueue(() => { });
}
public bool IsFirstRun
{
get => Get(SettingsKeys.IsFirstRun, SettingsDefaultValues.IsFirstRun);
set => Set(SettingsKeys.IsFirstRun, value);
}
@@ -24,9 +65,11 @@ namespace BetterLyrics.WinUI3.Services.Settings {
private bool _isRebuildingLyricsIndexDatabase = false;
// Theme
public int Theme {
public int Theme
{
get => Get(SettingsKeys.ThemeType, SettingsDefaultValues.ThemeType);
set {
set
{
Set(SettingsKeys.ThemeType, value);
WeakReferenceMessenger.Default.Send(new ThemeChangedMessage((ElementTheme)value));
}
@@ -35,12 +78,13 @@ namespace BetterLyrics.WinUI3.Services.Settings {
// Music
private ObservableCollection<string> _musicLibraries;
public ObservableCollection<string> MusicLibraries {
get {
return _musicLibraries;
}
set {
if (_musicLibraries != null) {
public ObservableCollection<string> MusicLibraries
{
get { return _musicLibraries; }
set
{
if (_musicLibraries != null)
{
_musicLibraries.CollectionChanged -= (_, _) => SaveMusicLibraries();
}
@@ -51,17 +95,20 @@ namespace BetterLyrics.WinUI3.Services.Settings {
}
}
private void SaveMusicLibraries() {
private void SaveMusicLibraries()
{
Set(SettingsKeys.MusicLibraries, JsonConvert.SerializeObject(MusicLibraries.ToList()));
}
// Language
public int Language {
public int Language
{
get => Get(SettingsKeys.Language, SettingsDefaultValues.Language);
set {
set
{
Set(SettingsKeys.Language, value);
switch ((Models.Language)Language) {
switch ((Models.Language)Language)
{
case Models.Language.FollowSystem:
ApplicationLanguages.PrimaryLanguageOverride = "";
break;
@@ -81,87 +128,142 @@ namespace BetterLyrics.WinUI3.Services.Settings {
}
// Backdrop
public int BackdropType {
public int BackdropType
{
get => Get(SettingsKeys.BackdropType, SettingsDefaultValues.BackdropType);
set {
set
{
Set(SettingsKeys.BackdropType, value);
WeakReferenceMessenger.Default.Send(new SystemBackdropChangedMessage((BackdropType)value));
WeakReferenceMessenger.Default.Send(
new SystemBackdropChangedMessage((BackdropType)value)
);
}
}
public bool IsCoverOverlayEnabled {
get => Get(SettingsKeys.IsCoverOverlayEnabled, SettingsDefaultValues.IsCoverOverlayEnabled);
public bool IsCoverOverlayEnabled
{
get =>
Get(
SettingsKeys.IsCoverOverlayEnabled,
SettingsDefaultValues.IsCoverOverlayEnabled
);
set => Set(SettingsKeys.IsCoverOverlayEnabled, value);
}
public bool IsDynamicCoverOverlay {
get => Get(SettingsKeys.IsDynamicCoverOverlay, SettingsDefaultValues.IsDynamicCoverOverlay);
public bool IsDynamicCoverOverlay
{
get =>
Get(
SettingsKeys.IsDynamicCoverOverlay,
SettingsDefaultValues.IsDynamicCoverOverlay
);
set => Set(SettingsKeys.IsDynamicCoverOverlay, value);
}
public int CoverOverlayOpacity {
public int CoverOverlayOpacity
{
get => Get(SettingsKeys.CoverOverlayOpacity, SettingsDefaultValues.CoverOverlayOpacity);
set => Set(SettingsKeys.CoverOverlayOpacity, value);
}
public int CoverOverlayBlurAmount {
get => Get(SettingsKeys.CoverOverlayBlurAmount, SettingsDefaultValues.CoverOverlayBlurAmount);
public int CoverOverlayBlurAmount
{
get =>
Get(
SettingsKeys.CoverOverlayBlurAmount,
SettingsDefaultValues.CoverOverlayBlurAmount
);
set => Set(SettingsKeys.CoverOverlayBlurAmount, value);
}
// Album art
public int CoverImageRadius
{
get => Get(SettingsKeys.CoverImageRadius, SettingsDefaultValues.CoverImageRadius);
set => Set(SettingsKeys.CoverImageRadius, value);
}
// Lyrics
public int LyricsAlignmentType {
public int LyricsAlignmentType
{
get => Get(SettingsKeys.LyricsAlignmentType, SettingsDefaultValues.LyricsAlignmentType);
set => Set(SettingsKeys.LyricsAlignmentType, value);
}
public int LyricsBlurAmount {
public int LyricsBlurAmount
{
get => Get(SettingsKeys.LyricsBlurAmount, SettingsDefaultValues.LyricsBlurAmount);
set => Set(SettingsKeys.LyricsBlurAmount, value);
}
public int LyricsVerticalEdgeOpacity {
get => Get(SettingsKeys.LyricsVerticalEdgeOpacity, SettingsDefaultValues.LyricsVerticalEdgeOpacity);
public int LyricsVerticalEdgeOpacity
{
get =>
Get(
SettingsKeys.LyricsVerticalEdgeOpacity,
SettingsDefaultValues.LyricsVerticalEdgeOpacity
);
set => Set(SettingsKeys.LyricsVerticalEdgeOpacity, value);
}
public float LyricsLineSpacingFactor {
get => Get(SettingsKeys.LyricsLineSpacingFactor, SettingsDefaultValues.LyricsLineSpacingFactor);
public float LyricsLineSpacingFactor
{
get =>
Get(
SettingsKeys.LyricsLineSpacingFactor,
SettingsDefaultValues.LyricsLineSpacingFactor
);
set => Set(SettingsKeys.LyricsLineSpacingFactor, value);
}
public int LyricsFontSize {
public int LyricsFontSize
{
get => Get(SettingsKeys.LyricsFontSize, SettingsDefaultValues.LyricsFontSize);
set => Set(SettingsKeys.LyricsFontSize, value);
}
public bool IsLyricsGlowEffectEnabled {
get => Get(SettingsKeys.IsLyricsGlowEffectEnabled, SettingsDefaultValues.IsLyricsGlowEffectEnabled);
public bool IsLyricsGlowEffectEnabled
{
get =>
Get(
SettingsKeys.IsLyricsGlowEffectEnabled,
SettingsDefaultValues.IsLyricsGlowEffectEnabled
);
set => Set(SettingsKeys.IsLyricsGlowEffectEnabled, value);
}
public bool IsLyricsDynamicGlowEffectEnabled {
get => Get(SettingsKeys.IsLyricsDynamicGlowEffectEnabled, SettingsDefaultValues.IsLyricsDynamicGlowEffectEnabled);
public bool IsLyricsDynamicGlowEffectEnabled
{
get =>
Get(
SettingsKeys.IsLyricsDynamicGlowEffectEnabled,
SettingsDefaultValues.IsLyricsDynamicGlowEffectEnabled
);
set => Set(SettingsKeys.IsLyricsDynamicGlowEffectEnabled, value);
}
public int LyricsFontColorType {
public int LyricsFontColorType
{
get => Get(SettingsKeys.LyricsFontColorType, SettingsDefaultValues.LyricsFontColorType);
set => Set(SettingsKeys.LyricsFontColorType, value);
}
private readonly ApplicationDataContainer _localSettings;
public SettingsService() {
_localSettings = ApplicationData.Current.LocalSettings;
_musicLibraries = [.. JsonConvert.DeserializeObject<List<string>>(
Get(SettingsKeys.MusicLibraries, SettingsDefaultValues.MusicLibraries)!)!];
_musicLibraries.CollectionChanged += (_, _) => SaveMusicLibraries();
public int LyricsFontSelectedAccentColorIndex
{
get =>
Get(
SettingsKeys.LyricsFontSelectedAccentColorIndex,
SettingsDefaultValues.LyricsFontSelectedAccentColorIndex
);
set
{
if (value >= 0)
Set(SettingsKeys.LyricsFontSelectedAccentColorIndex, value);
}
}
private T? Get<T>(string key, T? defaultValue = default) {
if (_localSettings.Values.TryGetValue(key, out object? value)) {
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) {
private void Set<T>(string key, T value, [CallerMemberName] string? propertyName = null)
{
_localSettings.Values[key] = value;
OnPropertyChanged(propertyName);
}
}
}

View File

@@ -279,7 +279,7 @@
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>Lyrics effect</value>
</data>
<data name="SettingsPageAlbum.Text" xml:space="preserve">
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
<value>Album background</value>
</data>
<data name="SettingsPageAbout.Text" xml:space="preserve">
@@ -363,4 +363,10 @@
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>Album art accent color</value>
</data>
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>Album art style</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>Corner radius</value>
</data>
</root>

View File

@@ -279,7 +279,7 @@
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌词效果</value>
</data>
<data name="SettingsPageAlbum.Text" xml:space="preserve">
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
<value>专辑背景</value>
</data>
<data name="SettingsPageAbout.Text" xml:space="preserve">
@@ -363,4 +363,10 @@
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>专辑强调色</value>
</data>
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>专辑封面样式</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>圆角半径</value>
</data>
</root>

View File

@@ -279,7 +279,7 @@
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌詞效果</value>
</data>
<data name="SettingsPageAlbum.Text" xml:space="preserve">
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
<value>專輯背景</value>
</data>
<data name="SettingsPageAbout.Text" xml:space="preserve">
@@ -363,4 +363,10 @@
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>專輯強調色</value>
</data>
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>專輯封面樣式</value>
</data>
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
<value>圓角半徑</value>
</data>
</root>

View File

@@ -1,26 +1,35 @@
using ATL;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ATL;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Database;
using BetterLyrics.WinUI3.Services.Settings;
using CommunityToolkit.Mvvm.ComponentModel;
using DevWinUI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading.Tasks;
using Ude;
using Windows.Graphics.Imaging;
using Windows.Media.Control;
using Windows.Storage.Streams;
using Windows.UI;
using static System.Net.Mime.MediaTypeNames;
using static ATL.LyricsInfo;
using static CommunityToolkit.WinUI.Animations.Expressions.ExpressionValues;
namespace BetterLyrics.WinUI3.ViewModels {
public partial class MainViewModel(DatabaseService databaseService) : ObservableObject {
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class MainViewModel : ObservableObject
{
[ObservableProperty]
private bool _isAnyMusicSessionExisted = false;
@@ -31,7 +40,7 @@ namespace BetterLyrics.WinUI3.ViewModels {
private string? _artist;
[ObservableProperty]
private ObservableCollection<Color> _coverImageDominantColors = [Colors.Transparent, Colors.Transparent, Colors.Transparent];
private ObservableCollection<Color> _coverImageDominantColors;
[ObservableProperty]
private BitmapImage? _coverImage;
@@ -48,17 +57,33 @@ namespace BetterLyrics.WinUI3.ViewModels {
[ObservableProperty]
private bool _lyricsExisted = false;
private readonly Helper.ColorThief _colorThief = new();
private readonly ColorThief _colorThief = new();
private readonly DatabaseService _databaseService = databaseService;
private readonly SettingsService _settingsService;
private readonly DatabaseService _databaseService;
public List<LyricsLine> GetLyrics(Track? track) {
private readonly int _accentColorCount = 3;
public MainViewModel(SettingsService settingsService, DatabaseService databaseService)
{
_settingsService = settingsService;
_databaseService = databaseService;
CoverImageDominantColors =
[
.. Enumerable.Repeat(Colors.Transparent, _accentColorCount),
];
}
public List<LyricsLine> GetLyrics(Track? track)
{
List<LyricsLine> result = [];
var lyricsPhrases = track?.Lyrics.SynchronizedLyrics;
var lyricsPhrases = track?.Lyrics?.SynchronizedLyrics;
if (lyricsPhrases?.Count > 0) {
if (lyricsPhrases[0].TimestampMs > 0) {
if (lyricsPhrases?.Count > 0)
{
if (lyricsPhrases[0].TimestampMs > 0)
{
var placeholder = new LyricsPhrase(0, " ");
lyricsPhrases.Insert(0, placeholder);
lyricsPhrases.Insert(0, placeholder);
@@ -67,44 +92,50 @@ namespace BetterLyrics.WinUI3.ViewModels {
LyricsLine? lyricsLine = null;
for (int i = 0; i < lyricsPhrases?.Count; i++) {
for (int i = 0; i < lyricsPhrases?.Count; i++)
{
var lyricsPhrase = lyricsPhrases[i];
int startTimestampMs = lyricsPhrase.TimestampMs;
int endTimestampMs;
if (i + 1 < lyricsPhrases.Count) {
if (i + 1 < lyricsPhrases.Count)
{
endTimestampMs = lyricsPhrases[i + 1].TimestampMs;
} else {
}
else
{
endTimestampMs = (int)track.DurationMs;
}
lyricsLine ??= new LyricsLine {
StartPlayingTimestampMs = startTimestampMs,
};
lyricsLine ??= new LyricsLine { StartPlayingTimestampMs = startTimestampMs };
lyricsLine.Texts.Add(lyricsPhrase.Text);
if (endTimestampMs == startTimestampMs) {
if (endTimestampMs == startTimestampMs)
{
continue;
} else {
}
else
{
lyricsLine.EndPlayingTimestampMs = endTimestampMs;
result.Add(lyricsLine);
lyricsLine = null;
}
}
LyricsExisted = result.Count != 0;
if (!LyricsExisted) {
if (!LyricsExisted)
{
ShowLyricsOnly = false;
}
return result;
}
public async Task<(List<LyricsLine>, SoftwareBitmap?, uint, uint)> SetSongInfoAsync(GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps) {
public async Task<(List<LyricsLine>, SoftwareBitmap?, uint, uint)> SetSongInfoAsync(
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps
)
{
SoftwareBitmap? coverSoftwareBitmap = null;
uint coverImagePixelWidth = 0;
uint coverImagePixelHeight = 0;
@@ -114,26 +145,37 @@ namespace BetterLyrics.WinUI3.ViewModels {
IRandomAccessStream? stream = null;
var track = _databaseService.GetMusicMetadata(Title, Artist);
var track = _databaseService.GetMusicMetadata(mediaProps);
if (mediaProps?.Thumbnail is IRandomAccessStreamReference reference) {
if (mediaProps?.Thumbnail is IRandomAccessStreamReference reference)
{
stream = await reference.OpenReadAsync();
} else {
if (track?.EmbeddedPictures.Count > 0) {
}
else
{
if (track?.EmbeddedPictures.Count > 0)
{
var bytes = track.EmbeddedPictures[0].PictureData;
if (bytes != null) {
if (bytes != null)
{
stream = await Helper.ImageHelper.GetStreamFromBytesAsync(bytes);
}
}
}
// Set cover image and dominant colors
if (stream == null) {
if (stream == null)
{
CoverImage = null;
for (int i = 0; i < 3; i++) {
CoverImageDominantColors[i] = Colors.Transparent;
}
} else {
CoverImageDominantColors =
[
.. Enumerable.Repeat(Colors.Transparent, _accentColorCount),
];
_settingsService.LyricsFontSelectedAccentColorIndex =
_settingsService.LyricsFontSelectedAccentColorIndex;
}
else
{
CoverImage = new BitmapImage();
await CoverImage.SetSourceAsync(stream);
stream.Seek(0);
@@ -147,19 +189,24 @@ namespace BetterLyrics.WinUI3.ViewModels {
BitmapAlphaMode.Premultiplied
);
var quantizedColors = await _colorThief.GetPalette(decoder, 3);
for (int i = 0; i < 3; i++) {
Helper.QuantizedColor quantizedColor = quantizedColors[i];
CoverImageDominantColors[i] = Color.FromArgb(
quantizedColor.Color.A, quantizedColor.Color.R, quantizedColor.Color.G, quantizedColor.Color.B);
}
CoverImageDominantColors =
[
.. (await _colorThief.GetPalette(decoder, _accentColorCount)).Select(color =>
Color.FromArgb(color.Color.A, color.Color.R, color.Color.G, color.Color.B)
),
];
_settingsService.LyricsFontSelectedAccentColorIndex =
_settingsService.LyricsFontSelectedAccentColorIndex;
stream.Dispose();
}
return (GetLyrics(track), coverSoftwareBitmap, coverImagePixelWidth, coverImagePixelHeight);
return (
GetLyrics(track),
coverSoftwareBitmap,
coverImagePixelWidth,
coverImagePixelHeight
);
}
}
}

View File

@@ -1,12 +1,12 @@
using BetterLyrics.WinUI3.Models;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Database;
using BetterLyrics.WinUI3.Services.Settings;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Media;
using Windows.Media.Playback;
@@ -14,9 +14,14 @@ using Windows.Storage.Pickers;
using Windows.System;
using WinRT.Interop;
namespace BetterLyrics.WinUI3.ViewModels {
public partial class SettingsViewModel(DatabaseService databaseService, SettingsService settingsService, MainViewModel mainViewModel) : ObservableObject {
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class SettingsViewModel(
DatabaseService databaseService,
SettingsService settingsService,
MainViewModel mainViewModel
) : ObservableObject
{
private readonly MediaPlayer _mediaPlayer = new();
private readonly DatabaseService _databaseService = databaseService;
@@ -28,19 +33,24 @@ namespace BetterLyrics.WinUI3.ViewModels {
public string Version => Helper.AppInfo.AppVersion;
[RelayCommand]
private async Task RebuildLyricsIndexDatabaseAsync() {
private async Task RebuildLyricsIndexDatabaseAsync()
{
SettingsService.IsRebuildingLyricsIndexDatabase = true;
await _databaseService.RebuildMusicMetadataIndexDatabaseAsync(SettingsService.MusicLibraries);
await _databaseService.RebuildMusicMetadataIndexDatabaseAsync(
SettingsService.MusicLibraries
);
SettingsService.IsRebuildingLyricsIndexDatabase = false;
}
public async Task RemoveFolderAsync(string path) {
public async Task RemoveFolderAsync(string path)
{
SettingsService.MusicLibraries.Remove(path);
await RebuildLyricsIndexDatabaseAsync();
}
[RelayCommand]
private async Task SelectAndAddFolderAsync() {
private async Task SelectAndAddFolderAsync()
{
var picker = new FolderPicker();
picker.FileTypeFilter.Add("*");
@@ -52,48 +62,62 @@ namespace BetterLyrics.WinUI3.ViewModels {
App.Current.SettingsWindow!.AppWindow.MoveInZOrderAtTop();
if (folder != null) {
if (folder != null)
{
await AddFolderAsync(folder.Path);
}
}
private async Task AddFolderAsync(string path) {
private async Task AddFolderAsync(string path)
{
bool existed = SettingsService.MusicLibraries.Any((x) => x == path);
if (existed) {
MainWindow.StackedNotificationsBehavior?.Show(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"),
Helper.AnimationHelper.StackedNotificationsShowingDuration);
} else {
if (existed)
{
MainWindow.StackedNotificationsBehavior?.Show(
App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"),
Helper.AnimationHelper.StackedNotificationsShowingDuration
);
}
else
{
SettingsService.MusicLibraries.Add(path);
await RebuildLyricsIndexDatabaseAsync();
}
}
[RelayCommand]
private static async Task LaunchProjectGitHubPageAsync() {
private static async Task LaunchProjectGitHubPageAsync()
{
await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl));
}
private static void OpenFolderInFileExplorer(string path) {
Process.Start(new ProcessStartInfo {
FileName = "explorer.exe",
Arguments = path,
UseShellExecute = true
});
private static void OpenFolderInFileExplorer(string path)
{
Process.Start(
new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = path,
UseShellExecute = true,
}
);
}
public static void OpenMusicFolder(string path) {
public static void OpenMusicFolder(string path)
{
OpenFolderInFileExplorer(path);
}
[RelayCommand]
private static void RestartApp() {
private static void RestartApp()
{
// The restart will be executed immediately.
AppRestartFailureReason failureReason =
Microsoft.Windows.AppLifecycle.AppInstance.Restart("");
// If the restart fails, handle it here.
switch (failureReason) {
switch (failureReason)
{
case AppRestartFailureReason.RestartPending:
break;
case AppRestartFailureReason.NotInForeground:
@@ -106,16 +130,17 @@ namespace BetterLyrics.WinUI3.ViewModels {
}
[RelayCommand]
private async Task PlayTestingMusicTask() {
private async Task PlayTestingMusicTask()
{
await AddFolderAsync(Helper.AppInfo.AssetsFolder);
_mediaPlayer.SetUriSource(new Uri(Helper.AppInfo.TestMusicPath));
_mediaPlayer.Play();
}
[RelayCommand]
private static void OpenLogFolder() {
private static void OpenLogFolder()
{
OpenFolderInFileExplorer(Helper.AppInfo.LogDirectory);
}
}
}

View File

@@ -138,7 +138,7 @@
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<TextBlock x:Uid="SettingsPageAlbum" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<TextBlock x:Uid="SettingsPageAlbumOverlay" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="SettingsPageCoverOverlay"
@@ -196,6 +196,27 @@
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<TextBlock x:Uid="SettingsPageAlbumStyle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon Glyph=&#xE71A;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.SettingsService.CoverImageRadius, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text=" %" />
<Slider
Maximum="100"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="2"
TickFrequency="2"
TickPlacement="Outside"
Value="{x:Bind ViewModel.SettingsService.CoverImageRadius, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<TextBlock x:Uid="SettingsPageLyricsStyle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon Glyph=&#xE8E3;}">
@@ -207,24 +228,60 @@
</controls:SettingsCard>
<controls:SettingsExpander x:Uid="SettingsPageLyricsFontColor" HeaderIcon="{ui:FontIcon Glyph=&#xE8D3;}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="IsExpanded" Value="False" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
ComparisonCondition="Equal"
Value="1">
<interactivity:ChangePropertyAction PropertyName="IsExpanded" Value="True" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<ComboBox SelectedIndex="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=TwoWay}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDefault" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDominant" />
</ComboBox>
<controls:SettingsExpander.Items>
<controls:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Vertical">
<GridView ItemsSource="{x:Bind ViewModel.MainViewModel.CoverImageDominantColors, Mode=OneWay}" SelectedIndex="0">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
ComparisonCondition="Equal"
Value="1">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<GridView ItemsSource="{x:Bind ViewModel.MainViewModel.CoverImageDominantColors, Mode=OneWay}" SelectedIndex="{x:Bind ViewModel.SettingsService.LyricsFontSelectedAccentColorIndex, Mode=TwoWay}">
<GridView.ItemTemplate>
<DataTemplate>
<GridViewItem>
<Border
Width="64"
Height="64"
CornerRadius="4">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
<StackPanel>
<Border
Width="64"
Height="64"
CornerRadius="4">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
<TextBlock
Margin="4,0,4,4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
TextWrapping="WrapWholeWords" />
</StackPanel>
</GridViewItem>
</DataTemplate>
</GridView.ItemTemplate>
@@ -331,7 +388,7 @@
<TextBlock x:Uid="SettingsPageAbout" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard Header="BetterLyrics" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/Icon.png}">
<controls:SettingsCard Header="BetterLyrics" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/Logo.png}">
<controls:SettingsCard.Description>
<RichTextBlock>
<Paragraph>

View File

@@ -1,29 +1,39 @@
using Microsoft.UI.Xaml.Controls;
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.
namespace BetterLyrics.WinUI3.Views {
namespace BetterLyrics.WinUI3.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SettingsPage : Page {
public sealed partial class SettingsPage : Page
{
public SettingsViewModel ViewModel => (SettingsViewModel)DataContext;
public SettingsPage() {
public SettingsPage()
{
this.InitializeComponent();
DataContext = Ioc.Default.GetService<SettingsViewModel>();
}
private void SettingsPageOpenPathButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) {
private void SettingsPageOpenPathButton_Click(
object sender,
Microsoft.UI.Xaml.RoutedEventArgs e
)
{
SettingsViewModel.OpenMusicFolder((string)(sender as HyperlinkButton)!.Tag);
}
private async void SettingsPageRemovePathButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) {
private async void SettingsPageRemovePathButton_Click(
object sender,
Microsoft.UI.Xaml.RoutedEventArgs e
)
{
await ViewModel.RemoveFolderAsync((string)(sender as HyperlinkButton)!.Tag);
}
}

View File

@@ -1,5 +1,5 @@
<div align="center">
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Icon.png" alt="" width="64"/>
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="64"/>
</div>
<h2 align="center">