Compare commits

...

13 Commits

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

View File

@@ -11,7 +11,7 @@
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.6.0" />
Version="1.0.7.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

View File

@@ -91,6 +91,8 @@
<!-- Dimensions -->
<!-- Fonts -->
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -1,13 +1,15 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -16,33 +18,18 @@ using Microsoft.UI.Xaml;
using Microsoft.Windows.ApplicationModel.Resources;
using Serilog;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class
/// </summary>
public partial class App : Application
{
#region Fields
/// <summary>
/// Defines the _logger
/// </summary>
private readonly ILogger<App> _logger;
#endregion
public static new App Current => (App)Application.Current;
public static DispatcherQueue? DispatcherQueue { get; private set; }
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
public static ResourceLoader? ResourceLoader { get; private set; }
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
public App()
{
this.InitializeComponent();
@@ -63,46 +50,23 @@ namespace BetterLyrics.WinUI3
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}
#endregion
#region Properties
/// <summary>
/// Gets the Current
/// </summary>
public static new App Current => (App)Application.Current;
/// <summary>
/// Gets the DispatcherQueue
/// </summary>
public static DispatcherQueue? DispatcherQueue { get; private set; }
/// <summary>
/// Gets the DispatcherQueueTimer
/// </summary>
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
/// <summary>
/// Gets the ResourceLoader
/// </summary>
public static ResourceLoader? ResourceLoader { get; private set; }
#endregion
#region Methods
/// <summary>
/// Invoked when the application is launched
/// </summary>
/// <param name="args">Details about the launch request and process</param>
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
WindowHelper.OpenLyricsWindow();
}
WindowHelper.OpenOrShowWindow<LyricsWindow>();
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
/// <summary>
/// The ConfigureServices
/// </summary>
string[] commandLineArguments = Environment.GetCommandLineArgs();
if (commandLineArguments.Length > 1)
{
commandLineArguments = commandLineArguments.Skip(1).ToArray();
if (commandLineArguments.First() == AppInfo.UnlockWindowTag)
{
lyricsWindow.AutoSelectLyricsMode(AutoStartWindowType.DesktopMode, false);
return;
}
}
lyricsWindow.AutoSelectLyricsMode();
}
private static void ConfigureServices()
{
Log.Logger = new LoggerConfiguration()
@@ -124,9 +88,10 @@ namespace BetterLyrics.WinUI3
.AddSingleton<IMusicSearchService, MusicSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>()
// ViewModels
.AddTransient<HostWindowViewModel>()
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>()
.AddSingleton<SystemTrayViewModel>()
.AddSingleton<SettingsViewModel>()
.AddSingleton<SettingsPageViewModel>()
.AddSingleton<LyricsPageViewModel>()
.AddSingleton<LyricsRendererViewModel>()
.AddSingleton<LyricsSettingsControlViewModel>()
@@ -134,59 +99,25 @@ namespace BetterLyrics.WinUI3
);
}
/// <summary>
/// The App_UnhandledException
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="Microsoft.UI.Xaml.UnhandledExceptionEventArgs"/></param>
private void App_UnhandledException(
object sender,
Microsoft.UI.Xaml.UnhandledExceptionEventArgs e
)
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
_logger.LogError(e.Exception, "App_UnhandledException");
e.Handled = true;
}
/// <summary>
/// The CurrentDomain_FirstChanceException
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs"/></param>
private void CurrentDomain_FirstChanceException(
object? sender,
System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e
)
private void CurrentDomain_FirstChanceException(object? sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
{
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
//_logger.LogError(e.Exception, "CurrentDomain_FirstChanceException");
}
/// <summary>
/// The CurrentDomain_UnhandledException
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="System.UnhandledExceptionEventArgs"/></param>
private void CurrentDomain_UnhandledException(
object sender,
System.UnhandledExceptionEventArgs e
)
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
{
_logger.LogError(e.ExceptionObject.ToString(), "CurrentDomain_UnhandledException");
}
/// <summary>
/// The TaskScheduler_UnobservedTaskException
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="UnobservedTaskExceptionEventArgs"/></param>
private void TaskScheduler_UnobservedTaskException(
object? sender,
UnobservedTaskExceptionEventArgs e
)
private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
//_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
}
#endregion
}
}

View File

@@ -19,8 +19,8 @@
<PRIResource Remove="ViewModels\Lyrics\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Controls\DependenciesSettingsExpander.xaml" />
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Views\SettingsWindow.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Logo.ico" />
@@ -50,17 +50,16 @@
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
<PackageReference Include="WinUIEx" Version="2.6.0" />
<PackageReference Include="z440.atl.core" Version="6.26.0" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\AI - 甜度爆表.mp3">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<PackageReference Include="z440.atl.core" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<Page Update="Rendering\InAppLyricsRenderer.xaml">
@@ -77,12 +76,12 @@
<TrimmerRootAssembly Include="TagLibSharp" />
</ItemGroup>
<ItemGroup>
<Page Update="Controls\SystemTray.xaml">
<Page Update="Views\SettingsWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\DependenciesSettingsExpander.xaml">
<Page Update="Controls\SystemTray.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,38 +2,14 @@
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the EasingType
/// </summary>
public enum EasingType
{
/// <summary>
/// Defines the EaseInOutQuad
/// </summary>
EaseInOutQuad,
/// <summary>
/// Defines the EaseInQuad
/// </summary>
EaseInQuad,
/// <summary>
/// Defines the EaseOutQuad
/// </summary>
EaseOutQuad,
/// <summary>
/// Defines the Linear
/// </summary>
EaseInOutExpo,
Linear,
/// <summary>
/// Defines the SmootherStep
/// </summary>
SmoothStep,
SmootherStep,
}
#endregion
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,93 +5,29 @@ using BetterLyrics.WinUI3.Enums;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="AnimationHelper" />
/// </summary>
public class AnimationHelper
{
#region Constants
/// <summary>
/// Defines the DebounceDefaultDuration
/// </summary>
public const int DebounceDefaultDuration = 200;
/// <summary>
/// Defines the StackedNotificationsShowingDuration
/// </summary>
public const int StackedNotificationsShowingDuration = 3900;
/// <summary>
/// Defines the StoryboardDefaultDuration
/// </summary>
public const int StoryboardDefaultDuration = 200;
#endregion
}
/// <summary>
/// Defines the <see cref="ValueTransition{T}" />
/// </summary>
/// <typeparam name="T"></typeparam>
public class ValueTransition<T>
where T : struct
{
#region Fields
/// <summary>
/// Defines the _currentValue
/// </summary>
private T _currentValue;
/// <summary>
/// Defines the _durationSeconds
/// </summary>
private float _durationSeconds;
/// <summary>
/// Defines the _interpolator
/// </summary>
private readonly EasingType? _easingType;
private Func<T, T, float, T> _interpolator;
/// <summary>
/// Defines the _isTransitioning
/// </summary>
private bool _isTransitioning;
/// <summary>
/// Defines the _progress
/// </summary>
private float _progress;
/// <summary>
/// Defines the _startValue
/// </summary>
private T _startValue;
/// <summary>
/// Defines the _targetValue
/// </summary>
private T _targetValue;
private EasingType? _easingType;
public bool IsTransitioning => _isTransitioning;
public T Value => _currentValue;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ValueTransition{T}"/> class.
/// </summary>
/// <param name="initialValue">The initialValue<see cref="T"/></param>
/// <param name="durationSeconds">The durationSeconds<see cref="float"/></param>
/// <param name="interpolator">The interpolator<see cref="Func{T, T, float, T}"/></param>
public ValueTransition(
T initialValue,
float durationSeconds,
Func<T, T, float, T>? interpolator = null,
EasingType? easingType = null
)
public ValueTransition(T initialValue, float durationSeconds, Func<T, T, float, T>? interpolator = null, EasingType? easingType = null)
{
_currentValue = initialValue;
_startValue = initialValue;
@@ -117,64 +53,15 @@ namespace BetterLyrics.WinUI3.Helper
}
}
#endregion
#region Properties
/// <summary>
/// Gets a value indicating whether IsTransitioning
/// </summary>
public bool IsTransitioning => _isTransitioning;
/// <summary>
/// Gets the Value
/// </summary>
public T Value => _currentValue;
#endregion
#region Methods
private Func<T, T, float, T> GetInterpolatorByEasingType(EasingType type)
public void JumpTo(T value)
{
// 这里只以float为例实际可根据T类型扩展
if (typeof(T) == typeof(float))
{
return (start, end, progress) =>
{
float s = (float)(object)start;
float e = (float)(object)end;
float t = progress;
switch (type)
{
case EasingType.EaseInOutQuad:
t = EasingHelper.EaseInOutQuad(t);
break;
case EasingType.EaseInQuad:
t = EasingHelper.EaseInQuad(t);
break;
case EasingType.EaseOutQuad:
t = EasingHelper.EaseOutQuad(t);
break;
case EasingType.Linear:
t = EasingHelper.Linear(t);
break;
case EasingType.SmootherStep:
t = EasingHelper.SmootherStep(t);
break;
default:
break;
}
return (T)(object)(s + (e - s) * t);
};
}
throw new NotSupportedException("当前类型未实现默认缓动插值");
_currentValue = value;
_startValue = value;
_targetValue = value;
_progress = 1f;
_isTransitioning = false;
}
/// <summary>
/// The Reset
/// </summary>
/// <param name="value">The value<see cref="T"/></param>
public void Reset(T value)
{
_currentValue = value;
@@ -184,10 +71,6 @@ namespace BetterLyrics.WinUI3.Helper
_isTransitioning = false;
}
/// <summary>
/// The StartTransition
/// </summary>
/// <param name="targetValue">The targetValue<see cref="T"/></param>
public void StartTransition(T targetValue)
{
if (!targetValue.Equals(_currentValue))
@@ -199,27 +82,9 @@ namespace BetterLyrics.WinUI3.Helper
}
}
/// <summary>
/// 立即跳转到指定值,无动画
/// </summary>
/// <param name="value">目标值</param>
public void JumpTo(T value)
{
_currentValue = value;
_startValue = value;
_targetValue = value;
_progress = 1f;
_isTransitioning = false;
}
/// <summary>
/// The Update
/// </summary>
/// <param name="elapsedTime">The elapsedTime<see cref="TimeSpan"/></param>
public void Update(TimeSpan elapsedTime)
{
if (!_isTransitioning)
return;
if (!_isTransitioning) return;
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
if (_progress >= 1f)
@@ -234,6 +99,45 @@ namespace BetterLyrics.WinUI3.Helper
}
}
#endregion
private Func<T, T, float, T> GetInterpolatorByEasingType(EasingType type)
{
if (typeof(T) == typeof(float))
{
return (start, end, progress) =>
{
float s = (float)(object)start;
float e = (float)(object)end;
float t = progress;
switch (type)
{
case EasingType.EaseInOutExpo:
t = EasingHelper.EaseInOutExpo(t);
break;
case EasingType.EaseInOutQuad:
t = EasingHelper.EaseInOutQuad(t);
break;
case EasingType.EaseInQuad:
t = EasingHelper.EaseInQuad(t);
break;
case EasingType.EaseOutQuad:
t = EasingHelper.EaseOutQuad(t);
break;
case EasingType.Linear:
t = EasingHelper.Linear(t);
break;
case EasingType.SmoothStep:
t = EasingHelper.SmoothStep(t);
break;
case EasingType.SmootherStep:
t = EasingHelper.SmootherStep(t);
break;
default:
break;
}
return (T)(object)(s + (e - s) * t);
};
}
throw new NotSupportedException($"Easing type {type} is not supported for type {typeof(T)}.");
}
}
}

View File

@@ -2,46 +2,19 @@
namespace BetterLyrics.WinUI3.Helper
{
using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Storage.FileProperties;
/// <summary>
/// Defines the <see cref="AppInfo" />
/// </summary>
public static class AppInfo
{
#region Constants
/// <summary>
/// Defines the AppAuthor
/// </summary>
public const string AppAuthor = "Zhe Fang";
/// <summary>
/// Defines the AppDisplayName
/// </summary>
public const string AppDisplayName = "Better Lyrics";
// App Metadata
/// <summary>
/// Defines the AppName
/// </summary>
public const string AppName = "BetterLyrics";
/// <summary>
/// Defines the GithubUrl
/// </summary>
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
#endregion
#region Properties
/// <summary>
/// Gets the AppVersion
/// </summary>
public static string AppVersion
{
get
@@ -51,70 +24,24 @@ namespace BetterLyrics.WinUI3.Helper
}
}
/// <summary>
/// Gets the AssetsFolder
/// </summary>
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
public const string UnlockWindowTag = "UnlockWindow";
public static string AmllTtmlDbIndexPath => Path.Combine(CacheFolder, "amll-ttml-db-index.json");
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
/// <summary>
/// Gets the CacheFolder
/// </summary>
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
// Environment Info
// Data Files
/// <summary>
/// Gets the LogDirectory
/// </summary>
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
/// <summary>
/// Gets the LogFilePattern
/// </summary>
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
/// <summary>
/// Gets the OnlineLyricsCacheDirectory
/// </summary>
public static string LrcLibLyricsCacheDirectory =>
Path.Combine(CacheFolder, "lrclib-lyrics");
public static string AmllTtmlDbLyricsCacheDirectory =>
Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
public static string QQLyricsCacheDirectory => Path.Combine(CacheFolder, "qq-lyrics");
public static string KugouLyricsCacheDirectory => Path.Combine(CacheFolder, "kugou-lyrics");
public static string NeteaseLyricsCacheDirectory =>
Path.Combine(CacheFolder, "netease-lyrics");
public static string AmllTtmlDbIndexPath =>
Path.Combine(CacheFolder, "amll-ttml-db-index.json");
/// <summary>
/// Gets the TestMusicPath
/// </summary>
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
// Base Folders
/// <summary>
/// Gets the LocalFolder
/// </summary>
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
public static string LrcLibLyricsCacheDirectory => Path.Combine(CacheFolder, "lrclib-lyrics");
public static string NeteaseLyricsCacheDirectory => Path.Combine(CacheFolder, "netease-lyrics");
public static string QQLyricsCacheDirectory => Path.Combine(CacheFolder, "qq-lyrics");
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
/// <summary>
/// Gets the TestMusicFileName
/// </summary>
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
#endregion
#region Methods
/// <summary>
/// The EnsureDirectories
/// </summary>
public static void EnsureDirectories()
{
Directory.CreateDirectory(LocalFolder);
@@ -126,6 +53,18 @@ namespace BetterLyrics.WinUI3.Helper
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
}
#endregion
public static async Task<DateTime> GetBuildDate()
{
var assembly = Assembly.GetExecutingAssembly();
var filePath = assembly.Location;
if (!File.Exists(filePath))
return DateTime.MinValue;
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
// 获取文件基本属性
BasicProperties props = await file.GetBasicPropertiesAsync();
// 返回修改日期
return props.DateModified.DateTime;
}
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -8,64 +8,37 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="EasingHelper" />
/// </summary>
public class EasingHelper
{
#region Methods
public static float EaseInOutExpo(float t)
{
return t == 0
? 0
: t == 1
? 1
: t < 0.5 ? MathF.Pow(2, 20 * t - 10) / 2
: (2 - MathF.Pow(2, -20 * t + 10)) / 2;
}
/// <summary>
/// Acceleration until halfway then deceleration
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float EaseInOutQuad(float t)
{
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
/// <summary>
/// Accelerating from 0
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float EaseInQuad(float t) => t * t;
/// <summary>
/// Decelerating to 0
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float EaseOutQuad(float t) => t * (2 - t);
/// <summary>
/// No easing
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float Linear(float t) => t;
/// <summary>
/// Even smoother transition with continuous first and second derivatives
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float SmootherStep(float t)
{
return t * t * t * (t * (6 * t - 15) + 10);
}
/// <summary>
/// Smoother transition than linear
/// </summary>
/// <param name="t">The t<see cref="float"/></param>
/// <returns>The <see cref="float"/></returns>
public static float SmoothStep(float t)
{
return t * t * (3 - 2 * t);
}
#endregion
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,21 +2,8 @@
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Messages
{
/// <summary>
/// Defines the <see cref="ShowNotificatonMessage" />
/// </summary>
public class ShowNotificatonMessage(Notification value)
: ValueChangedMessage<Notification>(value)
{
}
public class ShowNotificatonMessage(Notification value) : ValueChangedMessage<Notification>(value) { }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,60 +4,25 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="SongInfo" />
/// </summary>
public partial class SongInfo : ObservableObject
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SongInfo"/> class.
/// </summary>
public SongInfo()
{
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the Album
/// </summary>
[ObservableProperty]
public partial string? Album { get; set; }
/// <summary>
/// Gets or sets the AlbumArt
/// </summary>
public byte[]? AlbumArt { get; set; } = null;
/// <summary>
/// Gets or sets the Artist
/// </summary>
[ObservableProperty]
public partial string Artist { get; set; }
/// <summary>
/// Gets or sets the DurationMs
/// In milliseconds
/// </summary>
[ObservableProperty]
public partial double? DurationMs { get; set; }
/// <summary>
/// Gets or sets the SourceAppUserModelId
/// </summary>
[ObservableProperty]
public partial string? SourceAppUserModelId { get; set; } = null;
/// <summary>
/// Gets or sets the Title
/// </summary>
[ObservableProperty]
public partial string Title { get; set; }
#endregion
public SongInfo() { }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,143 +5,57 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Windows.UI;
using Windows.UI.Text;
namespace BetterLyrics.WinUI3.Services
{
#region Interfaces
/// <summary>
/// Defines the <see cref="ISettingsService" />
/// </summary>
public interface ISettingsService
{
#region Properties
// App behavior
/// <summary>
/// Gets or sets the AutoStartWindowType
/// </summary>
AutoStartWindowType AutoStartWindowType { get; set; }
/// <summary>
/// Gets or sets the BackdropType
/// </summary>
BackdropType BackdropType { get; set; }
// Album art cover style
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
int CoverImageRadius { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayBlurAmount
/// </summary>
int CoverOverlayBlurAmount { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayOpacity
/// </summary>
int CoverOverlayOpacity { get; set; }
// Album art background
/// <summary>
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
/// </summary>
bool IsCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
bool IsDynamicCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsFirstRun
/// </summary>
bool IsFanLyricsEnabled { get; set; }
bool IsFirstRun { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
/// </summary>
bool IsLyricsGlowEffectEnabled { get; set; }
/// <summary>
/// Gets or sets the Language
/// </summary>
Language Language { get; set; }
int DesktopWindowLeft { get; set; }
int DesktopWindowTop { get; set; }
int DesktopWindowWidth { get; set; }
int DesktopWindowHeight { get; set; }
bool AutoLockOnDesktopMode { get; set; }
// Lyrics lib
/// <summary>
/// Gets or sets the LocalLyricsFolders
/// </summary>
List<LocalLyricsFolder> LocalLyricsFolders { get; set; }
// Lyrics style and effetc
/// <summary>
/// Gets or sets the LyricsAlignmentType
/// </summary>
LyricsAlignmentType LyricsAlignmentType { get; set; }
/// <summary>
/// Gets or sets the LyricsBlurAmount
/// </summary>
int LyricsBlurAmount { get; set; }
/// <summary>
/// Gets or sets the LyricsFontColorType
/// </summary>
Color LyricsCustomFontColor { get; set; }
LyricsFontColorType LyricsFontColorType { get; set; }
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
int LyricsFontSize { get; set; }
/// <summary>
/// Gets or sets the LyricsFontWeight
/// </summary>
LyricsFontWeight LyricsFontWeight { get; set; }
/// <summary>
/// Gets or sets the LyricsGlowEffectScope
/// </summary>
LineRenderingType LyricsGlowEffectScope { get; set; }
/// <summary>
/// Gets or sets the LyricsLineSpacingFactor
/// </summary>
float LyricsLineSpacingFactor { get; set; }
/// <summary>
/// Gets or sets the LyricsSearchProvidersInfo
/// </summary>
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
/// <summary>
/// Gets or sets the LyricsVerticalEdgeOpacity
/// </summary>
int LyricsVerticalEdgeOpacity { get; set; }
// App appearance
/// <summary>
/// Gets or sets the ThemeType
/// </summary>
ElementTheme ThemeType { get; set; }
/// <summary>
/// Gets or sets the TitleBarType
/// </summary>
TitleBarType TitleBarType { get; set; }
#endregion
}
#endregion
}

View File

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

View File

@@ -10,38 +10,19 @@ using System.Threading.Tasks;
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Windows.Storage;
using Windows.Storage.FileProperties;
using Lyricify.Lyrics.Providers.Web.Kugou;
using Lyricify.Lyrics.Searchers;
namespace BetterLyrics.WinUI3.Services
{
/// <summary>
/// Defines the <see cref="MusicSearchService" />
/// </summary>
public class MusicSearchService : IMusicSearchService
{
#region Fields
/// <summary>
/// Defines the _httpClient
/// </summary>
private readonly HttpClient _lrcLibHttpClient;
private readonly HttpClient _amllTtmlDbHttpClient;
/// <summary>
/// Defines the _settingsService
/// </summary>
private readonly HttpClient _lrcLibHttpClient;
private readonly ISettingsService _settingsService;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MusicSearchService"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
public MusicSearchService(ISettingsService settingsService)
{
_settingsService = settingsService;
@@ -53,29 +34,38 @@ namespace BetterLyrics.WinUI3.Services
_amllTtmlDbHttpClient = new HttpClient();
}
#endregion
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
{
const string url = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
try
{
using var response = await _amllTtmlDbHttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode) return false;
#region Methods
await using var stream = await response.Content.ReadAsStreamAsync();
await using var fs = new FileStream(
AppInfo.AmllTtmlDbIndexPath,
FileMode.Create,
FileAccess.Write,
FileShare.None
);
await stream.CopyToAsync(fs);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// The SearchAlbumArtAsync
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <returns>The <see cref="byte[]?"/></returns>
public byte[]? SearchAlbumArtAsync(string title, string artist)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (
var file in Directory.GetFiles(
folder.Path,
$"*.*",
SearchOption.AllDirectories
)
)
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
{
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
@@ -93,21 +83,9 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
/// <summary>
/// The SearchLyricsAsync
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="album">The album<see cref="string"/></param>
/// <param name="durationMs">The durationMs<see cref="double"/></param>
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
/// <returns>The <see cref="Task{(string?, LyricsFormat?)}"/></returns>
public async Task<(string?, LyricsFormat?)> SearchLyricsAsync(
string title,
string artist,
string album = "",
double durationMs = 0.0,
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
string title, string artist, string album = "", double durationMs = 0.0,
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleArtistAlbumAndDuration
)
{
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
@@ -123,12 +101,7 @@ namespace BetterLyrics.WinUI3.Services
// Check cache first
if (provider.Provider.IsRemote())
{
cachedLyrics = ReadCache(
title,
artist,
lyricsFormat,
provider.Provider.GetCacheDirectory()
);
cachedLyrics = ReadCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
return (cachedLyrics, lyricsFormat);
@@ -145,11 +118,7 @@ namespace BetterLyrics.WinUI3.Services
}
else
{
searchedLyrics = await LocalLyricsSearchInLyricsFiles(
title,
artist,
lyricsFormat
);
searchedLyrics = await LocalLyricsSearchInLyricsFiles(title, artist, lyricsFormat);
}
}
else
@@ -157,40 +126,16 @@ namespace BetterLyrics.WinUI3.Services
switch (provider.Provider)
{
case LyricsSearchProvider.LrcLib:
searchedLyrics = await SearchLrcLibAsync(
title,
artist,
album,
(int)(durationMs / 1000),
matchMode
);
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000), matchMode);
break;
case LyricsSearchProvider.QQ:
searchedLyrics = await SearchQQAsync(
title,
artist,
album,
(int)durationMs,
matchMode
);
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, matchMode, Searchers.QQMusic);
break;
case LyricsSearchProvider.Kugou:
searchedLyrics = await SearchKugouAsync(
title,
artist,
album,
(int)durationMs,
matchMode
);
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, matchMode, Searchers.Kugou);
break;
case LyricsSearchProvider.Netease:
searchedLyrics = await SearchNeteaseAsync(
title,
artist,
album,
(int)durationMs,
matchMode
);
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, matchMode, Searchers.Netease);
break;
case LyricsSearchProvider.AmllTtmlDb:
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
@@ -204,21 +149,10 @@ namespace BetterLyrics.WinUI3.Services
{
if (provider.Provider.IsRemote())
{
WriteCache(
title,
artist,
searchedLyrics,
lyricsFormat,
provider.Provider.GetCacheDirectory()
);
WriteCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
}
return (
searchedLyrics,
lyricsFormat == LyricsFormat.NotSpecified
? searchedLyrics.DetectFormat()
: lyricsFormat
);
return (searchedLyrics, lyricsFormat == LyricsFormat.NotSpecified ? searchedLyrics.DetectFormat() : lyricsFormat);
}
}
@@ -230,12 +164,6 @@ namespace BetterLyrics.WinUI3.Services
return fileName.Contains(title) && fileName.Contains(artist);
}
/// <summary>
/// The SanitizeFileName
/// </summary>
/// <param name="fileName">The fileName<see cref="string"/></param>
/// <param name="replacement">The replacement<see cref="char"/></param>
/// <returns>The <see cref="string"/></returns>
private static string SanitizeFileName(string fileName, char replacement = '_')
{
var invalidChars = Path.GetInvalidFileNameChars();
@@ -247,37 +175,17 @@ namespace BetterLyrics.WinUI3.Services
return sb.ToString();
}
/// <summary>
/// The LocalLyricsSearchInLyricsFiles
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="format">The format<see cref="LyricsFormat"/></param>
/// <returns>The <see cref="Task{string?}"/></returns>
private async Task<string?> LocalLyricsSearchInLyricsFiles(
string title,
string artist,
LyricsFormat format
)
private async Task<string?> LocalLyricsSearchInLyricsFiles(string title, string artist, LyricsFormat format)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (
var file in Directory.GetFiles(
folder.Path,
$"*{format.ToFileExtension()}",
SearchOption.AllDirectories
)
)
foreach (var file in Directory.GetFiles(folder.Path, $"*{format.ToFileExtension()}", SearchOption.AllDirectories))
{
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
string? raw = await File.ReadAllTextAsync(
file,
FileHelper.GetEncoding(file)
);
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
if (raw != null)
{
return raw;
@@ -289,32 +197,16 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
/// <summary>
/// The LocalLyricsSearchInMusicFiles
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <returns>The <see cref="string?"/></returns>
private string? LocalLyricsSearchInMusicFiles(string title, string artist)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (
var file in Directory.GetFiles(
folder.Path,
$"*.*",
SearchOption.AllDirectories
)
)
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
{
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
//Track track = new(file);
//var test1 = track.Lyrics.SynchronizedLyrics;
//var test2 = track.Lyrics.UnsynchronizedLyrics;
try
{
var plain = TagLib.File.Create(file).Tag.Lyrics;
@@ -323,10 +215,7 @@ namespace BetterLyrics.WinUI3.Services
return plain;
}
}
catch (Exception e)
{
throw e;
}
catch (Exception) { }
}
}
}
@@ -335,26 +224,11 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
/// <summary>
/// The ReadCache
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="format">The format<see cref="LyricsFormat"/></param>
/// <returns>The <see cref="string?"/></returns>
private string? ReadCache(
string title,
string artist,
LyricsFormat format,
string cacheFolderPath
)
private string? ReadCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
{
var safeArtist = SanitizeFileName(artist);
var safeTitle = SanitizeFileName(title);
var cacheFilePath = Path.Combine(
cacheFolderPath,
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
);
var cacheFilePath = Path.Combine(cacheFolderPath, $"{safeArtist} - {safeTitle}{format.ToFileExtension()}");
if (File.Exists(cacheFilePath))
{
return File.ReadAllText(cacheFilePath);
@@ -362,184 +236,6 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
/// <summary>
/// The SearchLrcLib
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="album">The album<see cref="string"/></param>
/// <param name="duration">The duration<see cref="int"/></param>
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
/// <returns>The <see cref="Task{string?}"/></returns>
private async Task<string?> SearchLrcLibAsync(
string title,
string artist,
string album,
int duration,
MusicSearchMatchMode matchMode
)
{
// Build API query URL
var url =
$"https://lrclib.net/api/search?"
+ $"track_name={Uri.EscapeDataString(title)}&"
+ $"artist_name={Uri.EscapeDataString(artist)}";
if (matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration)
{
url +=
$"&album_name={Uri.EscapeDataString(album)}"
+ $"&durationMs={Uri.EscapeDataString(duration.ToString())}";
}
var response = await _lrcLibHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
return null;
var json = await response.Content.ReadAsStringAsync();
var jArr = JsonSerializer.Deserialize(
json,
Serialization.SourceGenerationContext.Default.JsonElement
);
if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
{
var first = jArr[0];
var syncedLyrics = first.GetProperty("syncedLyrics").GetString();
var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
if (!string.IsNullOrWhiteSpace(result))
{
return result;
}
}
return null;
}
private async Task<string?> SearchQQAsync(
string title,
string artist,
string album,
int durationMs,
MusicSearchMatchMode matchMode
)
{
string? queryId = (
(
await new Lyricify.Lyrics.Searchers.QQMusicSearcher().SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? durationMs
: null,
Album =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? album
: null,
AlbumArtists = [artist],
Artists = [artist],
Title = title,
}
)
) as Lyricify.Lyrics.Searchers.QQMusicSearchResult
)?.Id;
if (queryId is string id)
{
return (await Lyricify.Lyrics.Decrypter.Qrc.Helper.GetLyricsAsync(id))?.Lyrics;
}
return null;
}
private async Task<string?> SearchKugouAsync(
string title,
string artist,
string album,
int durationMs,
MusicSearchMatchMode matchMode
)
{
string? queryHash = (
(
await new Lyricify.Lyrics.Searchers.KugouSearcher().SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? durationMs
: null,
Album =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? album
: null,
AlbumArtists = [artist],
Artists = [artist],
Title = title,
}
)
) as Lyricify.Lyrics.Searchers.KugouSearchResult
)?.Hash;
if (queryHash != null)
{
var candidate = (
await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(
hash: queryHash
)
)?.Candidates.FirstOrDefault();
if (candidate != null)
{
return await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(
candidate.Id,
candidate.AccessKey
);
}
}
return null;
}
private async Task<string?> SearchNeteaseAsync(
string title,
string artist,
string album,
int durationMs,
MusicSearchMatchMode matchMode
)
{
string? queryId = (
(
await new Lyricify.Lyrics.Searchers.NeteaseSearcher().SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? durationMs
: null,
Album =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? album
: null,
AlbumArtists = [artist],
Artists = [artist],
Title = title,
}
)
) as Lyricify.Lyrics.Searchers.NeteaseSearchResult
)?.Id;
if (queryId != null)
{
return (await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(queryId))
?.Lrc
.Lyric;
}
return null;
}
/// <summary>
/// 本地检索 amll-ttml-db 索引并下载歌词内容
/// </summary>
/// <param name="title">歌曲名</param>
/// <param name="artist">歌手名</param>
/// <returns>歌词内容字符串,找不到返回 null</returns>
private async Task<string?> SearchAmllTtmlDbAsync(string title, string artist)
{
// 检索本地 JSONL 索引文件,查找 rawLyricFile
@@ -551,7 +247,7 @@ namespace BetterLyrics.WinUI3.Services
}
string? rawLyricFile = null;
foreach (var line in File.ReadLines(AppInfo.AmllTtmlDbIndexPath))
await foreach (var line in File.ReadLinesAsync(AppInfo.AmllTtmlDbIndexPath))
{
if (string.IsNullOrWhiteSpace(line))
continue;
@@ -593,8 +289,7 @@ namespace BetterLyrics.WinUI3.Services
return null;
// 下载歌词内容
var url =
$"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
try
{
var response = await _amllTtmlDbHttpClient.GetAsync(url);
@@ -608,47 +303,91 @@ namespace BetterLyrics.WinUI3.Services
}
}
/// <summary>
/// 下载 amll-ttml-db 的 JSONL 索引文件到本地缓存目录
/// </summary>
/// <returns>下载成功返回 true否则 false</returns>
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
private async Task<string?> SearchLrcLibAsync(string title, string artist, string album, int duration, MusicSearchMatchMode matchMode)
{
const string url =
"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
try
{
using var response = await _amllTtmlDbHttpClient.GetAsync(
url,
HttpCompletionOption.ResponseHeadersRead
);
if (!response.IsSuccessStatusCode)
return false;
// Build API query URL
var url =
$"https://lrclib.net/api/search?"
+ $"track_name={Uri.EscapeDataString(title)}&"
+ $"artist_name={Uri.EscapeDataString(artist)}";
await using var stream = await response.Content.ReadAsStreamAsync();
await using var fs = new FileStream(
AppInfo.AmllTtmlDbIndexPath,
FileMode.Create,
FileAccess.Write,
FileShare.None
);
await stream.CopyToAsync(fs);
return true;
}
catch
if (matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration)
{
return false;
url +=
$"&album_name={Uri.EscapeDataString(album)}"
+ $"&durationMs={Uri.EscapeDataString(duration.ToString())}";
}
var response = await _lrcLibHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
return null;
var json = await response.Content.ReadAsStringAsync();
var jArr = JsonSerializer.Deserialize(
json,
Serialization.SourceGenerationContext.Default.JsonElement
);
if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
{
var first = jArr[0];
var syncedLyrics = first.GetProperty("syncedLyrics").GetString();
var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
if (!string.IsNullOrWhiteSpace(result))
{
return result;
}
}
return null;
}
private async Task<string?> SearchUsingLyricifyAsync(
string title,
string artist,
string album,
int durationMs,
MusicSearchMatchMode matchMode,
Searchers searchers
)
{
var result = await SearchersHelper.GetSearcher(searchers).SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs = matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration ? durationMs : null,
Album = matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration ? album : null,
AlbumArtists = [artist],
Artists = [artist],
Title = title,
}
);
if (result is QQMusicSearchResult qqResult)
{
var response = await Lyricify.Lyrics.Decrypter.Qrc.Helper.GetLyricsAsync(qqResult.Id);
var original = response?.Lyrics;
return original;
}
else if (result is NeteaseSearchResult neteaseResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
return response?.Lrc.Lyric;
}
else if (result is KugouSearchResult kugouResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(kugouResult.Hash);
if (response?.Candidates.FirstOrDefault() is SearchLyricsResponse.Candidate candidate)
{
return Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyrics(
candidate.Id,
candidate.AccessKey
);
}
}
return null;
}
/// <summary>
/// The WriteCache
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="lyrics">The lyrics<see cref="string"/></param>
/// <param name="format">The format<see cref="LyricsFormat"/></param>
private void WriteCache(
string title,
string artist,
@@ -665,7 +404,5 @@ namespace BetterLyrics.WinUI3.Services
);
File.WriteAllText(cacheFilePath, lyrics);
}
#endregion
}
}

View File

@@ -1,199 +1,118 @@
// 2025/6/23 by Zhe Fang
using ATL;
using BetterLyrics.WinUI3.Enums;
using System;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Media.Control;
using Windows.Storage.Streams;
namespace BetterLyrics.WinUI3.Services
{
/// <summary>
/// Defines the <see cref="PlaybackService" />
/// </summary>
public partial class PlaybackService : IPlaybackService
{
#region Fields
/// <summary>
/// Defines the _dispatcherQueue
/// </summary>
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
/// <summary>
/// Defines the _musicSearchService
/// </summary>
private readonly IMusicSearchService _musicSearchService;
/// <summary>
/// Defines the _currentSession
/// </summary>
private GlobalSystemMediaTransportControlsSession? _currentSession = null;
/// <summary>
/// Defines the _sessionManager
/// </summary>
private GlobalSystemMediaTransportControlsSessionManager? _sessionManager = null;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="PlaybackService"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
/// <param name="musicSearchService">The musicSearchService<see cref="IMusicSearchService"/></param>
public PlaybackService(
ISettingsService settingsService,
IMusicSearchService musicSearchService
)
public PlaybackService(ISettingsService settingsService, IMusicSearchService musicSearchService)
{
_musicSearchService = musicSearchService;
InitMediaManager().ConfigureAwait(true);
}
#endregion
#region Events
/// <summary>
/// Defines the IsPlayingChanged
/// </summary>
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
/// <summary>
/// Defines the PositionChanged
/// </summary>
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
/// <summary>
/// Defines the SongInfoChanged
/// </summary>
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
#endregion
#region Properties
/// <summary>
/// Gets a value indicating whether IsPlaying
/// </summary>
public bool IsPlaying { get; private set; }
/// <summary>
/// Gets the Position
/// </summary>
public TimeSpan Position { get; private set; }
/// <summary>
/// Gets the SongInfo
/// </summary>
public SongInfo? SongInfo { get; private set; }
#endregion
#region Methods
/// <summary>
/// Note: this func is invoked by non-UI thread
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private async void CurrentSession_MediaPropertiesChanged(
GlobalSystemMediaTransportControlsSession? sender,
MediaPropertiesChangedEventArgs? args
)
private void CurrentSession_MediaPropertiesChanged(GlobalSystemMediaTransportControlsSession? sender, MediaPropertiesChangedEventArgs? args)
{
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
if (sender == null)
{
SongInfo = null;
}
else
{
try
App.DispatcherQueueTimer!.Debounce(
async () =>
{
mediaProps = await sender.TryGetMediaPropertiesAsync();
}
catch (Exception) { }
if (mediaProps == null)
{
SongInfo = null;
}
else
{
SongInfo = new SongInfo
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
if (sender == null)
{
Title = mediaProps.Title,
Artist = mediaProps.Artist,
Album = mediaProps?.AlbumTitle ?? string.Empty,
DurationMs = _currentSession
?.GetTimelineProperties()
.EndTime.TotalMilliseconds,
SourceAppUserModelId = _currentSession?.SourceAppUserModelId,
};
if (
SongInfo.SourceAppUserModelId?.Contains(Package.Current.Id.FamilyName)
?? false
)
{
SongInfo.Title = "甜度爆表";
SongInfo.Artist = "AI";
}
if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference)
{
SongInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(streamReference);
SongInfo = null;
}
else
{
SongInfo.AlbumArt = _musicSearchService.SearchAlbumArtAsync(
SongInfo.Title,
SongInfo.Artist
);
if (SongInfo.AlbumArt == null)
try
{
SongInfo.AlbumArt = await ImageHelper.CreateTextPlaceholderBytesAsync(
$"{SongInfo.Artist} - {SongInfo.Title}",
400,
400
);
mediaProps = await sender.TryGetMediaPropertiesAsync();
}
catch (Exception) { }
if (mediaProps == null)
{
SongInfo = null;
}
else
{
SongInfo = new SongInfo
{
Title = mediaProps.Title,
Artist = mediaProps.Artist,
Album = mediaProps?.AlbumTitle ?? string.Empty,
DurationMs = _currentSession
?.GetTimelineProperties()
.EndTime.TotalMilliseconds,
SourceAppUserModelId = _currentSession?.SourceAppUserModelId,
};
if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference)
{
SongInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(
streamReference
);
}
else
{
SongInfo.AlbumArt = _musicSearchService.SearchAlbumArtAsync(
SongInfo.Title,
SongInfo.Artist
);
if (SongInfo.AlbumArt == null)
{
SongInfo.AlbumArt =
await ImageHelper.CreateTextPlaceholderBytesAsync(
$"{SongInfo.Artist} - {SongInfo.Title}",
400,
400
);
}
}
}
}
}
}
_dispatcherQueue.TryEnqueue(
DispatcherQueuePriority.High,
() =>
{
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
}
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
{
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
}
);
},
TimeSpan.FromMilliseconds(1000)
);
}
/// <summary>
/// Note: Non-UI thread
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void CurrentSession_PlaybackInfoChanged(
GlobalSystemMediaTransportControlsSession? sender,
PlaybackInfoChangedEventArgs? args
)
private void CurrentSession_PlaybackInfoChanged(GlobalSystemMediaTransportControlsSession? sender, PlaybackInfoChangedEventArgs? args)
{
if (sender == null)
{
@@ -220,8 +139,7 @@ namespace BetterLyrics.WinUI3.Services
break;
}
}
_dispatcherQueue.TryEnqueue(
DispatcherQueuePriority.High,
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
{
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(IsPlaying));
@@ -229,15 +147,7 @@ namespace BetterLyrics.WinUI3.Services
);
}
/// <summary>
/// The CurrentSession_TimelinePropertiesChanged
/// </summary>
/// <param name="sender">The sender<see cref="GlobalSystemMediaTransportControlsSession?"/></param>
/// <param name="args">The args<see cref="TimelinePropertiesChangedEventArgs?"/></param>
private void CurrentSession_TimelinePropertiesChanged(
GlobalSystemMediaTransportControlsSession? sender,
TimelinePropertiesChangedEventArgs? args
)
private void CurrentSession_TimelinePropertiesChanged(GlobalSystemMediaTransportControlsSession? sender, TimelinePropertiesChangedEventArgs? args)
{
if (sender == null)
{
@@ -256,10 +166,6 @@ namespace BetterLyrics.WinUI3.Services
);
}
/// <summary>
/// The InitMediaManager
/// </summary>
/// <returns>The <see cref="Task"/></returns>
private async Task InitMediaManager()
{
_sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
@@ -268,11 +174,6 @@ namespace BetterLyrics.WinUI3.Services
SessionManager_CurrentSessionChanged(_sessionManager, null);
}
/// <summary>
/// The SessionManager_CurrentSessionChanged
/// </summary>
/// <param name="sender">The sender<see cref="GlobalSystemMediaTransportControlsSessionManager"/></param>
/// <param name="args">The args<see cref="CurrentSessionChangedEventArgs?"/></param>
private void SessionManager_CurrentSessionChanged(
GlobalSystemMediaTransportControlsSessionManager sender,
CurrentSessionChangedEventArgs? args
@@ -303,7 +204,5 @@ namespace BetterLyrics.WinUI3.Services
CurrentSession_PlaybackInfoChanged(_currentSession, null);
CurrentSession_TimelinePropertiesChanged(_currentSession, null);
}
#endregion
}
}

View File

@@ -4,154 +4,55 @@ using System;
using System.Collections.Generic;
using System.Linq;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization;
using CommunityToolkit.WinUI.Helpers;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Windows.Storage;
using Windows.UI;
namespace BetterLyrics.WinUI3.Services
{
/// <summary>
/// Defines the <see cref="SettingsService" />
/// </summary>
public class SettingsService : ISettingsService
{
#region Constants
public const string LyricsCustomFontColorKey = "LyricsCustomFontColor";
// App behavior
/// <summary>
/// Defines the AutoStartWindowTypeKey
/// </summary>
private const string AutoStartWindowTypeKey = "AutoStartWindowType";
/// <summary>
/// Defines the BackdropTypeKey
/// </summary>
private const string BackdropTypeKey = "BackdropType";
/// <summary>
/// Defines the CoverImageRadiusKey
/// </summary>
private const string CoverImageRadiusKey = "CoverImageRadius";
/// <summary>
/// Defines the CoverOverlayBlurAmountKey
/// </summary>
private const string CoverOverlayBlurAmountKey = "CoverOverlayBlurAmount";
/// <summary>
/// Defines the CoverOverlayOpacityKey
/// </summary>
private const string CoverOverlayOpacityKey = "CoverOverlayOpacity";
// Album art
/// <summary>
/// Defines the IsCoverOverlayEnabledKey
/// </summary>
private const string IsCoverOverlayEnabledKey = "IsCoverOverlayEnabled";
/// <summary>
/// Defines the IsDynamicCoverOverlayEnabledKey
/// </summary>
private const string DesktopWindowLeftKey = "DesktopWindowLeft";
private const string DesktopWindowTopKey = "DesktopWindowTop";
private const string DesktopWindowWidthKey = "DesktopWindowWidth";
private const string DesktopWindowHeightKey = "DesktopWindowHeight";
private const string AutoLockOnDesktopModeKey = "AutoLockOnDesktopMode";
private const string IsDynamicCoverOverlayEnabledKey = "IsDynamicCoverOverlayEnabled";
/// <summary>
/// Defines the IsFirstRunKey
/// </summary>
private const string IsFanLyricsEnabledKey = "IsFanLyricsEnabled";
private const string IsFirstRunKey = "IsFirstRun";
/// <summary>
/// Defines the IsLyricsGlowEffectEnabledKey
/// </summary>
private const string IsLyricsGlowEffectEnabledKey = "IsLyricsGlowEffectEnabled";
/// <summary>
/// Defines the LanguageKey
/// </summary>
private const string LanguageKey = "Language";
// Lyrics lib
/// <summary>
/// Defines the LocalLyricsFoldersKey
/// </summary>
private const string LocalLyricsFoldersKey = "LocalLyricsFolders";
/// <summary>
/// Defines the LyricsAlignmentTypeKey
/// </summary>
private const string LyricsAlignmentTypeKey = "LyricsAlignmentType";
/// <summary>
/// Defines the LyricsBlurAmountKey
/// </summary>
private const string LyricsBlurAmountKey = "LyricsBlurAmount";
/// <summary>
/// Defines the LyricsFontColorTypeKey
/// </summary>
private const string LyricsFontColorTypeKey = "LyricsFontColorType";
/// <summary>
/// Defines the LyricsFontSizeKey
/// </summary>
private const string LyricsFontSizeKey = "LyricsFontSize";
/// <summary>
/// Defines the LyricsFontWeightKey
/// </summary>
private const string LyricsFontWeightKey = "LyricsFontWeightKey";
/// <summary>
/// Defines the LyricsGlowEffectScopeKey
/// </summary>
private const string LyricsGlowEffectScopeKey = "LyricsGlowEffectScope";
/// <summary>
/// Defines the LyricsLineSpacingFactorKey
/// </summary>
private const string LyricsLineSpacingFactorKey = "LyricsLineSpacingFactor";
/// <summary>
/// Defines the LyricsSearchProvidersInfoKey
/// </summary>
private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo";
/// <summary>
/// Defines the LyricsVerticalEdgeOpacityKey
/// </summary>
private const string LyricsVerticalEdgeOpacityKey = "LyricsVerticalEdgeOpacity";
// App appearance
/// <summary>
/// Defines the ThemeTypeKey
/// </summary>
private const string ThemeTypeKey = "ThemeType";
/// <summary>
/// Defines the TitleBarTypeKey
/// </summary>
private const string TitleBarTypeKey = "TitleBarType";
#endregion
#region Fields
/// <summary>
/// Defines the _localSettings
/// </summary>
private readonly ApplicationDataContainer _localSettings;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SettingsService"/> class.
/// </summary>
public SettingsService()
{
_localSettings = ApplicationData.Current.LocalSettings;
@@ -181,9 +82,12 @@ namespace BetterLyrics.WinUI3.Services
.ToList();
}
// App appearance
SetDefault(ThemeTypeKey, (int)ElementTheme.Default);
SetDefault(LanguageKey, (int)Language.FollowSystem);
SetDefault(BackdropTypeKey, (int)BackdropType.DesktopAcrylic);
SetDefault(DesktopWindowHeightKey, 400);
SetDefault(DesktopWindowLeftKey, 0);
SetDefault(DesktopWindowTopKey, 0);
SetDefault(DesktopWindowWidthKey, 600);
SetDefault(AutoLockOnDesktopModeKey, false);
// App behavior
SetDefault(AutoStartWindowTypeKey, (int)AutoStartWindowType.StandardMode);
// Album art
@@ -191,117 +95,104 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(IsDynamicCoverOverlayEnabledKey, true);
SetDefault(CoverOverlayOpacityKey, 75); // 100 % = 1.0
SetDefault(CoverOverlayBlurAmountKey, 200);
SetDefault(TitleBarTypeKey, (int)TitleBarType.Compact);
SetDefault(CoverImageRadiusKey, 24); // 24 %
// Lyrics
SetDefault(LyricsAlignmentTypeKey, (int)LyricsAlignmentType.Center);
SetDefault(LyricsFontWeightKey, (int)LyricsFontWeight.Bold);
SetDefault(LyricsBlurAmountKey, 5);
SetDefault(LyricsFontColorTypeKey, (int)LyricsFontColorType.Default);
SetDefault(LyricsFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
SetDefault(LyricsCustomFontColorKey, Colors.White.ToInt());
SetDefault(LyricsFontSizeKey, 28);
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
SetDefault(IsLyricsGlowEffectEnabledKey, true);
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.UntilCurrentChar);
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.CurrentCharOnly);
SetDefault(IsFanLyricsEnabledKey, false);
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the AutoStartWindowType
/// </summary>
public AutoStartWindowType AutoStartWindowType
{
get => (AutoStartWindowType)GetValue<int>(AutoStartWindowTypeKey);
set => SetValue(AutoStartWindowTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the BackdropType
/// </summary>
public BackdropType BackdropType
public int DesktopWindowLeft
{
get => (BackdropType)GetValue<int>(BackdropTypeKey);
set => SetValue(BackdropTypeKey, (int)value);
get => GetValue<int>(DesktopWindowLeftKey);
set => SetValue(DesktopWindowLeftKey, value);
}
public int DesktopWindowTop
{
get => GetValue<int>(DesktopWindowTopKey);
set => SetValue(DesktopWindowTopKey, value);
}
public int DesktopWindowWidth
{
get => GetValue<int>(DesktopWindowWidthKey);
set => SetValue(DesktopWindowWidthKey, value);
}
public int DesktopWindowHeight
{
get => GetValue<int>(DesktopWindowHeightKey);
set => SetValue(DesktopWindowHeightKey, value);
}
public bool AutoLockOnDesktopMode
{
get => GetValue<bool>(AutoLockOnDesktopModeKey);
set => SetValue(AutoLockOnDesktopModeKey, value);
}
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
public int CoverImageRadius
{
get => GetValue<int>(CoverImageRadiusKey);
set => SetValue(CoverImageRadiusKey, value);
}
/// <summary>
/// Gets or sets the CoverOverlayBlurAmount
/// </summary>
public int CoverOverlayBlurAmount
{
get => GetValue<int>(CoverOverlayBlurAmountKey);
set => SetValue(CoverOverlayBlurAmountKey, value);
}
/// <summary>
/// Gets or sets the CoverOverlayOpacity
/// </summary>
public int CoverOverlayOpacity
{
get => GetValue<int>(CoverOverlayOpacityKey);
set => SetValue(CoverOverlayOpacityKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
/// </summary>
public bool IsCoverOverlayEnabled
{
get => GetValue<bool>(IsCoverOverlayEnabledKey);
set => SetValue(IsCoverOverlayEnabledKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
public bool IsDynamicCoverOverlayEnabled
{
get => GetValue<bool>(IsDynamicCoverOverlayEnabledKey);
set => SetValue(IsDynamicCoverOverlayEnabledKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsFirstRun
/// </summary>
public bool IsFanLyricsEnabled
{
get => GetValue<bool>(IsFanLyricsEnabledKey);
set => SetValue(IsFanLyricsEnabledKey, value);
}
public bool IsFirstRun
{
get => GetValue<bool>(IsFirstRunKey);
set => SetValue(IsFirstRunKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
/// </summary>
public bool IsLyricsGlowEffectEnabled
{
get => GetValue<bool>(IsLyricsGlowEffectEnabledKey);
set => SetValue(IsLyricsGlowEffectEnabledKey, value);
}
/// <summary>
/// Gets or sets the Language
/// </summary>
public Language Language
{
get => (Language)GetValue<int>(LanguageKey);
set => SetValue(LanguageKey, (int)value);
}
/// <summary>
/// Gets or sets the LocalLyricsFolders
/// </summary>
public List<LocalLyricsFolder> LocalLyricsFolders
{
get =>
@@ -319,72 +210,54 @@ namespace BetterLyrics.WinUI3.Services
);
}
/// <summary>
/// Gets or sets the LyricsAlignmentType
/// </summary>
public LyricsAlignmentType LyricsAlignmentType
{
get => (LyricsAlignmentType)GetValue<int>(LyricsAlignmentTypeKey);
set => SetValue(LyricsAlignmentTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsBlurAmount
/// </summary>
public int LyricsBlurAmount
{
get => GetValue<int>(LyricsBlurAmountKey);
set => SetValue(LyricsBlurAmountKey, value);
}
/// <summary>
/// Gets or sets the LyricsFontColorType
/// </summary>
public Color LyricsCustomFontColor
{
get => GetValue<int>(LyricsCustomFontColorKey)!.ToColor();
set => SetValue(LyricsCustomFontColorKey, value.ToInt());
}
public LyricsFontColorType LyricsFontColorType
{
get => (LyricsFontColorType)GetValue<int>(LyricsFontColorTypeKey);
set => SetValue(LyricsFontColorTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
public int LyricsFontSize
{
get => GetValue<int>(LyricsFontSizeKey);
set => SetValue(LyricsFontSizeKey, value);
}
/// <summary>
/// Gets or sets the LyricsFontWeight
/// </summary>
public LyricsFontWeight LyricsFontWeight
{
get => (LyricsFontWeight)GetValue<int>(LyricsFontWeightKey);
set => SetValue(LyricsFontWeightKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsGlowEffectScope
/// </summary>
public LineRenderingType LyricsGlowEffectScope
{
get => (LineRenderingType)GetValue<int>(LyricsGlowEffectScopeKey);
set => SetValue(LyricsGlowEffectScopeKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsLineSpacingFactor
/// </summary>
public float LyricsLineSpacingFactor
{
get => GetValue<float>(LyricsLineSpacingFactorKey);
set => SetValue(LyricsLineSpacingFactorKey, value);
}
/// <summary>
/// Gets or sets the LyricsSearchProvidersInfo
/// </summary>
public List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo
{
get =>
@@ -402,43 +275,12 @@ namespace BetterLyrics.WinUI3.Services
);
}
/// <summary>
/// Gets or sets the LyricsVerticalEdgeOpacity
/// </summary>
public int LyricsVerticalEdgeOpacity
{
get => GetValue<int>(LyricsVerticalEdgeOpacityKey);
set => SetValue(LyricsVerticalEdgeOpacityKey, value);
}
/// <summary>
/// Gets or sets the ThemeType
/// </summary>
public ElementTheme ThemeType
{
get => (ElementTheme)GetValue<int>(ThemeTypeKey);
set => SetValue(ThemeTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the TitleBarType
/// </summary>
public TitleBarType TitleBarType
{
get => (TitleBarType)GetValue<int>(TitleBarTypeKey);
set => SetValue(TitleBarTypeKey, (int)value);
}
#endregion
#region Methods
/// <summary>
/// The GetValue
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key<see cref="string"/></param>
/// <returns>The <see cref="T?"/></returns>
private T? GetValue<T>(string key)
{
if (_localSettings.Values.TryGetValue(key, out object? value))
@@ -448,12 +290,6 @@ namespace BetterLyrics.WinUI3.Services
return default;
}
/// <summary>
/// The SetDefault
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key<see cref="string"/></param>
/// <param name="value">The value<see cref="T"/></param>
private void SetDefault<T>(string key, T value)
{
if (_localSettings.Values.ContainsKey(key) && _localSettings.Values[key] is T)
@@ -461,17 +297,9 @@ namespace BetterLyrics.WinUI3.Services
_localSettings.Values[key] = value;
}
/// <summary>
/// The SetValue
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key<see cref="string"/></param>
/// <param name="value">The value<see cref="T"/></param>
private void SetValue<T>(string key, T value)
{
_localSettings.Values[key] = value;
}
#endregion
}
}

View File

@@ -139,13 +139,13 @@
<value>Add a folder</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>Theme</value>
<value>Lyrics window theme</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>Language</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>Follow system</value>
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>Adaptive to lyrics background (Colored)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>Light</value>
@@ -196,7 +196,7 @@
<value>Transparent</value>
</data>
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
<value>Backdrop</value>
<value>Lyrics backdrop</value>
</data>
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
<value>Default</value>
@@ -210,14 +210,14 @@
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
<value>The folder has been added. Please do not add it again.</value>
</data>
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
<value>Overlay album art background</value>
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
<value>Lyrics background</value>
</data>
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
<value>Dynamic album art background</value>
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
<value>Dynamic lyrics background</value>
</data>
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
<value>Album art background opacity</value>
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
<value>Lyrics background opacity</value>
</data>
<data name="SettingsPageTitle" xml:space="preserve">
<value>Settings - BetterLyrics</value>
@@ -237,8 +237,8 @@
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>Right</value>
</data>
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
<value>Album art background blur amount</value>
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
<value>Lyrics background blur amount</value>
</data>
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
<value>Blur amount</value>
@@ -252,9 +252,6 @@
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
<value>Significantly higher GPU usage when blur is enabled (&gt; 0)</value>
</data>
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
<value>Enabling this feature will slightly increase GPU utilization</value>
</data>
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>Top and bottom edge opacity</value>
</data>
@@ -273,8 +270,8 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>Immersive mode</value>
</data>
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
<value>Album background</value>
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
<value>Lyrics background</value>
</data>
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>About</value>
@@ -282,7 +279,7 @@
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>Lyrics library</value>
</data>
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>App appearance</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
@@ -316,7 +313,7 @@
<value>Play test music</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>Play using system player</value>
<value>Play "Cut To The Feeling" on "soundcloud.com"</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>Cache</value>
@@ -327,11 +324,8 @@
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>Font color</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>Default</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>Album art accent color</value>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>Adaptive to lyrics background (Grayed)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>Album art style</value>
@@ -384,19 +378,19 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>Exit picture-in-picture mode</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<data name="LyricsNotFound" xml:space="preserve">
<value>Lyrics not found</value>
</data>
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>Lyrics effect</value>
</data>
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>Lyrics style</value>
</data>
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
<value>This folder is already included in the existing folder and does not need to be added again</value>
</data>
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
<value>App behavior</value>
</data>
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
@@ -406,10 +400,10 @@
<value>Activate standard mode</value>
</data>
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
<value>Activate dock mode</value>
<value>Activate desktop mode</value>
</data>
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
<value>Lyrics not found</value>
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
<value>Activate dock mode</value>
</data>
<data name="SystemTrayPageTitle" xml:space="preserve">
<value>System tray - BetterLyrics</value>
@@ -468,7 +462,7 @@
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
<value>Settings</value>
</data>
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
<data name="LyricsLoading" xml:space="preserve">
<value>Loading lyrics...</value>
</data>
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
@@ -523,7 +517,7 @@
<value>Exit</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>Unlock the window</value>
<value>Unlock the window (Restart needed)</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>Lock</value>
@@ -531,4 +525,19 @@
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>To unlock after locking, go to the system tray to unlock</value>
</data>
<data name="SettingsPageFan.Header" xml:space="preserve">
<value>Fan lyrics</value>
</data>
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>Custom</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>Lyrics style and effect</value>
</data>
<data name="SettingsPageApp.Content" xml:space="preserve">
<value>App appearance and behavior</value>
</data>
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
<value>Auto-lock when activating desktop mode</value>
</data>
</root>

View File

@@ -139,13 +139,13 @@
<value>フォルダーを追加します</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>テーマ</value>
<value>歌詞ウィンドウのテーマ</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>言語</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>システムをフォローします</value>
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>歌詞の背景に適応する(色付き)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>ライト</value>
@@ -196,7 +196,7 @@
<value>透明</value>
</data>
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
<value>背景</value>
<value>歌詞の背景素材</value>
</data>
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
<value>デフォルト</value>
@@ -210,14 +210,14 @@
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
<value>フォルダーが追加されました。二度と追加しないでください。</value>
</data>
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
<value>オーバーレイアルバムアートの背景</value>
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
<value>歌詞の背景</value>
</data>
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
<value>ダイナミックアルバムアートの背景</value>
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
<value>ダイナミックな歌詞の背景</value>
</data>
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
<value>アルバムアートの背景不透明</value>
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
<value>歌詞の背景不透明</value>
</data>
<data name="SettingsPageTitle" xml:space="preserve">
<value>設定 - BetterLyrics</value>
@@ -237,8 +237,8 @@
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>右</value>
</data>
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
<value>アルバムアートバックグラウンドブラー量</value>
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
<value>歌詞の背景ぼやけ</value>
</data>
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
<value>ぼやけの量</value>
@@ -252,9 +252,6 @@
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
<value>ぼかしが有効になっている場合のGPU使用量が大幅に高くなります&gt; 0</value>
</data>
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
<value>この機能を有効にすると、GPUの使用率がわずかに増加します</value>
</data>
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>上端と下端の不透明度</value>
</data>
@@ -273,8 +270,8 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>没入モード</value>
</data>
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
<value>アルバムの背景</value>
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
<value>歌詞の背景</value>
</data>
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>について</value>
@@ -282,7 +279,7 @@
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>歌詞ライブラリ</value>
</data>
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>アプリの外観</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
@@ -316,7 +313,7 @@
<value>テスト音楽を再生します</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>システムプレーヤーを使用して再生します</value>
<value>「SoundCloud.com」で「Cut to the Feeling」を再生する</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>キャッシュ</value>
@@ -327,11 +324,8 @@
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>フォントカラー</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>デフォルト</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>アルバムアートアクセントカラー</value>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>歌詞の背景に適応する(灰色)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>アルバムアートスタイル</value>
@@ -384,19 +378,19 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>ピクチャーインピクチャーモードを終了します</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<data name="LyricsNotFound" xml:space="preserve">
<value>歌詞が見つかりません</value>
</data>
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌詞効果</value>
</data>
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>歌詞スタイル</value>
</data>
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
<value>このフォルダーは既存のフォルダーに既に含まれており、再度追加する必要はありません</value>
</data>
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
<value>アプリの動作</value>
</data>
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
@@ -406,10 +400,10 @@
<value>標準モードをアクティブにします</value>
</data>
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
<value>ドックモードをアクティブにします</value>
<value>デスクトップモードを開始します</value>
</data>
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
<value>歌詞が見つかりません</value>
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
<value>ドックモードをアクティブにします</value>
</data>
<data name="SystemTrayPageTitle" xml:space="preserve">
<value>システムトレイ - BetterLyrics</value>
@@ -468,7 +462,7 @@
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
<value>設定</value>
</data>
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
<data name="LyricsLoading" xml:space="preserve">
<value>歌詞の読み込み...</value>
</data>
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
@@ -523,7 +517,7 @@
<value>プログラムを終了します</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>ウィンドウのロックを解除します</value>
<value>ウィンドウのロックを解除する(再起動が必要)</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>ロック</value>
@@ -531,4 +525,19 @@
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>ロック後にロックを解除するには、システムトレイに移動してロックを解除します</value>
</data>
<data name="SettingsPageFan.Header" xml:space="preserve">
<value>ファンの歌詞</value>
</data>
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>カスタマイズ</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>歌詞のスタイルと効果</value>
</data>
<data name="SettingsPageApp.Content" xml:space="preserve">
<value>アプリの外観と動作</value>
</data>
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
<value>デスクトップモードをアクティブにするときの自動ロック</value>
</data>
</root>

View File

@@ -139,13 +139,13 @@
<value>폴더를 추가하십시오</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>주제</value>
<value>가사 창 테마</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>언어</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>시스템을 따르십시오</value>
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>가사 배경 (색상)에 적응</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>빛</value>
@@ -196,7 +196,7 @@
<value>투명한</value>
</data>
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
<value>배경</value>
<value>가사 배경 자료</value>
</data>
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
<value>기본</value>
@@ -210,14 +210,14 @@
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
<value>폴더가 추가되었습니다. 다시 추가하지 마십시오.</value>
</data>
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
<value>오버레이 앨범 아트 배경</value>
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
<value>가사 배경</value>
</data>
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
<value>동적 앨범 아트 배경</value>
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
<value>동적 인 가사 배경</value>
</data>
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
<value>앨범 아트 배경 불투명도</value>
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
<value>가사 배경 불투명도</value>
</data>
<data name="SettingsPageTitle" xml:space="preserve">
<value>설정 - BetterLyrics</value>
@@ -237,8 +237,8 @@
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>오른쪽</value>
</data>
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
<value>앨범 아트 배경 흐림 금액</value>
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
<value>가사 배경 블러</value>
</data>
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
<value>흐림 금액</value>
@@ -252,9 +252,6 @@
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
<value>Blur가 활성화 될 때 상당히 높은 GPU 사용량 (&gt; 0)</value>
</data>
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
<value>이 기능을 활성화하면 GPU 사용률이 약간 증가합니다</value>
</data>
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>상단 및 하단 가장자리 불투명도</value>
</data>
@@ -273,8 +270,8 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>몰입 형 모드</value>
</data>
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
<value>앨범 배경</value>
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
<value>가사 배경</value>
</data>
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>에 대한</value>
@@ -282,7 +279,7 @@
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>가사 도서관</value>
</data>
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>앱 모양</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
@@ -316,7 +313,7 @@
<value>테스트 음악을 재생하십시오</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>시스템 플레이어를 사용하여 재생하십시오</value>
<value>"soundcloud.com"에서 "Fut to the Feeling"을 재생하십시오.</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>은닉처</value>
@@ -327,11 +324,8 @@
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>글꼴 색상</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>기본</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>앨범 아트 악센트 색상</value>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>가사 배경 (회색)에 적응</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>앨범 아트 스타일</value>
@@ -384,19 +378,19 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>Picture-in-Picture 모드 종료</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<data name="LyricsNotFound" xml:space="preserve">
<value>가사를 찾을 수 없습니다</value>
</data>
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>가사 효과</value>
</data>
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>가사 스타일</value>
</data>
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
<value>이 폴더는 이미 기존 폴더에 포함되어 있으며 다시 추가 할 필요가 없습니다.</value>
</data>
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
<value>앱 동작</value>
</data>
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
@@ -406,10 +400,10 @@
<value>표준 모드를 ​​활성화합니다</value>
</data>
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
<value>도크 모드를 활성화하십시오</value>
<value>데스크탑 모드를 시작하십시오</value>
</data>
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
<value>가사를 찾을 수 없습니다</value>
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
<value>도크 모드를 활성화하십시오</value>
</data>
<data name="SystemTrayPageTitle" xml:space="preserve">
<value>시스템 트레이 - BetterLyrics</value>
@@ -468,7 +462,7 @@
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
<value>설정</value>
</data>
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
<data name="LyricsLoading" xml:space="preserve">
<value>가사로드 ...</value>
</data>
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
@@ -523,7 +517,7 @@
<value>프로그램을 종료하십시오</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>창 잠금 해제하십시오</value>
<value>창 잠금 해제 (다시 시작)</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>잠금</value>
@@ -531,4 +525,19 @@
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>잠금 잠금을 해제하려면 시스템 트레이로 이동하여 잠금을 해제하십시오.</value>
</data>
<data name="SettingsPageFan.Header" xml:space="preserve">
<value>팬 가사</value>
</data>
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>사용자 정의하십시오</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>가사 스타일과 효과</value>
</data>
<data name="SettingsPageApp.Content" xml:space="preserve">
<value>앱 외관과 행동</value>
</data>
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
<value>데스크탑 모드를 활성화 할 때 자동 잠금</value>
</data>
</root>

View File

@@ -139,13 +139,13 @@
<value>添加文件夹</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>主题</value>
<value>歌词窗口主题</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>语言</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>跟随系统</value>
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>适应歌词背景(彩色)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>浅色</value>
@@ -196,7 +196,7 @@
<value>透明</value>
</data>
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
<value>背景材质</value>
<value>歌词背景材质</value>
</data>
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
<value>默认</value>
@@ -210,14 +210,14 @@
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
<value>已添加过该文件夹,请勿重复添加</value>
</data>
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
<value>叠加专辑图片背景</value>
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
<value>歌词背景</value>
</data>
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
<value>动态专辑图片背景</value>
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
<value>动态歌词背景</value>
</data>
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
<value>专辑图片背景不透明度</value>
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
<value>歌词背景不透明度</value>
</data>
<data name="SettingsPageTitle" xml:space="preserve">
<value>设置 - BetterLyrics</value>
@@ -237,8 +237,8 @@
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
<value>专辑图片背景模糊度</value>
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
<value>歌词背景模糊度</value>
</data>
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
<value>模糊度</value>
@@ -252,9 +252,6 @@
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
<value>启用模糊(&gt; 0时将显著提升 GPU 占用率</value>
</data>
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
<value>启用该功能将略微提升 GPU 占用率</value>
</data>
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>上下边缘不透明度</value>
</data>
@@ -273,8 +270,8 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>沉浸模式</value>
</data>
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
<value>专辑背景</value>
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
<value>歌词背景</value>
</data>
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>关于</value>
@@ -282,7 +279,7 @@
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>歌词库</value>
</data>
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>应用外观</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
@@ -316,7 +313,7 @@
<value>播放测试音乐</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>使用系统播放器播放</value>
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>缓存</value>
@@ -327,11 +324,8 @@
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>字体颜色</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>默认</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>专辑强调色</value>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>适应歌词背景(灰色)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>专辑封面样式</value>
@@ -384,19 +378,19 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>退出画中画模式</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<data name="LyricsNotFound" xml:space="preserve">
<value>未找到歌词</value>
</data>
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌词动效</value>
</data>
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>歌词样式</value>
</data>
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
<value>该文件夹已包含在已有文件夹中,无需再次添加</value>
</data>
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
<value>应用行为</value>
</data>
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
@@ -406,10 +400,10 @@
<value>启动标准模式</value>
</data>
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
<value>启动停靠模式</value>
<value>启动桌面模式</value>
</data>
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
<value>未找到歌词</value>
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
<value>启动停靠模式</value>
</data>
<data name="SystemTrayPageTitle" xml:space="preserve">
<value>系统托盘 - BetterLyrics</value>
@@ -468,7 +462,7 @@
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
<value>设置</value>
</data>
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
<data name="LyricsLoading" xml:space="preserve">
<value>加载歌词中...</value>
</data>
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
@@ -523,7 +517,7 @@
<value>退出程序</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>解锁窗口</value>
<value>解锁窗口(需要重新启动)</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>锁定</value>
@@ -531,4 +525,19 @@
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>锁定后解锁,请转到系统托盘解锁</value>
</data>
<data name="SettingsPageFan.Header" xml:space="preserve">
<value>扇形歌词</value>
</data>
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>自定义</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>歌词样式与动效</value>
</data>
<data name="SettingsPageApp.Content" xml:space="preserve">
<value>应用外观与行为</value>
</data>
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
<value>启动桌面模式时随即锁定窗口</value>
</data>
</root>

View File

@@ -139,13 +139,13 @@
<value>新增資料夾</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>主題</value>
<value>歌詞窗口主題</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>語言</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>跟隨系統</value>
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>適應歌詞背景(彩色)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>淺色</value>
@@ -196,7 +196,7 @@
<value>透明</value>
</data>
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
<value>背景材質</value>
<value>歌詞背景材質</value>
</data>
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
<value>預設</value>
@@ -210,14 +210,14 @@
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
<value>已新增過該資料夾,請勿重複新增</value>
</data>
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
<value>疊加專輯圖片背景</value>
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
<value>歌詞背景</value>
</data>
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
<value>動態專輯圖片背景</value>
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
<value>動態歌詞背景</value>
</data>
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
<value>專輯圖片背景不透明度</value>
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
<value>歌詞背景不透明度</value>
</data>
<data name="SettingsPageTitle" xml:space="preserve">
<value>設定 - BetterLyrics</value>
@@ -237,8 +237,8 @@
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
<value>專輯圖片背景模糊度</value>
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
<value>歌詞背景模糊度</value>
</data>
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
<value>模糊度</value>
@@ -252,9 +252,6 @@
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
<value>啟用模糊(&gt; 0時將顯著提升 GPU 佔用率</value>
</data>
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
<value>啟用此功能將略微提升 GPU 佔用率</value>
</data>
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>上下邊緣不透明度</value>
</data>
@@ -273,8 +270,8 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>沉浸模式</value>
</data>
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
<value>專輯背景</value>
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
<value>歌詞背景</value>
</data>
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>關於</value>
@@ -282,7 +279,7 @@
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>歌詞庫</value>
</data>
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>應用外觀</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
@@ -316,7 +313,7 @@
<value>播放測試音樂</value>
</data>
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>使用系統播放器播放</value>
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>快取</value>
@@ -327,11 +324,8 @@
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>字體顏色</value>
</data>
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
<value>預設</value>
</data>
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
<value>專輯強調色</value>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>適應歌詞背景(灰色)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>專輯封面樣式</value>
@@ -384,19 +378,19 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>退出畫中畫模式</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<data name="LyricsNotFound" xml:space="preserve">
<value>找不到歌詞</value>
</data>
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌詞動效</value>
</data>
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>歌詞樣式</value>
</data>
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
<value>該資料夾已包含在已有資料夾中,無需再次添加</value>
</data>
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
<value>應用行為</value>
</data>
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
@@ -406,10 +400,10 @@
<value>啟動標準模式</value>
</data>
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
<value>啟動停靠模式</value>
<value>啟動桌面模式</value>
</data>
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
<value>找不到歌詞</value>
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
<value>啟動停靠模式</value>
</data>
<data name="SystemTrayPageTitle" xml:space="preserve">
<value>系統托盤 - BetterLyrics</value>
@@ -468,7 +462,7 @@
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
<value>設定</value>
</data>
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
<data name="LyricsLoading" xml:space="preserve">
<value>載入歌詞中...</value>
</data>
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
@@ -523,7 +517,7 @@
<value>退出程序</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>解鎖窗口</value>
<value>解鎖窗口(需要重新啟動)</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>鎖定</value>
@@ -531,4 +525,19 @@
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>鎖定後解鎖,請轉到系統托盤解鎖</value>
</data>
<data name="SettingsPageFan.Header" xml:space="preserve">
<value>扇形歌詞</value>
</data>
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>自定義</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>歌詞樣式與動效</value>
</data>
<data name="SettingsPageApp.Content" xml:space="preserve">
<value>應用外觀與行為</value>
</data>
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
<value>啟動桌面模式時隨即鎖定窗口</value>
</data>
</root>

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
@@ -18,41 +19,13 @@ using WinUIEx.Messaging;
namespace BetterLyrics.WinUI3.ViewModels
{
/// <summary>
/// Defines the <see cref="LyricsPageViewModel" />
/// </summary>
public partial class LyricsPageViewModel
: BaseViewModel,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<LyricsStatus>>
public partial class LyricsPageViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<int>>, IRecipient<PropertyChangedMessage<bool>>
{
#region Fields
/// <summary>
/// Defines the _playbackService
/// </summary>
private readonly IPlaybackService _playbackService;
/// <summary>
/// Defines the _preferredDisplayTypeBeforeSwitchToDockMode
/// </summary>
private LyricsDisplayType? _preferredDisplayTypeBeforeSwitchToNonStandardMode;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LyricsPageViewModel"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
/// <param name="playbackService">The playbackService<see cref="IPlaybackService"/></param>
public LyricsPageViewModel(
ISettingsService settingsService,
IPlaybackService playbackService
)
: base(settingsService)
public LyricsPageViewModel(ISettingsService settingsService, IPlaybackService playbackService) : base(settingsService)
{
LyricsFontSize = _settingsService.LyricsFontSize;
CoverImageRadius = _settingsService.CoverImageRadius;
@@ -66,106 +39,47 @@ namespace BetterLyrics.WinUI3.ViewModels
UpdateSongInfoUI(_playbackService.SongInfo).ConfigureAwait(true);
}
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether AboutToUpdateUI
/// </summary>
[ObservableProperty]
public partial bool AboutToUpdateUI { get; set; }
/// <summary>
/// Gets or sets the CoverImage
/// </summary>
[ObservableProperty]
public partial BitmapImage? CoverImage { get; set; }
/// <summary>
/// Gets or sets the CoverImageGridActualHeight
/// </summary>
[ObservableProperty]
public partial double CoverImageGridActualHeight { get; set; }
/// <summary>
/// Gets or sets the CoverImageGridCornerRadius
/// </summary>
[ObservableProperty]
public partial CornerRadius CoverImageGridCornerRadius { get; set; }
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
[ObservableProperty]
public partial int CoverImageRadius { get; set; }
/// <summary>
/// Gets or sets the DisplayType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsDisplayType DisplayType { get; set; } =
LyricsDisplayType.PlaceholderOnly;
public partial LyricsDisplayType DisplayType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsFirstRun
/// </summary>
[ObservableProperty]
public partial bool IsFirstRun { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsNotMockMode
/// </summary>
[ObservableProperty]
public partial bool IsNotMockMode { get; set; } = true;
public partial bool IsNotDockMode { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether IsWelcomeTeachingTipOpen
/// </summary>
[ObservableProperty]
public partial bool IsWelcomeTeachingTipOpen { get; set; }
/// <summary>
/// Gets or sets the LimitedLineWidth
/// </summary>
[ObservableProperty]
public partial int LyricsFontSize { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial double MaxLyricsWidth { get; set; } = 0.0;
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
[ObservableProperty]
public partial int LyricsFontSize { get; set; }
public partial LyricsDisplayType? PreferredDisplayType { get; set; } = LyricsDisplayType.SplitView;
/// <summary>
/// Gets or sets the LyricsStatus
/// </summary>
[ObservableProperty]
public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading;
/// <summary>
/// Gets or sets the PreferredDisplayType
/// </summary>
[ObservableProperty]
public partial LyricsDisplayType? PreferredDisplayType { get; set; } =
LyricsDisplayType.SplitView;
/// <summary>
/// Gets or sets the SongInfo
/// </summary>
[ObservableProperty]
public partial SongInfo? SongInfo { get; set; } = null;
#endregion
#region Methods
/// <summary>
/// The OpenMatchedFileFolderInFileExplorer
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
public void OpenMatchedFileFolderInFileExplorer(string path)
{
Process.Start(
@@ -178,21 +92,17 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{bool}"/></param>
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is HostWindowViewModel)
if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(HostWindowViewModel.IsDockMode))
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
{
IsNotMockMode = !message.NewValue;
IsNotDockMode = !message.NewValue;
SetNonStandardModePreferredDisplayType(message.NewValue);
TrySwitchToPreferredDisplayType(SongInfo);
}
else if (message.PropertyName == nameof(HostWindowViewModel.IsDesktopMode))
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
{
SetNonStandardModePreferredDisplayType(message.NewValue);
TrySwitchToPreferredDisplayType(SongInfo);
@@ -200,28 +110,11 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
private void SetNonStandardModePreferredDisplayType(bool isEnabled)
{
if (isEnabled)
{
_preferredDisplayTypeBeforeSwitchToNonStandardMode = PreferredDisplayType;
PreferredDisplayType = LyricsDisplayType.LyricsOnly;
}
else
{
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode;
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{int}"/></param>
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is SettingsViewModel)
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
if (message.PropertyName == nameof(SettingsPageViewModel.CoverImageRadius))
{
CoverImageRadius = message.NewValue;
}
@@ -235,26 +128,6 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsStatus}"/></param>
public void Receive(PropertyChangedMessage<LyricsStatus> message)
{
if (message.Sender is LyricsRendererViewModel)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsStatus))
{
LyricsStatus = message.NewValue;
}
}
}
/// <summary>
/// The UpdateSongInfoUI
/// </summary>
/// <param name="songInfo">The songInfo<see cref="SongInfo?"/></param>
/// <returns>The <see cref="Task"/></returns>
public async Task UpdateSongInfoUI(SongInfo? songInfo)
{
AboutToUpdateUI = true;
@@ -272,31 +145,25 @@ namespace BetterLyrics.WinUI3.ViewModels
AboutToUpdateUI = false;
}
/// <summary>
/// The OnDisplayTypeChanged
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
[RelayCommand]
private void OnDisplayTypeChanged(object value)
{
int index = Convert.ToInt32(value);
PreferredDisplayType = (LyricsDisplayType)index;
DisplayType = (LyricsDisplayType)index;
}
/// <summary>
/// The OpenSettingsWindow
/// </summary>
[RelayCommand]
private void OpenSettingsWindow()
{
WindowHelper.OpenSettingsWindow();
WindowHelper.OpenOrShowWindow<SettingsWindow>();
}
private void SetNonStandardModePreferredDisplayType(bool isEnabled)
{
if (isEnabled)
{
_preferredDisplayTypeBeforeSwitchToNonStandardMode = PreferredDisplayType;
PreferredDisplayType = LyricsDisplayType.LyricsOnly;
}
else
{
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode;
}
}
/// <summary>
/// The TrySwitchToPreferredDisplayType
/// </summary>
/// <param name="songInfo">The songInfo<see cref="SongInfo?"/></param>
private void TrySwitchToPreferredDisplayType(SongInfo? songInfo)
{
LyricsDisplayType displayType;
@@ -315,12 +182,9 @@ namespace BetterLyrics.WinUI3.ViewModels
}
DisplayType = displayType;
}
/// <summary>
/// The OnCoverImageGridActualHeightChanged
/// </summary>
/// <param name="value">The value<see cref="double"/></param>
partial void OnCoverImageGridActualHeightChanged(double value)
{
if (double.IsNaN(value))
@@ -329,10 +193,6 @@ namespace BetterLyrics.WinUI3.ViewModels
CoverImageGridCornerRadius = new CornerRadius(CoverImageRadius / 100f * value / 2);
}
/// <summary>
/// The OnCoverImageRadiusChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverImageRadiusChanged(int value)
{
if (double.IsNaN(CoverImageGridActualHeight))
@@ -343,16 +203,10 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
/// <summary>
/// The OnIsFirstRunChanged
/// </summary>
/// <param name="value">The value<see cref="bool"/></param>
partial void OnIsFirstRunChanged(bool value)
{
IsWelcomeTeachingTipOpen = value;
_settingsService.IsFirstRun = false;
}
#endregion
}
}

View File

@@ -2,13 +2,12 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
@@ -20,11 +19,6 @@ namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
{
/// <summary>
/// The Draw
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
// Blurred lyrics layer
@@ -45,73 +39,21 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
// Masked mock gradient blurred lyrics layer
using var maskedBlurredLyrics = new CanvasCommandList(control);
using (var maskedBlurredLyricsDs = maskedBlurredLyrics.CreateDrawingSession())
{
if (LyricsVerticalEdgeOpacity == 100)
{
maskedBlurredLyricsDs.DrawImage(blurredLyrics);
}
else
{
using var mask = new CanvasCommandList(control);
using (var maskDs = mask.CreateDrawingSession())
{
DrawGradientOpacityMask(control, maskDs);
}
maskedBlurredLyricsDs.DrawImage(
new AlphaMaskEffect { Source = blurredLyrics, AlphaMask = mask }
);
}
}
using var combined = new CanvasCommandList(control);
using var combinedDs = combined.CreateDrawingSession();
if (IsCoverOverlayEnabled)
{
DrawAlbumArtBackground(control, combinedDs);
}
DrawAlbumArtBackground(control, combinedDs);
if (_isDockMode)
{
DrawImmersiveBackground(control, combinedDs, IsCoverOverlayEnabled);
DrawImmersiveBackground(control, combinedDs);
}
combinedDs.DrawImage(maskedBlurredLyrics);
combinedDs.DrawImage(blurredLyrics);
if (_isDesktopMode)
{
float w = (float)control.Size.Width;
float h = (float)control.Size.Height;
float maskThickness = Math.Min(18f, Math.Min(w / 2, h / 2)); // 遮罩宽度
float cornerRadius = maskThickness / 2; // 圆角半径
float blurAmount = maskThickness / 2; // 高斯模糊强度
using var mask = new CanvasCommandList(control);
using (var maskDs = mask.CreateDrawingSession())
{
// 画一个比窗口小一圈的圆角矩形
var rect = new Rect(
maskThickness,
maskThickness,
w - maskThickness * 2,
h - maskThickness * 2
);
maskDs.FillRoundedRectangle(rect, cornerRadius, cornerRadius, Colors.White);
}
// 对圆角矩形做高斯模糊
var blurredMask = new GaussianBlurEffect
{
Source = mask,
BlurAmount = blurAmount,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
};
ds.DrawImage(new AlphaMaskEffect { Source = combined, AlphaMask = blurredMask });
ds.DrawImage(blurredLyrics);
}
else
{
@@ -136,23 +78,18 @@ namespace BetterLyrics.WinUI3.ViewModels
ds.DrawText(
$"DEBUG: "
+ $"播放行 {currentPlayingLineIndex}, 字符 {charStartIndex}, 长度 {charLength}, 进度 {charProgress}\n"
+ $"可见行 [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n"
+ $"当前时刻 {TotalTime}",
+ $"Cur playing {currentPlayingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n"
+ $"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n"
+ $"Cur time {TotalTime}\n" +
$"Lang size: {_multiLangLyrics.Count}\n" +
$"{_lyricsOpacityTransition.Value}",
new Vector2(10, 10),
Colors.Red
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
);
}
}
}
/// <summary>
/// The DrawImgae
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
/// <param name="softwareBitmap">The softwareBitmap<see cref="SoftwareBitmap"/></param>
/// <param name="opacity">The opacity<see cref="float"/></param>
private static void DrawImgae(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
@@ -185,11 +122,6 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
/// <summary>
/// The DrawAlbumArtBackground
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
@@ -197,30 +129,23 @@ namespace BetterLyrics.WinUI3.ViewModels
var overlappedCovers = new CanvasCommandList(control.Device);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
if (_albumArtBgTransition.IsTransitioning)
if (_lastAlbumArtBitmap != null)
{
if (_lastAlbumArtBitmap != null)
{
DrawImgae(
control,
overlappedCoversDs,
_lastAlbumArtBitmap,
1 - _albumArtBgTransition.Value
);
}
if (_albumArtBitmap != null)
{
DrawImgae(
control,
overlappedCoversDs,
_albumArtBitmap,
_albumArtBgTransition.Value
);
}
DrawImgae(
control,
overlappedCoversDs,
_lastAlbumArtBitmap,
1 - _albumArtBgTransition.Value
);
}
else if (_albumArtBitmap != null)
if (_albumArtBitmap != null)
{
DrawImgae(control, overlappedCoversDs, _albumArtBitmap, 1f);
DrawImgae(
control,
overlappedCoversDs,
_albumArtBitmap,
_albumArtBgTransition.Value
);
}
using var coverOverlayEffect = new OpacityEffect
@@ -237,85 +162,19 @@ namespace BetterLyrics.WinUI3.ViewModels
ds.Transform = Matrix3x2.Identity;
}
/// <summary>
/// The DrawGradientOpacityMask
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
private void DrawGradientOpacityMask(
ICanvasAnimatedControl control,
CanvasDrawingSession ds
)
{
byte verticalEdgeAlpha = (byte)(255 * LyricsVerticalEdgeOpacity / 100f);
using var maskBrush = new CanvasLinearGradientBrush(
control,
[
new() { Position = 0, Color = Color.FromArgb(verticalEdgeAlpha, 0, 0, 0) },
new() { Position = 0.5f, Color = Color.FromArgb(255, 0, 0, 0) },
new() { Position = 1, Color = Color.FromArgb(verticalEdgeAlpha, 0, 0, 0) },
]
)
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(0, (float)control.Size.Height),
};
ds.FillRectangle(new Rect(0, 0, control.Size.Width, control.Size.Height), maskBrush);
}
/// <summary>
/// The DrawImmersiveBackground
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
/// <param name="withGradient">The withGradient<see cref="bool"/></param>
private void DrawImmersiveBackground(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
bool withGradient
)
{
ds.FillRectangle(
new Rect(0, 0, control.Size.Width, control.Size.Height),
new CanvasLinearGradientBrush(
control,
[
new CanvasGradientStop
{
Position = 0f,
Color = withGradient
? Color.FromArgb(
211,
_immersiveBgTransition.Value.R,
_immersiveBgTransition.Value.G,
_immersiveBgTransition.Value.B
)
: _immersiveBgTransition.Value,
},
new CanvasGradientStop
{
Position = 1,
Color = _immersiveBgTransition.Value,
},
]
)
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(0, (float)control.Size.Height),
}
);
}
/// <summary>
/// The DrawLyrics
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
/// <param name="currentLineHighlightType">The currentLineHighlightType<see cref="LyricsHighlightType"/></param>
private void DrawBlurredLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
var currentPlayingLine = _multiLangLyrics
.SafeGet(_langIndex)
?.SafeGet(currentPlayingLineIndex);
if (currentPlayingLine == null)
{
return;
}
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
@@ -365,9 +224,13 @@ namespace BetterLyrics.WinUI3.ViewModels
float offsetToLeft =
(float)control.Size.Width - _rightMargin - _maxLyricsWidthTransition.Value;
// Scale
// 组合变换:缩放 -> 旋转 -> 平移
ds.Transform =
Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
* Matrix3x2.CreateRotation(
line.AngleTransition.Value,
currentPlayingLine.Position
)
* Matrix3x2.CreateTranslation(
offsetToLeft,
_canvasYScrollTransition.Value + (float)(control.Size.Height / 2)
@@ -384,7 +247,7 @@ namespace BetterLyrics.WinUI3.ViewModels
ds.DrawImage(
new GaussianBlurEffect
{
Source = new OpacityEffect { Source = lyrics, Opacity = _defaultOpacity },
Source = new OpacityEffect { Source = lyrics, Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
BlurAmount = line.BlurAmountTransition.Value,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
@@ -511,7 +374,6 @@ namespace BetterLyrics.WinUI3.ViewModels
},
BlurAmount = _lyricsGlowEffectAmount,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
}
: new CanvasCommandList(control.Device),
Foreground = new AlphaMaskEffect
@@ -520,7 +382,7 @@ namespace BetterLyrics.WinUI3.ViewModels
AlphaMask = mask,
},
},
Opacity = line.HighlightOpacityTransition.Value,
Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
}
);
}
@@ -530,15 +392,43 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The GetHorizontalFillBrush
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="stopPosition">The stopPosition<see cref="float[]"/></param>
/// <param name="stopOpacity">The stopOpacity<see cref="float[]"/></param>
/// <param name="startX">The startX<see cref="float"/></param>
/// <param name="endX">The endX<see cref="float"/></param>
/// <returns>The <see cref="CanvasLinearGradientBrush"/></returns>
private void DrawImmersiveBackground(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
bool withGradient = true
)
{
ds.FillRectangle(
new Rect(0, 0, control.Size.Width, control.Size.Height),
new CanvasLinearGradientBrush(
control,
[
new CanvasGradientStop
{
Position = 0f,
Color = withGradient
? Color.FromArgb(
211,
_immersiveBgTransition.Value.R,
_immersiveBgTransition.Value.G,
_immersiveBgTransition.Value.B
)
: _immersiveBgTransition.Value,
},
new CanvasGradientStop
{
Position = 1,
Color = _immersiveBgTransition.Value,
},
]
)
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(0, (float)control.Size.Height),
}
);
}
private CanvasLinearGradientBrush GetHorizontalFillBrush(
ICanvasAnimatedControl control,
List<(float position, float opacity)> stops,
@@ -561,5 +451,87 @@ namespace BetterLyrics.WinUI3.ViewModels
EndPoint = new Vector2(startX + width, 0),
};
}
void DrawShenGuang(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
float w = (float)control.Size.Width;
float h = (float)control.Size.Height;
float beamLength = h; // 光束长度等于画布高度
float beamAngle = (float)(Math.PI / 6); // 30°
float centerX = w / 2;
float centerY = h;
float angle = _shenGuangAngleTransition.Value;
var p0 = new Vector2(centerX, centerY);
var p1 = new Vector2(
centerX + beamLength * (float)Math.Cos(angle - beamAngle / 2),
centerY + beamLength * (float)Math.Sin(angle - beamAngle / 2)
);
var p2 = new Vector2(
centerX + beamLength * (float)Math.Cos(angle + beamAngle / 2),
centerY + beamLength * (float)Math.Sin(angle + beamAngle / 2)
);
using var path = new CanvasPathBuilder(control);
path.BeginFigure(p0);
path.AddLine(p1);
path.AddArc(
p2,
beamLength,
beamLength,
0,
CanvasSweepDirection.Clockwise,
CanvasArcSize.Small
);
path.EndFigure(CanvasFigureLoop.Closed);
using var geometry = CanvasGeometry.CreatePath(path);
// 渐变为白色,透明度递减
using var brush = new CanvasRadialGradientBrush(
control,
new[]
{
new CanvasGradientStop
{
Position = 0f,
Color = Color.FromArgb(180, 255, 255, 255),
},
new CanvasGradientStop
{
Position = 0.5f,
Color = Color.FromArgb(60, 255, 255, 255),
},
new CanvasGradientStop
{
Position = 1f,
Color = Color.FromArgb(0, 255, 255, 255),
},
}
)
{
Center = p0,
OriginOffset = new Vector2(0, 0),
RadiusX = beamLength * 0.8f,
RadiusY = beamLength * 0.8f,
};
using var beamCmd = new CanvasCommandList(control);
using (var beamDs = beamCmd.CreateDrawingSession())
{
beamDs.FillGeometry(geometry, brush);
}
var blur = new GaussianBlurEffect
{
Source = beamCmd,
BlurAmount = 36f,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
};
ds.DrawImage(blur);
}
}
}

View File

@@ -1,15 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Windows.Graphics.Imaging;
using Windows.UI;
@@ -25,111 +23,55 @@ namespace BetterLyrics.WinUI3.ViewModels
IRecipient<PropertyChangedMessage<LyricsDisplayType>>,
IRecipient<PropertyChangedMessage<LyricsFontColorType>>,
IRecipient<PropertyChangedMessage<LyricsAlignmentType>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
IRecipient<PropertyChangedMessage<LineRenderingType>>,
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
IRecipient<PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>>>
{
/// <summary>
/// The OnLyricsFontColorTypeChanged
/// </summary>
/// <param name="value">The value<see cref="LyricsFontColorType"/></param>
partial void OnLyricsFontColorTypeChanged(LyricsFontColorType value)
public async void Receive(
PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>> message
)
{
UpdateFontColor();
}
/// <summary>
/// The OnLyricsFontSizeChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnLyricsFontSizeChanged(int value)
{
_isRelayoutNeeded = true;
}
/// <summary>
/// The OnLyricsFontWeightChanged
/// </summary>
/// <param name="value">The value<see cref="LyricsFontWeight"/></param>
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
{
_textFormat.FontWeight = value.ToFontWeight();
_isRelayoutNeeded = true;
}
/// <summary>
/// The OnLyricsLineSpacingFactorChanged
/// </summary>
/// <param name="value">The value<see cref="float"/></param>
partial void OnLyricsLineSpacingFactorChanged(float value)
{
_isRelayoutNeeded = true;
}
/// <summary>
/// The OnSongInfoChanged
/// </summary>
/// <param name="oldValue">The oldValue<see cref="SongInfo?"/></param>
/// <param name="newValue">The newValue<see cref="SongInfo?"/></param>
async partial void OnSongInfoChanged(SongInfo? oldValue, SongInfo? newValue)
{
TotalTime = TimeSpan.Zero;
_lastAlbumArtBitmap = _albumArtBitmap;
if (newValue?.AlbumArt is byte[] bytes)
if (message.Sender is SettingsPageViewModel)
{
_albumArtBitmap = await (
await ImageHelper.GetDecoderFromByte(bytes)
).GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
_albumArtAccentColor = (
await ImageHelper.GetAccentColorsFromByte(bytes)
).FirstOrDefault();
if (message.PropertyName == nameof(SettingsPageViewModel.LocalLyricsFolders))
{
// Music lib changed, re-fetch lyrics
await RefreshLyricsAsync();
}
}
else
{
_albumArtBitmap = null;
_albumArtAccentColor = null;
}
UpdateFontColor();
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
await RefreshLyricsAsync();
}
/// <summary>
/// The OnThemeChanged
/// </summary>
/// <param name="value">The value<see cref="ElementTheme"/></param>
partial void OnThemeChanged(ElementTheme value)
public async void Receive(
PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>> message
)
{
UpdateFontColor();
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsSearchProvidersInfo))
{
// Lyrics search providers info changed, re-fetch lyrics
await RefreshLyricsAsync();
}
}
}
// Receive methods for handling messages from other view models
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{bool}"/></param>
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is SettingsViewModel)
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.IsDynamicCoverOverlayEnabled))
if (
message.PropertyName
== nameof(SettingsPageViewModel.IsDynamicCoverOverlayEnabled)
)
{
IsDynamicCoverOverlayEnabled = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.IsCoverOverlayEnabled))
{
IsCoverOverlayEnabled = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.IsDebugOverlayEnabled))
else if (
message.PropertyName == nameof(SettingsPageViewModel.IsDebugOverlayEnabled)
)
{
_isDebugOverlayEnabled = message.NewValue;
}
@@ -143,39 +85,49 @@ namespace BetterLyrics.WinUI3.ViewModels
{
IsLyricsGlowEffectEnabled = message.NewValue;
}
else if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.IsFanLyricsEnabled)
)
{
_isFanLyricsEnabled = message.NewValue;
}
}
else if (message.Sender is HostWindowViewModel)
else if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(HostWindowViewModel.IsDockMode))
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
{
_isDockMode = message.NewValue;
}
else if (message.PropertyName == nameof(HostWindowViewModel.IsDesktopMode))
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
{
_isDesktopMode = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{Color}"/></param>
public void Receive(PropertyChangedMessage<Color> message)
{
if (message.Sender is HostWindowViewModel)
if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(HostWindowViewModel.ActivatedWindowAccentColor))
if (message.PropertyName == nameof(LyricsWindowViewModel.ActivatedWindowAccentColor))
{
_immersiveBgTransition.StartTransition(message.NewValue);
_lyricsWindowBgColor = message.NewValue;
_adaptiveFontColor = Helper.ColorHelper.GetForegroundColor(_lyricsWindowBgColor);
UpdateFontColor();
}
}
else if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsCustomFontColor))
{
_customFontColor = message.NewValue;
UpdateFontColor();
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{double}"/></param>
public void Receive(PropertyChangedMessage<double> message)
{
if (message.Sender is LyricsPageViewModel)
@@ -187,25 +139,6 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{ElementTheme}"/></param>
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.ThemeType))
{
Theme = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{float}"/></param>
public void Receive(PropertyChangedMessage<float> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
@@ -220,23 +153,21 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{int}"/></param>
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is SettingsViewModel)
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
if (message.PropertyName == nameof(SettingsPageViewModel.CoverImageRadius))
{
CoverImageRadius = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.CoverOverlayOpacity))
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayOpacity))
{
CoverOverlayOpacity = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.CoverOverlayBlurAmount))
else if (
message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayBlurAmount)
)
{
CoverOverlayBlurAmount = message.NewValue;
}
@@ -265,70 +196,6 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsAlignmentType}"/></param>
public void Receive(PropertyChangedMessage<LyricsAlignmentType> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsAlignmentType)
)
{
LyricsAlignmentType = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsDisplayType}"/></param>
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
{
DisplayType = message.NewValue;
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsFontColorType}"/></param>
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsFontColorType)
)
{
LyricsFontColorType = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsFontWeight}"/></param>
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontWeight))
{
LyricsFontWeight = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsGlowEffectScope}"/></param>
public void Receive(PropertyChangedMessage<LineRenderingType> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
@@ -343,38 +210,104 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{ObservableCollection{LocalLyricsFolder}}"/></param>
public void Receive(PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>> message)
public void Receive(PropertyChangedMessage<LyricsAlignmentType> message)
{
if (message.Sender is SettingsViewModel)
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.LocalLyricsFolders))
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsAlignmentType)
)
{
// Music lib changed, re-fetch lyrics
RefreshLyricsAsync().ConfigureAwait(true);
LyricsAlignmentType = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{ObservableCollection{LyricsSearchProviderInfo}}"/></param>
public void Receive(
PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>> message
)
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
{
if (message.Sender is SettingsViewModel)
DisplayType = message.NewValue;
}
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.LyricsSearchProvidersInfo))
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsFontColorType)
)
{
// Lyrics search providers info changed, re-fetch lyrics
RefreshLyricsAsync().ConfigureAwait(true);
LyricsFontColorType = message.NewValue;
}
}
}
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontWeight))
{
LyricsFontWeight = message.NewValue;
}
}
}
partial void OnLyricsFontColorTypeChanged(LyricsFontColorType value)
{
UpdateFontColor();
}
partial void OnLyricsFontSizeChanged(int value)
{
_isRelayoutNeeded = true;
}
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
{
_textFormat.FontWeight = value.ToFontWeight();
_isRelayoutNeeded = true;
}
partial void OnLyricsLineSpacingFactorChanged(float value)
{
_isRelayoutNeeded = true;
}
async partial void OnSongInfoChanged(SongInfo? oldValue, SongInfo? newValue)
{
TotalTime = TimeSpan.Zero;
SoftwareBitmap? newalbumArtBitmap;
Color? newAlbumArtAccentColor;
if (newValue?.AlbumArt is byte[] bytes)
{
var decoder = await ImageHelper.GetDecoderFromByte(bytes);
newalbumArtBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
newAlbumArtAccentColor = (ImageHelper.GetAccentColorsFromByte(bytes)).SafeGet(0);
}
else
{
newalbumArtBitmap = null;
newAlbumArtAccentColor = null;
}
_lastAlbumArtBitmap = _albumArtBitmap;
_albumArtBitmap = newalbumArtBitmap;
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
_albumArtAccentColor = newAlbumArtAccentColor;
_lyricsWindowBgColor = _albumArtAccentColor ?? Colors.Gray;
if (!_isDesktopMode && !_isDockMode) _adaptiveFontColor = Helper.ColorHelper.GetForegroundColor(_lyricsWindowBgColor);
UpdateFontColor();
await RefreshLyricsAsync();
}
}
}

View File

@@ -13,11 +13,6 @@ namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
{
/// <summary>
/// The Update
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="args">The args<see cref="CanvasAnimatedUpdateEventArgs"/></param>
public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
{
if (_isPlaying)
@@ -49,6 +44,36 @@ namespace BetterLyrics.WinUI3.ViewModels
_isRelayoutNeeded = true;
}
switch (DisplayType)
{
case Enums.LyricsDisplayType.AlbumArtOnly:
_lyricsOpacityTransition.StartTransition(0f);
break;
case Enums.LyricsDisplayType.LyricsOnly:
case Enums.LyricsDisplayType.SplitView:
_lyricsOpacityTransition.StartTransition(1f);
break;
case Enums.LyricsDisplayType.PlaceholderOnly:
break;
default:
break;
}
if (_lyricsOpacityTransition.IsTransitioning)
{
_lyricsOpacityTransition.Update(ElapsedTime);
}
// 神光角度目标值左右±15度摆动周期约4秒
double t = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0;
float targetAngle = (float)(-Math.PI / 2 + Math.Sin(t * Math.PI / 2) * (Math.PI / 12)); // -90°为正上±15°摆动
_shenGuangAngleTransition.StartTransition(targetAngle);
if (_shenGuangAngleTransition.IsTransitioning)
{
_shenGuangAngleTransition.Update(ElapsedTime);
}
if (_isRelayoutNeeded)
{
ReLayout(control);
@@ -63,10 +88,49 @@ namespace BetterLyrics.WinUI3.ViewModels
UpdateLinesProps();
}
/// <summary>
/// The UpdateCanvasYScrollOffset
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
private void ReLayout(ICanvasAnimatedControl control)
{
if (control == null)
return;
_textFormat.FontSize = LyricsFontSize;
float y = _topMargin;
// Init Positions
for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
{
var line = _multiLangLyrics[_langIndex].SafeGet(i);
if (line == null)
{
continue;
}
if (line.CanvasTextLayout != null)
{
line.CanvasTextLayout.Dispose();
line.CanvasTextLayout = null;
}
// Calculate layout bounds
line.CanvasTextLayout = new CanvasTextLayout(
control,
line.Text,
_textFormat,
(float)_maxLyricsWidthTransition.Value,
(float)control.Size.Height
);
line.Position = new Vector2(0, y);
y +=
(float)line.CanvasTextLayout.LayoutBounds.Height
/ line.CanvasTextLayout.LineCount
* (line.CanvasTextLayout.LineCount + LyricsLineSpacingFactor);
}
}
private void UpdateCanvasYScrollOffset(ICanvasAnimatedControl control, bool withAnimation)
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
@@ -159,32 +223,19 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The UpdateFontColor
/// </summary>
private protected void UpdateFontColor()
{
Color fallback = Colors.Transparent;
switch (Theme)
ThemeTypeSent =
Helper.ColorHelper.GetElementThemeFromBackgroundColor(_lyricsWindowBgColor);
Color fallbackFg = Colors.Transparent;
switch (ThemeTypeSent)
{
case ElementTheme.Default:
switch (Application.Current.RequestedTheme)
{
case ApplicationTheme.Light:
fallback = _darkFontColor;
break;
case ApplicationTheme.Dark:
fallback = _lightFontColor;
break;
default:
break;
}
break;
case ElementTheme.Light:
fallback = _darkFontColor;
fallbackFg = _darkFontColor;
break;
case ElementTheme.Dark:
fallback = _lightFontColor;
fallbackFg = _lightFontColor;
break;
default:
break;
@@ -192,31 +243,29 @@ namespace BetterLyrics.WinUI3.ViewModels
switch (LyricsFontColorType)
{
case Enums.LyricsFontColorType.Default:
_fontColor = fallback;
case Enums.LyricsFontColorType.AdaptiveGrayed:
_fontColor = fallbackFg;
break;
case Enums.LyricsFontColorType.Dominant:
_fontColor = _albumArtAccentColor ?? fallback;
case Enums.LyricsFontColorType.AdaptiveColored:
_fontColor = _adaptiveFontColor ?? fallbackFg;
break;
case Enums.LyricsFontColorType.Custom:
_fontColor = _customFontColor ?? fallbackFg;
break;
default:
break;
}
}
/// <summary>
/// The UpdateLinesProps
/// </summary>
/// <param name="source">The source<see cref="List{LyricsLine}?"/></param>
/// <param name="defaultOpacity">The defaultOpacity<see cref="float"/></param>
private void UpdateLinesProps()
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
int halfVisibleLineCount =
Math.Max(
Math.Max(1, Math.Max(
currentPlayingLineIndex - _startVisibleLineIndex,
_endVisibleLineIndex - currentPlayingLineIndex
) + 1;
));
if (halfVisibleLineCount < 1)
{
@@ -229,21 +278,31 @@ namespace BetterLyrics.WinUI3.ViewModels
if (line == null)
{
return;
continue;
}
int distanceFromPlayingLine = Math.Abs(i - currentPlayingLineIndex);
if (distanceFromPlayingLine > halfVisibleLineCount)
{
return;
continue;
}
float distanceZoomFactor = distanceFromPlayingLine / (float)halfVisibleLineCount;
float distanceFactor = distanceFromPlayingLine / (float)halfVisibleLineCount;
line.BlurAmountTransition.StartTransition(LyricsBlurAmount * distanceZoomFactor);
line.ScaleTransition.StartTransition(
_highlightedScale - distanceZoomFactor * (_highlightedScale - _defaultScale)
line.AngleTransition.StartTransition(
_isFanLyricsEnabled
? (float)Math.PI
* (30f / 180f)
* distanceFactor
* (i - currentPlayingLineIndex > 0 ? 1 : -1)
: 0
);
line.BlurAmountTransition.StartTransition(LyricsBlurAmount * distanceFactor);
line.ScaleTransition.StartTransition(
_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale)
);
line.OpacityTransition.StartTransition(_defaultOpacity - distanceFactor * _defaultOpacity * (1 - LyricsVerticalEdgeOpacity / 100f));
// Only calculate highlight opacity for the current line and the two lines around it
// to avoid unnecessary calculations
if (distanceFromPlayingLine <= 1)
@@ -253,6 +312,10 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
if (line.AngleTransition.IsTransitioning)
{
line.AngleTransition.Update(ElapsedTime);
}
if (line.ScaleTransition.IsTransitioning)
{
line.ScaleTransition.Update(ElapsedTime);
@@ -261,6 +324,10 @@ namespace BetterLyrics.WinUI3.ViewModels
{
line.BlurAmountTransition.Update(ElapsedTime);
}
if (line.OpacityTransition.IsTransitioning)
{
line.OpacityTransition.Update(ElapsedTime);
}
// Only update highlight opacity for the current line and the two lines around it
if (distanceFromPlayingLine <= 1)
{
@@ -271,52 +338,5 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
}
/// <summary>
/// Reassigns positions (x,y) to lyrics lines based on the current control size and font size
/// </summary>
/// <param name="control"></param>
private void ReLayout(ICanvasAnimatedControl control)
{
if (control == null)
return;
_textFormat.FontSize = LyricsFontSize;
float y = _topMargin;
// Init Positions
for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
{
var line = _multiLangLyrics[_langIndex].SafeGet(i);
if (line == null)
{
continue;
}
if (line.CanvasTextLayout != null)
{
line.CanvasTextLayout.Dispose();
line.CanvasTextLayout = null;
}
// Calculate layout bounds
line.CanvasTextLayout = new CanvasTextLayout(
control,
line.Text,
_textFormat,
(float)_maxLyricsWidthTransition.Value,
(float)control.Size.Height
);
line.Position = new Vector2(0, y);
y +=
(float)line.CanvasTextLayout.LayoutBounds.Height
/ line.CanvasTextLayout.LineCount
* (line.CanvasTextLayout.LineCount + LyricsLineSpacingFactor);
}
}
}
}

View File

@@ -17,205 +17,115 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
{
/// <summary>
/// Defines the <see cref="LyricsRendererViewModel" />
/// </summary>
public partial class LyricsRendererViewModel : BaseViewModel
{
#region Fields
/// <summary>
/// Defines the _albumArtBgTransition
/// </summary>
private readonly ValueTransition<float> _albumArtBgTransition = new(
initialValue: 0f,
durationSeconds: 1.0f
);
/// <summary>
/// Defines the _canvasYScrollTransition
/// </summary>
private readonly ValueTransition<float> _canvasYScrollTransition = new(
initialValue: 0f,
durationSeconds: 0.8f,
easingType: EasingType.SmootherStep
);
/// <summary>
/// Defines the _coverRotateSpeed
/// </summary>
private readonly float _coverRotateSpeed = 0.003f;
/// <summary>
/// Defines the _defaultOpacity
/// </summary>
private readonly float _defaultOpacity = 0.3f;
/// <summary>
/// Defines the _defaultScale
/// </summary>
private readonly float _defaultScale = 0.75f;
/// <summary>
/// Defines the _highlightedOpacity
/// </summary>
private readonly float _highlightedOpacity = 1.0f;
/// <summary>
/// Defines the _highlightedScale
/// </summary>
private readonly float _highlightedScale = 1.0f;
private bool _isDebugOverlayEnabled = false;
/// <summary>
/// Defines the _immersiveBgrTransition
/// </summary>
private readonly ValueTransition<Color> _immersiveBgTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) =>
Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
/// <summary>
/// Defines the _libWatcherService
/// </summary>
private readonly ILibWatcherService _libWatcherService;
/// <summary>
/// Defines the _limitedLineWidthTransition
/// </summary>
private readonly ValueTransition<float> _maxLyricsWidthTransition = new(
initialValue: 0f,
durationSeconds: 0.8f,
interpolator: (from, to, progress) => to
);
/// <summary>
/// Defines the _lyricsGlowEffectAmount
/// </summary>
private readonly float _lyricsGlowEffectAmount = 8f;
/// <summary>
/// Defines the _musicSearchService
/// </summary>
private readonly ValueTransition<float> _maxLyricsWidthTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.SmoothStep
);
private readonly ValueTransition<float> _lyricsOpacityTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
);
private protected readonly IMusicSearchService _musicSearchService;
/// <summary>
/// Defines the _playbackService
/// </summary>
private protected readonly IPlaybackService _playbackService;
/// <summary>
/// Defines the _rightMargin
/// </summary>
private readonly float _rightMargin = 36f;
/// <summary>
/// Defines the _topMargin
/// </summary>
private readonly ValueTransition<float> _shenGuangAngleTransition = new(0f, 0.2f);
private readonly float _topMargin = 0f;
/// <summary>
/// Defines the _albumArtAccentColor
/// </summary>
private Color? _adaptiveFontColor = null;
private Color? _albumArtAccentColor = null;
/// <summary>
/// Defines the _albumArtBitmap
/// </summary>
private SoftwareBitmap? _albumArtBitmap = null;
/// <summary>
/// Defines the _darkFontColor
/// </summary>
private Color? _customFontColor;
private Color _darkFontColor = Colors.Black;
/// <summary>
/// Defines the _endVisibleLineIndex
/// </summary>
private int _endVisibleLineIndex = -1;
/// <summary>
/// Defines the _fontColor
/// </summary>
private protected Color _fontColor;
/// <summary>
/// Defines the _isPlaying
/// </summary>
private bool _isDebugOverlayEnabled = false;
private bool _isDesktopMode = false;
private bool _isDockMode = false;
private bool _isFanLyricsEnabled = false;
private bool _isPlaying = true;
/// <summary>
/// Defines the _isRelayoutNeeded
/// </summary>
private protected bool _isRelayoutNeeded = true;
/// <summary>
/// Defines the _langIndex
/// </summary>
private int _langIndex = 0;
/// <summary>
/// Defines the _lastAlbumArtBitmap
/// </summary>
private SoftwareBitmap? _lastAlbumArtBitmap = null;
/// <summary>
/// Defines the _lightFontColor
/// </summary>
private Color _lightFontColor = Colors.White;
/// <summary>
/// Defines the _multiLangLyrics
/// </summary>
private Color _lyricsWindowBgColor = Colors.Transparent;
private List<List<LyricsLine>> _multiLangLyrics = [];
/// <summary>
/// Defines the _rotateAngle
/// </summary>
private float _rotateAngle = 0f;
/// <summary>
/// Defines the _startVisibleLineIndex
/// </summary>
private int _startVisibleLineIndex = -1;
/// <summary>
/// Defines the _textFormat
/// </summary>
private protected CanvasTextFormat _textFormat = new()
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
};
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LyricsRendererViewModel"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
/// <param name="playbackService">The playbackService<see cref="IPlaybackService"/></param>
/// <param name="musicSearchService">The musicSearchService<see cref="IMusicSearchService"/></param>
/// <param name="libWatcherService">The libWatcherService<see cref="ILibWatcherService"/></param>
public LyricsRendererViewModel(
ISettingsService settingsService,
IPlaybackService playbackService,
IMusicSearchService musicSearchService,
ILibWatcherService libWatcherService
)
: base(settingsService)
ISettingsService settingsService, IPlaybackService playbackService,
IMusicSearchService musicSearchService, ILibWatcherService libWatcherService) : base(settingsService)
{
_musicSearchService = musicSearchService;
_playbackService = playbackService;
_libWatcherService = libWatcherService;
CoverImageRadius = _settingsService.CoverImageRadius;
IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
@@ -229,6 +139,7 @@ namespace BetterLyrics.WinUI3.ViewModels
LyricsBlurAmount = _settingsService.LyricsBlurAmount;
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
_customFontColor = _settingsService.LyricsCustomFontColor;
_libWatcherService.MusicLibraryFilesChanged +=
LibWatcherService_MusicLibraryFilesChanged;
@@ -244,130 +155,49 @@ namespace BetterLyrics.WinUI3.ViewModels
UpdateFontColor();
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
public int CoverImageRadius { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayBlurAmount
/// </summary>
public int CoverOverlayBlurAmount { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayOpacity
/// </summary>
public int CoverOverlayOpacity { get; set; }
/// <summary>
/// Gets or sets the DisplayType
/// </summary>
public LyricsDisplayType DisplayType { get; set; }
/// <summary>
/// Gets or sets the ElapsedTime
/// </summary>
public TimeSpan ElapsedTime { get; set; } = TimeSpan.Zero;
/// <summary>
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
/// </summary>
public bool IsCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
public bool IsDynamicCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
/// </summary>
public bool IsLyricsGlowEffectEnabled { get; set; }
/// <summary>
/// Gets or sets the LyricsAlignmentType
/// </summary>
public LyricsAlignmentType LyricsAlignmentType { get; set; }
/// <summary>
/// Gets or sets the LyricsBlurAmount
/// </summary>
public int LyricsBlurAmount { get; set; }
/// <summary>
/// Gets or sets the LyricsFontColorType
/// </summary>
[ObservableProperty]
public partial LyricsFontColorType LyricsFontColorType { get; set; }
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
[ObservableProperty]
public partial int LyricsFontSize { get; set; }
/// <summary>
/// Gets or sets the LyricsFontWeight
/// </summary>
[ObservableProperty]
public partial LyricsFontWeight LyricsFontWeight { get; set; }
/// <summary>
/// Gets or sets the LyricsGlowEffectScope
/// </summary>
public LineRenderingType LyricsGlowEffectScope { get; set; }
/// <summary>
/// Gets or sets the LyricsLineSpacingFactor
/// </summary>
[ObservableProperty]
public partial float LyricsLineSpacingFactor { get; set; }
/// <summary>
/// Gets or sets the LyricsStatus
/// </summary>
[NotifyPropertyChangedRecipients]
[ObservableProperty]
public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading;
/// <summary>
/// Gets or sets the LyricsVerticalEdgeOpacity
/// </summary>
public int LyricsVerticalEdgeOpacity { get; set; }
/// <summary>
/// Gets or sets the SongInfo
/// </summary>
[ObservableProperty]
public partial SongInfo? SongInfo { get; set; }
/// <summary>
/// Gets or sets the Theme
/// </summary>
[ObservableProperty]
public partial ElementTheme Theme { get; set; }
[NotifyPropertyChangedRecipients]
public partial ElementTheme ThemeTypeSent { get; set; }
/// <summary>
/// Gets or sets the TotalTime
/// </summary>
public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
private bool _isDockMode = false;
private bool _isDesktopMode = false;
#endregion
#region Methods
/// <summary>
/// The GetCurrentPlayingLineIndex
/// </summary>
/// <returns>The <see cref="int"/></returns>
private int GetCurrentPlayingLineIndex()
{
for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
@@ -389,17 +219,7 @@ namespace BetterLyrics.WinUI3.ViewModels
return -1;
}
/// <summary>
/// The GetLinePlayingProgress
/// </summary>
/// <param name="line">The line<see cref="LyricsLine"/></param>
/// <returns>The <see cref="float"/></returns>
private void GetLinePlayingProgress(
LyricsLine line,
out int charStartIndex,
out int charLength,
out float charProgress
)
private void GetLinePlayingProgress(LyricsLine line, out int charStartIndex, out int charLength, out float charProgress)
{
charStartIndex = 0;
charLength = 0;
@@ -454,10 +274,6 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The GetMaxLyricsLineIndexBoundaries
/// </summary>
/// <returns>The <see cref="Tuple{int, int}"/></returns>
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
{
if (
@@ -472,62 +288,30 @@ namespace BetterLyrics.WinUI3.ViewModels
return new Tuple<int, int>(0, _multiLangLyrics[_langIndex].Count - 1);
}
/// <summary>
/// The LibWatcherService_MusicLibraryFilesChanged
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="Events.LibChangedEventArgs"/></param>
private void LibWatcherService_MusicLibraryFilesChanged(
object? sender,
LibChangedEventArgs e
)
private async void LibWatcherService_MusicLibraryFilesChanged(object? sender, LibChangedEventArgs e)
{
RefreshLyricsAsync().ConfigureAwait(true);
await RefreshLyricsAsync();
}
/// <summary>
/// The PlaybackService_IsPlayingChanged
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="IsPlayingChangedEventArgs"/></param>
private void PlaybackService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e)
{
_isPlaying = e.IsPlaying;
}
/// <summary>
/// The PlaybackService_PositionChanged
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="PositionChangedEventArgs"/></param>
private void PlaybackService_PositionChanged(object? sender, PositionChangedEventArgs e)
{
if (Math.Abs(TotalTime.TotalMilliseconds - e.Position.TotalMilliseconds) > 100)
TotalTime = e.Position;
TotalTime = e.Position;
}
/// <summary>
/// The PlaybackService_SongInfoChanged
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="SongInfoChangedEventArgs"/></param>
private void PlaybackService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
{
SongInfo = e.SongInfo;
}
/// <summary>
/// Should invoke this function when:
/// 1. The song info is changed (new song is played).
/// 2. Lyrics search provider info is changed (change order, enable or disable any provider).
/// 3. Local music/lyrics files are changed (added, removed, renamed)
/// </summary>
/// <returns></returns>
private async Task RefreshLyricsAsync()
{
_multiLangLyrics = [];
_isRelayoutNeeded = true;
LyricsStatus = LyricsStatus.Loading;
SetLyricsLoadingPlaceholder();
string? lyricsRaw = null;
LyricsFormat? lyricsFormat = null;
@@ -541,24 +325,31 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
if (lyricsRaw == null)
{
LyricsStatus = LyricsStatus.NotFound;
}
else if (SongInfo != null)
{
_multiLangLyrics = new LyricsParser().Parse(
lyricsRaw,
lyricsFormat,
SongInfo.Title,
SongInfo.Artist,
(int)(SongInfo.DurationMs ?? 0)
);
_isRelayoutNeeded = true;
LyricsStatus = LyricsStatus.Found;
}
_multiLangLyrics = new LyricsParser().Parse(
lyricsRaw,
lyricsFormat,
SongInfo?.Title,
SongInfo?.Artist,
(int)(SongInfo?.DurationMs ?? 0)
);
_isRelayoutNeeded = true;
}
#endregion
private void SetLyricsLoadingPlaceholder()
{
_multiLangLyrics = [];
_multiLangLyrics.Add(
[
new LyricsLine
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
Text = App.ResourceLoader!.GetString("LyricsLoading"),
CharTimings = [],
},
]
);
_isRelayoutNeeded = true;
}
}
}

View File

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

View File

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

View File

@@ -1,212 +1,100 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Core;
using Windows.Globalization;
using Windows.Media;
using Windows.Media.Playback;
using Windows.System;
using WinRT.Interop;
namespace BetterLyrics.WinUI3.ViewModels
{
/// <summary>
/// Defines the <see cref="SettingsViewModel" />
/// </summary>
public partial class SettingsViewModel : ObservableRecipient
public partial class SettingsPageViewModel : ObservableRecipient
{
#region Fields
/// <summary>
/// Defines the _libWatcherService
/// </summary>
private readonly ILibWatcherService _libWatcherService;
/// <summary>
/// Defines the _mediaPlayer
/// </summary>
private readonly MediaPlayer _mediaPlayer = new();
/// <summary>
/// Defines the _playbackService
/// </summary>
private readonly IPlaybackService _playbackService;
/// <summary>
/// Defines the _settingsService
/// </summary>
private readonly ISettingsService _settingsService;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SettingsViewModel"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
/// <param name="libWatcherService">The libWatcherService<see cref="ILibWatcherService"/></param>
/// <param name="playbackService">The playbackService<see cref="IPlaybackService"/></param>
public SettingsViewModel(
ISettingsService settingsService,
ILibWatcherService libWatcherService,
IPlaybackService playbackService
)
public SettingsPageViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService)
{
_settingsService = settingsService;
_libWatcherService = libWatcherService;
_playbackService = playbackService;
RootGridMargin = new Thickness(0, _settingsService.TitleBarType.GetHeight(), 0, 0);
LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
Language = _settingsService.Language;
CoverImageRadius = _settingsService.CoverImageRadius;
ThemeType = _settingsService.ThemeType;
BackdropType = _settingsService.BackdropType;
TitleBarType = _settingsService.TitleBarType;
AutoStartWindowType = _settingsService.AutoStartWindowType;
AutoLockOnDesktopMode = _settingsService.AutoLockOnDesktopMode;
IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
Task.Run(async () =>
{
BuildDate = (await AppInfo.GetBuildDate()).ToString("(yyyy/MM/dd HH:mm:ss)");
});
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the AutoStartWindowType
/// </summary>
[ObservableProperty]
public partial AutoStartWindowType AutoStartWindowType { get; set; }
[ObservableProperty]
public partial bool AutoLockOnDesktopMode { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverImageRadius { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayBlurAmount { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayOpacity { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDebugOverlayEnabled { get; set; } = false;
/// <summary>
/// Gets or sets the BackdropType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial BackdropType BackdropType { get; set; }
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverImageRadius { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayBlurAmount
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayBlurAmount { get; set; }
/// <summary>
/// Gets or sets the CoverOverlayOpacity
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayOpacity { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDynamicCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets the Language
/// </summary>
[ObservableProperty]
public partial Enums.Language Language { get; set; }
/// <summary>
/// Gets or sets the LocalLyricsFolders
/// </summary>
[ObservableProperty]
public partial ObservableCollection<LocalLyricsFolder> LocalLyricsFolders { get; set; }
/// <summary>
/// Gets or sets the LyricsSearchProvidersInfo
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
/// <summary>
/// Gets or sets the NavViewSelectedItemTag
/// </summary>
[ObservableProperty]
public partial object NavViewSelectedItemTag { get; set; } = "LyricsLib";
public partial object NavViewSelectedItemTag { get; set; }
/// <summary>
/// Gets or sets the RootGridMargin
/// </summary>
[ObservableProperty]
public partial Thickness RootGridMargin { get; set; } = new(0, 0, 0, 0);
/// <summary>
/// Gets or sets the ThemeType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ElementTheme ThemeType { get; set; }
/// <summary>
/// Gets or sets the TitleBarType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial TitleBarType TitleBarType { get; set; }
/// <summary>
/// Gets or sets the Version
/// </summary>
public string Version { get; set; } = Helper.AppInfo.AppVersion;
#endregion
public string BuildDate { get; set; }
#region Methods
/// <summary>
/// The OnLyricsSearchProvidersReordered
/// </summary>
public void OnLyricsSearchProvidersReordered()
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
@@ -217,19 +105,11 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
/// <summary>
/// The OpenMusicFolder
/// </summary>
/// <param name="folder">The folder<see cref="LocalLyricsFolder"/></param>
public void OpenMusicFolder(LocalLyricsFolder folder)
{
OpenFolderInFileExplorer(folder.Path);
}
/// <summary>
/// The RemoveFolderAsync
/// </summary>
/// <param name="folder">The folder<see cref="LocalLyricsFolder"/></param>
public void RemoveFolderAsync(LocalLyricsFolder folder)
{
LocalLyricsFolders.Remove(folder);
@@ -238,20 +118,12 @@ namespace BetterLyrics.WinUI3.ViewModels
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
/// <summary>
/// The ToggleLocalLyricsFolder
/// </summary>
/// <param name="folder">The folder<see cref="LocalLyricsFolder"/></param>
public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
{
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
/// <summary>
/// The ToggleLyricsSearchProvider
/// </summary>
/// <param name="providerInfo">The providerInfo<see cref="LyricsSearchProviderInfo"/></param>
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
@@ -262,10 +134,6 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
/// <summary>
/// The AddFolderAsync
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
private void AddFolderAsync(string path)
{
var normalizedPath =
@@ -336,20 +204,18 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The LaunchProjectGitHubPageAsync
/// </summary>
/// <returns>The <see cref="Task"/></returns>
[RelayCommand]
private async Task LaunchProjectGitHubPageAsync()
{
await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl));
}
/// <summary>
/// The OpenFolderInFileExplorer
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
[RelayCommand]
private void OpenCacheFolder()
{
OpenFolderInFileExplorer(Helper.AppInfo.CacheFolder);
}
private void OpenFolderInFileExplorer(string path)
{
Process.Start(
@@ -362,55 +228,18 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
/// <summary>
/// The OpenLogFolder
/// </summary>
[RelayCommand]
private void OpenCacheFolder()
{
OpenFolderInFileExplorer(Helper.AppInfo.CacheFolder);
}
/// <summary>
/// The PlayTestingMusicTask
/// </summary>
[RelayCommand]
private void PlayTestingMusicTask()
{
AddFolderAsync(Helper.AppInfo.AssetsFolder);
_mediaPlayer.SetUriSource(new Uri(Helper.AppInfo.TestMusicPath));
_mediaPlayer.Play();
WindowHelper.OpenOrShowWindow<LyricsWindow>();
}
/// <summary>
/// The RestartApp
/// </summary>
[RelayCommand]
private void RestartApp()
{
// The restart will be executed immediately.
AppRestartFailureReason failureReason =
Microsoft.Windows.AppLifecycle.AppInstance.Restart("");
// If the restart fails, handle it here.
switch (failureReason)
{
case AppRestartFailureReason.RestartPending:
break;
case AppRestartFailureReason.NotInForeground:
break;
case AppRestartFailureReason.InvalidUser:
break;
default: //AppRestartFailureReason.Other
break;
}
WindowHelper.RestartApp();
}
/// <summary>
/// The SelectAndAddFolderAsync
/// </summary>
/// <param name="sender">The sender<see cref="UIElement"/></param>
/// <returns>The <see cref="Task"/></returns>
[RelayCommand]
private async Task SelectAndAddFolderAsync(UIElement sender)
{
@@ -418,7 +247,7 @@ namespace BetterLyrics.WinUI3.ViewModels
picker.FileTypeFilter.Add("*");
var hwnd = WindowNative.GetWindowHandle(WindowHelper.GetWindowForElement(sender));
var hwnd = WindowNative.GetWindowHandle(WindowHelper.GetWindowByWindowType<SettingsWindow>());
InitializeWithWindow.Initialize(picker, hwnd);
var folder = await picker.PickSingleFolderAsync();
@@ -429,73 +258,36 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The OnAutoStartWindowTypeChanged
/// </summary>
/// <param name="value">The value<see cref="AutoStartWindowType"/></param>
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
{
_settingsService.AutoStartWindowType = value;
}
/// <summary>
/// The OnBackdropTypeChanged
/// </summary>
/// <param name="value">The value<see cref="BackdropType"/></param>
partial void OnBackdropTypeChanged(BackdropType value)
partial void OnAutoLockOnDesktopModeChanged(bool value)
{
_settingsService.BackdropType = value;
_settingsService.AutoLockOnDesktopMode = value;
}
/// <summary>
/// The OnCoverImageRadiusChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverImageRadiusChanged(int value)
{
_settingsService.CoverImageRadius = value;
}
/// <summary>
/// The OnCoverOverlayBlurAmountChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverOverlayBlurAmountChanged(int value)
{
_settingsService.CoverOverlayBlurAmount = value;
}
/// <summary>
/// The OnCoverOverlayOpacityChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverOverlayOpacityChanged(int value)
{
_settingsService.CoverOverlayOpacity = value;
}
/// <summary>
/// The OnIsCoverOverlayEnabledChanged
/// </summary>
/// <param name="value">The value<see cref="bool"/></param>
partial void OnIsCoverOverlayEnabledChanged(bool value)
{
_settingsService.IsCoverOverlayEnabled = value;
}
/// <summary>
/// The OnIsDynamicCoverOverlayEnabledChanged
/// </summary>
/// <param name="value">The value<see cref="bool"/></param>
partial void OnIsDynamicCoverOverlayEnabledChanged(bool value)
{
_settingsService.IsDynamicCoverOverlayEnabled = value;
}
/// <summary>
/// The OnLanguageChanged
/// </summary>
/// <param name="value">The value<see cref="Enums.Language"/></param>
partial void OnLanguageChanged(Enums.Language value)
{
switch (value)
@@ -523,26 +315,5 @@ namespace BetterLyrics.WinUI3.ViewModels
}
_settingsService.Language = Language;
}
/// <summary>
/// The OnThemeTypeChanged
/// </summary>
/// <param name="value">The value<see cref="ElementTheme"/></param>
partial void OnThemeTypeChanged(ElementTheme value)
{
_settingsService.ThemeType = value;
}
/// <summary>
/// The OnTitleBarTypeChanged
/// </summary>
/// <param name="value">The value<see cref="TitleBarType"/></param>
partial void OnTitleBarTypeChanged(TitleBarType value)
{
_settingsService.TitleBarType = value;
RootGridMargin = new Thickness(0, value.GetHeight(), 0, 0);
}
#endregion
}
}

View File

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

View File

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

View File

@@ -45,30 +45,6 @@
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<StackPanel
x:Name="LyricsNotFoundPlaceholder"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Opacity="0"
Orientation="Horizontal"
Spacing="12">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<TextBlock x:Uid="MainPageLyricsNotFound" FontSize="{x:Bind ViewModel.LyricsFontSize, Mode=OneWay}" />
</StackPanel>
<StackPanel
x:Name="LyricsLoadingPlaceholder"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Opacity="0"
Orientation="Horizontal"
Spacing="12">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<TextBlock x:Uid="MainPageLyricsLoading" FontSize="{x:Bind ViewModel.LyricsFontSize, Mode=OneWay}" />
</StackPanel>
</Grid>
@@ -265,17 +241,54 @@
Opacity="0"
PointerEntered="BottomCommandGrid_PointerEntered"
PointerExited="BottomCommandGrid_PointerExited"
Visibility="{x:Bind ViewModel.IsNotMockMode, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
Visibility="{x:Bind ViewModel.IsNotDockMode, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<StackPanel HorizontalAlignment="Right" Spacing="4">
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button Style="{StaticResource GhostButtonStyle}" Visibility="Collapsed">
<Grid>
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontWeight="Bold"
Glyph="&#xF83E;" />
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="11"
FontWeight="Bold"
Text="0.1" />
</Grid>
</Button>
<Button Style="{StaticResource GhostButtonStyle}" Visibility="Collapsed">
<Grid>
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontWeight="Bold"
Glyph="&#xF83E;"
RenderTransformOrigin="0.5,0.5">
<FontIcon.RenderTransform>
<ScaleTransform ScaleX="-1" />
</FontIcon.RenderTransform>
</FontIcon>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="11"
FontWeight="Bold"
Text="0.1" />
</Grid>
</Button>
<Button
x:Name="DisplayTypeSwitchButton"
x:Uid="MainPageDisplayTypeSwitcher"
Content="{ui:FontIcon Glyph=&#xF246;}"
Content="{ui:FontIcon FontWeight=Bold,
FontFamily={StaticResource IconFontFamily},
Glyph=&#xF246;}"
Style="{StaticResource GhostButtonStyle}">
<Button.OpacityTransition>
<ScalarTransition />
@@ -289,21 +302,9 @@
</Style>
</Flyout.FlyoutPresenterStyle>
<RadioButtons MaxColumns="1" SelectedIndex="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}">
<RadioButton
x:Uid="MainPageAlbumArtOnly"
Command="{x:Bind ViewModel.DisplayTypeChangedCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}"
Tag="0" />
<RadioButton
x:Uid="MainPageLyriscOnly"
Command="{x:Bind ViewModel.DisplayTypeChangedCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}"
Tag="1" />
<RadioButton
x:Uid="MainPageSplitView"
Command="{x:Bind ViewModel.DisplayTypeChangedCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}"
Tag="2" />
<RadioButton x:Uid="MainPageAlbumArtOnly" Click="AlbumArtOnlyRadioButton_Click" />
<RadioButton x:Uid="MainPageLyriscOnly" Click="LyricsOnlyRadioButton_Click" />
<RadioButton x:Uid="MainPageSplitView" Click="SplitViewRadioButton_Click" />
</RadioButtons>
</Flyout>
</Button.Flyout>
@@ -311,21 +312,23 @@
<Button
x:Name="MusicInfoButton"
Content="{ui:FontIcon Glyph=&#xF167;}"
Content="{ui:FontIcon FontWeight=Bold,
FontFamily={StaticResource IconFontFamily},
Glyph=&#xE946;}"
Style="{StaticResource GhostButtonStyle}">
<Button.Flyout>
<Flyout>
<StackPanel Spacing="16">
<StackPanel Orientation="Horizontal" Spacing="12">
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xED35;" />
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="&#xED35;" />
<TextBlock Text="{x:Bind ViewModel.SongInfo.SourceAppUserModelId, Mode=OneWay}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="12">
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xEC4F;" />
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="&#xEC4F;" />
<TextBlock Text="{x:Bind ViewModel.SongInfo.Title, Mode=OneWay}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="12">
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xE77B;" />
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="&#xE77B;" />
<TextBlock Text="{x:Bind ViewModel.SongInfo.Artist, Mode=OneWay}" />
</StackPanel>
</StackPanel>
@@ -336,7 +339,9 @@
<Button
x:Name="SettingsButton"
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
Content="{ui:FontIcon Glyph=&#xF8B0;}"
Content="{ui:FontIcon FontWeight=Bold,
FontFamily={StaticResource IconFontFamily},
Glyph=&#xE713;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
@@ -353,115 +358,6 @@
<uc:SystemTray />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LayoutStates">
<!-- Album art only -->
<VisualState x:Name="AlbumArtOnly">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="LyricsGrid.Opacity" Value="1" />
<Setter Target="LyricsPlaceholderGrid.Opacity" Value="0" />
<Setter Target="LyricsPlaceholderGrid.(Grid.Column)" Value="0" />
<Setter Target="LyricsPlaceholderGrid.(Grid.ColumnSpan)" Value="3" />
<Setter Target="SongInfoInnerGrid.(Grid.Column)" Value="0" />
<Setter Target="SongInfoInnerGrid.(Grid.ColumnSpan)" Value="3" />
<Setter Target="SongInfoInnerGrid.Opacity" Value="1" />
<Setter Target="MainPageNoMusicPlayingTextBlock.Opacity" Value="0" />
</VisualState.Setters>
</VisualState>
<!-- Lyrics only -->
<VisualState x:Name="LyricsOnly">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="1" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SongInfoInnerGrid.Opacity" Value="0" />
<Setter Target="SongInfoInnerGrid.(Grid.Column)" Value="0" />
<Setter Target="SongInfoInnerGrid.(Grid.ColumnSpan)" Value="3" />
<Setter Target="LyricsGrid.Opacity" Value="1" />
<Setter Target="LyricsPlaceholderGrid.(Grid.Column)" Value="0" />
<Setter Target="LyricsPlaceholderGrid.(Grid.ColumnSpan)" Value="3" />
<Setter Target="LyricsPlaceholderGrid.Opacity" Value="1" />
<Setter Target="MainPageNoMusicPlayingTextBlock.Opacity" Value="0" />
</VisualState.Setters>
</VisualState>
<!-- Split view -->
<VisualState x:Name="SplitView">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="2" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SongInfoInnerGrid.(Grid.Column)" Value="0" />
<Setter Target="SongInfoInnerGrid.(Grid.ColumnSpan)" Value="1" />
<Setter Target="SongInfoInnerGrid.Opacity" Value="1" />
<Setter Target="LyricsGrid.Opacity" Value="1" />
<Setter Target="LyricsPlaceholderGrid.(Grid.Column)" Value="2" />
<Setter Target="LyricsPlaceholderGrid.(Grid.ColumnSpan)" Value="1" />
<Setter Target="LyricsPlaceholderGrid.Opacity" Value="1" />
<Setter Target="MainPageNoMusicPlayingTextBlock.Opacity" Value="0" />
</VisualState.Setters>
</VisualState>
<!-- Placeholder only -->
<VisualState x:Name="PlaceholderOnly">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="3" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SongInfoInnerGrid.Opacity" Value="0" />
<Setter Target="LyricsGrid.Opacity" Value="0" />
<Setter Target="LyricsPlaceholderGrid.Opacity" Value="0" />
<Setter Target="MainPageNoMusicPlayingTextBlock.Opacity" Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="LyricsStatus">
<VisualState x:Name="Loading">
<VisualState.StateTriggers>
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="2" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="LyricsNotFoundPlaceholder.Opacity" Value="0" />
<Setter Target="LyricsLoadingPlaceholder.Opacity" Value=".5" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Found">
<VisualState.StateTriggers>
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="1" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="LyricsNotFoundPlaceholder.Opacity" Value="0" />
<Setter Target="LyricsLoadingPlaceholder.Opacity" Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NotFound">
<VisualState.StateTriggers>
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="LyricsNotFoundPlaceholder.Opacity" Value=".5" />
<Setter Target="LyricsLoadingPlaceholder.Opacity" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="MusicPlayingStates">
<VisualState x:Name="MusicPlaying">
<VisualState.StateTriggers>

View File

@@ -1,54 +1,60 @@
// 2025/6/23 by Zhe Fang
using System;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using WinUIEx.Messaging;
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 LyricsPage : Page
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LyricsPage"/> class.
/// </summary>
public LyricsPage()
{
this.InitializeComponent();
DataContext = Ioc.Default.GetService<LyricsPageViewModel>();
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsDisplayType>>(
this,
async (r, m) =>
{
if (m.Sender is LyricsPageViewModel)
{
if (m.PropertyName == nameof(LyricsPageViewModel.DisplayType))
{
switch (m.NewValue)
{
case LyricsDisplayType.AlbumArtOnly:
await SwitchToAlbumArtOnlyDisplayTypeAsync();
break;
case LyricsDisplayType.LyricsOnly:
await SwitchToLyricsOnlyDisplayTypeAsync();
break;
case LyricsDisplayType.SplitView:
await SwitchToSplitViewDisplayTypeAsync();
break;
case LyricsDisplayType.PlaceholderOnly:
await SwitchToPlaceholderOnlyDisplayTypeAsync();
break;
default:
break;
}
}
}
}
);
}
#endregion
#region Properties
/// <summary>
/// Gets the ViewModel
/// </summary>
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
#endregion
#region Methods
/// <summary>
/// The BottomCommandGrid_PointerEntered
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="Microsoft.UI.Xaml.Input.PointerRoutedEventArgs"/></param>
private void BottomCommandGrid_PointerEntered(
object sender,
Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e
@@ -58,11 +64,6 @@ namespace BetterLyrics.WinUI3.Views
BottomCommandGrid.Opacity = .5;
}
/// <summary>
/// The BottomCommandGrid_PointerExited
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="Microsoft.UI.Xaml.Input.PointerRoutedEventArgs"/></param>
private void BottomCommandGrid_PointerExited(
object sender,
Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e
@@ -72,11 +73,6 @@ namespace BetterLyrics.WinUI3.Views
BottomCommandGrid.Opacity = 0;
}
/// <summary>
/// The CoverArea_SizeChanged
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="SizeChangedEventArgs"/></param>
private void CoverArea_SizeChanged(object sender, SizeChangedEventArgs e)
{
CoverImageGrid.Width = CoverImageGrid.Height = Math.Min(
@@ -85,36 +81,93 @@ namespace BetterLyrics.WinUI3.Views
);
}
/// <summary>
/// The CoverImageGrid_SizeChanged
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="SizeChangedEventArgs"/></param>
private void CoverImageGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
ViewModel.CoverImageGridActualHeight = e.NewSize.Height;
}
/// <summary>
/// The LyricsPlaceholderGrid_SizeChanged
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="SizeChangedEventArgs"/></param>
private void LyricsPlaceholderGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
ViewModel.MaxLyricsWidth = e.NewSize.Width;
}
/// <summary>
/// The WelcomeTeachingTip_Closed
/// </summary>
/// <param name="sender">The sender<see cref="TeachingTip"/></param>
/// <param name="args">The args<see cref="TeachingTipClosedEventArgs"/></param>
private void WelcomeTeachingTip_Closed(TeachingTip sender, TeachingTipClosedEventArgs args)
{
ViewModel.IsFirstRun = false;
}
#endregion
private async void LyricsOnlyRadioButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.LyricsOnly;
await SwitchToLyricsOnlyDisplayTypeAsync();
}
private async void AlbumArtOnlyRadioButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.AlbumArtOnly;
await SwitchToAlbumArtOnlyDisplayTypeAsync();
}
private async void SplitViewRadioButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.SplitView;
await SwitchToSplitViewDisplayTypeAsync();
}
private async Task SwitchToLyricsOnlyDisplayTypeAsync()
{
await BeforeSwitchDisplayTypeAsync();
Grid.SetColumn(LyricsPlaceholderGrid, 0);
Grid.SetColumnSpan(LyricsPlaceholderGrid, 3);
LyricsPlaceholderGrid.Opacity = 1;
LyricsGrid.Opacity = 1;
}
private async Task SwitchToAlbumArtOnlyDisplayTypeAsync()
{
await BeforeSwitchDisplayTypeAsync();
Grid.SetColumn(SongInfoInnerGrid, 0);
Grid.SetColumnSpan(SongInfoInnerGrid, 3);
SongInfoInnerGrid.Opacity = 1;
LyricsGrid.Opacity = 1;
}
private async Task BeforeSwitchDisplayTypeAsync()
{
SongInfoInnerGrid.Opacity = 0;
LyricsPlaceholderGrid.Opacity = 0;
//LyricsGrid.Opacity = 0;
MainPageNoMusicPlayingTextBlock.Opacity = 0;
await Task.Delay(300);
}
private async Task SwitchToSplitViewDisplayTypeAsync()
{
await BeforeSwitchDisplayTypeAsync();
Grid.SetColumn(SongInfoInnerGrid, 0);
Grid.SetColumnSpan(SongInfoInnerGrid, 1);
Grid.SetColumn(LyricsPlaceholderGrid, 2);
Grid.SetColumnSpan(LyricsPlaceholderGrid, 1);
SongInfoInnerGrid.Opacity = 1;
LyricsPlaceholderGrid.Opacity = 1;
LyricsGrid.Opacity = 1;
}
private async Task SwitchToPlaceholderOnlyDisplayTypeAsync()
{
await BeforeSwitchDisplayTypeAsync();
MainPageNoMusicPlayingTextBlock.Opacity = 1;
}
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<Window
x:Class="BetterLyrics.WinUI3.Views.HostWindow"
x:Class="BetterLyrics.WinUI3.Views.LyricsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
@@ -11,51 +11,40 @@
xmlns:media="using:CommunityToolkit.WinUI.Media"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<Grid
x:Name="RootGrid"
PointerMoved="RootGrid_PointerMoved"
RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
<Frame
x:Name="RootFrame"
Navigated="RootFrame_Navigated"
NavigationFailed="RootFrame_NavigationFailed" />
<local:LyricsPage />
<Grid
x:Name="TopCommandGrid"
Height="{x:Bind ViewModel.TitleBarHeight, Mode=OneWay}"
VerticalAlignment="Top"
Background="Transparent"
Opacity="0"
PointerMoved="TopCommandGrid_PointerMoved">
Opacity="0">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
<ImageIcon
x:Name="AppLogoImageIcon"
Height="{x:Bind ViewModel.AppLogoImageIconHeight, Mode=OneWay}"
Margin="16,0"
Source="ms-appx:///Assets/Logo.png" />
<TextBlock
x:Name="AppTitleTextBlock"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
FontWeight="SemiBold"
Text="{x:Bind Title, Mode=OneWay}" />
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button
x:Name="ClickThroughButton"
x:Uid="HostWindowClickThroughButton"
Command="{x:Bind ViewModel.LockWindowCommand}"
Style="{StaticResource TitleBarButtonStyle}">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
FontWeight="Bold"
Glyph="&#xE72E;" />
</Grid>
<ToolTipService.ToolTip>
<ToolTip x:Name="LockToolTip" x:Uid="HostWindowLockToolTip" />
</ToolTipService.ToolTip>
@@ -79,18 +68,18 @@
<Grid>
<FontIcon
Margin="0,0,0,8"
FontFamily="Segoe Fluent Icons"
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
FontWeight="ExtraBold"
Glyph="&#xEF2D;" />
<FontIcon
FontFamily="Segoe Fluent Icons"
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
FontWeight="ExtraBold"
Glyph="&#xEF2D;" />
<FontIcon
Margin="0,8,0,0"
FontFamily="Segoe Fluent Icons"
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
FontWeight="ExtraBold"
Glyph="&#xEF2D;" />
@@ -133,7 +122,7 @@
Click="MinimiseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="Segoe Fluent Icons"
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xEF2D;" />
</Button>
@@ -143,7 +132,7 @@
Click="MaximiseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="Segoe Fluent Icons"
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xEF2E;" />
</Button>
@@ -154,7 +143,7 @@
Style="{StaticResource TitleBarButtonStyle}"
Visibility="Collapsed">
<FontIcon
FontFamily="Segoe Fluent Icons"
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xEF2F;" />
</Button>
@@ -164,7 +153,7 @@
Click="CloseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="Segoe Fluent Icons"
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xEF2C;" />
</Button>

View File

@@ -1,143 +1,87 @@
// 2025/6/23 by Zhe Fang
using System;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI.Behaviors;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using WinRT.Interop;
using WinUIEx;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Views
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame
/// </summary>
public sealed partial class HostWindow : Window
public sealed partial class LyricsWindow : Window
{
#region Fields
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
/// <summary>
/// Defines the _settingsService
/// </summary>
private readonly ISettingsService _settingsService =
Ioc.Default.GetRequiredService<ISettingsService>();
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="HostWindow"/> class.
/// </summary>
/// <param name="alwaysOnTop">The alwaysOnTop<see cref="bool"/></param>
/// <param name="clickThrough">The clickThrough<see cref="bool"/></param>
public HostWindow()
public LyricsWindow()
{
this.InitializeComponent();
AppWindow.Changed += AppWindow_Changed;
AppWindow.Closing += AppWindow_Closing;
this.HideSystemTitleBarAndSetCustomTitleBar(TopCommandGrid);
ExtendsContentIntoTitleBar = true;
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
Title = App.ResourceLoader!.GetString("LyricsPageTitle");
SetTitleBar(TopCommandGrid);
}
private void CloseOrExit()
public LyricsWindowViewModel ViewModel { get; private set; } = Ioc.Default.GetRequiredService<LyricsWindowViewModel>();
public void AutoSelectLyricsMode(AutoStartWindowType? type = null, bool? autoLook = null)
{
if (RootFrame.SourcePageType == typeof(LyricsPage))
type ??= _settingsService.AutoStartWindowType;
switch (type!)
{
App.Current.Exit();
}
else
{
AppWindow.Hide();
case AutoStartWindowType.StandardMode:
break;
case AutoStartWindowType.DockMode:
DockFlyoutItem.IsChecked = true;
ViewModel.ToggleDockModeCommand.Execute(null);
break;
case AutoStartWindowType.DesktopMode:
DesktopFlyoutItem.IsChecked = true;
ViewModel.ToggleDesktopModeCommand.Execute(null);
if (autoLook == null && _settingsService.AutoLockOnDesktopMode)
{
ViewModel.LockWindowCommand.Execute(null);
}
break;
default:
break;
}
}
private void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
{
args.Cancel = true;
CloseOrExit();
}
#endregion
#region Properties
/// <summary>
/// Gets the ViewModel
/// </summary>
public HostWindowViewModel ViewModel { get; private set; } =
Ioc.Default.GetRequiredService<HostWindowViewModel>();
#endregion
#region Methods
/// <summary>
/// The Navigate
/// </summary>
/// <param name="type">The type<see cref="Type"/></param>
public void Navigate(Type type)
{
RootFrame.Navigate(type);
}
/// <summary>
/// The AOTFlyoutItem_Click
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="RoutedEventArgs"/></param>
private void AOTFlyoutItem_Click(object sender, RoutedEventArgs e)
{
var overlappedPresenter = (OverlappedPresenter)AppWindow.Presenter;
overlappedPresenter.IsAlwaysOnTop = !overlappedPresenter.IsAlwaysOnTop;
}
/// <summary>
/// The AppWindow_Changed
/// </summary>
/// <param name="sender">The sender<see cref="AppWindow"/></param>
/// <param name="args">The args<see cref="AppWindowChangedEventArgs"/></param>
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
if (args.DidPresenterChange)
UpdateTitleBarWindowButtonsVisibility();
if (ViewModel.IsDesktopMode && (args.DidPositionChange || args.DidSizeChange))
OnPosOrSizeChanged();
}
private void OnPosOrSizeChanged()
{
var rect = AppWindow.Position;
var size = AppWindow.Size;
_settingsService.DesktopWindowLeft = rect.X;
_settingsService.DesktopWindowTop = rect.Y;
_settingsService.DesktopWindowWidth = size.Width;
_settingsService.DesktopWindowHeight = size.Height;
}
/// <summary>
/// The CloseButton_Click
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="RoutedEventArgs"/></param>
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
CloseOrExit();
WindowHelper.ExitAllWindows();
}
/// <summary>
/// The FullScreenFlyoutItem_Click
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="RoutedEventArgs"/></param>
private void FullScreenFlyoutItem_Click(object sender, RoutedEventArgs e)
{
switch (AppWindow.Presenter.Kind)
@@ -157,11 +101,6 @@ namespace BetterLyrics.WinUI3.Views
}
}
/// <summary>
/// The MaximiseButton_Click
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="RoutedEventArgs"/></param>
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
{
if (AppWindow.Presenter is OverlappedPresenter presenter)
@@ -170,11 +109,6 @@ namespace BetterLyrics.WinUI3.Views
}
}
/// <summary>
/// The MiniFlyoutItem_Click
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="RoutedEventArgs"/></param>
private void MiniFlyoutItem_Click(object sender, RoutedEventArgs e)
{
if (MiniFlyoutItem.IsChecked)
@@ -187,11 +121,6 @@ namespace BetterLyrics.WinUI3.Views
}
}
/// <summary>
/// The MinimiseButton_Click
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="RoutedEventArgs"/></param>
private void MinimiseButton_Click(object sender, RoutedEventArgs e)
{
if (AppWindow.Presenter is OverlappedPresenter presenter)
@@ -200,11 +129,6 @@ namespace BetterLyrics.WinUI3.Views
}
}
/// <summary>
/// The RestoreButton_Click
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="RoutedEventArgs"/></param>
private void RestoreButton_Click(object sender, RoutedEventArgs e)
{
if (AppWindow.Presenter is OverlappedPresenter presenter)
@@ -213,41 +137,6 @@ namespace BetterLyrics.WinUI3.Views
}
}
/// <summary>
/// The RootFrame_Navigated
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="NavigationEventArgs"/></param>
private void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
AppWindow.Title = Title = App.ResourceLoader!.GetString(
$"{e.SourcePageType.Name}Title"
);
if (e.SourcePageType == typeof(LyricsPage))
{
if (_settingsService.AutoStartWindowType == AutoStartWindowType.DockMode)
{
DockFlyoutItem.IsChecked = true;
ViewModel.ToggleDockModeCommand.Execute(null);
}
}
}
/// <summary>
/// The RootFrame_NavigationFailed
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="NavigationFailedEventArgs"/></param>
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// The RootGrid_PointerMoved
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="PointerRoutedEventArgs"/></param>
private void RootGrid_PointerMoved(object sender, PointerRoutedEventArgs e)
{
var point = e.GetCurrentPoint(RootGrid);
@@ -269,26 +158,11 @@ namespace BetterLyrics.WinUI3.Views
}
}
/// <summary>
/// The SettingsMenuFlyoutItem_Click
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="RoutedEventArgs"/></param>
private void SettingsMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
WindowHelper.OpenSettingsWindow();
WindowHelper.OpenOrShowWindow<SettingsWindow>();
}
/// <summary>
/// The TopCommandGrid_PointerMoved
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="PointerRoutedEventArgs"/></param>
private void TopCommandGrid_PointerMoved(object sender, PointerRoutedEventArgs e) { }
/// <summary>
/// The UpdateTitleBarWindowButtonsVisibility
/// </summary>
private void UpdateTitleBarWindowButtonsVisibility()
{
switch (AppWindow.Presenter.Kind)
@@ -376,14 +250,5 @@ namespace BetterLyrics.WinUI3.Views
break;
}
}
#endregion
private void ClickThroughButton_Click(object sender, RoutedEventArgs e)
{
this.SetExtendedWindowStyle(
ExtendedWindowStyle.Transparent | ExtendedWindowStyle.Layered
);
}
}
}

View File

@@ -14,54 +14,52 @@
xmlns:vm="using:BetterLyrics.WinUI3.ViewModels"
mc:Ignorable="d">
<Grid x:Name="RootGrid" Margin="{x:Bind ViewModel.RootGridMargin, Mode=OneWay}">
<Grid x:Name="RootGrid">
<NavigationView
x:Name="NavView"
IsBackButtonVisible="Collapsed"
IsPaneToggleButtonVisible="False"
IsSettingsVisible="False"
PaneDisplayMode="Auto"
SelectionChanged="NavView_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem
x:Uid="SettingsPageLyricsLib"
Icon="{ui:FontIcon Glyph=&#xE838;}"
x:Uid="SettingsPageApp"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xECAA;}"
IsSelected="True"
Tag="LyricsLib" />
<NavigationViewItem
x:Uid="SettingsPageAppAppearance"
Icon="{ui:FontIcon Glyph=&#xE771;}"
Tag="AppAppearance" />
<NavigationViewItem
x:Uid="SettingsPageAppBehavior"
Icon="{ui:FontIcon Glyph=&#xE805;}"
Tag="AppBehavior" />
<NavigationViewItem
x:Uid="SettingsPageAlbumOverlay"
Icon="{ui:FontIcon Glyph=&#xE81E;}"
Tag="AlbumArtOverlay" />
Tag="App" />
<NavigationViewItem
x:Uid="SettingsPageAlbumStyle"
Icon="{ui:FontIcon Glyph=&#xE80A;}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE93C;}"
Tag="AlbumArtStyle" />
<NavigationViewItem
x:Uid="SettingsPageLyricsStyle"
Icon="{ui:FontIcon Glyph=&#xEF60;}"
Tag="LyricsStyle" />
x:Uid="SettingsPageLyricsLib"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8F1;}"
Tag="LyricsLib" />
<NavigationViewItem
x:Uid="SettingsPageLyricsEffect"
Icon="{ui:FontIcon Glyph=&#xF4A5;}"
Tag="LyricsEffect" />
x:Uid="SettingsPageBackgroundOverlay"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF5EF;}"
Tag="Background" />
<NavigationViewItem
x:Uid="SettingsPageLyrics"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEDC6;}"
Tag="Lyrics" />
<NavigationViewItem
x:Uid="SettingsPageAbout"
Icon="{ui:FontIcon Glyph=&#xE946;}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE946;}"
Tag="About" />
<NavigationViewItem
x:Uid="SettingsPageDev"
Icon="{ui:FontIcon Glyph=&#xEC7A;}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEC7A;}"
Tag="Dev" />
</NavigationView.MenuItems>
<ScrollViewer Padding="36,0">
<Grid Margin="0,36">
<controls:SwitchPresenter Value="{x:Bind ViewModel.NavViewSelectedItemTag, Mode=OneWay}">
@@ -70,21 +68,22 @@
<PopupThemeTransition />
</TransitionCollection>
</controls:SwitchPresenter.ContentTransitions>
<!-- Lyrics lib -->
<controls:Case Value="LyricsLib">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsExpander
x:Uid="SettingsPageMusicLib"
HeaderIcon="{ui:FontIcon Glyph=&#xE8B7;}"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8B7;}"
IsExpanded="True"
ItemsSource="{x:Bind ViewModel.LocalLyricsFolders, Mode=OneWay}">
<controls:SettingsExpander.ItemTemplate>
<DataTemplate>
<controls:SettingsCard>
<controls:SettingsCard.Header>
<HyperlinkButton
Click="SettingsPageOpenPathButton_Click"
Content="{Binding Path, Mode=OneWay}"
Tag="{Binding}" />
<HyperlinkButton Content="{Binding Path, Mode=OneWay}" NavigateUri="{Binding Path, Mode=OneWay}" />
</controls:SettingsCard.Header>
<StackPanel Orientation="Horizontal">
<HyperlinkButton
@@ -140,7 +139,8 @@
<controls:SettingsCard
x:Name="LyricsSearchProvidersSettingsExpander"
x:Uid="SettingsPageLyricsSearchProvidersConfig"
HeaderIcon="{ui:FontIcon Glyph=&#xF6FA;}" />
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF6FA;}" />
<ListView
x:Name="LyricsSearchProvidersListView"
@@ -174,36 +174,19 @@
</StackPanel>
</controls:Case>
<controls:Case Value="AppAppearance">
<!-- App appearance and behavior -->
<controls:Case Value="App">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageTheme" HeaderIcon="{ui:FontIcon Glyph=&#xE790;}">
<ComboBox x:Name="ThemeComboBox" SelectedIndex="{x:Bind ViewModel.ThemeType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageFollowSystem" />
<ComboBoxItem x:Uid="SettingsPageLight" />
<ComboBoxItem x:Uid="SettingsPageDark" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageBackdrop" HeaderIcon="{ui:FontIcon Glyph=&#xF5EF;}">
<ComboBox x:Name="BackdropComboBox" SelectedIndex="{x:Bind ViewModel.BackdropType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageNoBackdrop" />
<ComboBoxItem x:Uid="SettingsPageMica" />
<ComboBoxItem x:Uid="SettingsPageMicaAlt" />
<ComboBoxItem x:Uid="SettingsPageDesktopAcrylic" />
<ComboBoxItem x:Uid="SettingsPageTransparent" />
</ComboBox>
</controls:SettingsCard>
<!-- App appearance -->
<controls:SettingsCard x:Uid="SettingsPageTitleBarType" HeaderIcon="{ui:FontIcon Glyph=&#xE66A;}">
<ComboBox x:Name="TitleBarTypeComboBox" SelectedIndex="{x:Bind ViewModel.TitleBarType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageCompactTitleBar" />
<ComboBoxItem x:Uid="SettingsPageExtendedTitleBar" />
</ComboBox>
</controls:SettingsCard>
<TextBlock x:Uid="SettingsPageAppAppearance" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="SettingsPageLanguage"
HeaderIcon="{ui:FontIcon Glyph=&#xF2B7;}"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF2B7;}"
IsExpanded="True">
<ComboBox SelectedIndex="{x:Bind ViewModel.Language, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageSystemLanguage" />
@@ -220,84 +203,81 @@
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<!-- App behavior -->
<TextBlock x:Uid="SettingsPageAppBehavior" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageAutoStartWindow">
<ComboBox SelectedIndex="{x:Bind ViewModel.AutoStartWindowType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageAutoStartInAppLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDockLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDesktopLyrics" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageAutoLock">
<ToggleSwitch IsOn="{x:Bind ViewModel.AutoLockOnDesktopMode, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
</StackPanel>
</controls:Case>
<controls:Case Value="AppBehavior">
<!-- Lyrics background -->
<controls:Case Value="Background">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageAutoStartWindow">
<ComboBox SelectedIndex="{x:Bind ViewModel.AutoStartWindowType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageAutoStartInAppLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDesktopLyrics" />
</ComboBox>
<controls:SettingsCard x:Uid="SettingsPageDynamicLyricsBackground">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDynamicCoverOverlayEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
</controls:Case>
<controls:Case Value="AlbumArtOverlay">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsExpander
x:Uid="SettingsPageCoverOverlay"
HeaderIcon="{ui:FontIcon Glyph=&#xE93C;}"
IsExpanded="True">
<controls:SettingsExpander.Description>
<StackPanel>
<TextBlock x:Uid="SettingsPageCoverOverlayGPUUsage" />
</StackPanel>
</controls:SettingsExpander.Description>
<ToggleSwitch IsOn="{x:Bind ViewModel.IsCoverOverlayEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundOpacity">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.CoverOverlayOpacity, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text=" %" />
<Slider
Maximum="100"
Minimum="1"
SnapsTo="Ticks"
StepFrequency="1"
TickFrequency="1"
TickPlacement="Outside"
Value="{x:Bind ViewModel.CoverOverlayOpacity, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDynamicCoverOverlay" IsEnabled="{x:Bind ViewModel.IsCoverOverlayEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDynamicCoverOverlayEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageCoverOverlayOpacity" IsEnabled="{x:Bind ViewModel.IsCoverOverlayEnabled, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.CoverOverlayOpacity, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text=" %" />
<Slider
Maximum="100"
Minimum="1"
SnapsTo="Ticks"
StepFrequency="1"
TickFrequency="1"
TickPlacement="Outside"
Value="{x:Bind ViewModel.CoverOverlayOpacity, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageCoverOverlayBlurAmount" IsEnabled="{x:Bind ViewModel.IsCoverOverlayEnabled, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text="{x:Bind ViewModel.CoverOverlayBlurAmount, Mode=OneWay}" />
<Slider
Maximum="200"
Minimum="50"
SnapsTo="Ticks"
StepFrequency="10"
TickFrequency="10"
TickPlacement="Outside"
Value="{x:Bind ViewModel.CoverOverlayBlurAmount, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundBlurAmount">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text="{x:Bind ViewModel.CoverOverlayBlurAmount, Mode=OneWay}" />
<Slider
Maximum="200"
Minimum="50"
SnapsTo="Ticks"
StepFrequency="10"
TickFrequency="10"
TickPlacement="Outside"
Value="{x:Bind ViewModel.CoverOverlayBlurAmount, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
</StackPanel>
</controls:Case>
<!-- Album art style -->
<controls:Case Value="AlbumArtStyle">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon Glyph=&#xE71A;}">
<controls:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE71A;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.CoverImageRadius, Mode=OneWay}" />
@@ -319,10 +299,16 @@
</StackPanel>
</controls:Case>
<controls:Case Value="LyricsStyle">
<!-- Lyrics style and effect -->
<controls:Case Value="Lyrics">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon Glyph=&#xE8E3;}">
<!-- Lyrics style -->
<TextBlock x:Uid="SettingsPageLyricsStyle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind LyricsSettingsControlViewModel.LyricsAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsLeft" />
<ComboBoxItem x:Uid="SettingsPageLyricsCenter" />
@@ -330,7 +316,7 @@
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontWeight" HeaderIcon="{ui:FontIcon Glyph=&#xE8DD;}">
<controls:SettingsCard x:Uid="SettingsPageLyricsFontWeight" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8DD;}">
<ComboBox SelectedIndex="{x:Bind LyricsSettingsControlViewModel.LyricsFontWeight, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsThin" />
<ComboBoxItem x:Uid="SettingsPageLyricsExtraLight" />
@@ -346,14 +332,41 @@
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontColor" HeaderIcon="{ui:FontIcon Glyph=&#xE8D3;}">
<controls:SettingsCard x:Uid="SettingsPageLyricsFontColor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D3;}">
<ComboBox SelectedIndex="{x:Bind LyricsSettingsControlViewModel.LyricsFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDefault" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDominant" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveColored" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveGrayed" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorCustom" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon Glyph=&#xE8E9;}">
<ColorPicker
ColorSpectrumShape="Box"
IsAlphaEnabled="True"
IsAlphaSliderVisible="True"
IsAlphaTextInputVisible="True"
IsColorChannelTextInputVisible="True"
IsColorSliderVisible="True"
IsHexInputVisible="True"
IsMoreButtonVisible="True"
Color="{x:Bind LyricsSettingsControlViewModel.LyricsCustomFontColor, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsSettingsControlViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsSettingsControlViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="NotEqual"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ColorPicker>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E9;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
@@ -372,7 +385,7 @@
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon Glyph=&#xF579;}">
<controls:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF579;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=LyricsLineSpacingFactorSlider, Path=Value, Mode=OneWay}" />
@@ -392,13 +405,14 @@
</StackPanel>
</controls:SettingsCard>
</StackPanel>
</controls:Case>
<!-- Effect -->
<controls:Case Value="LyricsEffect">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock
x:Uid="SettingsPageLyricsEffect"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="Effect" />
<controls:SettingsCard x:Uid="SettingsPageLyricsVerticalEdgeOpacity" HeaderIcon="{ui:FontIcon Glyph=&#xF573;}">
<controls:SettingsCard x:Uid="SettingsPageLyricsVerticalEdgeOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF573;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=LyricsVerticalEdgeOpacitySlider, Path=Value, Mode=OneWay}" />
@@ -418,7 +432,7 @@
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon Glyph=&#xE727;}">
<controls:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
@@ -439,7 +453,8 @@
<controls:SettingsExpander
x:Uid="SettingsPageLyricsGlowEffect"
HeaderIcon="{ui:FontIcon Glyph=&#xE9A9;}"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE9A9;}"
IsExpanded="{x:Bind LyricsSettingsControlViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsSettingsControlViewModel.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
@@ -452,9 +467,15 @@
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageFan" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEBC5;}">
<ToggleSwitch IsOn="{x:Bind LyricsSettingsControlViewModel.IsFanLyricsEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
</controls:Case>
<!-- About -->
<controls:Case Value="About">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard Header="BetterLyrics" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/Logo.png}">
@@ -463,6 +484,7 @@
<Paragraph>
<Run x:Uid="SettingsPageVersion" />
<Run Text="{x:Bind ViewModel.Version, Mode=OneWay}" />
<Run Text="{x:Bind ViewModel.BuildDate, Mode=OneWay}" />
</Paragraph>
</RichTextBlock>
</controls:SettingsCard.Description>
@@ -470,20 +492,23 @@
<controls:SettingsCard
x:Uid="SettingsPageGitHub"
ActionIcon="{ui:FontIcon Glyph=&#xE8A7;}"
ActionIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8A7;}"
Command="{x:Bind ViewModel.LaunchProjectGitHubPageCommand}"
HeaderIcon="{ui:FontIcon Glyph=&#xE943;}"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE943;}"
IsClickEnabled="True" />
<uc:DependenciesSettingsExpander />
</StackPanel>
</controls:Case>
<controls:Case Value="Dev">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageMockMusicPlaying">
<Button x:Uid="SettingsPagePlayingMockMusicButton" Command="{x:Bind ViewModel.PlayTestingMusicTaskCommand}" />
<HyperlinkButton
x:Uid="SettingsPagePlayingMockMusicButton"
Command="{x:Bind ViewModel.PlayTestingMusicTaskCommand}"
NavigateUri="https://soundcloud.com/carlyraejepsen/cut-to-the-feeling" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageCache">
<Button x:Uid="SettingsPageOpenLogFolderButton" Command="{x:Bind ViewModel.OpenCacheFolderCommand}" />

View File

@@ -1,63 +1,27 @@
// 2025/6/23 by Zhe Fang
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Threading.Tasks;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.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
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SettingsPage"/> class.
/// </summary>
public SettingsPage()
{
this.InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<SettingsViewModel>();
DataContext = Ioc.Default.GetRequiredService<SettingsPageViewModel>();
}
#endregion
#region Properties
/// <summary>
/// Gets the LyricsSettingsControlViewModel
/// </summary>
public LyricsSettingsControlViewModel LyricsSettingsControlViewModel =>
Ioc.Default.GetRequiredService<LyricsSettingsControlViewModel>();
/// <summary>
/// Gets the ViewModel
/// </summary>
public SettingsViewModel ViewModel => (SettingsViewModel)DataContext;
public SettingsPageViewModel ViewModel => (SettingsPageViewModel)DataContext;
#endregion
#region Methods
/// <summary>
/// The LocalLyricsFolderToggleSwitch_Toggled
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="RoutedEventArgs"/></param>
private void LocalLyricsFolderToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (sender is ToggleSwitch toggleSwitch)
@@ -69,11 +33,6 @@ namespace BetterLyrics.WinUI3.Views
}
}
/// <summary>
/// The LyricsSearchProvidersListView_DragItemsCompleted
/// </summary>
/// <param name="sender">The sender<see cref="ListViewBase"/></param>
/// <param name="args">The args<see cref="DragItemsCompletedEventArgs"/></param>
private void LyricsSearchProvidersListView_DragItemsCompleted(
ListViewBase sender,
DragItemsCompletedEventArgs args
@@ -82,11 +41,6 @@ namespace BetterLyrics.WinUI3.Views
ViewModel.OnLyricsSearchProvidersReordered();
}
/// <summary>
/// The LyricsSearchProviderToggleSwitch_Toggled
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="RoutedEventArgs"/></param>
private void LyricsSearchProviderToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (sender is ToggleSwitch toggleSwitch)
@@ -98,11 +52,6 @@ namespace BetterLyrics.WinUI3.Views
}
}
/// <summary>
/// The NavView_SelectionChanged
/// </summary>
/// <param name="sender">The sender<see cref="NavigationView"/></param>
/// <param name="args">The args<see cref="NavigationViewSelectionChangedEventArgs"/></param>
private void NavView_SelectionChanged(
NavigationView sender,
NavigationViewSelectionChangedEventArgs args
@@ -111,24 +60,6 @@ namespace BetterLyrics.WinUI3.Views
ViewModel.NavViewSelectedItemTag = (args.SelectedItem as NavigationViewItem)!.Tag;
}
/// <summary>
/// The SettingsPageOpenPathButton_Click
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="Microsoft.UI.Xaml.RoutedEventArgs"/></param>
private void SettingsPageOpenPathButton_Click(
object sender,
Microsoft.UI.Xaml.RoutedEventArgs e
)
{
ViewModel.OpenMusicFolder((LocalLyricsFolder)(sender as HyperlinkButton)!.Tag);
}
/// <summary>
/// The SettingsPageRemovePathButton_Click
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="Microsoft.UI.Xaml.RoutedEventArgs"/></param>
private void SettingsPageRemovePathButton_Click(
object sender,
Microsoft.UI.Xaml.RoutedEventArgs e
@@ -136,7 +67,5 @@ namespace BetterLyrics.WinUI3.Views
{
ViewModel.RemoveFolderAsync((LocalLyricsFolder)(sender as HyperlinkButton)!.Tag);
}
#endregion
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<Window
x:Class="BetterLyrics.WinUI3.Views.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<Grid>
<local:SettingsPage />
</Grid>
</Window>

View File

@@ -0,0 +1,33 @@
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using H.NotifyIcon;
using Microsoft.UI.Xaml;
using WinUIEx;
namespace BetterLyrics.WinUI3.Views
{
public sealed partial class SettingsWindow : Window
{
public SettingsWindow()
{
InitializeComponent();
Title = App.ResourceLoader!.GetString("SettingsPageTitle");
ExtendsContentIntoTitleBar = true;
AppWindow.Closing += AppWindow_Closing;
}
public SettingsWindowViewModel ViewModel { get; set; } =
Ioc.Default.GetRequiredService<SettingsWindowViewModel>();
private void AppWindow_Closing(
Microsoft.UI.Windowing.AppWindow sender,
Microsoft.UI.Windowing.AppWindowClosingEventArgs args
)
{
args.Cancel = true; // Prevent the window from closing
this.Hide(true);
}
}
}

View File

@@ -1,188 +1,127 @@
<a href="https://github.com/jayfunc/BetterLyrics/blob/dev/README.md">_**Click here to see the English version**_</a>
<div align="center">
  <img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="64"/>
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="64"/>
</div>
<h2 align="center">
BetterLyrics
</h2>
</div>
<h3 align="center">
基于 WinUI 3 打造的流畅动态本地歌词显示工具
</h3>
使用 WinUI 3 构建的流畅动态歌词显示工具
</div>
---
## 亮点
## 亮点功能
- 支持将模糊专辑封面为背景
- 歌词淡入淡出、缩放等动画流畅自然
- 切换歌曲时界面无缝过渡
- 支持每个字符渐变卡拉OK发光)效果
- 沉浸式桌面歌词(Dock 模式)
- 动态模糊专辑封面为背景
- 流畅的歌词淡入/淡出、放大/缩小效果
- 流畅的用户界面随歌曲切换
- 每个字符均支持渐变卡拉 OK带光晕)效果
- 沉浸式桌面歌词(停靠模式)
> 项目仍在开发中,`dev` 分支可能存在 bug
> 项目目前仍在开发中,最新的开发分支可能存在错误和意外行为
---
## 支持的歌词来源
## 支持的歌词源
- 来自您的本地存储
- 音乐文件(内嵌歌词)
- [.lrc](https://en.wikipedia.org/wiki/LRC_(file_format)) 文件(包含核心格式和增强格式)
- [.eslrc](https://github.com/ESLyric/release) 文件
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) 文件
- 本地歌词:
- 音乐文件内嵌歌词(通过 [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet) 读取和解析)
- `.lrc` 文件
(歌词下载,您可以使用 [LDDC](https://github.com/chenmozhijin/LDDC))
- 在线歌词源:
- [LRCLIB](https://lrclib.net/)
- QQ 音乐(通过 [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper) 获取和解码)
- 来自在线歌词提供商
- QQ 音乐
- 网易云音乐
- 酷狗音乐
- [amll-ttml-db](https://github.com/Steve-xmh/amll-ttml-db)
- [LRCLIB](https://lrclib.net/)
---
## 截图
## 多种个性化设置选项
![alt text](Screenshots/mode.png)
提供了丰富的自定义项:
![alt text](Screenshots/glow.png)
- 主题模式(浅色、深色、跟随系统)
- 背景样式无、Mica 云母、Acrylic 亚克力、透明)
- 专辑封面背景(动态显示、模糊程度、透明度)
- 歌词样式(对齐方式、字体大小、颜色 **(从专辑封面中提取主题色)**、行间距、透明度、模糊强度、动态**发光**特效)
- 语言(英文、简体中文、繁体中文)
![alt text](Screenshots/glow.gif)
---
![alt text](Screenshots/dock.png)
## 软件截图
![alt text](Screenshots/immersive-dock.gif)
![模式](Screenshots/mode.png)
![发光效果](Screenshots/glow.png)
![发光动画](Screenshots/glow.gif)
![Dock 模式](Screenshots/dock.png)
![沉浸式 Dock](Screenshots/immersive-dock.gif)
![歌词 Dock 动画](Screenshots/dock.gif)
![画中画](Screenshots/pip.png)
![设置界面](Screenshots/settings.png)
![全屏歌词](Screenshots/fs.png)
![alt text](Screenshots/dock.gif)
---
![alt text](Screenshots/pip.png)
## 演示视频
![alt text](Screenshots/settings.png)
观看我们的介绍视频「BetterLyrics 阶段性开发成果展示」(上传于 2025 年 5 月 31 日):
[点此观看 B 站视频](https://b23.tv/QjKkYmL)
![alt text](Screenshots/fs.png)
---
## 演示
在 Bilibili 上观看我们的介绍视频(上传于 2025 年 5 月 31 日) [此处](https://b23.tv/QjKkYmL)。
## 立即体验
### 稳定版本
- 稳定版本
<a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct">
 <img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/>
<img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/>
</a>
> **推荐方式****永久免费试用或购买**(免费与付费功能上无差别,若喜欢可购买支持作者
> **最简单**的获取方式。 **无限**免费试用或购买(免费版和付费版**没有区别**,如果您喜欢,可以购买支持
也可从 Google Drive 下载(详见 [release 页面](https://github.com/jayfunc/BetterLyrics/releases/latest)
或者您也可从 Google Drive 获取(链接见 [release](https://github.com/jayfunc/BetterLyrics/releases/latest) 页面
> 注意:这是一个 `.zip` 压缩包,请参考[安装指南](How2Install/How2Install.md)进行安装
> 注意,您正在下载“.zip”文件有关安装指南,请参考[此文档](How2Install/How2Install.md)。
### 最新开发版本
- 最新开发版本
可通过 `git clone` 克隆本仓库后自行构建运行
您可以使用 `git clone` 命令克隆此项目并自行构建。
---
## 已知不支持的音乐播放器
## 播放器适配说明
- 网易云音乐
本项目通过监听 [SMTC](https://learn.microsoft.com/en-ca/windows/uwp/audio-video-camera/integrate-with-systemmediatransportcontrols) 获取当前播放歌曲信息。
理论上,**只要你的播放器支持 SMTC 控件**,加载本地音乐或歌词后即可使用。
兼容性良好的播放器包括但不限于:
- Spotify
- Groove 音乐
- Apple Music
- Windows 媒体播放器
- VLC
- QQ 音乐
- 酷狗音乐
- 酷我音乐
>(注:未测试全部播放器,如有异常欢迎反馈 issue
---
## 后续工作
敬请期待。
---
## 特别感谢
## 非常感谢
- [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper)
- 提供 QQ、网易、酷狗等平台歌词的获取、解密和解析功能
- [LRCLIB](https://lrclib.net/)
- 在线歌词 API 提供
- LRCLIB 歌词 API 提供程序
- [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet)
- 本地音频元信息读取
- 用于提取音乐文件中的图片
- [WinUIEx](https://github.com/dotMorten/WinUIEx)
- 简化 Win32 窗口操作
- 提供访问 Win32 窗口 API 的便捷方法
- [TagLib#](https://github.com/mono/taglib-sharp)
- 曾用作元信息解析库
- [Stackoverflow - WPF 动画 Margin 属性](https://stackoverflow.com/a/21542882/11048731)
- [DevWinUI](https://github.com/ghost1372/DevWinUI)
- [Bilibili -【WinUI3】SystemBackdropController 教程](https://www.bilibili.com/video/BV1PY4FevEkS)
- [博客园 - .NET App 与 SMTC 交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
- [Win2D 游戏循环教程](https://www.cnblogs.com/walterlv/p/10236395.html)
- [Win2D Iris Blur 示例](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
- [CommunityToolkit - 教程合集](https://mvvm.coldwind.top/)
- 用于读取原版歌词内容
- [Stackoverflow - 如何在 WPF 中为 Margin 属性设置动画](https://stackoverflow.com/a/21542882/11048731)
- [DevWinUI](https://github.com/ghost1372/DevWinUI)
- [Bilibili -【WinUI3】SystemBackdropController:定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS)
- [cnblogs - .NET App 与 Windows 系统媒体控制(SMTC)交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
- [Win2D中的游戏循环CanvasAnimatedControl](https://www.cnblogs.com/walterlv/p/10236395.html)
- [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
- [CommunityToolkit - 从入门到精通](https://mvvm.coldwind.top/)
---
## 灵感来自
## 灵感来源
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)
---
## 使用的第三方库
```xml
<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.Primitives" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="Lyricify.Lyrics.Helper" Version="0.1.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
<PackageReference Include="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="System.Drawing.Common" Version="9.0.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="WinUIEx" Version="2.5.1" />
<PackageReference Include="z440.atl.core" Version="6.25.0" />
```
## Star 历史
[![Star History Chart](https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date)](https://www.star-history.com/#jayfunc/BetterLyrics&Date)
[![星盘历史Chart](https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date)](https://www.star-history.com/#jayfunc/BetterLyrics&Date)
## 欢迎提出反馈或建议
感谢。
## 欢迎提出任何问题和 PR
如果您发现错误,请提交至 issues如果您有任何想法请随时在此处分享。
或者,您也可以加入群聊,分享您的宝贵反馈:
- QQ[「BetterLyrics」反馈交流群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388)
- Discord [「BetterLyrics」反馈交流群](https://discord.gg/rbnF556r)

View File

@@ -9,7 +9,7 @@ BetterLyrics
</div>
<h3 align="center">
Your smooth dynamic local lyrics display built with WinUI 3
Your smooth dynamic lyrics display tool built with WinUI 3
</div>
---
@@ -32,52 +32,15 @@ Your smooth dynamic local lyrics display built with WinUI 3
- [.eslrc](https://github.com/ESLyric/release) files
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) files
(For lyrics downloading, you can use [LDDC](https://github.com/chenmozhijin/LDDC))
- From online lyrics providers
- QQ Music
- 网易云音乐 NetEase Cloud Music
- 酷狗音乐 Kugou Music
- [amll-ttml-db](https://github.com/Steve-xmh/amll-ttml-db)
- [LRCLIB](https://lrclib.net/)
## Customize in your way
We provide more than one setting item to better align with your preference
- Theme
- Light
- Dark
- Follow system
- Backdrop
- None
- Mica
- Acrylic
- Transparent
- Album art as background
- Dynamic
- Blur amount
- Opacity
- Album art as cover
- Corner radius
- Lyrics
- Alignment
- Font size
- Font color **(from album art accent color)**
- Line spacing
- Opacity
- Blur amount
- Dynamic **glow** effect
- Whole lyrics
- Line by line
- Word by word
- Language
- English
- Simplified Chinese
- Traditional Chinese
- Japanese
- Korean
## Screenshots
![alt text](Screenshots/mode.png)
@@ -104,7 +67,7 @@ Watch our introduction video (uploaded on 31 May 2025) on Bilibili [here](https:
## Try it now
### Stable version
- Stable version
<a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct">
<img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/>
@@ -116,26 +79,20 @@ Or alternatively get it from Google Drive (see [release](https://github.com/jayf
> Please note you are downloading ".zip" file, for guide on how to install it, please kindly follow [this doc](How2Install/How2Install.md).
### Latest dev version
- Latest dev version
You can `git clone` this project and build it yourself.
## Setup your app
## Known unsupported music player
This project relies on listening messages from [SMTC](https://learn.microsoft.com/en-ca/windows/uwp/audio-video-camera/integrate-with-systemmediatransportcontrols), so most of the music players will work.
### About lyrics
For a better experience, you can use [LDDC](https://github.com/chenmozhijin/LDDC) to download lyrics.
## Future work
To be added later.
- 网易云音乐 NetEase Cloud Music
## Many thanks to
- [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper)
- Provide lyrics fetch, decryption, and parse for QQ, Netease, Kugou sources
- [LRCLIB](https://lrclib.net/)
- Online lyrics API provider
- LRCLIB lyrics API provider
- [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet)
- Used for extracting pictures in music files
- [WinUIEx](https://github.com/dotMorten/WinUIEx)
@@ -157,29 +114,6 @@ To be added later.
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)
## Third-party libraries that this project uses
```
<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="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="WinUIEx" Version="2.5.1" />
<PackageReference Include="z440.atl.core" Version="6.26.0" />S
```
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date)](https://www.star-history.com/#jayfunc/BetterLyrics&Date)
@@ -187,3 +121,7 @@ To be added later.
## Any issues and PRs are welcomed
If you find a bug please file it in issues or if you have any ideas feel free to share it here.
Or alternatively join group chat to share your valuable feedback:
- [「BetterLyrics」反馈交流群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388) on QQ
- [「BetterLyrics」Feedback Chat Group](https://discord.gg/rbnF556r) on Discord