Compare commits

..

7 Commits

Author SHA1 Message Date
Zhe Fang
5e74468194 Merge pull request #8 from jayfunc/dev
add multiple online lyrics providers; add desktop mode; improve blur/shadow/scrolling effect performance; fix bugs
2025-06-26 21:51:36 -04:00
Zhe Fang
ff65429b16 add desktop mode; fix 2025-06-26 21:48:07 -04:00
Zhe Fang
ab03870b6a add: support qq music, kugou music, netease music as lyrics providers 2025-06-26 14:43:06 -04:00
Zhe Fang
23bafc4d75 chore: split renderer viewmodel 2025-06-26 08:30:19 -04:00
Zhe Fang
3bdce0d975 fix: dock mode; improve lyrics blur effect 2025-06-24 19:31:17 -04:00
Zhe Fang
454edbeaba change opacity effect rendering method 2025-06-24 16:18:47 -04:00
Zhe Fang
1e7e63032a chore: format code 2025-06-23 16:26:19 -04:00
90 changed files with 8247 additions and 3547 deletions

View File

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

View File

@@ -45,6 +45,7 @@
<converter:ColorToBrushConverter x:Key="ColorToBrushConverter" />
<converter:MatchedLocalFilesPathToVisibilityConverter x:Key="MatchedLocalFilesPathToVisibilityConverter" />
<converter:IntToCornerRadius x:Key="IntToCornerRadius" />
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Text;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
@@ -17,25 +19,29 @@ 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.
/// Provides application-specific behavior to supplement the default Application class
/// </summary>
public partial class App : Application
{
private readonly ILogger<App> _logger;
public static new App Current => (App)Application.Current;
public static ResourceLoader? ResourceLoader { get; private set; }
public static DispatcherQueue? DispatcherQueue { get; private set; }
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
#region Fields
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// Defines the _logger
/// </summary>
private readonly ILogger<App> _logger;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
public App()
{
@@ -57,30 +63,46 @@ namespace BetterLyrics.WinUI3
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}
private void CurrentDomain_FirstChanceException(
object? sender,
System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e
)
#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)
{
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
}
private void TaskScheduler_UnobservedTaskException(
object? sender,
UnobservedTaskExceptionEventArgs e
)
{
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
}
private void CurrentDomain_UnhandledException(
object sender,
System.UnhandledExceptionEventArgs e
)
{
_logger.LogError(e.ExceptionObject.ToString(), "CurrentDomain_UnhandledException");
WindowHelper.OpenLyricsWindow();
}
/// <summary>
/// The ConfigureServices
/// </summary>
private static void ConfigureServices()
{
Log.Logger = new LoggerConfiguration()
@@ -103,6 +125,7 @@ namespace BetterLyrics.WinUI3
.AddSingleton<ILibWatcherService, LibWatcherService>()
// ViewModels
.AddTransient<HostWindowViewModel>()
.AddSingleton<SystemTrayViewModel>()
.AddSingleton<SettingsViewModel>()
.AddSingleton<LyricsPageViewModel>()
.AddSingleton<LyricsRendererViewModel>()
@@ -111,6 +134,11 @@ 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
@@ -121,12 +149,44 @@ namespace BetterLyrics.WinUI3
}
/// <summary>
/// Invoked when the application is launched.
/// The CurrentDomain_FirstChanceException
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs args)
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs"/></param>
private void CurrentDomain_FirstChanceException(
object? sender,
System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e
)
{
WindowHelper.OpenLyricsWindow();
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
}
/// <summary>
/// The CurrentDomain_UnhandledException
/// </summary>
/// <param name="sender">The sender<see cref="object"/></param>
/// <param name="e">The e<see cref="System.UnhandledExceptionEventArgs"/></param>
private void CurrentDomain_UnhandledException(
object sender,
System.UnhandledExceptionEventArgs e
)
{
_logger.LogError(e.ExceptionObject.ToString(), "CurrentDomain_UnhandledException");
}
/// <summary>
/// The TaskScheduler_UnobservedTaskException
/// </summary>
/// <param name="sender">The sender<see cref="object?"/></param>
/// <param name="e">The e<see cref="UnobservedTaskExceptionEventArgs"/></param>
private void TaskScheduler_UnobservedTaskException(
object? sender,
UnobservedTaskExceptionEventArgs e
)
{
//_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
}
#endregion
}
}

View File

@@ -10,6 +10,18 @@
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Remove="ViewModels\Lyrics\**" />
<Content Remove="ViewModels\Lyrics\**" />
<EmbeddedResource Remove="ViewModels\Lyrics\**" />
<None Remove="ViewModels\Lyrics\**" />
<Page Remove="ViewModels\Lyrics\**" />
<PRIResource Remove="ViewModels\Lyrics\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Controls\DependenciesSettingsExpander.xaml" />
<None Remove="Controls\SystemTray.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Logo.ico" />
</ItemGroup>
@@ -18,22 +30,18 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
<PackageReference
Include="CommunityToolkit.Labs.WinUI.OpacityMaskView"
Version="0.1.250513-build.2126"
/>
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.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.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="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
@@ -46,7 +54,7 @@
<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="WinUIEx" Version="2.6.0" />
<PackageReference Include="z440.atl.core" Version="6.26.0" />
</ItemGroup>
<ItemGroup>
@@ -64,14 +72,20 @@
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Folder Include="Controls\" />
<Folder Include="ViewModels\Lyrics\" />
</ItemGroup>
<!--Disable Trimming for Specific Packages-->
<ItemGroup>
<TrimmerRootAssembly Include="TagLibSharp" />
</ItemGroup>
<ItemGroup>
<Page Update="Controls\SystemTray.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\DependenciesSettingsExpander.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!-- Publish Properties -->
<PropertyGroup>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>

View File

@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.DependenciesSettingsExpander"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<controls:SettingsExpander
x:Uid="DependenciesSettingsExpander"
HeaderIcon="{ui:FontIcon Glyph=&#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

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

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.SystemTray"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tb="using:H.NotifyIcon"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<tb:TaskbarIcon
x:Name="TrayIcon"
x:FieldModifier="public"
ContextMenuMode="SecondWindow"
IconSource="ms-appx:///Assets/Logo.ico"
NoLeftClickDelay="True"
ToolTipText="{x:Bind ViewModel.ToolTipText, Mode=OneWay}">
<tb:TaskbarIcon.ContextFlyout>
<MenuFlyout
AreOpenCloseAnimationsEnabled="True"
LightDismissOverlayMode="On"
ShowMode="TransientWithDismissOnPointerMoveAway">
<MenuFlyoutItem x:Uid="SystemTraySettings" Command="{x:Bind ViewModel.OpenSettingsCommand}" />
<MenuFlyoutItem x:Uid="SystemTrayExit" Command="{x:Bind ViewModel.ExitAppCommand}" />
<MenuFlyoutItem
x:Uid="SystemTrayUnlock"
Command="{x:Bind ViewModel.UnlockWindowCommand}"
Visibility="{x:Bind ViewModel.IsLyricsWindowLocked, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</MenuFlyout>
</tb:TaskbarIcon.ContextFlyout>
</tb:TaskbarIcon>
</UserControl>

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class SystemTray : UserControl
{
public SystemTrayViewModel ViewModel => (SystemTrayViewModel)DataContext;
public SystemTray()
{
InitializeComponent();
DataContext = Ioc.Default.GetService<SystemTrayViewModel>();
}
}
}

View File

@@ -1,16 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
using System;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
namespace BetterLyrics.WinUI3.Converter
{
public class ColorToBrushConverter : IValueConverter
/// <summary>
/// Defines the <see cref="ColorToBrushConverter" />
/// </summary>
public partial class ColorToBrushConverter : IValueConverter
{
#region Methods
/// <summary>
/// The Convert
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is Color color)
@@ -20,9 +31,19 @@ namespace BetterLyrics.WinUI3.Converter
return new SolidColorBrush();
}
/// <summary>
/// The ConvertBack
/// </summary>
/// <param name="value">The value<see cref="object"/></param>
/// <param name="targetType">The targetType<see cref="Type"/></param>
/// <param name="parameter">The parameter<see cref="object"/></param>
/// <param name="language">The language<see cref="string"/></param>
/// <returns>The <see cref="object"/></returns>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -0,0 +1,27 @@
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
{
internal partial class CornerRadiusToDoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
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
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,15 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
// 2025/6/23 by Zhe Fang
using System;
using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
internal class EnumToIntConverter : IValueConverter
/// <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)
@@ -19,6 +29,14 @@ 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)
@@ -27,5 +45,7 @@ namespace BetterLyrics.WinUI3.Converter
}
return Enum.ToObject(targetType, 0);
}
#endregion
}
}

View File

@@ -1,15 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.ViewModels;
// 2025/6/23 by Zhe Fang
using System;
using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
public class IntToCornerRadius : IValueConverter
/// <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)
@@ -19,9 +29,19 @@ 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

@@ -1,45 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
using System;
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
public class LyricsSearchProviderToDisplayNameConverter : IValueConverter
/// <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.LrcLib => App.ResourceLoader!.GetString(
"LyricsSearchProviderLrcLib"
),
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString(
"LyricsSearchProviderEslrcFile"
),
LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString(
"LyricsSearchProviderTtmlFile"
),
_ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null),
_ => "",
};
}
return "";
}
/// <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,11 +1,26 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace BetterLyrics.WinUI3.Converter
{
public class MatchedLocalFilesPathToVisibilityConverter : IValueConverter
/// <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)
@@ -22,9 +37,19 @@ 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

@@ -1,14 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the AutoStartWindowType
/// </summary>
public enum AutoStartWindowType
{
/// <summary>
/// Defines the StandardMode
/// </summary>
StandardMode,
/// <summary>
/// Defines the DockMode
/// </summary>
DockMode,
}
#endregion
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,12 +8,38 @@ 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

@@ -0,0 +1,39 @@
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the EasingType
/// </summary>
public enum EasingType
{
/// <summary>
/// Defines the EaseInOutQuad
/// </summary>
EaseInOutQuad,
/// <summary>
/// Defines the EaseInQuad
/// </summary>
EaseInQuad,
/// <summary>
/// Defines the EaseOutQuad
/// </summary>
EaseOutQuad,
/// <summary>
/// Defines the Linear
/// </summary>
Linear,
/// <summary>
/// Defines the SmootherStep
/// </summary>
SmootherStep,
}
#endregion
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,13 +8,43 @@ 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

@@ -6,10 +6,9 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum LyricsGlowEffectScope
public enum LineMaskType
{
WholeLyrics,
CurrentLine,
CurrentChar,
Glow,
Highlight,
}
}

View File

@@ -0,0 +1,21 @@
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LineMaskType
/// </summary>
public enum LineRenderingType
{
UntilCurrentChar,
/// <summary>
/// Current char only
/// </summary>
CurrentCharOnly,
}
#endregion
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,9 +8,23 @@ 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,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,10 +8,28 @@ 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

@@ -1,10 +1,34 @@
namespace BetterLyrics.WinUI3.Enums
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsDisplayType
/// </summary>
public enum LyricsDisplayType
{
/// <summary>
/// Defines the AlbumArtOnly
/// </summary>
AlbumArtOnly,
/// <summary>
/// Defines the LyricsOnly
/// </summary>
LyricsOnly,
/// <summary>
/// Defines the SplitView
/// </summary>
SplitView,
/// <summary>
/// Defines the PlaceholderOnly
/// </summary>
PlaceholderOnly,
}
#endregion
}

View File

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

View File

@@ -1,30 +1,92 @@
using System;
// 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
@@ -47,5 +109,7 @@ namespace BetterLyrics.WinUI3.Enums
),
};
}
#endregion
}
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,27 +8,47 @@ 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
{
public static string ToFileExtension(this LyricsFormat format)
{
return format switch
{
LyricsFormat.Lrc => ".lrc",
LyricsFormat.Eslrc => ".eslrc",
LyricsFormat.Ttml => ".ttml",
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null),
};
}
#region Methods
public static LyricsFormat? Detect(string content)
/// <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")
@@ -51,5 +73,25 @@ namespace BetterLyrics.WinUI3.Enums
return null;
}
}
/// <summary>
/// The ToFileExtension
/// </summary>
/// <param name="format">The format<see cref="LyricsFormat"/></param>
/// <returns>The <see cref="string"/></returns>
public static string ToFileExtension(this LyricsFormat format)
{
return format switch
{
LyricsFormat.Lrc => ".lrc",
LyricsFormat.Qrc => ".qrc",
LyricsFormat.Krc => ".krc",
LyricsFormat.Eslrc => ".eslrc",
LyricsFormat.Ttml => ".ttml",
_ => ".*",
};
}
#endregion
}
}

View File

@@ -1,8 +0,0 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum LyricsHighlightType
{
LineByLine,
CharByChar,
}
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,21 +8,28 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsPlayingState
/// </summary>
public enum LyricsPlayingState
{
/// <summary>
/// Not played yet, will be playing in the future
/// Defines the NotPlayed
/// </summary>
NotPlayed,
/// <summary>
/// Playing
/// Defines the Playing
/// </summary>
Playing,
/// <summary>
/// Has already played
/// Defines the Played
/// </summary>
Played,
}
#endregion
}

View File

@@ -1,11 +1,94 @@
namespace BetterLyrics.WinUI3.Enums
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Helper;
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the LyricsSearchProvider
/// </summary>
public enum LyricsSearchProvider
{
QQ,
Kugou,
Netease,
LrcLib,
AmllTtmlDb,
/// <summary>
/// Defines the LocalMusicFile
/// </summary>
LocalMusicFile,
/// <summary>
/// Defines the LocalLrcFile
/// </summary>
LocalLrcFile,
/// <summary>
/// Defines the LocalEslrcFile
/// </summary>
LocalEslrcFile,
/// <summary>
/// Defines the LocalTtmlFile
/// </summary>
LocalTtmlFile,
}
public static class LyricsSearchProviderExtensions
{
/// <summary>
/// The IsLocal
/// </summary>
/// <param name="provider">The provider<see cref="LyricsSearchProvider"/></param>
/// <returns>The <see cref="bool"/></returns>
public static bool IsLocal(this LyricsSearchProvider provider)
{
return provider
is LyricsSearchProvider.LocalMusicFile
or LyricsSearchProvider.LocalLrcFile
or LyricsSearchProvider.LocalEslrcFile
or LyricsSearchProvider.LocalTtmlFile;
}
public static bool IsRemote(this LyricsSearchProvider provider)
{
return !provider.IsLocal();
}
public static string GetCacheDirectory(this LyricsSearchProvider provider)
{
return provider switch
{
LyricsSearchProvider.LrcLib => AppInfo.LrcLibLyricsCacheDirectory,
LyricsSearchProvider.QQ => AppInfo.QQLyricsCacheDirectory,
LyricsSearchProvider.Netease => AppInfo.NeteaseLyricsCacheDirectory,
LyricsSearchProvider.Kugou => AppInfo.KugouLyricsCacheDirectory,
LyricsSearchProvider.AmllTtmlDb => AppInfo.AmllTtmlDbLyricsCacheDirectory,
_ => throw new System.ArgumentOutOfRangeException(nameof(provider)),
};
}
public static LyricsFormat GetLyricsFormat(this LyricsSearchProvider provider)
{
return provider switch
{
LyricsSearchProvider.LrcLib => LyricsFormat.Lrc,
LyricsSearchProvider.QQ => LyricsFormat.Qrc,
LyricsSearchProvider.Kugou => LyricsFormat.Krc,
LyricsSearchProvider.Netease => LyricsFormat.Lrc,
LyricsSearchProvider.AmllTtmlDb => LyricsFormat.Ttml,
LyricsSearchProvider.LocalLrcFile => LyricsFormat.Lrc,
LyricsSearchProvider.LocalEslrcFile => LyricsFormat.Eslrc,
LyricsSearchProvider.LocalTtmlFile => LyricsFormat.Ttml,
_ => LyricsFormat.NotSpecified,
};
}
}
#endregion
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,10 +8,28 @@ 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,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,9 +8,23 @@ 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

@@ -1,8 +1,24 @@
namespace BetterLyrics.WinUI3.Enums
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Enums
{
#region Enums
/// <summary>
/// Defines the MusicSearchMatchMode
/// </summary>
public enum MusicSearchMatchMode
{
/// <summary>
/// Defines the TitleAndArtist
/// </summary>
TitleAndArtist,
/// <summary>
/// Defines the TitleArtistAlbumAndDuration
/// </summary>
TitleArtistAlbumAndDuration,
}
#endregion
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,14 +8,38 @@ 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
@@ -27,5 +53,7 @@ namespace BetterLyrics.WinUI3.Enums
),
};
}
#endregion
}
}

View File

@@ -1,13 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
using System;
namespace BetterLyrics.WinUI3.Events
{
/// <summary>
/// Defines the <see cref="IsPlayingChangedEventArgs" />
/// </summary>
public class IsPlayingChangedEventArgs(bool isPlaying) : EventArgs
{
#region Properties
/// <summary>
/// Gets or sets a value indicating whether IsPlaying
/// </summary>
public bool IsPlaying { get; set; } = isPlaying;
}
#endregion
}
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -7,17 +9,45 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Events
{
/// <summary>
/// Defines the <see cref="LibChangedEventArgs" />
/// </summary>
public class LibChangedEventArgs : EventArgs
{
public string Folder { get; }
public string FilePath { get; }
public WatcherChangeTypes ChangeType { get; }
#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
}
}

View File

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

View File

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

View File

@@ -1,29 +1,96 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using BetterLyrics.WinUI3.Enums;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="AnimationHelper" />
/// </summary>
public class AnimationHelper
{
public const int StackedNotificationsShowingDuration = 3900;
public const int StoryboardDefaultDuration = 200;
#region Constants
/// <summary>
/// Defines the DebounceDefaultDuration
/// </summary>
public const int DebounceDefaultDuration = 200;
/// <summary>
/// Defines the StackedNotificationsShowingDuration
/// </summary>
public const int StackedNotificationsShowingDuration = 3900;
/// <summary>
/// Defines the StoryboardDefaultDuration
/// </summary>
public const int StoryboardDefaultDuration = 200;
#endregion
}
/// <summary>
/// Defines the <see cref="ValueTransition{T}" />
/// </summary>
/// <typeparam name="T"></typeparam>
public class ValueTransition<T>
where T : struct
{
#region Fields
/// <summary>
/// Defines the _currentValue
/// </summary>
private T _currentValue;
private T _startValue;
private T _targetValue;
private float _progress;
/// <summary>
/// Defines the _durationSeconds
/// </summary>
private float _durationSeconds;
private bool _isTransitioning;
/// <summary>
/// Defines the _interpolator
/// </summary>
private Func<T, T, float, T> _interpolator;
/// <summary>
/// Defines the _isTransitioning
/// </summary>
private bool _isTransitioning;
/// <summary>
/// Defines the _progress
/// </summary>
private float _progress;
/// <summary>
/// Defines the _startValue
/// </summary>
private T _startValue;
/// <summary>
/// Defines the _targetValue
/// </summary>
private T _targetValue;
private EasingType? _easingType;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ValueTransition{T}"/> class.
/// </summary>
/// <param name="initialValue">The initialValue<see cref="T"/></param>
/// <param name="durationSeconds">The durationSeconds<see cref="float"/></param>
/// <param name="interpolator">The interpolator<see cref="Func{T, T, float, T}"/></param>
public ValueTransition(
T initialValue,
float durationSeconds,
Func<T, T, float, T> interpolator
Func<T, T, float, T>? interpolator = null,
EasingType? easingType = null
)
{
_currentValue = initialValue;
@@ -32,12 +99,95 @@ namespace BetterLyrics.WinUI3.Helper
_durationSeconds = durationSeconds;
_progress = 1f;
_isTransitioning = false;
_interpolator = interpolator;
if (interpolator != null)
{
_interpolator = interpolator;
_easingType = null;
}
else if (easingType.HasValue)
{
_easingType = easingType;
_interpolator = GetInterpolatorByEasingType(easingType.Value);
}
else
{
_interpolator = GetInterpolatorByEasingType(EasingType.Linear);
_easingType = EasingType.Linear;
}
}
public T Value => _currentValue;
#endregion
#region Properties
/// <summary>
/// Gets a value indicating whether IsTransitioning
/// </summary>
public bool IsTransitioning => _isTransitioning;
/// <summary>
/// Gets the Value
/// </summary>
public T Value => _currentValue;
#endregion
#region Methods
private Func<T, T, float, T> GetInterpolatorByEasingType(EasingType type)
{
// 这里只以float为例实际可根据T类型扩展
if (typeof(T) == typeof(float))
{
return (start, end, progress) =>
{
float s = (float)(object)start;
float e = (float)(object)end;
float t = progress;
switch (type)
{
case EasingType.EaseInOutQuad:
t = EasingHelper.EaseInOutQuad(t);
break;
case EasingType.EaseInQuad:
t = EasingHelper.EaseInQuad(t);
break;
case EasingType.EaseOutQuad:
t = EasingHelper.EaseOutQuad(t);
break;
case EasingType.Linear:
t = EasingHelper.Linear(t);
break;
case EasingType.SmootherStep:
t = EasingHelper.SmootherStep(t);
break;
default:
break;
}
return (T)(object)(s + (e - s) * t);
};
}
throw new NotSupportedException("当前类型未实现默认缓动插值");
}
/// <summary>
/// The Reset
/// </summary>
/// <param name="value">The value<see cref="T"/></param>
public void Reset(T value)
{
_currentValue = value;
_startValue = value;
_targetValue = value;
_progress = 0f;
_isTransitioning = false;
}
/// <summary>
/// The StartTransition
/// </summary>
/// <param name="targetValue">The targetValue<see cref="T"/></param>
public void StartTransition(T targetValue)
{
if (!targetValue.Equals(_currentValue))
@@ -49,6 +199,23 @@ 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)
@@ -67,13 +234,6 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public void Reset(T value)
{
_currentValue = value;
_startValue = value;
_targetValue = value;
_progress = 0f;
_isTransitioning = false;
}
#endregion
}
}

View File

@@ -1,23 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Helper
{
using System;
using System.IO;
using Windows.ApplicationModel;
using Windows.Storage;
/// <summary>
/// Defines the <see cref="AppInfo" />
/// </summary>
public static class AppInfo
{
// App Metadata
public const string AppName = "BetterLyrics";
public const string AppDisplayName = "Better Lyrics";
#region Constants
/// <summary>
/// Defines the AppAuthor
/// </summary>
public const string AppAuthor = "Zhe Fang";
/// <summary>
/// Defines the AppDisplayName
/// </summary>
public const string AppDisplayName = "Better Lyrics";
// App Metadata
/// <summary>
/// Defines the AppName
/// </summary>
public const string AppName = "BetterLyrics";
/// <summary>
/// Defines the GithubUrl
/// </summary>
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
#endregion
#region Properties
/// <summary>
/// Gets the AppVersion
/// </summary>
public static string AppVersion
{
get
@@ -27,35 +51,81 @@ namespace BetterLyrics.WinUI3.Helper
}
}
// Environment Info
public static bool IsDebug =>
#if DEBUG
true;
#else
false;
#endif
// Base Folders
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
/// <summary>
/// Gets the AssetsFolder
/// </summary>
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
/// <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");
public static string OnlineLyricsCacheDirectory =>
Path.Combine(CacheFolder, "online-lyrics");
/// <summary>
/// Gets the OnlineLyricsCacheDirectory
/// </summary>
public static string LrcLibLyricsCacheDirectory =>
Path.Combine(CacheFolder, "lrclib-lyrics");
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
public static string AmllTtmlDbLyricsCacheDirectory =>
Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
public static string QQLyricsCacheDirectory => Path.Combine(CacheFolder, "qq-lyrics");
public static string KugouLyricsCacheDirectory => Path.Combine(CacheFolder, "kugou-lyrics");
public static string NeteaseLyricsCacheDirectory =>
Path.Combine(CacheFolder, "netease-lyrics");
public static string AmllTtmlDbIndexPath =>
Path.Combine(CacheFolder, "amll-ttml-db-index.json");
/// <summary>
/// Gets the TestMusicPath
/// </summary>
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
// Base Folders
/// <summary>
/// Gets the LocalFolder
/// </summary>
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
/// <summary>
/// Gets the TestMusicFileName
/// </summary>
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
#endregion
#region Methods
/// <summary>
/// The EnsureDirectories
/// </summary>
public static void EnsureDirectories()
{
Directory.CreateDirectory(LocalFolder);
Directory.CreateDirectory(LogDirectory);
Directory.CreateDirectory(OnlineLyricsCacheDirectory);
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
Directory.CreateDirectory(QQLyricsCacheDirectory);
Directory.CreateDirectory(KugouLyricsCacheDirectory);
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
}
#endregion
}
}

View File

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

View File

@@ -1,19 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
using Windows.UI;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="ColorHelper" />
/// </summary>
public static class ColorHelper
{
public static Windows.UI.Color ToWindowsUIColor(this System.Drawing.Color color)
{
return Windows.UI.Color.FromArgb(color.A, color.R, color.G, color.B);
}
#region Methods
/// <summary>
/// The GetInterpolatedColor
/// </summary>
/// <param name="progress">The progress<see cref="float"/></param>
/// <param name="startColor">The startColor<see cref="Color"/></param>
/// <param name="targetColor">The targetColor<see cref="Color"/></param>
/// <returns>The <see cref="Color"/></returns>
public static Color GetInterpolatedColor(
float progress,
Color startColor,
@@ -28,5 +32,17 @@ namespace BetterLyrics.WinUI3.Helper
Lerp(startColor.B, targetColor.B)
);
}
/// <summary>
/// The ToWindowsUIColor
/// </summary>
/// <param name="color">The color<see cref="System.Drawing.Color"/></param>
/// <returns>The <see cref="Windows.UI.Color"/></returns>
public static Windows.UI.Color ToWindowsUIColor(this System.Drawing.Color color)
{
return Windows.UI.Color.FromArgb(color.A, color.R, color.G, color.B);
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.UI.Xaml;
using WinRT.Interop;
using WinUIEx;
namespace BetterLyrics.WinUI3.Helper
{
public static class DesktopModeHelper
{
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
private static readonly Dictionary<IntPtr, bool> _clickThroughStates = [];
private static readonly Dictionary<IntPtr, bool> _originalTopmostStates = [];
private static readonly Dictionary<
IntPtr,
(double X, double Y, double Width, double Height)
> _originalWindowBounds = [];
// <20><><EFBFBD><EFBFBD><E0BBAF><EFBFBD><EFBFBD>
private delegate nint WndProcDelegate(nint hWnd, uint msg, nint wParam, nint lParam);
public static void Enable(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// <20><>¼ԭʼ<D4AD><CABC><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
var windowManager = WindowManager.Get(window);
if (!_originalWindowBounds.ContainsKey(hwnd))
{
_originalWindowBounds[hwnd] = (
windowManager.AppWindow.Position.X,
windowManager.AppWindow.Position.Y,
windowManager.Width,
windowManager.Height
);
}
// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
var displayArea = Microsoft.UI.Windowing.DisplayArea.GetFromWindowId(
windowManager.AppWindow.Id,
Microsoft.UI.Windowing.DisplayAreaFallback.Primary
);
var workArea = displayArea.WorkArea;
// <20><><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD>λ<EFBFBD><CEBB>
int targetWidth = workArea.Width / 3;
int targetHeight = workArea.Height / 4;
int targetX = workArea.X + (workArea.Width - targetWidth) / 2; // <20><><EFBFBD><EFBFBD>
int targetY = workArea.Y + workArea.Height - targetHeight - 64;
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
windowManager.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
);
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
if (!_originalWindowStyles.ContainsKey(hwnd))
_originalWindowStyles[hwnd] = window.GetWindowStyle();
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
if (!_originalTopmostStates.ContainsKey(hwnd))
_originalTopmostStates[hwnd] = window.GetIsAlwaysOnTop();
// <20><><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD>ö<EFBFBD>
window.SetIsAlwaysOnTop(true);
window.SetIsShownInSwitchers(false);
}
public static void Disable(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// <20>ָ<EFBFBD>TopMost״̬
if (_originalTopmostStates.TryGetValue(hwnd, out var wasTopMost))
{
window.SetIsAlwaysOnTop(wasTopMost);
_originalTopmostStates.Remove(hwnd);
}
// <20>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
var windowManager = WindowManager.Get(window);
if (_originalWindowBounds.TryGetValue(hwnd, out var bounds))
{
windowManager.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(
(int)bounds.X,
(int)bounds.Y,
(int)bounds.Width,
(int)bounds.Height
)
);
_originalWindowBounds.Remove(hwnd);
}
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
{
window.SetWindowStyle(style);
_originalWindowStyles.Remove(hwnd);
}
window.SetIsShownInSwitchers(true);
}
public static void Lock(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// <20><><EFBFBD><EFBFBD><EFBFBD>ޱ߿<DEB1><DFBF><EFBFBD>͸<EFBFBD><CDB8>
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
window.ExtendsContentIntoTitleBar = false;
SetClickThrough(window, true);
}
public static void Unlock(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD>ֻ<EFBFBD><D6BB><EFBFBD><EFBFBD> Disable ʱ<><CAB1><EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD>
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
{
window.SetWindowStyle(style);
}
window.ExtendsContentIntoTitleBar = true;
SetClickThrough(window, false);
}
/// <summary>
/// <20>л<EFBFBD><D0BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͸״̬
/// </summary>
public static void SetClickThrough(Window window, bool enable)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
if (enable)
{
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TRANSPARENT | WS_EX_LAYERED);
_clickThroughStates[hwnd] = true;
}
else
{
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TRANSPARENT);
_clickThroughStates[hwnd] = false;
}
}
#region Win32
private const int GWL_EXSTYLE = -20;
private const int WS_EX_TRANSPARENT = 0x00000020;
private const int WS_EX_LAYERED = 0x00080000;
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
#endregion
}
}

View File

@@ -11,7 +11,7 @@ using WinUIEx;
namespace BetterLyrics.WinUI3.Helper
{
public static class DockHelper
public static class DockModeHelper
{
private static readonly HashSet<IntPtr> _registered = [];

View File

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

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -8,8 +10,18 @@ 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);
@@ -23,5 +35,7 @@ namespace BetterLyrics.WinUI3.Helper
}
return Encoding.GetEncoding(encoding);
}
#endregion
}
}

View File

@@ -1,16 +1,18 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
@@ -18,36 +20,36 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="ImageHelper" />
/// </summary>
public class ImageHelper
{
private static readonly ColorThief _colorThief = new();
#region Constants
/// <summary>
/// Defines the AccentColorCount
/// </summary>
public const int AccentColorCount = 3;
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(
byte[] imageBytes
)
{
if (imageBytes == null || imageBytes.Length == 0)
return null;
#endregion
InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(imageBytes.AsBuffer());
#region Fields
return stream;
}
/// <summary>
/// Defines the _colorThief
/// </summary>
private static readonly ColorThief _colorThief = new();
public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
{
var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(imageBytes.AsBuffer());
stream.Seek(0);
#endregion
var bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(stream);
return bitmapImage;
}
#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();
@@ -57,26 +59,13 @@ namespace BetterLyrics.WinUI3.Helper
return stream;
}
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
{
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
using var memoryStream = new MemoryStream();
await stream.AsStreamForRead().CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
public static async Task<List<Color>> GetAccentColorsFromByte(byte[] bytes) =>
[
.. (
await _colorThief.GetPalette(await GetDecoderFromByte(bytes), AccentColorCount)
).Select(color =>
Color.FromArgb(color.Color.A, color.Color.R, color.Color.G, color.Color.B)
),
];
public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
/// <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,
@@ -152,5 +141,77 @@ namespace BetterLyrics.WinUI3.Helper
return buffer;
}
}
/// <summary>
/// The GetAccentColorsFromByte
/// </summary>
/// <param name="bytes">The bytes<see cref="byte[]"/></param>
/// <returns>The <see cref="Task{List{Color}}"/></returns>
public static async Task<List<Color>> GetAccentColorsFromByte(byte[] bytes) =>
[
.. (
await _colorThief.GetPalette(await GetDecoderFromByte(bytes), AccentColorCount)
).Select(color =>
Color.FromArgb(color.Color.A, color.Color.R, color.Color.G, color.Color.B)
),
];
/// <summary>
/// The GetBitmapImageFromBytesAsync
/// </summary>
/// <param name="imageBytes">The imageBytes<see cref="byte[]"/></param>
/// <returns>The <see cref="Task{BitmapImage}"/></returns>
public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
{
var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(imageBytes.AsBuffer());
stream.Seek(0);
var bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(stream);
return bitmapImage;
}
/// <summary>
/// The GetDecoderFromByte
/// </summary>
/// <param name="bytes">The bytes<see cref="byte[]"/></param>
/// <returns>The <see cref="Task{BitmapDecoder}"/></returns>
public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
/// <summary>
/// The GetStreamFromBytesAsync
/// </summary>
/// <param name="imageBytes">The imageBytes<see cref="byte[]"/></param>
/// <returns>The <see cref="Task{InMemoryRandomAccessStream}"/></returns>
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(
byte[] imageBytes
)
{
if (imageBytes == null || imageBytes.Length == 0)
return null;
InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(imageBytes.AsBuffer());
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();
using var memoryStream = new MemoryStream();
await stream.AsStreamForRead().CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
#endregion
}
}

View File

@@ -1,18 +1,42 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using Lyricify.Lyrics.Models;
using Microsoft.UI.Xaml.Shapes;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="LyricsParser" />
/// </summary>
public class LyricsParser
{
#region Fields
/// <summary>
/// Defines the _multiLangLyricsLines
/// </summary>
private List<List<LyricsLine>> _multiLangLyricsLines = [];
#endregion
#region Methods
/// <summary>
/// The Parse
/// </summary>
/// <param name="raw">The raw<see cref="string"/></param>
/// <param name="lyricsFormat">The lyricsFormat<see cref="LyricsFormat?"/></param>
/// <param name="title">The title<see cref="string?"/></param>
/// <param name="artist">The artist<see cref="string?"/></param>
/// <param name="durationMs">The durationMs<see cref="int"/></param>
/// <returns>The <see cref="List{List{LyricsLine}}"/></returns>
public List<List<LyricsLine>> Parse(
string raw,
LyricsFormat? lyricsFormat = null,
@@ -28,6 +52,18 @@ namespace BetterLyrics.WinUI3.Helper
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;
@@ -37,26 +73,14 @@ namespace BetterLyrics.WinUI3.Helper
return _multiLangLyricsLines;
}
private void PostProcessLyricsLines(List<LyricsLine> lines)
{
if (lines.Count > 0 && lines[0].StartMs > 0)
{
lines.Insert(
0,
new LyricsLine
{
StartMs = 0,
EndMs = lines[0].StartMs,
Text = "",
CharTimings = [],
}
);
}
}
/// <summary>
/// The ParseLrc
/// </summary>
/// <param name="raw">The raw<see cref="string"/></param>
/// <param name="durationMs">The durationMs<see cref="int"/></param>
private void ParseLrc(string raw, int durationMs)
{
var lines = raw.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
var lines = raw.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
var lrcLines =
new List<(int time, string text, List<(int time, string text)> syllables)>();
@@ -104,7 +128,7 @@ namespace BetterLyrics.WinUI3.Helper
int sec = int.Parse(m.Groups[2].Value);
int ms = int.Parse(m.Groups[3].Value.PadRight(3, '0'));
lineStartTime = min * 60_000 + sec * 1000 + ms;
content = bracketRegex.Replace(line, "").Trim();
content = bracketRegex.Replace(line, "");
lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
}
}
@@ -137,13 +161,21 @@ namespace BetterLyrics.WinUI3.Helper
};
if (syllables != null && syllables.Count > 0)
{
int currentIndex = 0;
for (int j = 0; j < syllables.Count; j++)
{
var (charStart, charText) = syllables[j];
int charEnd = (j + 1 < syllables.Count) ? syllables[j + 1].Item1 : 0;
int startIndex = currentIndex;
line.CharTimings.Add(
new CharTiming { StartMs = charStart, EndMs = charEnd }
new CharTiming
{
StartMs = charStart,
EndMs = 0, // Fixed later
Text = charText ?? "",
StartIndex = startIndex,
}
);
currentIndex += charText?.Length ?? 0;
}
}
_multiLangLyricsLines[langIdx].Add(line);
@@ -157,20 +189,28 @@ namespace BetterLyrics.WinUI3.Helper
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
// 修正 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;
}
}
}
}
@@ -178,6 +218,74 @@ namespace BetterLyrics.WinUI3.Helper
}
}
private void ParseUsingLyricify(List<ILineInfo>? lines, int durationMs)
{
List<LyricsLine> lyricsLines = [];
if (lines != null && lines.Count > 0)
{
lyricsLines = [];
for (int lineIndex = 0; lineIndex < lines.Count; lineIndex++)
{
var lineRead = lines[lineIndex];
var lineWrite = new LyricsLine
{
StartMs = lineRead.StartTime ?? 0,
Text = lineRead.Text,
CharTimings = [],
};
if (lineIndex + 1 < lines.Count)
{
lineWrite.EndMs = lines[lineIndex + 1].StartTime ?? 0;
}
else
{
lineWrite.EndMs = durationMs;
}
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
if (syllables != null)
{
int startIndex = 0;
for (
int syllableIndex = 0;
syllableIndex < syllables.Count;
syllableIndex++
)
{
var syllable = syllables[syllableIndex];
var charTiming = new CharTiming
{
StartMs = syllable.StartTime,
Text = syllable.Text,
StartIndex = startIndex,
};
if (syllableIndex + 1 < syllables.Count)
{
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
}
else
{
charTiming.EndMs = lineWrite.EndMs;
}
lineWrite.CharTimings.Add(charTiming);
startIndex += syllable.Text.Length;
}
}
lyricsLines.Add(lineWrite);
}
}
_multiLangLyricsLines.Add(lyricsLines);
}
/// <summary>
/// The ParseTtml
/// </summary>
/// <param name="raw">The raw<see cref="string"/></param>
/// <param name="durationMs">The durationMs<see cref="int"/></param>
private void ParseTtml(string raw, int durationMs)
{
try
@@ -205,7 +313,7 @@ namespace BetterLyrics.WinUI3.Helper
)
.ToList();
string text = string.Concat(spans.Select(s => s.Value));
string text = string.Concat(spans.Select(s => s));
var charTimings = new List<CharTiming>();
for (int i = 0; i < spans.Count; i++)
@@ -229,7 +337,7 @@ namespace BetterLyrics.WinUI3.Helper
}
if (spans.Count == 0)
text = p.Value.Trim();
text = p.Value;
singleLangLyricsLine.Add(
new LyricsLine
@@ -250,6 +358,11 @@ 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))
@@ -310,5 +423,28 @@ 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)
{
if (lines.Count > 0 && lines[0].StartMs > 0)
{
lines.Insert(
0,
new LyricsLine
{
StartMs = 0,
EndMs = lines[0].StartMs,
Text = "● ● ●",
CharTimings = [],
}
);
}
}
#endregion
}
}

View File

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

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using BetterLyrics.WinUI3.Views;
using Microsoft.UI.Windowing;
@@ -8,16 +10,91 @@ using WinUIEx;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// Defines the <see cref="WindowHelper" />
/// </summary>
public static class WindowHelper
{
#region Fields
/// <summary>
/// Defines the _windowCache
/// </summary>
private static readonly Dictionary<Type, Window> _windowCache = new();
/// <summary>
/// Defines the _activeWindows
/// </summary>
private static List<Window> _activeWindows = new List<Window>();
#endregion
#region Properties
/// <summary>
/// Gets the ActiveWindows
/// </summary>
public static List<Window> ActiveWindows
{
get { return _activeWindows; }
}
#endregion
#region Methods
/// <summary>
/// The GetWindowByFramePageType
/// </summary>
/// <param name="type">The type<see cref="Type"/></param>
/// <returns>The <see cref="Window"/></returns>
public static Window GetWindowByFramePageType(Type type)
{
foreach (var cachedWindow in _windowCache)
{
if (cachedWindow.Key == type)
{
return cachedWindow.Value;
}
}
return null;
}
/// <summary>
/// The GetWindowForElement
/// </summary>
/// <param name="element">The element<see cref="UIElement"/></param>
/// <returns>The <see cref="Window"/></returns>
public static Window GetWindowForElement(UIElement element)
{
if (element.XamlRoot != null)
{
foreach (Window window in _activeWindows)
{
if (element.XamlRoot == window.Content.XamlRoot)
{
return window;
}
}
}
return null;
}
/// <summary>
/// The HideSystemTitleBar
/// </summary>
/// <param name="window">The window<see cref="Window"/></param>
public static void HideSystemTitleBar(this Window window)
{
window.ExtendsContentIntoTitleBar = true;
window.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
}
/// <summary>
/// The HideSystemTitleBarAndSetCustomTitleBar
/// </summary>
/// <param name="window">The window<see cref="Window"/></param>
/// <param name="titleBar">The titleBar<see cref="UIElement"/></param>
public static void HideSystemTitleBarAndSetCustomTitleBar(
this Window window,
UIElement titleBar
@@ -27,16 +104,87 @@ namespace BetterLyrics.WinUI3.Helper
window.SetTitleBar(titleBar);
}
public static void OpenSettingsWindow()
{
OpenOrShowWindow(typeof(SettingsPage));
}
/// <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))
@@ -53,33 +201,13 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public static void TrackWindow(Window window, Type pageType = null)
{
if (pageType != null)
{
_windowCache[pageType] = window;
}
if (!_activeWindows.Contains(window))
_activeWindows.Add(window);
}
public static Window GetWindowForElement(UIElement element)
{
if (element.XamlRoot != null)
{
foreach (Window window in _activeWindows)
{
if (element.XamlRoot == window.Content.XamlRoot)
{
return window;
}
}
}
return null;
}
// 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)
@@ -95,39 +223,6 @@ namespace BetterLyrics.WinUI3.Helper
return 0.0;
}
public static List<Window> ActiveWindows
{
get { return _activeWindows; }
}
private static List<Window> _activeWindows = new List<Window>();
public static void TryShow(this Window window)
{
if (window is not null)
{
window.Activate();
}
}
public static void TryHide(this Window window)
{
if (window is not null)
{
window.Hide();
}
}
public static Window GetWindowByFramePageType(Type type)
{
foreach (var cachedWindow in _windowCache)
{
if (cachedWindow.Key == type)
{
return cachedWindow.Value;
}
}
return null;
}
#endregion
}
}

View File

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

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,9 +8,27 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="CharTiming" />
/// </summary>
public class CharTiming
{
public int StartMs { get; set; }
#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
}
}

View File

@@ -1,26 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="LocalLyricsFolder" />
/// </summary>
public partial class LocalLyricsFolder : ObservableObject
{
[ObservableProperty]
public partial string Path { get; set; }
#region Constructors
[ObservableProperty]
public partial bool IsEnabled { get; set; }
public LocalLyricsFolder() { }
/// <summary>
/// Initializes a new instance of the <see cref="LocalLyricsFolder"/> class.
/// </summary>
public LocalLyricsFolder()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LocalLyricsFolder"/> class.
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
/// <param name="isEnabled">The isEnabled<see cref="bool"/></param>
public LocalLyricsFolder(string path, bool isEnabled)
{
Path = path;
IsEnabled = isEnabled;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether IsEnabled
/// </summary>
[ObservableProperty]
public partial bool IsEnabled { get; set; }
/// <summary>
/// Gets or sets the Path
/// </summary>
[ObservableProperty]
public partial string Path { get; set; }
#endregion
}
}

View File

@@ -1,16 +1,31 @@
using System;
// 2025/6/23 by Zhe Fang
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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

@@ -1,54 +1,74 @@
using System.Collections.Generic;
// 2025/6/23 by Zhe Fang
using System.Collections.Generic;
using System.Numerics;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas.Text;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="LyricsLine" />
/// </summary>
public class LyricsLine
{
public string Text { get; set; } = "";
#region Properties
public List<CharTiming> CharTimings { get; set; } = [];
/// <summary>
/// Gets or sets the BlurAmountTransition
/// </summary>
public ValueTransition<float> BlurAmountTransition { get; set; } =
new(initialValue: 0f, durationSeconds: 0.3f);
public int StartMs { get; set; }
public int EndMs { get; set; }
public LyricsPlayingState PlayingState { get; set; }
public int DurationMs => EndMs - StartMs;
public float EnteringProgress { get; set; }
public float ExitingProgress { get; set; }
public float PlayingProgress { get; set; }
public Vector2 Position { get; set; }
/// <summary>
/// Gets or sets the CanvasTextLayout
/// </summary>
public CanvasTextLayout? CanvasTextLayout { get; set; }
/// <summary>
/// Gets or sets the CenterPosition
/// </summary>
public Vector2 CenterPosition { get; set; }
public float Scale { get; set; }
/// <summary>
/// Gets or sets the CharTimings
/// </summary>
public List<CharTiming> CharTimings { get; set; } = [];
public float Opacity { get; set; }
/// <summary>
/// Gets the DurationMs
/// </summary>
public int DurationMs => EndMs - StartMs;
public LyricsLine Clone()
{
return new LyricsLine
{
Text = this.Text,
CharTimings = this.CharTimings,
StartMs = this.StartMs,
EndMs = this.EndMs,
PlayingState = this.PlayingState,
EnteringProgress = this.EnteringProgress,
ExitingProgress = this.ExitingProgress,
PlayingProgress = this.PlayingProgress,
Position = this.Position,
CenterPosition = this.CenterPosition,
Scale = this.Scale,
Opacity = this.Opacity,
};
}
/// <summary>
/// Gets or sets the EndMs
/// </summary>
public int EndMs { get; set; }
public ValueTransition<float> HighlightOpacityTransition { get; set; } =
new(initialValue: 0f, durationSeconds: 0.3f);
/// <summary>
/// Gets or sets the Position
/// </summary>
public Vector2 Position { get; set; }
/// <summary>
/// Gets or sets the ScaleTransition
/// </summary>
public ValueTransition<float> ScaleTransition { get; set; } =
new(initialValue: 0.95f, durationSeconds: 0.3f);
/// <summary>
/// Gets or sets the StartMs
/// </summary>
public int StartMs { get; set; }
/// <summary>
/// Gets or sets the Text
/// </summary>
public string Text { get; set; } = "";
#endregion
}
}

View File

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

View File

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

View File

@@ -1,29 +1,63 @@
using CommunityToolkit.Mvvm.ComponentModel;
// 2025/6/23 by Zhe Fang
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
/// <summary>
/// Defines the <see cref="SongInfo" />
/// </summary>
public partial class SongInfo : ObservableObject
{
[ObservableProperty]
public partial string Title { get; set; }
#region Constructors
[ObservableProperty]
public partial string Artist { get; set; }
/// <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;
public byte[]? AlbumArt { get; set; } = null;
/// <summary>
/// Gets or sets the Title
/// </summary>
[ObservableProperty]
public partial string Title { get; set; }
public SongInfo() { }
#endregion
}
}

View File

@@ -13,7 +13,6 @@
<canvas:CanvasAnimatedControl
x:Name="LyricsCanvas"
Draw="LyricsCanvas_Draw"
Loaded="LyricsCanvas_Loaded"
Update="LyricsCanvas_Update" />
</Grid>
</UserControl>

View File

@@ -1,3 +1,5 @@
// 2025/6/23 by Zhe Fang
using System.Diagnostics;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
@@ -7,18 +9,45 @@ 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
{
public LyricsRendererViewModel ViewModel { get; set; }
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LyricsRenderer"/> class.
/// </summary>
public LyricsRenderer()
{
InitializeComponent();
ViewModel = Ioc.Default.GetRequiredService<LyricsRendererViewModel>();
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the ViewModel
/// </summary>
public LyricsRendererViewModel ViewModel { get; set; }
#endregion
#region Methods
/// <summary>
/// The LyricsCanvas_Draw
/// </summary>
/// <param name="sender">The sender<see cref="Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl"/></param>
/// <param name="args">The args<see cref="Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs"/></param>
private void LyricsCanvas_Draw(
Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender,
Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args
@@ -27,6 +56,11 @@ namespace BetterLyrics.WinUI3.Renderer
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
@@ -35,9 +69,6 @@ namespace BetterLyrics.WinUI3.Renderer
ViewModel.Update(sender, args);
}
private void LyricsCanvas_Loaded(object sender, RoutedEventArgs e)
{
ViewModel.RequestRelayout();
}
#endregion
}
}

View File

@@ -1,18 +1,21 @@
using System;
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Models;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
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,16 +1,41 @@
using System;
// 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,11 +1,37 @@
using System.Collections.Generic;
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
{
#region Interfaces
/// <summary>
/// Defines the <see cref="IMusicSearchService" />
/// </summary>
public interface IMusicSearchService
{
#region Methods
/// <summary>
/// The SearchAlbumArtAsync
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <returns>The <see cref="byte[]?"/></returns>
byte[]? SearchAlbumArtAsync(string title, string artist);
/// <summary>
/// The SearchLyricsAsync
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="album">The album<see cref="string"/></param>
/// <param name="durationMs">The durationMs<see cref="double"/></param>
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
/// <returns>The <see cref="Task{(string?, LyricsFormat?)}"/></returns>
Task<(string?, LyricsFormat?)> SearchLyricsAsync(
string title,
string artist,
@@ -14,6 +40,8 @@ namespace BetterLyrics.WinUI3.Services
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
);
byte[]? SearchAlbumArtAsync(string title, string artist);
#endregion
}
#endregion
}

View File

@@ -1,17 +1,56 @@
using System;
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
using System;
namespace BetterLyrics.WinUI3.Services
{
#region Interfaces
/// <summary>
/// Defines the <see cref="IPlaybackService" />
/// </summary>
public interface IPlaybackService
{
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
#region Events
/// <summary>
/// Defines the IsPlayingChanged
/// </summary>
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
/// <summary>
/// Defines the PositionChanged
/// </summary>
event EventHandler<PositionChangedEventArgs>? PositionChanged;
SongInfo? SongInfo { get; }
/// <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

@@ -1,4 +1,6 @@
using System.Collections.Generic;
// 2025/6/23 by Zhe Fang
using System.Collections.Generic;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using Microsoft.UI.Text;
@@ -7,42 +9,139 @@ using Windows.UI.Text;
namespace BetterLyrics.WinUI3.Services
{
#region Interfaces
/// <summary>
/// Defines the <see cref="ISettingsService" />
/// </summary>
public interface ISettingsService
{
bool IsFirstRun { get; set; }
// Lyrics lib
List<LocalLyricsFolder> LocalLyricsFolders { get; set; }
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
// App appearance
ElementTheme ThemeType { get; set; }
BackdropType BackdropType { get; set; }
TitleBarType TitleBarType { get; set; }
Language Language { get; set; }
#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; }
bool IsDynamicCoverOverlayEnabled { get; set; }
int CoverOverlayOpacity { get; set; }
int CoverOverlayBlurAmount { get; set; }
// Album art cover style
int CoverImageRadius { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
bool IsDynamicCoverOverlayEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsFirstRun
/// </summary>
bool IsFirstRun { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
/// </summary>
bool IsLyricsGlowEffectEnabled { get; set; }
/// <summary>
/// Gets or sets the Language
/// </summary>
Language Language { get; set; }
// Lyrics lib
/// <summary>
/// Gets or sets the LocalLyricsFolders
/// </summary>
List<LocalLyricsFolder> LocalLyricsFolders { get; set; }
// Lyrics style and effetc
/// <summary>
/// Gets or sets the LyricsAlignmentType
/// </summary>
LyricsAlignmentType LyricsAlignmentType { get; set; }
LyricsFontWeight LyricsFontWeight { get; set; }
/// <summary>
/// Gets or sets the LyricsBlurAmount
/// </summary>
int LyricsBlurAmount { get; set; }
int LyricsVerticalEdgeOpacity { get; set; }
float LyricsLineSpacingFactor { get; set; }
int LyricsFontSize { get; set; }
bool IsLyricsGlowEffectEnabled { get; set; }
LyricsGlowEffectScope LyricsGlowEffectScope { get; set; }
/// <summary>
/// Gets or sets the LyricsFontColorType
/// </summary>
LyricsFontColorType LyricsFontColorType { get; set; }
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
int LyricsFontSize { get; set; }
/// <summary>
/// Gets or sets the LyricsFontWeight
/// </summary>
LyricsFontWeight LyricsFontWeight { get; set; }
/// <summary>
/// Gets or sets the LyricsGlowEffectScope
/// </summary>
LineRenderingType LyricsGlowEffectScope { get; set; }
/// <summary>
/// Gets or sets the LyricsLineSpacingFactor
/// </summary>
float LyricsLineSpacingFactor { get; set; }
/// <summary>
/// Gets or sets the LyricsSearchProvidersInfo
/// </summary>
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
/// <summary>
/// Gets or sets the LyricsVerticalEdgeOpacity
/// </summary>
int LyricsVerticalEdgeOpacity { get; set; }
// App appearance
/// <summary>
/// Gets or sets the ThemeType
/// </summary>
ElementTheme ThemeType { get; set; }
/// <summary>
/// Gets or sets the TitleBarType
/// </summary>
TitleBarType TitleBarType { get; set; }
#endregion
}
#endregion
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,28 +8,75 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
{
using global::BetterLyrics.WinUI3.Events;
using global::BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using global::BetterLyrics.WinUI3.Events;
using global::BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Services
{
/// <summary>
/// Defines the <see cref="LibWatcherService" />
/// </summary>
public class LibWatcherService : IDisposable, ILibWatcherService
{
#region Fields
/// <summary>
/// Defines the _settingsService
/// </summary>
private readonly ISettingsService _settingsService;
/// <summary>
/// Defines the _watchers
/// </summary>
private readonly Dictionary<string, FileSystemWatcher> _watchers = [];
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LibWatcherService"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
public LibWatcherService(ISettingsService settingsService)
{
_settingsService = settingsService;
UpdateWatchers(_settingsService.LocalLyricsFolders);
}
#endregion
#region Events
/// <summary>
/// Defines the MusicLibraryFilesChanged
/// </summary>
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
#endregion
#region Methods
/// <summary>
/// The Dispose
/// </summary>
public void Dispose()
{
foreach (var watcher in _watchers.Values)
{
watcher.Dispose();
}
_watchers.Clear();
}
/// <summary>
/// The UpdateWatchers
/// </summary>
/// <param name="folders">The folders<see cref="List{LocalLyricsFolder}"/></param>
public void UpdateWatchers(List<LocalLyricsFolder> folders)
{
// 移除不再监听的
@@ -63,6 +112,11 @@ namespace BetterLyrics.WinUI3.Services
}
}
/// <summary>
/// The OnChanged
/// </summary>
/// <param name="folder">The folder<see cref="string"/></param>
/// <param name="e">The e<see cref="FileSystemEventArgs"/></param>
private void OnChanged(string folder, FileSystemEventArgs e)
{
App.DispatcherQueue!.TryEnqueue(
@@ -77,14 +131,7 @@ namespace BetterLyrics.WinUI3.Services
);
}
public void Dispose()
{
foreach (var watcher in _watchers.Values)
{
watcher.Dispose();
}
_watchers.Clear();
}
#endregion
}
}
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -13,21 +15,54 @@ using Windows.Storage.FileProperties;
namespace BetterLyrics.WinUI3.Services
{
/// <summary>
/// Defines the <see cref="MusicSearchService" />
/// </summary>
public class MusicSearchService : IMusicSearchService
{
private readonly HttpClient _httpClient;
#region Fields
/// <summary>
/// Defines the _httpClient
/// </summary>
private readonly HttpClient _lrcLibHttpClient;
private readonly HttpClient _amllTtmlDbHttpClient;
/// <summary>
/// Defines the _settingsService
/// </summary>
private readonly ISettingsService _settingsService;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MusicSearchService"/> class.
/// </summary>
/// <param name="settingsService">The settingsService<see cref="ISettingsService"/></param>
public MusicSearchService(ISettingsService settingsService)
{
_settingsService = settingsService;
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add(
_lrcLibHttpClient = new HttpClient();
_lrcLibHttpClient.DefaultRequestHeaders.Add(
"User-Agent",
$"{AppInfo.AppName} {AppInfo.AppVersion} ({AppInfo.GithubUrl})"
);
_amllTtmlDbHttpClient = new HttpClient();
}
#endregion
#region Methods
/// <summary>
/// The SearchAlbumArtAsync
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <returns>The <see cref="byte[]?"/></returns>
public byte[]? SearchAlbumArtAsync(string title, string artist)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
@@ -42,7 +77,7 @@ namespace BetterLyrics.WinUI3.Services
)
)
{
if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
Track track = new(file);
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
@@ -58,6 +93,15 @@ 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,
@@ -73,167 +117,143 @@ namespace BetterLyrics.WinUI3.Services
continue;
}
switch (provider.Provider)
string? cachedLyrics;
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
// Check cache first
if (provider.Provider.IsRemote())
{
case LyricsSearchProvider.LrcLib:
// Check cache first
var cachedLyrics = ReadCache(title, artist, LyricsFormat.Lrc);
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
return (cachedLyrics, LyricsFormat.Lrc);
}
break;
default:
break;
cachedLyrics = ReadCache(
title,
artist,
lyricsFormat,
provider.Provider.GetCacheDirectory()
);
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
return (cachedLyrics, lyricsFormat);
}
}
string? searchedLyrics = null;
switch (provider.Provider)
if (provider.Provider.IsLocal())
{
case LyricsSearchProvider.LocalMusicFile:
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
{
searchedLyrics = LocalLyricsSearchInMusicFiles(title, artist);
break;
case LyricsSearchProvider.LocalLrcFile:
}
else
{
searchedLyrics = await LocalLyricsSearchInLyricsFiles(
title,
artist,
LyricsFormat.Lrc
lyricsFormat
);
break;
case LyricsSearchProvider.LocalEslrcFile:
searchedLyrics = await LocalLyricsSearchInLyricsFiles(
title,
artist,
LyricsFormat.Eslrc
);
break;
case LyricsSearchProvider.LocalTtmlFile:
searchedLyrics = await LocalLyricsSearchInLyricsFiles(
title,
artist,
LyricsFormat.Ttml
);
break;
case LyricsSearchProvider.LrcLib:
searchedLyrics = await SearchLrcLib(
title,
artist,
album,
(int)(durationMs / 1000),
matchMode
);
break;
default:
break;
}
}
if (!string.IsNullOrWhiteSpace(searchedLyrics))
else
{
switch (provider.Provider)
{
case LyricsSearchProvider.LrcLib:
WriteCache(title, artist, searchedLyrics, LyricsFormat.Lrc);
return (searchedLyrics, LyricsFormat.Lrc);
case LyricsSearchProvider.LocalMusicFile:
return (searchedLyrics, LyricsFormatExtensions.Detect(searchedLyrics));
case LyricsSearchProvider.LocalLrcFile:
return (searchedLyrics, LyricsFormat.Lrc);
case LyricsSearchProvider.LocalEslrcFile:
return (searchedLyrics, LyricsFormat.Eslrc);
case LyricsSearchProvider.LocalTtmlFile:
return (searchedLyrics, LyricsFormat.Ttml);
searchedLyrics = await SearchLrcLibAsync(
title,
artist,
album,
(int)(durationMs / 1000),
matchMode
);
break;
case LyricsSearchProvider.QQ:
searchedLyrics = await SearchQQAsync(
title,
artist,
album,
(int)durationMs,
matchMode
);
break;
case LyricsSearchProvider.Kugou:
searchedLyrics = await SearchKugouAsync(
title,
artist,
album,
(int)durationMs,
matchMode
);
break;
case LyricsSearchProvider.Netease:
searchedLyrics = await SearchNeteaseAsync(
title,
artist,
album,
(int)durationMs,
matchMode
);
break;
case LyricsSearchProvider.AmllTtmlDb:
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
break;
default:
break;
}
}
if (!string.IsNullOrWhiteSpace(searchedLyrics))
{
if (provider.Provider.IsRemote())
{
WriteCache(
title,
artist,
searchedLyrics,
lyricsFormat,
provider.Provider.GetCacheDirectory()
);
}
return (
searchedLyrics,
lyricsFormat == LyricsFormat.NotSpecified
? searchedLyrics.DetectFormat()
: lyricsFormat
);
}
}
return (null, null);
}
private static int LevenshteinDistance(string a, string b)
private static bool MusicMatch(string fileName, string title, string artist)
{
if (string.IsNullOrEmpty(a))
return b.Length;
if (string.IsNullOrEmpty(b))
return a.Length;
int[,] d = new int[a.Length + 1, b.Length + 1];
for (int i = 0; i <= a.Length; i++)
d[i, 0] = i;
for (int j = 0; j <= b.Length; j++)
d[0, j] = j;
for (int i = 1; i <= a.Length; i++)
for (int j = 1; j <= b.Length; j++)
d[i, j] = Math.Min(
Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
d[i - 1, j - 1] + (a[i - 1] == b[j - 1] ? 0 : 1)
);
return d[a.Length, b.Length];
return fileName.Contains(title) && fileName.Contains(artist);
}
// 判断相似度
private static bool FuzzyMatch(string fileName, string title, string 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 normFile = Normalize(fileName);
var normTarget1 = Normalize(title + artist);
var normTarget2 = Normalize(artist + title);
int dist1 = LevenshteinDistance(normFile, normTarget1);
int dist2 = LevenshteinDistance(normFile, normTarget2);
return dist1 <= 3 || dist2 <= 3; // 阈值可调整
}
private static string Normalize(string s)
{
if (string.IsNullOrWhiteSpace(s))
return "";
var sb = new StringBuilder();
foreach (var c in s.ToLowerInvariant())
var invalidChars = Path.GetInvalidFileNameChars();
var sb = new StringBuilder(fileName.Length);
foreach (var c in fileName)
{
if (char.IsLetterOrDigit(c))
sb.Append(c);
sb.Append(Array.IndexOf(invalidChars, c) >= 0 ? replacement : c);
}
return sb.ToString();
}
private string? LocalLyricsSearchInMusicFiles(string title, string artist)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (
var file in Directory.GetFiles(
folder.Path,
$"*.*",
SearchOption.AllDirectories
)
)
{
if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
//Track track = new(file);
//var plain = track.Lyrics.UnsynchronizedLyrics;
try
{
var plain = TagLib.File.Create(file).Tag.Lyrics;
if (plain != null && plain != string.Empty)
{
return plain;
}
}
catch (Exception) { }
}
}
}
}
return null;
}
/// <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,
@@ -252,7 +272,7 @@ namespace BetterLyrics.WinUI3.Services
)
)
{
if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
string? raw = await File.ReadAllTextAsync(
file,
@@ -269,7 +289,89 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
private async Task<string?> SearchLrcLib(
/// <summary>
/// The LocalLyricsSearchInMusicFiles
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <returns>The <see cref="string?"/></returns>
private string? LocalLyricsSearchInMusicFiles(string title, string artist)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (
var file in Directory.GetFiles(
folder.Path,
$"*.*",
SearchOption.AllDirectories
)
)
{
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
//Track track = new(file);
//var test1 = track.Lyrics.SynchronizedLyrics;
//var test2 = track.Lyrics.UnsynchronizedLyrics;
try
{
var plain = TagLib.File.Create(file).Tag.Lyrics;
if (plain != null && plain != string.Empty)
{
return plain;
}
}
catch (Exception e)
{
throw e;
}
}
}
}
}
return null;
}
/// <summary>
/// The ReadCache
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="format">The format<see cref="LyricsFormat"/></param>
/// <returns>The <see cref="string?"/></returns>
private string? ReadCache(
string title,
string artist,
LyricsFormat format,
string cacheFolderPath
)
{
var safeArtist = SanitizeFileName(artist);
var safeTitle = SanitizeFileName(title);
var cacheFilePath = Path.Combine(
cacheFolderPath,
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
);
if (File.Exists(cacheFilePath))
{
return File.ReadAllText(cacheFilePath);
}
return null;
}
/// <summary>
/// The SearchLrcLib
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="album">The album<see cref="string"/></param>
/// <param name="duration">The duration<see cref="int"/></param>
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
/// <returns>The <see cref="Task{string?}"/></returns>
private async Task<string?> SearchLrcLibAsync(
string title,
string artist,
string album,
@@ -290,7 +392,7 @@ namespace BetterLyrics.WinUI3.Services
+ $"&durationMs={Uri.EscapeDataString(duration.ToString())}";
}
var response = await _httpClient.GetAsync(url);
var response = await _lrcLibHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
return null;
@@ -314,41 +416,256 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
private void WriteCache(string title, string artist, string lyrics, LyricsFormat format)
private async Task<string?> SearchQQAsync(
string title,
string artist,
string album,
int durationMs,
MusicSearchMatchMode matchMode
)
{
string? queryId = (
(
await new Lyricify.Lyrics.Searchers.QQMusicSearcher().SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? durationMs
: null,
Album =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? album
: null,
AlbumArtists = [artist],
Artists = [artist],
Title = title,
}
)
) as Lyricify.Lyrics.Searchers.QQMusicSearchResult
)?.Id;
if (queryId is string id)
{
return (await Lyricify.Lyrics.Decrypter.Qrc.Helper.GetLyricsAsync(id))?.Lyrics;
}
return null;
}
private async Task<string?> SearchKugouAsync(
string title,
string artist,
string album,
int durationMs,
MusicSearchMatchMode matchMode
)
{
string? queryHash = (
(
await new Lyricify.Lyrics.Searchers.KugouSearcher().SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? durationMs
: null,
Album =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? album
: null,
AlbumArtists = [artist],
Artists = [artist],
Title = title,
}
)
) as Lyricify.Lyrics.Searchers.KugouSearchResult
)?.Hash;
if (queryHash != null)
{
var candidate = (
await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(
hash: queryHash
)
)?.Candidates.FirstOrDefault();
if (candidate != null)
{
return await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(
candidate.Id,
candidate.AccessKey
);
}
}
return null;
}
private async Task<string?> SearchNeteaseAsync(
string title,
string artist,
string album,
int durationMs,
MusicSearchMatchMode matchMode
)
{
string? queryId = (
(
await new Lyricify.Lyrics.Searchers.NeteaseSearcher().SearchForResult(
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
{
DurationMs =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? durationMs
: null,
Album =
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
? album
: null,
AlbumArtists = [artist],
Artists = [artist],
Title = title,
}
)
) as Lyricify.Lyrics.Searchers.NeteaseSearchResult
)?.Id;
if (queryId != null)
{
return (await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(queryId))
?.Lrc
.Lyric;
}
return null;
}
/// <summary>
/// 本地检索 amll-ttml-db 索引并下载歌词内容
/// </summary>
/// <param name="title">歌曲名</param>
/// <param name="artist">歌手名</param>
/// <returns>歌词内容字符串,找不到返回 null</returns>
private async Task<string?> SearchAmllTtmlDbAsync(string title, string artist)
{
// 检索本地 JSONL 索引文件,查找 rawLyricFile
if (!File.Exists(AppInfo.AmllTtmlDbIndexPath))
{
var downloadOk = await DownloadAmllTtmlDbIndexAsync();
if (!downloadOk || !File.Exists(AppInfo.AmllTtmlDbIndexPath))
return null;
}
string? rawLyricFile = null;
foreach (var line in File.ReadLines(AppInfo.AmllTtmlDbIndexPath))
{
if (string.IsNullOrWhiteSpace(line))
continue;
try
{
using var doc = JsonDocument.Parse(line);
var root = doc.RootElement;
if (!root.TryGetProperty("metadata", out var metadataArr))
continue;
string? musicName = null;
string? artists = null;
foreach (var meta in metadataArr.EnumerateArray())
{
if (meta.GetArrayLength() != 2)
continue;
var key = meta[0].GetString();
var valueArr = meta[1];
if (key == "musicName" && valueArr.GetArrayLength() > 0)
musicName = valueArr[0].GetString();
if (key == "artists" && valueArr.GetArrayLength() > 0)
artists = valueArr[0].GetString();
}
if (musicName == null || artists == null)
continue;
if (MusicMatch($"{artists} - {musicName}", title, artist))
{
if (root.TryGetProperty("rawLyricFile", out var rawLyricFileProp))
{
rawLyricFile = rawLyricFileProp.GetString();
break;
}
}
}
catch { }
}
if (string.IsNullOrWhiteSpace(rawLyricFile))
return null;
// 下载歌词内容
var url =
$"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
try
{
var response = await _amllTtmlDbHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
return null;
return await response.Content.ReadAsStringAsync();
}
catch
{
return null;
}
}
/// <summary>
/// 下载 amll-ttml-db 的 JSONL 索引文件到本地缓存目录
/// </summary>
/// <returns>下载成功返回 true否则 false</returns>
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
{
const string url =
"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
try
{
using var response = await _amllTtmlDbHttpClient.GetAsync(
url,
HttpCompletionOption.ResponseHeadersRead
);
if (!response.IsSuccessStatusCode)
return false;
await using var stream = await response.Content.ReadAsStreamAsync();
await using var fs = new FileStream(
AppInfo.AmllTtmlDbIndexPath,
FileMode.Create,
FileAccess.Write,
FileShare.None
);
await stream.CopyToAsync(fs);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// The WriteCache
/// </summary>
/// <param name="title">The title<see cref="string"/></param>
/// <param name="artist">The artist<see cref="string"/></param>
/// <param name="lyrics">The lyrics<see cref="string"/></param>
/// <param name="format">The format<see cref="LyricsFormat"/></param>
private void WriteCache(
string title,
string artist,
string lyrics,
LyricsFormat format,
string cacheFolderPath
)
{
var safeArtist = SanitizeFileName(artist);
var safeTitle = SanitizeFileName(title);
var cacheFilePath = Path.Combine(
AppInfo.OnlineLyricsCacheDirectory,
cacheFolderPath,
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
);
File.WriteAllText(cacheFilePath, lyrics);
}
private string? ReadCache(string title, string artist, LyricsFormat format)
{
var safeArtist = SanitizeFileName(artist);
var safeTitle = SanitizeFileName(title);
var cacheFilePath = Path.Combine(
AppInfo.OnlineLyricsCacheDirectory,
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
);
if (File.Exists(cacheFilePath))
{
return File.ReadAllText(cacheFilePath);
}
return null;
}
private static string SanitizeFileName(string fileName, char replacement = '_')
{
var invalidChars = Path.GetInvalidFileNameChars();
var sb = new StringBuilder(fileName.Length);
foreach (var c in fileName)
{
sb.Append(Array.IndexOf(invalidChars, c) >= 0 ? replacement : c);
}
return sb.ToString();
}
#endregion
}
}

View File

@@ -1,8 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
@@ -10,29 +7,53 @@ 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();
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
private GlobalSystemMediaTransportControlsSessionManager? _sessionManager = null;
private GlobalSystemMediaTransportControlsSession? _currentSession = null;
public SongInfo? SongInfo { get; private set; }
public bool IsPlaying { get; private set; }
public TimeSpan Position { get; private set; }
/// <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
@@ -42,88 +63,47 @@ namespace BetterLyrics.WinUI3.Services
InitMediaManager().ConfigureAwait(true);
}
private async Task InitMediaManager()
{
_sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
_sessionManager.CurrentSessionChanged += SessionManager_CurrentSessionChanged;
#endregion
SessionManager_CurrentSessionChanged(_sessionManager, null);
}
#region Events
/// <summary>
/// Note: Non-UI thread
/// Defines the IsPlayingChanged
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void CurrentSession_PlaybackInfoChanged(
GlobalSystemMediaTransportControlsSession? sender,
PlaybackInfoChangedEventArgs? args
)
{
if (sender == null)
{
IsPlaying = false;
}
else
{
var playbackState = sender.GetPlaybackInfo().PlaybackStatus;
// _logger.LogDebug(playbackState.ToString());
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
switch (playbackState)
{
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Opened:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Changing:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Stopped:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Paused:
IsPlaying = false;
break;
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing:
IsPlaying = true;
break;
default:
break;
}
}
_dispatcherQueue.TryEnqueue(
DispatcherQueuePriority.High,
() =>
{
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(IsPlaying));
}
);
}
/// <summary>
/// Defines the PositionChanged
/// </summary>
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
private void SessionManager_CurrentSessionChanged(
GlobalSystemMediaTransportControlsSessionManager sender,
CurrentSessionChangedEventArgs? args
)
{
// _logger.LogDebug("SessionManager_CurrentSessionChanged");
// Unregister events associated with the previous session
if (_currentSession != null)
{
_currentSession.MediaPropertiesChanged -= CurrentSession_MediaPropertiesChanged;
_currentSession.PlaybackInfoChanged -= CurrentSession_PlaybackInfoChanged;
_currentSession.TimelinePropertiesChanged -=
CurrentSession_TimelinePropertiesChanged;
}
/// <summary>
/// Defines the SongInfoChanged
/// </summary>
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
// Record and register events for current session
_currentSession = sender.GetCurrentSession();
#endregion
if (_currentSession != null)
{
_currentSession.MediaPropertiesChanged += CurrentSession_MediaPropertiesChanged;
_currentSession.PlaybackInfoChanged += CurrentSession_PlaybackInfoChanged;
_currentSession.TimelinePropertiesChanged +=
CurrentSession_TimelinePropertiesChanged;
}
#region Properties
CurrentSession_MediaPropertiesChanged(_currentSession, null);
CurrentSession_PlaybackInfoChanged(_currentSession, null);
CurrentSession_TimelinePropertiesChanged(_currentSession, null);
}
/// <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
@@ -205,6 +185,55 @@ namespace BetterLyrics.WinUI3.Services
);
}
/// <summary>
/// Note: Non-UI thread
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void CurrentSession_PlaybackInfoChanged(
GlobalSystemMediaTransportControlsSession? sender,
PlaybackInfoChangedEventArgs? args
)
{
if (sender == null)
{
IsPlaying = false;
}
else
{
var playbackState = sender.GetPlaybackInfo().PlaybackStatus;
// _logger.LogDebug(playbackState.ToString());
switch (playbackState)
{
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Opened:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Changing:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Stopped:
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Paused:
IsPlaying = false;
break;
case GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing:
IsPlaying = true;
break;
default:
break;
}
}
_dispatcherQueue.TryEnqueue(
DispatcherQueuePriority.High,
() =>
{
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(IsPlaying));
}
);
}
/// <summary>
/// The CurrentSession_TimelinePropertiesChanged
/// </summary>
/// <param name="sender">The sender<see cref="GlobalSystemMediaTransportControlsSession?"/></param>
/// <param name="args">The args<see cref="TimelinePropertiesChangedEventArgs?"/></param>
private void CurrentSession_TimelinePropertiesChanged(
GlobalSystemMediaTransportControlsSession? sender,
TimelinePropertiesChangedEventArgs? args
@@ -225,7 +254,56 @@ namespace BetterLyrics.WinUI3.Services
PositionChanged?.Invoke(this, new PositionChangedEventArgs(Position));
}
);
// _logger.LogDebug(_currentTime);
}
/// <summary>
/// The InitMediaManager
/// </summary>
/// <returns>The <see cref="Task"/></returns>
private async Task InitMediaManager()
{
_sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
_sessionManager.CurrentSessionChanged += SessionManager_CurrentSessionChanged;
SessionManager_CurrentSessionChanged(_sessionManager, null);
}
/// <summary>
/// The SessionManager_CurrentSessionChanged
/// </summary>
/// <param name="sender">The sender<see cref="GlobalSystemMediaTransportControlsSessionManager"/></param>
/// <param name="args">The args<see cref="CurrentSessionChangedEventArgs?"/></param>
private void SessionManager_CurrentSessionChanged(
GlobalSystemMediaTransportControlsSessionManager sender,
CurrentSessionChangedEventArgs? args
)
{
// _logger.LogDebug("SessionManager_CurrentSessionChanged");
// Unregister events associated with the previous session
if (_currentSession != null)
{
_currentSession.MediaPropertiesChanged -= CurrentSession_MediaPropertiesChanged;
_currentSession.PlaybackInfoChanged -= CurrentSession_PlaybackInfoChanged;
_currentSession.TimelinePropertiesChanged -=
CurrentSession_TimelinePropertiesChanged;
}
// Record and register events for current session
_currentSession = sender.GetCurrentSession();
if (_currentSession != null)
{
_currentSession.MediaPropertiesChanged += CurrentSession_MediaPropertiesChanged;
_currentSession.PlaybackInfoChanged += CurrentSession_PlaybackInfoChanged;
_currentSession.TimelinePropertiesChanged +=
CurrentSession_TimelinePropertiesChanged;
}
CurrentSession_MediaPropertiesChanged(_currentSession, null);
CurrentSession_PlaybackInfoChanged(_currentSession, null);
CurrentSession_TimelinePropertiesChanged(_currentSession, null);
}
#endregion
}
}

View File

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using BetterLyrics.WinUI3.Enums;
@@ -9,195 +11,147 @@ using Windows.Storage;
namespace BetterLyrics.WinUI3.Services
{
/// <summary>
/// Defines the <see cref="SettingsService" />
/// </summary>
public class SettingsService : ISettingsService
{
private readonly ApplicationDataContainer _localSettings;
private const string IsFirstRunKey = "IsFirstRun";
// Lyrics lib
private const string LocalLyricsFoldersKey = "LocalLyricsFolders";
private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo";
// App appearance
private const string ThemeTypeKey = "ThemeType";
private const string LanguageKey = "Language";
private const string BackdropTypeKey = "BackdropType";
#region Constants
// App behavior
/// <summary>
/// Defines the AutoStartWindowTypeKey
/// </summary>
private const string AutoStartWindowTypeKey = "AutoStartWindowType";
// Album art
private const string IsCoverOverlayEnabledKey = "IsCoverOverlayEnabled";
private const string IsDynamicCoverOverlayEnabledKey = "IsDynamicCoverOverlayEnabled";
private const string CoverOverlayOpacityKey = "CoverOverlayOpacity";
private const string CoverOverlayBlurAmountKey = "CoverOverlayBlurAmount";
private const string TitleBarTypeKey = "TitleBarType";
/// <summary>
/// Defines the BackdropTypeKey
/// </summary>
private const string BackdropTypeKey = "BackdropType";
/// <summary>
/// Defines the CoverImageRadiusKey
/// </summary>
private const string CoverImageRadiusKey = "CoverImageRadius";
private const string LyricsAlignmentTypeKey = "LyricsAlignmentType";
private const string LyricsFontWeightKey = "LyricsFontWeightKey";
private const string LyricsBlurAmountKey = "LyricsBlurAmount";
private const string LyricsVerticalEdgeOpacityKey = "LyricsVerticalEdgeOpacity";
private const string LyricsLineSpacingFactorKey = "LyricsLineSpacingFactor";
private const string LyricsFontSizeKey = "LyricsFontSize";
/// <summary>
/// Defines the CoverOverlayBlurAmountKey
/// </summary>
private const string CoverOverlayBlurAmountKey = "CoverOverlayBlurAmount";
/// <summary>
/// Defines the CoverOverlayOpacityKey
/// </summary>
private const string CoverOverlayOpacityKey = "CoverOverlayOpacity";
// Album art
/// <summary>
/// Defines the IsCoverOverlayEnabledKey
/// </summary>
private const string IsCoverOverlayEnabledKey = "IsCoverOverlayEnabled";
/// <summary>
/// Defines the IsDynamicCoverOverlayEnabledKey
/// </summary>
private const string IsDynamicCoverOverlayEnabledKey = "IsDynamicCoverOverlayEnabled";
/// <summary>
/// Defines the IsFirstRunKey
/// </summary>
private const string IsFirstRunKey = "IsFirstRun";
/// <summary>
/// Defines the IsLyricsGlowEffectEnabledKey
/// </summary>
private const string IsLyricsGlowEffectEnabledKey = "IsLyricsGlowEffectEnabled";
/// <summary>
/// Defines the LanguageKey
/// </summary>
private const string LanguageKey = "Language";
// Lyrics lib
/// <summary>
/// Defines the LocalLyricsFoldersKey
/// </summary>
private const string LocalLyricsFoldersKey = "LocalLyricsFolders";
/// <summary>
/// Defines the LyricsAlignmentTypeKey
/// </summary>
private const string LyricsAlignmentTypeKey = "LyricsAlignmentType";
/// <summary>
/// Defines the LyricsBlurAmountKey
/// </summary>
private const string LyricsBlurAmountKey = "LyricsBlurAmount";
/// <summary>
/// Defines the LyricsFontColorTypeKey
/// </summary>
private const string LyricsFontColorTypeKey = "LyricsFontColorType";
/// <summary>
/// Defines the LyricsFontSizeKey
/// </summary>
private const string LyricsFontSizeKey = "LyricsFontSize";
/// <summary>
/// Defines the LyricsFontWeightKey
/// </summary>
private const string LyricsFontWeightKey = "LyricsFontWeightKey";
/// <summary>
/// Defines the LyricsGlowEffectScopeKey
/// </summary>
private const string LyricsGlowEffectScopeKey = "LyricsGlowEffectScope";
public bool IsFirstRun
{
get => GetValue<bool>(IsFirstRunKey);
set => SetValue(IsFirstRunKey, value);
}
public List<LocalLyricsFolder> LocalLyricsFolders
{
get =>
System.Text.Json.JsonSerializer.Deserialize(
GetValue<string>(LocalLyricsFoldersKey) ?? "[]",
SourceGenerationContext.Default.ListLocalLyricsFolder
)!;
set =>
SetValue(
LocalLyricsFoldersKey,
System.Text.Json.JsonSerializer.Serialize(
value,
SourceGenerationContext.Default.ListLocalLyricsFolder
)
);
}
/// <summary>
/// Defines the LyricsLineSpacingFactorKey
/// </summary>
private const string LyricsLineSpacingFactorKey = "LyricsLineSpacingFactor";
public List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo
{
get =>
System.Text.Json.JsonSerializer.Deserialize(
GetValue<string>(LyricsSearchProvidersInfoKey) ?? "[]",
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
)!;
set =>
SetValue(
LyricsSearchProvidersInfoKey,
System.Text.Json.JsonSerializer.Serialize(
value,
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
)
);
}
/// <summary>
/// Defines the LyricsSearchProvidersInfoKey
/// </summary>
private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo";
public ElementTheme ThemeType
{
get => (ElementTheme)GetValue<int>(ThemeTypeKey);
set => SetValue(ThemeTypeKey, (int)value);
}
/// <summary>
/// Defines the LyricsVerticalEdgeOpacityKey
/// </summary>
private const string LyricsVerticalEdgeOpacityKey = "LyricsVerticalEdgeOpacity";
public Language Language
{
get => (Language)GetValue<int>(LanguageKey);
set => SetValue(LanguageKey, (int)value);
}
// App appearance
public BackdropType BackdropType
{
get => (BackdropType)GetValue<int>(BackdropTypeKey);
set => SetValue(BackdropTypeKey, (int)value);
}
/// <summary>
/// Defines the ThemeTypeKey
/// </summary>
private const string ThemeTypeKey = "ThemeType";
public AutoStartWindowType AutoStartWindowType
{
get => (AutoStartWindowType)GetValue<int>(AutoStartWindowTypeKey);
set => SetValue(AutoStartWindowTypeKey, (int)value);
}
/// <summary>
/// Defines the TitleBarTypeKey
/// </summary>
private const string TitleBarTypeKey = "TitleBarType";
public bool IsCoverOverlayEnabled
{
get => GetValue<bool>(IsCoverOverlayEnabledKey);
set => SetValue(IsCoverOverlayEnabledKey, value);
}
#endregion
public bool IsDynamicCoverOverlayEnabled
{
get => GetValue<bool>(IsDynamicCoverOverlayEnabledKey);
set => SetValue(IsDynamicCoverOverlayEnabledKey, value);
}
#region Fields
public int CoverOverlayOpacity
{
get => GetValue<int>(CoverOverlayOpacityKey);
set => SetValue(CoverOverlayOpacityKey, value);
}
/// <summary>
/// Defines the _localSettings
/// </summary>
private readonly ApplicationDataContainer _localSettings;
public int CoverOverlayBlurAmount
{
get => GetValue<int>(CoverOverlayBlurAmountKey);
set => SetValue(CoverOverlayBlurAmountKey, value);
}
#endregion
public TitleBarType TitleBarType
{
get => (TitleBarType)GetValue<int>(TitleBarTypeKey);
set => SetValue(TitleBarTypeKey, (int)value);
}
public int CoverImageRadius
{
get => GetValue<int>(CoverImageRadiusKey);
set => SetValue(CoverImageRadiusKey, value);
}
public LyricsAlignmentType LyricsAlignmentType
{
get => (LyricsAlignmentType)GetValue<int>(LyricsAlignmentTypeKey);
set => SetValue(LyricsAlignmentTypeKey, (int)value);
}
public LyricsFontWeight LyricsFontWeight
{
get => (LyricsFontWeight)GetValue<int>(LyricsFontWeightKey);
set => SetValue(LyricsFontWeightKey, (int)value);
}
public int LyricsBlurAmount
{
get => GetValue<int>(LyricsBlurAmountKey);
set => SetValue(LyricsBlurAmountKey, value);
}
public int LyricsVerticalEdgeOpacity
{
get => GetValue<int>(LyricsVerticalEdgeOpacityKey);
set => SetValue(LyricsVerticalEdgeOpacityKey, value);
}
public float LyricsLineSpacingFactor
{
get => GetValue<float>(LyricsLineSpacingFactorKey);
set => SetValue(LyricsLineSpacingFactorKey, value);
}
public int LyricsFontSize
{
get => GetValue<int>(LyricsFontSizeKey);
set => SetValue(LyricsFontSizeKey, value);
}
public bool IsLyricsGlowEffectEnabled
{
get => GetValue<bool>(IsLyricsGlowEffectEnabledKey);
set => SetValue(IsLyricsGlowEffectEnabledKey, value);
}
public LyricsGlowEffectScope LyricsGlowEffectScope
{
get => (LyricsGlowEffectScope)GetValue<int>(LyricsGlowEffectScopeKey);
set => SetValue(LyricsGlowEffectScopeKey, (int)value);
}
public LyricsFontColorType LyricsFontColorType
{
get => (LyricsFontColorType)GetValue<int>(LyricsFontColorTypeKey);
set => SetValue(LyricsFontColorTypeKey, (int)value);
}
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SettingsService"/> class.
/// </summary>
public SettingsService()
{
_localSettings = ApplicationData.Current.LocalSettings;
@@ -242,15 +196,249 @@ namespace BetterLyrics.WinUI3.Services
// Lyrics
SetDefault(LyricsAlignmentTypeKey, (int)LyricsAlignmentType.Center);
SetDefault(LyricsFontWeightKey, (int)LyricsFontWeight.Bold);
SetDefault(LyricsBlurAmountKey, 0);
SetDefault(LyricsBlurAmountKey, 5);
SetDefault(LyricsFontColorTypeKey, (int)LyricsFontColorType.Default);
SetDefault(LyricsFontSizeKey, 28);
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
SetDefault(IsLyricsGlowEffectEnabledKey, true);
SetDefault(LyricsGlowEffectScopeKey, (int)LyricsGlowEffectScope.CurrentChar);
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.UntilCurrentChar);
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the AutoStartWindowType
/// </summary>
public AutoStartWindowType AutoStartWindowType
{
get => (AutoStartWindowType)GetValue<int>(AutoStartWindowTypeKey);
set => SetValue(AutoStartWindowTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the BackdropType
/// </summary>
public BackdropType BackdropType
{
get => (BackdropType)GetValue<int>(BackdropTypeKey);
set => SetValue(BackdropTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
public int CoverImageRadius
{
get => GetValue<int>(CoverImageRadiusKey);
set => SetValue(CoverImageRadiusKey, value);
}
/// <summary>
/// Gets or sets the CoverOverlayBlurAmount
/// </summary>
public int CoverOverlayBlurAmount
{
get => GetValue<int>(CoverOverlayBlurAmountKey);
set => SetValue(CoverOverlayBlurAmountKey, value);
}
/// <summary>
/// Gets or sets the CoverOverlayOpacity
/// </summary>
public int CoverOverlayOpacity
{
get => GetValue<int>(CoverOverlayOpacityKey);
set => SetValue(CoverOverlayOpacityKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
/// </summary>
public bool IsCoverOverlayEnabled
{
get => GetValue<bool>(IsCoverOverlayEnabledKey);
set => SetValue(IsCoverOverlayEnabledKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
public bool IsDynamicCoverOverlayEnabled
{
get => GetValue<bool>(IsDynamicCoverOverlayEnabledKey);
set => SetValue(IsDynamicCoverOverlayEnabledKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsFirstRun
/// </summary>
public bool IsFirstRun
{
get => GetValue<bool>(IsFirstRunKey);
set => SetValue(IsFirstRunKey, value);
}
/// <summary>
/// Gets or sets a value indicating whether IsLyricsGlowEffectEnabled
/// </summary>
public bool IsLyricsGlowEffectEnabled
{
get => GetValue<bool>(IsLyricsGlowEffectEnabledKey);
set => SetValue(IsLyricsGlowEffectEnabledKey, value);
}
/// <summary>
/// Gets or sets the Language
/// </summary>
public Language Language
{
get => (Language)GetValue<int>(LanguageKey);
set => SetValue(LanguageKey, (int)value);
}
/// <summary>
/// Gets or sets the LocalLyricsFolders
/// </summary>
public List<LocalLyricsFolder> LocalLyricsFolders
{
get =>
System.Text.Json.JsonSerializer.Deserialize(
GetValue<string>(LocalLyricsFoldersKey) ?? "[]",
SourceGenerationContext.Default.ListLocalLyricsFolder
)!;
set =>
SetValue(
LocalLyricsFoldersKey,
System.Text.Json.JsonSerializer.Serialize(
value,
SourceGenerationContext.Default.ListLocalLyricsFolder
)
);
}
/// <summary>
/// Gets or sets the LyricsAlignmentType
/// </summary>
public LyricsAlignmentType LyricsAlignmentType
{
get => (LyricsAlignmentType)GetValue<int>(LyricsAlignmentTypeKey);
set => SetValue(LyricsAlignmentTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsBlurAmount
/// </summary>
public int LyricsBlurAmount
{
get => GetValue<int>(LyricsBlurAmountKey);
set => SetValue(LyricsBlurAmountKey, value);
}
/// <summary>
/// Gets or sets the LyricsFontColorType
/// </summary>
public LyricsFontColorType LyricsFontColorType
{
get => (LyricsFontColorType)GetValue<int>(LyricsFontColorTypeKey);
set => SetValue(LyricsFontColorTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
public int LyricsFontSize
{
get => GetValue<int>(LyricsFontSizeKey);
set => SetValue(LyricsFontSizeKey, value);
}
/// <summary>
/// Gets or sets the LyricsFontWeight
/// </summary>
public LyricsFontWeight LyricsFontWeight
{
get => (LyricsFontWeight)GetValue<int>(LyricsFontWeightKey);
set => SetValue(LyricsFontWeightKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsGlowEffectScope
/// </summary>
public LineRenderingType LyricsGlowEffectScope
{
get => (LineRenderingType)GetValue<int>(LyricsGlowEffectScopeKey);
set => SetValue(LyricsGlowEffectScopeKey, (int)value);
}
/// <summary>
/// Gets or sets the LyricsLineSpacingFactor
/// </summary>
public float LyricsLineSpacingFactor
{
get => GetValue<float>(LyricsLineSpacingFactorKey);
set => SetValue(LyricsLineSpacingFactorKey, value);
}
/// <summary>
/// Gets or sets the LyricsSearchProvidersInfo
/// </summary>
public List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo
{
get =>
System.Text.Json.JsonSerializer.Deserialize(
GetValue<string>(LyricsSearchProvidersInfoKey) ?? "[]",
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
)!;
set =>
SetValue(
LyricsSearchProvidersInfoKey,
System.Text.Json.JsonSerializer.Serialize(
value,
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
)
);
}
/// <summary>
/// Gets or sets the LyricsVerticalEdgeOpacity
/// </summary>
public int LyricsVerticalEdgeOpacity
{
get => GetValue<int>(LyricsVerticalEdgeOpacityKey);
set => SetValue(LyricsVerticalEdgeOpacityKey, value);
}
/// <summary>
/// Gets or sets the ThemeType
/// </summary>
public ElementTheme ThemeType
{
get => (ElementTheme)GetValue<int>(ThemeTypeKey);
set => SetValue(ThemeTypeKey, (int)value);
}
/// <summary>
/// Gets or sets the TitleBarType
/// </summary>
public TitleBarType TitleBarType
{
get => (TitleBarType)GetValue<int>(TitleBarTypeKey);
set => SetValue(TitleBarTypeKey, (int)value);
}
#endregion
#region Methods
/// <summary>
/// The GetValue
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key<see cref="string"/></param>
/// <returns>The <see cref="T?"/></returns>
private T? GetValue<T>(string key)
{
if (_localSettings.Values.TryGetValue(key, out object? value))
@@ -260,16 +448,30 @@ namespace BetterLyrics.WinUI3.Services
return default;
}
private void SetValue<T>(string key, T value)
{
_localSettings.Values[key] = value;
}
/// <summary>
/// The SetDefault
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key<see cref="string"/></param>
/// <param name="value">The value<see cref="T"/></param>
private void SetDefault<T>(string key, T value)
{
if (_localSettings.Values.ContainsKey(key) && _localSettings.Values[key] is T)
return;
_localSettings.Values[key] = value;
}
/// <summary>
/// The SetValue
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key<see cref="string"/></param>
/// <param name="value">The value<see cref="T"/></param>
private void SetValue<T>(string key, T value)
{
_localSettings.Values[key] = value;
}
#endregion
}
}

View File

@@ -294,6 +294,9 @@
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
<value>Configure lyrics search providers</value>
</data>
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
<value>Drag to sort, the lyrics search order will be in the following order</value>
</data>
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
<value>Add</value>
</data>
@@ -315,8 +318,11 @@
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>Play using system player</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>Log</value>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>Cache</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>Including log files, network lyrics cache</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>Font color</value>
@@ -411,6 +417,9 @@
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
<value>Dock mode</value>
</data>
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
<value>Desktop mode</value>
</data>
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>Font weight</value>
</data>
@@ -486,4 +495,40 @@
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
<value>This folder contains added folders, please delete these folders to add the folder</value>
</data>
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
<value>amll-ttml-db</value>
</data>
<data name="LyricsSearchProviderQQ" xml:space="preserve">
<value>QQ</value>
</data>
<data name="LyricsSearchProviderNetease" xml:space="preserve">
<value>Netease</value>
</data>
<data name="LyricsSearchProviderKugou" xml:space="preserve">
<value>Kugou</value>
</data>
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
<value>Show debug overlay</value>
</data>
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
<value>Dependencies</value>
</data>
<data name="HostWindowClickThroughFlyoutItem.Text" xml:space="preserve">
<value>Lock</value>
</data>
<data name="SystemTraySettings.Text" xml:space="preserve">
<value>Settings</value>
</data>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>Exit</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>Unlock the window</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>Lock</value>
</data>
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>To unlock after locking, go to the system tray to unlock</value>
</data>
</root>

View File

@@ -294,6 +294,9 @@
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
<value>歌詞検索プロバイダーを構成します</value>
</data>
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
<value>ドラッグしてソートすると、歌詞の検索注文は次の順序で行われます</value>
</data>
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
<value>追加</value>
</data>
@@ -315,8 +318,11 @@
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>システムプレーヤーを使用して再生します</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>ログ</value>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>キャッシュ</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>ログファイル、ネットワーク歌詞キャッシュを含む</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>フォントカラー</value>
@@ -411,6 +417,9 @@
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
<value>ドックモード</value>
</data>
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
<value>デスクトップモード</value>
</data>
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>フォント重量</value>
</data>
@@ -486,4 +495,40 @@
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
<value>このフォルダーには追加されたフォルダーが含まれています。これらのフォルダを削除してフォルダーを追加してください</value>
</data>
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
<value>amll-ttml-db</value>
</data>
<data name="LyricsSearchProviderQQ" xml:space="preserve">
<value>QQ</value>
</data>
<data name="LyricsSearchProviderNetease" xml:space="preserve">
<value>Netease</value>
</data>
<data name="LyricsSearchProviderKugou" xml:space="preserve">
<value>Kugou</value>
</data>
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
<value>デバッグオーバーレイを表示します</value>
</data>
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
<value>依存関係</value>
</data>
<data name="HostWindowClickThroughFlyoutItem.Text" xml:space="preserve">
<value>ロック</value>
</data>
<data name="SystemTraySettings.Text" xml:space="preserve">
<value>設定を開く</value>
</data>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>プログラムを終了します</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>ウィンドウのロックを解除します</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>ロック</value>
</data>
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>ロック後にロックを解除するには、システムトレイに移動してロックを解除します</value>
</data>
</root>

View File

@@ -294,6 +294,9 @@
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
<value>가사 검색 제공 업체를 구성하십시오</value>
</data>
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
<value>정렬하기 위해 드래그하면 가사 검색 순서는 다음 순서로됩니다.</value>
</data>
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
<value>추가하다</value>
</data>
@@ -315,8 +318,11 @@
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>시스템 플레이어를 사용하여 재생하십시오</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>통나무</value>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>은닉처</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>로그 파일, 네트워크 가사 캐시 포함</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>글꼴 색상</value>
@@ -411,6 +417,9 @@
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
<value>도크 모드</value>
</data>
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
<value>데스크탑 모드</value>
</data>
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>글꼴 무게</value>
</data>
@@ -486,4 +495,40 @@
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
<value>이 폴더에는 추가 된 폴더가 포함되어 있습니다. 폴더를 추가하려면이 폴더를 삭제하십시오.</value>
</data>
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
<value>amll-ttml-db</value>
</data>
<data name="LyricsSearchProviderQQ" xml:space="preserve">
<value>QQ</value>
</data>
<data name="LyricsSearchProviderNetease" xml:space="preserve">
<value>Netease</value>
</data>
<data name="LyricsSearchProviderKugou" xml:space="preserve">
<value>Kugou</value>
</data>
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
<value>디버그 오버레이를 표시하십시오</value>
</data>
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
<value>의존성</value>
</data>
<data name="HostWindowClickThroughFlyoutItem.Text" xml:space="preserve">
<value>잠금</value>
</data>
<data name="SystemTraySettings.Text" xml:space="preserve">
<value>열기 설정</value>
</data>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>프로그램을 종료하십시오</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>창을 잠금 해제하십시오</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>잠금</value>
</data>
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>잠금 잠금을 해제하려면 시스템 트레이로 이동하여 잠금을 해제하십시오.</value>
</data>
</root>

View File

@@ -294,6 +294,9 @@
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
<value>配置歌词搜索服务</value>
</data>
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
<value>拖动排序,歌词搜索顺序将按以下顺序</value>
</data>
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
<value>添加</value>
</data>
@@ -315,8 +318,11 @@
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>使用系统播放器播放</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>日志</value>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>缓存</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>包括日志文件,网络歌词缓存</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>字体颜色</value>
@@ -411,6 +417,9 @@
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
<value>停靠模式</value>
</data>
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
<value>桌面模式</value>
</data>
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>字体粗细</value>
</data>
@@ -486,4 +495,40 @@
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
<value>该文件夹包含已添加文件夹,请删除这些文件夹以添加该文件夹</value>
</data>
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
<value>amll-ttml-db</value>
</data>
<data name="LyricsSearchProviderQQ" xml:space="preserve">
<value>QQ</value>
</data>
<data name="LyricsSearchProviderNetease" xml:space="preserve">
<value>Netease</value>
</data>
<data name="LyricsSearchProviderKugou" xml:space="preserve">
<value>Kugou</value>
</data>
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
<value>显示调试覆盖层</value>
</data>
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
<value>依赖</value>
</data>
<data name="HostWindowClickThroughFlyoutItem.Text" xml:space="preserve">
<value>锁定</value>
</data>
<data name="SystemTraySettings.Text" xml:space="preserve">
<value>打开设置</value>
</data>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>退出程序</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>解锁窗口</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>锁定</value>
</data>
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>锁定后解锁,请转到系统托盘解锁</value>
</data>
</root>

View File

@@ -294,6 +294,9 @@
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
<value>配置歌詞搜尋服務</value>
</data>
<data name="SettingsPageLyricsSearchProvidersConfig.Description" xml:space="preserve">
<value>拖動排序,歌詞搜索順序將按以下順序</value>
</data>
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
<value>添加</value>
</data>
@@ -315,8 +318,11 @@
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
<value>使用系統播放器播放</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>紀錄</value>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>快取</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>包括日誌文件,網絡歌詞緩存</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>字體顏色</value>
@@ -411,6 +417,9 @@
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
<value>停靠模式</value>
</data>
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
<value>桌面模式</value>
</data>
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>字體粗細</value>
</data>
@@ -486,4 +495,40 @@
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
<value>該文件夾包含已添加文件夾,請刪除這些文件夾以添加該文件夾</value>
</data>
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
<value>amll-ttml-db</value>
</data>
<data name="LyricsSearchProviderQQ" xml:space="preserve">
<value>QQ</value>
</data>
<data name="LyricsSearchProviderNetease" xml:space="preserve">
<value>Netease</value>
</data>
<data name="LyricsSearchProviderKugou" xml:space="preserve">
<value>Kugou</value>
</data>
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
<value>顯示調試覆蓋層</value>
</data>
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
<value>依賴</value>
</data>
<data name="HostWindowClickThroughFlyoutItem.Text" xml:space="preserve">
<value>鎖定</value>
</data>
<data name="SystemTraySettings.Text" xml:space="preserve">
<value>打開設置</value>
</data>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>退出程序</value>
</data>
<data name="SystemTrayUnlock.Text" xml:space="preserve">
<value>解鎖窗口</value>
</data>
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
<value>鎖定</value>
</data>
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
<value>鎖定後解鎖,請轉到系統托盤解鎖</value>
</data>
</root>

View File

@@ -1,27 +1,57 @@
using System;
using System.Runtime.CompilerServices;
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Dispatching;
using System;
using System.Runtime.CompilerServices;
namespace BetterLyrics.WinUI3.ViewModels
{
/// <summary>
/// Defines the <see cref="BaseViewModel" />
/// </summary>
public partial class BaseViewModel : ObservableRecipient, IDisposable
{
private protected readonly ISettingsService _settingsService;
#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

@@ -1,4 +1,6 @@
using System;
// 2025/6/23 by Zhe Fang
using System;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
@@ -7,6 +9,7 @@ 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;
@@ -16,9 +19,13 @@ 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>>,
@@ -26,40 +33,21 @@ namespace BetterLyrics.WinUI3
IRecipient<PropertyChangedMessage<BackdropType>>,
IRecipient<PropertyChangedMessage<int>>
{
#region Fields
/// <summary>
/// Defines the _watcherHelper
/// </summary>
private ForegroundWindowWatcherHelper? _watcherHelper = null;
[ObservableProperty]
public partial Type FramePageType { get; set; }
#endregion
[ObservableProperty]
public partial ElementTheme ThemeType { get; set; }
[ObservableProperty]
public partial double AppLogoImageIconHeight { get; set; }
[ObservableProperty]
public partial double TitleBarFontSize { get; set; }
[ObservableProperty]
public partial double TitleBarHeight { get; set; }
[ObservableProperty]
public partial Notification Notification { get; set; } = new();
[ObservableProperty]
public partial bool ShowInfoBar { get; set; } = false;
[ObservableProperty]
public partial TitleBarType TitleBarType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDockMode { get; set; } = false;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial Color ActivatedWindowAccentColor { get; set; }
#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)
{
@@ -88,6 +76,167 @@ namespace BetterLyrics.WinUI3
);
}
#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(
@@ -104,12 +253,68 @@ namespace BetterLyrics.WinUI3
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)
@@ -121,13 +326,10 @@ namespace BetterLyrics.WinUI3
}
}
public void UpdateAccentColor(nint hwnd)
{
ActivatedWindowAccentColor = WindowColorHelper
.GetDominantColorBelow(hwnd)
.ToWindowsUIColor();
}
/// <summary>
/// The OnTitleBarTypeChanged
/// </summary>
/// <param name="value">The value<see cref="TitleBarType"/></param>
partial void OnTitleBarTypeChanged(TitleBarType value)
{
switch (value)
@@ -146,78 +348,6 @@ namespace BetterLyrics.WinUI3
TitleBarHeight = value.GetHeight();
}
[RelayCommand]
private void SwitchInfoBarNeverShowItAgainCheckBox(bool value)
{
//if (Notification.RelatedSettingsKeyName is string key)
// _settingsService.SetValue(key, value);
}
private bool? AlreadyForeverDismissedThisMessage()
{
//if (Notification.RelatedSettingsKeyName is string key)
// return _settingsService.Get(key, SettingsDefaultValues.NeverShowMessage);
//return null;
return null;
}
[RelayCommand]
private void ToggleDockMode()
{
var window = WindowHelper.GetWindowByFramePageType(FramePageType);
IsDockMode = !IsDockMode;
if (IsDockMode)
{
DockHelper.Enable(window, _settingsService.LyricsFontSize * 3);
StartWatchWindowColorChange();
}
else
{
DockHelper.Disable(window);
StopWatchWindowColorChange();
}
}
public void Receive(PropertyChangedMessage<TitleBarType> message)
{
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.TitleBarType))
{
TitleBarType = message.NewValue;
}
}
}
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
ThemeType = message.NewValue;
}
public void Receive(PropertyChangedMessage<BackdropType> message)
{
WindowHelper.GetWindowByFramePageType(FramePageType).SystemBackdrop =
SystemBackdropHelper.CreateSystemBackdrop(message.NewValue);
}
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize))
{
if (IsDockMode)
{
DockHelper.UpdateAppBarHeight(
WindowNative.GetWindowHandle(
WindowHelper.GetWindowByFramePageType(FramePageType)
),
message.NewValue * 3
);
}
}
}
}
#endregion
}
}

View File

@@ -1,11 +1,11 @@
using System;
using System.Collections.ObjectModel;
// 2025/6/23 by Zhe Fang
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -18,62 +18,36 @@ 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>>
{
private LyricsDisplayType? _preferredDisplayTypeBeforeSwitchToDockMode;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial double LimitedLineWidth { get; set; } = 0.0;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsDisplayType DisplayType { get; set; } =
LyricsDisplayType.PlaceholderOnly;
[ObservableProperty]
public partial BitmapImage? CoverImage { get; set; }
[ObservableProperty]
public partial SongInfo? SongInfo { get; set; } = null;
[ObservableProperty]
public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading;
[ObservableProperty]
public partial LyricsDisplayType? PreferredDisplayType { get; set; } =
LyricsDisplayType.SplitView;
[ObservableProperty]
public partial int LyricsFontSize { get; set; }
[ObservableProperty]
public partial bool AboutToUpdateUI { get; set; }
[ObservableProperty]
public partial double CoverImageGridActualHeight { get; set; }
[ObservableProperty]
public partial int CoverImageRadius { get; set; }
[ObservableProperty]
public partial CornerRadius CoverImageGridCornerRadius { get; set; }
[ObservableProperty]
public partial bool IsWelcomeTeachingTipOpen { get; set; }
[ObservableProperty]
public partial bool IsFirstRun { get; set; }
[ObservableProperty]
public partial bool IsNotMockMode { get; set; } = true;
#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
@@ -92,44 +66,195 @@ namespace BetterLyrics.WinUI3.ViewModels
UpdateSongInfoUI(_playbackService.SongInfo).ConfigureAwait(true);
}
partial void OnCoverImageRadiusChanged(int value)
{
if (double.IsNaN(CoverImageGridActualHeight))
return;
#endregion
CoverImageGridCornerRadius = new CornerRadius(
value / 100f * CoverImageGridActualHeight / 2
#region Properties
/// <summary>
/// Gets or sets a value indicating whether AboutToUpdateUI
/// </summary>
[ObservableProperty]
public partial bool AboutToUpdateUI { get; set; }
/// <summary>
/// Gets or sets the CoverImage
/// </summary>
[ObservableProperty]
public partial BitmapImage? CoverImage { get; set; }
/// <summary>
/// Gets or sets the CoverImageGridActualHeight
/// </summary>
[ObservableProperty]
public partial double CoverImageGridActualHeight { get; set; }
/// <summary>
/// Gets or sets the CoverImageGridCornerRadius
/// </summary>
[ObservableProperty]
public partial CornerRadius CoverImageGridCornerRadius { get; set; }
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
[ObservableProperty]
public partial int CoverImageRadius { get; set; }
/// <summary>
/// Gets or sets the DisplayType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsDisplayType DisplayType { get; set; } =
LyricsDisplayType.PlaceholderOnly;
/// <summary>
/// Gets or sets a value indicating whether IsFirstRun
/// </summary>
[ObservableProperty]
public partial bool IsFirstRun { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IsNotMockMode
/// </summary>
[ObservableProperty]
public partial bool IsNotMockMode { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether IsWelcomeTeachingTipOpen
/// </summary>
[ObservableProperty]
public partial bool IsWelcomeTeachingTipOpen { get; set; }
/// <summary>
/// Gets or sets the LimitedLineWidth
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial double MaxLyricsWidth { get; set; } = 0.0;
/// <summary>
/// Gets or sets the LyricsFontSize
/// </summary>
[ObservableProperty]
public partial int LyricsFontSize { get; set; }
/// <summary>
/// Gets or sets the LyricsStatus
/// </summary>
[ObservableProperty]
public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading;
/// <summary>
/// Gets or sets the PreferredDisplayType
/// </summary>
[ObservableProperty]
public partial LyricsDisplayType? PreferredDisplayType { get; set; } =
LyricsDisplayType.SplitView;
/// <summary>
/// Gets or sets the SongInfo
/// </summary>
[ObservableProperty]
public partial SongInfo? SongInfo { get; set; } = null;
#endregion
#region Methods
/// <summary>
/// The OpenMatchedFileFolderInFileExplorer
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
public void OpenMatchedFileFolderInFileExplorer(string path)
{
Process.Start(
new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"/select,\"{path}\"",
UseShellExecute = true,
}
);
}
partial void OnCoverImageGridActualHeightChanged(double value)
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{bool}"/></param>
public void Receive(PropertyChangedMessage<bool> message)
{
if (double.IsNaN(value))
return;
CoverImageGridCornerRadius = new CornerRadius(CoverImageRadius / 100f * value / 2);
if (message.Sender is HostWindowViewModel)
{
if (message.PropertyName == nameof(HostWindowViewModel.IsDockMode))
{
IsNotMockMode = !message.NewValue;
SetNonStandardModePreferredDisplayType(message.NewValue);
TrySwitchToPreferredDisplayType(SongInfo);
}
else if (message.PropertyName == nameof(HostWindowViewModel.IsDesktopMode))
{
SetNonStandardModePreferredDisplayType(message.NewValue);
TrySwitchToPreferredDisplayType(SongInfo);
}
}
}
partial void OnIsFirstRunChanged(bool value)
private void SetNonStandardModePreferredDisplayType(bool isEnabled)
{
IsWelcomeTeachingTipOpen = value;
_settingsService.IsFirstRun = false;
if (isEnabled)
{
_preferredDisplayTypeBeforeSwitchToNonStandardMode = PreferredDisplayType;
PreferredDisplayType = LyricsDisplayType.LyricsOnly;
}
else
{
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode;
}
}
[RelayCommand]
private void OnDisplayTypeChanged(object value)
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{int}"/></param>
public void Receive(PropertyChangedMessage<int> message)
{
int index = Convert.ToInt32(value);
PreferredDisplayType = (LyricsDisplayType)index;
DisplayType = (LyricsDisplayType)index;
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
{
CoverImageRadius = message.NewValue;
}
}
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize))
{
LyricsFontSize = message.NewValue;
}
}
}
[RelayCommand]
private void OpenSettingsWindow()
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsStatus}"/></param>
public void Receive(PropertyChangedMessage<LyricsStatus> message)
{
WindowHelper.OpenSettingsWindow();
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;
@@ -147,6 +272,31 @@ 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();
}
/// <summary>
/// The TrySwitchToPreferredDisplayType
/// </summary>
/// <param name="songInfo">The songInfo<see cref="SongInfo?"/></param>
private void TrySwitchToPreferredDisplayType(SongInfo? songInfo)
{
LyricsDisplayType displayType;
@@ -167,66 +317,42 @@ namespace BetterLyrics.WinUI3.ViewModels
DisplayType = displayType;
}
public void OpenMatchedFileFolderInFileExplorer(string path)
/// <summary>
/// The OnCoverImageGridActualHeightChanged
/// </summary>
/// <param name="value">The value<see cref="double"/></param>
partial void OnCoverImageGridActualHeightChanged(double value)
{
Process.Start(
new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"/select,\"{path}\"",
UseShellExecute = true,
}
if (double.IsNaN(value))
return;
CoverImageGridCornerRadius = new CornerRadius(CoverImageRadius / 100f * value / 2);
}
/// <summary>
/// The OnCoverImageRadiusChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverImageRadiusChanged(int value)
{
if (double.IsNaN(CoverImageGridActualHeight))
return;
CoverImageGridCornerRadius = new CornerRadius(
value / 100f * CoverImageGridActualHeight / 2
);
}
public void Receive(PropertyChangedMessage<int> message)
/// <summary>
/// The OnIsFirstRunChanged
/// </summary>
/// <param name="value">The value<see cref="bool"/></param>
partial void OnIsFirstRunChanged(bool value)
{
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
{
CoverImageRadius = message.NewValue;
}
}
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize))
{
LyricsFontSize = message.NewValue;
}
}
IsWelcomeTeachingTipOpen = value;
_settingsService.IsFirstRun = false;
}
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is HostWindowViewModel)
{
if (message.PropertyName == nameof(HostWindowViewModel.IsDockMode))
{
IsNotMockMode = !message.NewValue;
if (message.NewValue)
{
_preferredDisplayTypeBeforeSwitchToDockMode = PreferredDisplayType;
PreferredDisplayType = LyricsDisplayType.LyricsOnly;
}
else
{
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToDockMode;
}
TrySwitchToPreferredDisplayType(SongInfo);
}
}
}
public void Receive(PropertyChangedMessage<LyricsStatus> message)
{
if (message.Sender is LyricsRendererViewModel)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsStatus))
{
LyricsStatus = message.NewValue;
}
}
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,380 @@
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.Xaml;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
: IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<float>>,
IRecipient<PropertyChangedMessage<double>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<Color>>,
IRecipient<PropertyChangedMessage<LyricsDisplayType>>,
IRecipient<PropertyChangedMessage<LyricsFontColorType>>,
IRecipient<PropertyChangedMessage<LyricsAlignmentType>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<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)
{
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)
{
_albumArtBitmap = await (
await ImageHelper.GetDecoderFromByte(bytes)
).GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
_albumArtAccentColor = (
await ImageHelper.GetAccentColorsFromByte(bytes)
).FirstOrDefault();
}
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)
{
UpdateFontColor();
}
// 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.PropertyName == nameof(SettingsViewModel.IsDynamicCoverOverlayEnabled))
{
IsDynamicCoverOverlayEnabled = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.IsCoverOverlayEnabled))
{
IsCoverOverlayEnabled = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.IsDebugOverlayEnabled))
{
_isDebugOverlayEnabled = message.NewValue;
}
}
else if (message.Sender is LyricsSettingsControlViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.IsLyricsGlowEffectEnabled)
)
{
IsLyricsGlowEffectEnabled = message.NewValue;
}
}
else if (message.Sender is HostWindowViewModel)
{
if (message.PropertyName == nameof(HostWindowViewModel.IsDockMode))
{
_isDockMode = message.NewValue;
}
else if (message.PropertyName == nameof(HostWindowViewModel.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.PropertyName == nameof(HostWindowViewModel.ActivatedWindowAccentColor))
{
_immersiveBgTransition.StartTransition(message.NewValue);
}
}
}
/// <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)
{
if (message.PropertyName == nameof(LyricsPageViewModel.MaxLyricsWidth))
{
_maxLyricsWidthTransition.StartTransition((float)message.NewValue);
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{ElementTheme}"/></param>
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.ThemeType))
{
Theme = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{float}"/></param>
public void Receive(PropertyChangedMessage<float> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsLineSpacingFactor)
)
{
LyricsLineSpacingFactor = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{int}"/></param>
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
{
CoverImageRadius = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.CoverOverlayOpacity))
{
CoverOverlayOpacity = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsViewModel.CoverOverlayBlurAmount))
{
CoverOverlayBlurAmount = message.NewValue;
}
}
else if (message.Sender is LyricsSettingsControlViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsVerticalEdgeOpacity)
)
{
LyricsVerticalEdgeOpacity = message.NewValue;
}
else if (
message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsBlurAmount)
)
{
LyricsBlurAmount = message.NewValue;
}
else if (
message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize)
)
{
LyricsFontSize = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsAlignmentType}"/></param>
public void Receive(PropertyChangedMessage<LyricsAlignmentType> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsAlignmentType)
)
{
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)
{
if (
message.PropertyName
== nameof(LyricsSettingsControlViewModel.LyricsGlowEffectScope)
)
{
LyricsGlowEffectScope = message.NewValue;
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{ObservableCollection{LocalLyricsFolder}}"/></param>
public void Receive(PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>> message)
{
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.LocalLyricsFolders))
{
// Music lib changed, re-fetch lyrics
RefreshLyricsAsync().ConfigureAwait(true);
}
}
}
/// <summary>
/// The Receive
/// </summary>
/// <param name="message">The message<see cref="PropertyChangedMessage{ObservableCollection{LyricsSearchProviderInfo}}"/></param>
public void Receive(
PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>> message
)
{
if (message.Sender is SettingsViewModel)
{
if (message.PropertyName == nameof(SettingsViewModel.LyricsSearchProvidersInfo))
{
// Lyrics search providers info changed, re-fetch lyrics
RefreshLyricsAsync().ConfigureAwait(true);
}
}
}
}
}

View File

@@ -0,0 +1,565 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
{
/// <summary>
/// The Draw
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
// Blurred lyrics layer
using var blurredLyrics = new CanvasCommandList(control);
using (var blurredLyricsDs = blurredLyrics.CreateDrawingSession())
{
switch (DisplayType)
{
case LyricsDisplayType.AlbumArtOnly:
case LyricsDisplayType.PlaceholderOnly:
break;
case LyricsDisplayType.LyricsOnly:
case LyricsDisplayType.SplitView:
DrawBlurredLyrics(control, blurredLyricsDs);
break;
default:
break;
}
}
// 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);
}
if (_isDockMode)
{
DrawImmersiveBackground(control, combinedDs, IsCoverOverlayEnabled);
}
combinedDs.DrawImage(maskedBlurredLyrics);
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 });
}
else
{
ds.DrawImage(combined);
}
if (_isDebugOverlayEnabled)
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
var currentPlayingLine = _multiLangLyrics
.SafeGet(_langIndex)
?.SafeGet(currentPlayingLineIndex);
if (currentPlayingLine != null)
{
GetLinePlayingProgress(
currentPlayingLine,
out int charStartIndex,
out int charLength,
out float charProgress
);
ds.DrawText(
$"DEBUG: "
+ $"播放行 {currentPlayingLineIndex}, 字符 {charStartIndex}, 长度 {charLength}, 进度 {charProgress}\n"
+ $"可见行 [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n"
+ $"当前时刻 {TotalTime}",
new Vector2(10, 10),
Colors.Red
);
}
}
}
/// <summary>
/// The DrawImgae
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
/// <param name="softwareBitmap">The softwareBitmap<see cref="SoftwareBitmap"/></param>
/// <param name="opacity">The opacity<see cref="float"/></param>
private static void DrawImgae(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
SoftwareBitmap softwareBitmap,
float opacity
)
{
using var canvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, softwareBitmap);
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
var scaleFactor =
(float)Math.Sqrt(Math.Pow(control.Size.Width, 2) + Math.Pow(control.Size.Height, 2))
/ Math.Min(imageWidth, imageHeight);
ds.DrawImage(
new OpacityEffect
{
Source = new ScaleEffect
{
InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
BorderMode = EffectBorderMode.Hard,
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
Opacity = opacity,
},
(float)control.Size.Width / 2 - imageWidth * scaleFactor / 2,
(float)control.Size.Height / 2 - imageHeight * scaleFactor / 2
);
}
/// <summary>
/// The DrawAlbumArtBackground
/// </summary>
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
var overlappedCovers = new CanvasCommandList(control.Device);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
if (_albumArtBgTransition.IsTransitioning)
{
if (_lastAlbumArtBitmap != null)
{
DrawImgae(
control,
overlappedCoversDs,
_lastAlbumArtBitmap,
1 - _albumArtBgTransition.Value
);
}
if (_albumArtBitmap != null)
{
DrawImgae(
control,
overlappedCoversDs,
_albumArtBitmap,
_albumArtBgTransition.Value
);
}
}
else if (_albumArtBitmap != null)
{
DrawImgae(control, overlappedCoversDs, _albumArtBitmap, 1f);
}
using var coverOverlayEffect = new OpacityEffect
{
Opacity = CoverOverlayOpacity / 100f,
Source = new GaussianBlurEffect
{
BlurAmount = CoverOverlayBlurAmount,
Source = overlappedCovers,
},
};
ds.DrawImage(coverOverlayEffect);
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();
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
if (line == null)
{
continue;
}
var textLayout = line.CanvasTextLayout;
if (textLayout == null)
{
continue;
}
var position = new Vector2(line.Position.X, line.Position.Y);
float layoutWidth = (float)textLayout.LayoutBounds.Width;
float layoutHeight = (float)textLayout.LayoutBounds.Height;
if (layoutWidth <= 0 || layoutHeight <= 0)
{
continue;
}
float centerX = position.X;
float centerY = position.Y + layoutHeight / 2;
switch (LyricsAlignmentType)
{
case LyricsAlignmentType.Left:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
break;
case LyricsAlignmentType.Center:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
centerX += (float)_maxLyricsWidthTransition.Value / 2;
break;
case LyricsAlignmentType.Right:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
centerX += (float)_maxLyricsWidthTransition.Value;
break;
default:
break;
}
float offsetToLeft =
(float)control.Size.Width - _rightMargin - _maxLyricsWidthTransition.Value;
// Scale
ds.Transform =
Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
* Matrix3x2.CreateTranslation(
offsetToLeft,
_canvasYScrollTransition.Value + (float)(control.Size.Height / 2)
);
// Create the original lyrics line
using var lyrics = new CanvasCommandList(control.Device);
using var lyricsDs = lyrics.CreateDrawingSession();
lyricsDs.DrawTextLayout(textLayout, position, _fontColor);
// Mock gradient blurred lyrics layer
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层
// Current line will not be blurred
ds.DrawImage(
new GaussianBlurEffect
{
Source = new OpacityEffect { Source = lyrics, Opacity = _defaultOpacity },
BlurAmount = line.BlurAmountTransition.Value,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
}
);
// 再叠加当前行歌词层
// Only draw the current line and the two lines around it
// This layer is to highlight the current line
// and for fade-in and fade-out effects, two lines around it is also drawn
if (Math.Abs(i - currentPlayingLineIndex) <= 1)
{
using var mask = new CanvasCommandList(control.Device);
using var maskDs = mask.CreateDrawingSession();
using var highlightMask = new CanvasCommandList(control.Device);
using var highlightMaskDs = highlightMask.CreateDrawingSession();
if (i == currentPlayingLineIndex)
{
GetLinePlayingProgress(
line,
out int charStartIndex,
out int charLength,
out float charProgress
);
var regions = textLayout.GetCharacterRegions(0, charStartIndex);
var highlightRegion = textLayout
.GetCharacterRegions(charStartIndex, charLength)
.FirstOrDefault();
if (regions.Length > 0)
{
// Draw the mask for the current line
for (int j = 0; j < regions.Length; j++)
{
var region = regions[j];
var rect = new Rect(
region.LayoutBounds.X,
region.LayoutBounds.Y + position.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
maskDs.FillRectangle(rect, Colors.Black);
}
}
float highlightTotalWidth = (float)highlightRegion.LayoutBounds.Width;
// Draw the highlight for the current character
float highlightWidth = highlightTotalWidth * charProgress;
float fadingWidth = (float)highlightRegion.LayoutBounds.Height / 2;
// Rects
var highlightRect = new Rect(
highlightRegion.LayoutBounds.X,
highlightRegion.LayoutBounds.Y + position.Y,
highlightWidth,
highlightRegion.LayoutBounds.Height
);
var fadeInRect = new Rect(
highlightRect.Right - fadingWidth,
highlightRegion.LayoutBounds.Y + position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
var fadeOutRect = new Rect(
highlightRect.Right,
highlightRegion.LayoutBounds.Y + position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
// Brushes
using var fadeInBrush = GetHorizontalFillBrush(
control,
[(0f, 0f), (1f, 1f)],
(float)highlightRect.Right - fadingWidth,
fadingWidth
);
using var fadeOutBrush = GetHorizontalFillBrush(
control,
[(0f, 1f), (1f, 0f)],
(float)highlightRect.Right,
fadingWidth
);
maskDs.FillRectangle(highlightRect, Colors.White);
maskDs.FillRectangle(fadeOutRect, fadeOutBrush);
highlightMaskDs.FillRectangle(fadeInRect, fadeInBrush);
highlightMaskDs.FillRectangle(fadeOutRect, fadeOutBrush);
}
else
{
maskDs.FillRectangle(
new Rect(
textLayout.LayoutBounds.X,
position.Y,
textLayout.LayoutBounds.Width,
textLayout.LayoutBounds.Height
),
Colors.White
);
}
ds.DrawImage(
new OpacityEffect
{
Source = new BlendEffect
{
Background = IsLyricsGlowEffectEnabled
? new GaussianBlurEffect
{
Source = new AlphaMaskEffect
{
Source = lyrics,
AlphaMask = LyricsGlowEffectScope switch
{
LineRenderingType.UntilCurrentChar => mask,
LineRenderingType.CurrentCharOnly => highlightMask,
_ => mask,
},
},
BlurAmount = _lyricsGlowEffectAmount,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
}
: new CanvasCommandList(control.Device),
Foreground = new AlphaMaskEffect
{
Source = lyrics,
AlphaMask = mask,
},
},
Opacity = line.HighlightOpacityTransition.Value,
}
);
}
// Reset scale
ds.Transform = Matrix3x2.Identity;
}
}
/// <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 CanvasLinearGradientBrush GetHorizontalFillBrush(
ICanvasAnimatedControl control,
List<(float position, float opacity)> stops,
float startX,
float width
)
{
return new CanvasLinearGradientBrush(
control,
stops
.Select(stops => new CanvasGradientStop
{
Position = stops.position,
Color = Color.FromArgb((byte)(stops.opacity * 255), 0, 0, 0),
})
.ToArray()
)
{
StartPoint = new Vector2(startX, 0),
EndPoint = new Vector2(startX + width, 0),
};
}
}
}

View File

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

View File

@@ -1,9 +1,12 @@
using System;
// 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;
@@ -15,6 +18,7 @@ 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;
@@ -24,66 +28,43 @@ using WinRT.Interop;
namespace BetterLyrics.WinUI3.ViewModels
{
/// <summary>
/// Defines the <see cref="SettingsViewModel" />
/// </summary>
public partial class SettingsViewModel : ObservableRecipient
{
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ElementTheme ThemeType { get; set; }
#region Fields
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial BackdropType BackdropType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial TitleBarType TitleBarType { get; set; }
[ObservableProperty]
public partial AutoStartWindowType AutoStartWindowType { get; set; }
[ObservableProperty]
public partial ObservableCollection<LocalLyricsFolder> LocalLyricsFolders { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverImageRadius { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsCoverOverlayEnabled { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDynamicCoverOverlayEnabled { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayOpacity { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayBlurAmount { get; set; }
[ObservableProperty]
public partial Enums.Language Language { get; set; }
public string Version { get; set; } = AppInfo.AppVersion;
[ObservableProperty]
public partial object NavViewSelectedItemTag { get; set; } = "LyricsLib";
[ObservableProperty]
public partial Thickness RootGridMargin { get; set; } = new(0, 0, 0, 0);
private readonly MediaPlayer _mediaPlayer = new();
private readonly ISettingsService _settingsService;
/// <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,
@@ -113,88 +94,119 @@ namespace BetterLyrics.WinUI3.ViewModels
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
}
partial void OnLanguageChanged(Enums.Language value)
{
switch (value)
{
case Enums.Language.FollowSystem:
ApplicationLanguages.PrimaryLanguageOverride = "";
break;
case Enums.Language.English:
ApplicationLanguages.PrimaryLanguageOverride = "en-US";
break;
case Enums.Language.SimplifiedChinese:
ApplicationLanguages.PrimaryLanguageOverride = "zh-CN";
break;
case Enums.Language.TraditionalChinese:
ApplicationLanguages.PrimaryLanguageOverride = "zh-TW";
break;
case Enums.Language.Japanese:
ApplicationLanguages.PrimaryLanguageOverride = "ja-JP";
break;
case Enums.Language.Korean:
ApplicationLanguages.PrimaryLanguageOverride = "ko-KR";
break;
default:
break;
}
_settingsService.Language = Language;
}
#endregion
partial void OnThemeTypeChanged(ElementTheme value)
{
_settingsService.ThemeType = value;
}
#region Properties
partial void OnBackdropTypeChanged(BackdropType value)
{
_settingsService.BackdropType = value;
}
/// <summary>
/// Gets or sets the AutoStartWindowType
/// </summary>
[ObservableProperty]
public partial AutoStartWindowType AutoStartWindowType { get; set; }
partial void OnTitleBarTypeChanged(TitleBarType value)
{
_settingsService.TitleBarType = value;
RootGridMargin = new Thickness(0, value.GetHeight(), 0, 0);
}
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDebugOverlayEnabled { get; set; } = false;
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
{
_settingsService.AutoStartWindowType = value;
}
/// <summary>
/// Gets or sets the BackdropType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial BackdropType BackdropType { get; set; }
partial void OnCoverImageRadiusChanged(int value)
{
_settingsService.CoverImageRadius = value;
}
/// <summary>
/// Gets or sets the CoverImageRadius
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverImageRadius { get; set; }
partial void OnIsCoverOverlayEnabledChanged(bool value)
{
_settingsService.IsCoverOverlayEnabled = value;
}
/// <summary>
/// Gets or sets the CoverOverlayBlurAmount
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayBlurAmount { get; set; }
partial void OnIsDynamicCoverOverlayEnabledChanged(bool value)
{
_settingsService.IsDynamicCoverOverlayEnabled = value;
}
/// <summary>
/// Gets or sets the CoverOverlayOpacity
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int CoverOverlayOpacity { get; set; }
partial void OnCoverOverlayOpacityChanged(int value)
{
_settingsService.CoverOverlayOpacity = value;
}
/// <summary>
/// Gets or sets a value indicating whether IsCoverOverlayEnabled
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsCoverOverlayEnabled { get; set; }
partial void OnCoverOverlayBlurAmountChanged(int value)
{
_settingsService.CoverOverlayBlurAmount = value;
}
/// <summary>
/// Gets or sets a value indicating whether IsDynamicCoverOverlayEnabled
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsDynamicCoverOverlayEnabled { get; set; }
public void RemoveFolderAsync(LocalLyricsFolder folder)
{
LocalLyricsFolders.Remove(folder);
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
/// <summary>
/// Gets or sets the Language
/// </summary>
[ObservableProperty]
public partial Enums.Language Language { get; set; }
/// <summary>
/// Gets or sets the LocalLyricsFolders
/// </summary>
[ObservableProperty]
public partial ObservableCollection<LocalLyricsFolder> LocalLyricsFolders { get; set; }
/// <summary>
/// Gets or sets the LyricsSearchProvidersInfo
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
/// <summary>
/// Gets or sets the NavViewSelectedItemTag
/// </summary>
[ObservableProperty]
public partial object NavViewSelectedItemTag { get; set; } = "LyricsLib";
/// <summary>
/// Gets or sets the RootGridMargin
/// </summary>
[ObservableProperty]
public partial Thickness RootGridMargin { get; set; } = new(0, 0, 0, 0);
/// <summary>
/// Gets or sets the ThemeType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ElementTheme ThemeType { get; set; }
/// <summary>
/// Gets or sets the TitleBarType
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial TitleBarType TitleBarType { get; set; }
/// <summary>
/// Gets or sets the Version
/// </summary>
public string Version { get; set; } = Helper.AppInfo.AppVersion;
#endregion
#region Methods
/// <summary>
/// The OnLyricsSearchProvidersReordered
/// </summary>
public void OnLyricsSearchProvidersReordered()
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
@@ -205,24 +217,55 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
[RelayCommand]
private async Task SelectAndAddFolderAsync(UIElement sender)
/// <summary>
/// The OpenMusicFolder
/// </summary>
/// <param name="folder">The folder<see cref="LocalLyricsFolder"/></param>
public void OpenMusicFolder(LocalLyricsFolder folder)
{
var picker = new Windows.Storage.Pickers.FolderPicker();
picker.FileTypeFilter.Add("*");
var hwnd = WindowNative.GetWindowHandle(WindowHelper.GetWindowForElement(sender));
InitializeWithWindow.Initialize(picker, hwnd);
var folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
AddFolderAsync(folder.Path);
}
OpenFolderInFileExplorer(folder.Path);
}
/// <summary>
/// The RemoveFolderAsync
/// </summary>
/// <param name="folder">The folder<see cref="LocalLyricsFolder"/></param>
public void RemoveFolderAsync(LocalLyricsFolder folder)
{
LocalLyricsFolders.Remove(folder);
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
/// <summary>
/// The ToggleLocalLyricsFolder
/// </summary>
/// <param name="folder">The folder<see cref="LocalLyricsFolder"/></param>
public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
{
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
}
/// <summary>
/// The ToggleLyricsSearchProvider
/// </summary>
/// <param name="providerInfo">The providerInfo<see cref="LyricsSearchProviderInfo"/></param>
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
Broadcast(
LyricsSearchProvidersInfo,
LyricsSearchProvidersInfo,
nameof(LyricsSearchProvidersInfo)
);
}
/// <summary>
/// The AddFolderAsync
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
private void AddFolderAsync(string path)
{
var normalizedPath =
@@ -293,12 +336,20 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The LaunchProjectGitHubPageAsync
/// </summary>
/// <returns>The <see cref="Task"/></returns>
[RelayCommand]
private async Task LaunchProjectGitHubPageAsync()
{
await Launcher.LaunchUriAsync(new Uri(AppInfo.GithubUrl));
await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl));
}
/// <summary>
/// The OpenFolderInFileExplorer
/// </summary>
/// <param name="path">The path<see cref="string"/></param>
private void OpenFolderInFileExplorer(string path)
{
Process.Start(
@@ -311,11 +362,29 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
public void OpenMusicFolder(LocalLyricsFolder folder)
/// <summary>
/// The OpenLogFolder
/// </summary>
[RelayCommand]
private void OpenCacheFolder()
{
OpenFolderInFileExplorer(folder.Path);
OpenFolderInFileExplorer(Helper.AppInfo.CacheFolder);
}
/// <summary>
/// The PlayTestingMusicTask
/// </summary>
[RelayCommand]
private void PlayTestingMusicTask()
{
AddFolderAsync(Helper.AppInfo.AssetsFolder);
_mediaPlayer.SetUriSource(new Uri(Helper.AppInfo.TestMusicPath));
_mediaPlayer.Play();
}
/// <summary>
/// The RestartApp
/// </summary>
[RelayCommand]
private void RestartApp()
{
@@ -337,34 +406,143 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
/// <summary>
/// The SelectAndAddFolderAsync
/// </summary>
/// <param name="sender">The sender<see cref="UIElement"/></param>
/// <returns>The <see cref="Task"/></returns>
[RelayCommand]
private void PlayTestingMusicTask()
private async Task SelectAndAddFolderAsync(UIElement sender)
{
AddFolderAsync(AppInfo.AssetsFolder);
_mediaPlayer.SetUriSource(new Uri(AppInfo.TestMusicPath));
_mediaPlayer.Play();
var picker = new Windows.Storage.Pickers.FolderPicker();
picker.FileTypeFilter.Add("*");
var hwnd = WindowNative.GetWindowHandle(WindowHelper.GetWindowForElement(sender));
InitializeWithWindow.Initialize(picker, hwnd);
var folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
AddFolderAsync(folder.Path);
}
}
[RelayCommand]
private void OpenLogFolder()
/// <summary>
/// The OnAutoStartWindowTypeChanged
/// </summary>
/// <param name="value">The value<see cref="AutoStartWindowType"/></param>
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
{
OpenFolderInFileExplorer(AppInfo.LogDirectory);
_settingsService.AutoStartWindowType = value;
}
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
/// <summary>
/// The OnBackdropTypeChanged
/// </summary>
/// <param name="value">The value<see cref="BackdropType"/></param>
partial void OnBackdropTypeChanged(BackdropType value)
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
Broadcast(
LyricsSearchProvidersInfo,
LyricsSearchProvidersInfo,
nameof(LyricsSearchProvidersInfo)
);
_settingsService.BackdropType = value;
}
public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
/// <summary>
/// The OnCoverImageRadiusChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverImageRadiusChanged(int value)
{
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
_settingsService.CoverImageRadius = value;
}
/// <summary>
/// The OnCoverOverlayBlurAmountChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverOverlayBlurAmountChanged(int value)
{
_settingsService.CoverOverlayBlurAmount = value;
}
/// <summary>
/// The OnCoverOverlayOpacityChanged
/// </summary>
/// <param name="value">The value<see cref="int"/></param>
partial void OnCoverOverlayOpacityChanged(int value)
{
_settingsService.CoverOverlayOpacity = value;
}
/// <summary>
/// The OnIsCoverOverlayEnabledChanged
/// </summary>
/// <param name="value">The value<see cref="bool"/></param>
partial void OnIsCoverOverlayEnabledChanged(bool value)
{
_settingsService.IsCoverOverlayEnabled = value;
}
/// <summary>
/// The OnIsDynamicCoverOverlayEnabledChanged
/// </summary>
/// <param name="value">The value<see cref="bool"/></param>
partial void OnIsDynamicCoverOverlayEnabledChanged(bool value)
{
_settingsService.IsDynamicCoverOverlayEnabled = value;
}
/// <summary>
/// The OnLanguageChanged
/// </summary>
/// <param name="value">The value<see cref="Enums.Language"/></param>
partial void OnLanguageChanged(Enums.Language value)
{
switch (value)
{
case Enums.Language.FollowSystem:
ApplicationLanguages.PrimaryLanguageOverride = "";
break;
case Enums.Language.English:
ApplicationLanguages.PrimaryLanguageOverride = "en-US";
break;
case Enums.Language.SimplifiedChinese:
ApplicationLanguages.PrimaryLanguageOverride = "zh-CN";
break;
case Enums.Language.TraditionalChinese:
ApplicationLanguages.PrimaryLanguageOverride = "zh-TW";
break;
case Enums.Language.Japanese:
ApplicationLanguages.PrimaryLanguageOverride = "ja-JP";
break;
case Enums.Language.Korean:
ApplicationLanguages.PrimaryLanguageOverride = "ko-KR";
break;
default:
break;
}
_settingsService.Language = Language;
}
/// <summary>
/// The OnThemeTypeChanged
/// </summary>
/// <param name="value">The value<see cref="ElementTheme"/></param>
partial void OnThemeTypeChanged(ElementTheme value)
{
_settingsService.ThemeType = value;
}
/// <summary>
/// The OnTitleBarTypeChanged
/// </summary>
/// <param name="value">The value<see cref="TitleBarType"/></param>
partial void OnTitleBarTypeChanged(TitleBarType value)
{
_settingsService.TitleBarType = value;
RootGridMargin = new Thickness(0, value.GetHeight(), 0, 0);
}
#endregion
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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>>
{
[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()
{
// 打开设置窗口
WindowHelper.OpenSettingsWindow();
}
[RelayCommand]
private void ExitApp()
{
// 退出应用程序
App.Current.Exit();
}
[RelayCommand]
private void UnlockWindow()
{
var window = WindowHelper.GetWindowByFramePageType(typeof(LyricsPage));
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

@@ -51,6 +51,30 @@
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button
x:Name="ClickThroughButton"
x:Uid="HostWindowClickThroughButton"
Command="{x:Bind ViewModel.LockWindowCommand}"
Style="{StaticResource TitleBarButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="LockToolTip" x:Uid="HostWindowLockToolTip" />
</ToolTipService.ToolTip>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction
PropertyName="IsOpen"
TargetObject="{x:Bind LockToolTip}"
Value="True" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction
PropertyName="IsOpen"
TargetObject="{x:Bind LockToolTip}"
Value="False" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button x:Name="MoreButton" Style="{StaticResource TitleBarButtonStyle}">
<Grid>
<FontIcon
@@ -86,6 +110,11 @@
x:Uid="HostWindowDockFlyoutItem"
Command="{x:Bind ViewModel.ToggleDockModeCommand}"
IsChecked="{x:Bind ViewModel.IsDockMode, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="DesktopFlyoutItem"
x:Uid="HostWindowDesktopFlyoutItem"
Command="{x:Bind ViewModel.ToggleDesktopModeCommand}"
IsChecked="{x:Bind ViewModel.IsDesktopMode, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="MiniFlyoutItem"
x:Uid="BaseWindowMiniFlyoutItem"

View File

@@ -1,3 +1,5 @@
// 2025/6/23 by Zhe Fang
using System;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
@@ -21,57 +23,48 @@ 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.
/// An empty window that can be used on its own or navigated to within a Frame
/// </summary>
public sealed partial class HostWindow : Window
{
public HostWindowViewModel ViewModel { get; private set; } =
Ioc.Default.GetRequiredService<HostWindowViewModel>();
#region Fields
/// <summary>
/// Defines the _settingsService
/// </summary>
private readonly ISettingsService _settingsService =
Ioc.Default.GetRequiredService<ISettingsService>();
public HostWindow(bool alwaysOnTop = false, bool clickThrough = false)
#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()
{
this.InitializeComponent();
AppWindow.Changed += AppWindow_Changed;
AppWindow.Closing += AppWindow_Closing;
this.HideSystemTitleBarAndSetCustomTitleBar(TopCommandGrid);
if (clickThrough)
this.SetExtendedWindowStyle(
ExtendedWindowStyle.Transparent | ExtendedWindowStyle.Layered
);
if (alwaysOnTop)
((OverlappedPresenter)AppWindow.Presenter).IsAlwaysOnTop = true;
}
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
if (args.DidPresenterChange)
UpdateTitleBarWindowButtonsVisibility();
}
public void Navigate(Type type)
{
RootFrame.Navigate(type);
}
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
private void CloseOrExit()
{
if (RootFrame.SourcePageType == typeof(LyricsPage))
{
Application.Current.Exit();
App.Current.Exit();
}
else
{
@@ -79,117 +72,72 @@ namespace BetterLyrics.WinUI3.Views
}
}
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
private void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
{
if (AppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.Maximize();
}
args.Cancel = true;
CloseOrExit();
}
private void MinimiseButton_Click(object sender, RoutedEventArgs e)
#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)
{
if (AppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.Minimize();
}
}
private void RestoreButton_Click(object sender, RoutedEventArgs e)
{
if (AppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.Restore();
}
}
private void UpdateTitleBarWindowButtonsVisibility()
{
switch (AppWindow.Presenter.Kind)
{
case AppWindowPresenterKind.Default:
break;
case AppWindowPresenterKind.CompactOverlay:
MinimiseButton.Visibility =
MaximiseButton.Visibility =
RestoreButton.Visibility =
AOTFlyoutItem.Visibility =
FullScreenFlyoutItem.Visibility =
DockFlyoutItem.Visibility =
Visibility.Collapsed;
break;
case AppWindowPresenterKind.FullScreen:
MinimiseButton.Visibility =
MaximiseButton.Visibility =
RestoreButton.Visibility =
AOTFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
DockFlyoutItem.Visibility =
Visibility.Collapsed;
FullScreenFlyoutItem.IsChecked = true;
break;
case AppWindowPresenterKind.Overlapped:
DockFlyoutItem.Visibility = Visibility.Visible;
var overlappedPresenter = (OverlappedPresenter)AppWindow.Presenter;
if (DockFlyoutItem.IsChecked)
{
MinimiseButton.Visibility =
MaximiseButton.Visibility =
RestoreButton.Visibility =
AOTFlyoutItem.Visibility =
FullScreenFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
Visibility.Collapsed;
}
else
{
MinimiseButton.Visibility =
AOTFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
FullScreenFlyoutItem.Visibility =
Visibility.Visible;
FullScreenFlyoutItem.IsChecked = false;
AOTFlyoutItem.IsChecked = overlappedPresenter.IsAlwaysOnTop;
if (overlappedPresenter.State == OverlappedPresenterState.Maximized)
{
MaximiseButton.Visibility = Visibility.Collapsed;
RestoreButton.Visibility = Visibility.Visible;
}
else if (overlappedPresenter.State == OverlappedPresenterState.Restored)
{
MaximiseButton.Visibility = Visibility.Visible;
RestoreButton.Visibility = Visibility.Collapsed;
}
}
TopCommandGrid.Opacity = 0;
break;
default:
break;
}
}
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);
}
}
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();
}
/// <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();
}
/// <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)
@@ -209,6 +157,24 @@ 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)
{
presenter.Maximize();
}
}
/// <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)
@@ -221,13 +187,67 @@ namespace BetterLyrics.WinUI3.Views
}
}
private void SettingsMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
/// <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)
{
WindowHelper.OpenSettingsWindow();
if (AppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.Minimize();
}
}
private void TopCommandGrid_PointerMoved(object sender, PointerRoutedEventArgs e) { }
/// <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)
{
presenter.Restore();
}
}
/// <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);
@@ -248,5 +268,122 @@ 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();
}
/// <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)
{
case AppWindowPresenterKind.Default:
break;
case AppWindowPresenterKind.CompactOverlay:
MinimiseButton.Visibility =
MaximiseButton.Visibility =
RestoreButton.Visibility =
AOTFlyoutItem.Visibility =
DesktopFlyoutItem.Visibility =
ClickThroughButton.Visibility =
FullScreenFlyoutItem.Visibility =
DockFlyoutItem.Visibility =
Visibility.Collapsed;
break;
case AppWindowPresenterKind.FullScreen:
MinimiseButton.Visibility =
MaximiseButton.Visibility =
RestoreButton.Visibility =
AOTFlyoutItem.Visibility =
ClickThroughButton.Visibility =
DesktopFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
DockFlyoutItem.Visibility =
Visibility.Collapsed;
FullScreenFlyoutItem.IsChecked = true;
break;
case AppWindowPresenterKind.Overlapped:
DockFlyoutItem.Visibility = Visibility.Visible;
var overlappedPresenter = (OverlappedPresenter)AppWindow.Presenter;
if (DockFlyoutItem.IsChecked)
{
MinimiseButton.Visibility =
MaximiseButton.Visibility =
RestoreButton.Visibility =
AOTFlyoutItem.Visibility =
DesktopFlyoutItem.Visibility =
ClickThroughButton.Visibility =
FullScreenFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
Visibility.Collapsed;
}
else if (DesktopFlyoutItem.IsChecked)
{
MinimiseButton.Visibility =
MaximiseButton.Visibility =
RestoreButton.Visibility =
DockFlyoutItem.Visibility =
AOTFlyoutItem.Visibility =
FullScreenFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
Visibility.Collapsed;
ClickThroughButton.Visibility = Visibility.Visible;
}
else
{
MinimiseButton.Visibility =
AOTFlyoutItem.Visibility =
DesktopFlyoutItem.Visibility =
DockFlyoutItem.Visibility =
MiniFlyoutItem.Visibility =
FullScreenFlyoutItem.Visibility =
Visibility.Visible;
FullScreenFlyoutItem.IsChecked = false;
ClickThroughButton.Visibility = Visibility.Collapsed;
AOTFlyoutItem.IsChecked = overlappedPresenter.IsAlwaysOnTop;
if (overlappedPresenter.State == OverlappedPresenterState.Maximized)
{
MaximiseButton.Visibility = Visibility.Collapsed;
RestoreButton.Visibility = Visibility.Visible;
}
else if (overlappedPresenter.State == OverlappedPresenterState.Restored)
{
MaximiseButton.Visibility = Visibility.Visible;
RestoreButton.Visibility = Visibility.Collapsed;
}
}
TopCommandGrid.Opacity = 0;
break;
default:
break;
}
}
#endregion
private void ClickThroughButton_Click(object sender, RoutedEventArgs e)
{
this.SetExtendedWindowStyle(
ExtendedWindowStyle.Transparent | ExtendedWindowStyle.Layered
);
}
}
}

View File

@@ -14,6 +14,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:media="using:CommunityToolkit.WinUI.Media"
xmlns:renderer="using:BetterLyrics.WinUI3.Renderer"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
@@ -93,45 +94,52 @@
Grid.Row="1"
SizeChanged="CoverArea_SizeChanged">
<Grid
x:Name="CoverImageGrid"
CornerRadius="{x:Bind ViewModel.CoverImageGridCornerRadius, Mode=OneWay}"
SizeChanged="CoverImageGrid_SizeChanged">
<Image
x:Name="CoverImage"
Source="{x:Bind ViewModel.CoverImage, Mode=OneWay}"
Stretch="Uniform">
<Image.Resources>
<Storyboard x:Key="CoverIamgeFadeInStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CoverImage" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="CoverIamgeFadeOutStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CoverImage" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Image.Resources>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource CoverIamgeFadeOutStoryboard}" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource CoverIamgeFadeInStoryboard}" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Image>
<Grid x:Name="CoverImageGrid" SizeChanged="CoverImageGrid_SizeChanged">
<Grid CornerRadius="{x:Bind ViewModel.CoverImageGridCornerRadius, Mode=OneWay}">
<Image
x:Name="CoverImage"
Source="{x:Bind ViewModel.CoverImage, Mode=OneWay}"
Stretch="Uniform">
<Image.Resources>
<Storyboard x:Key="CoverIamgeFadeInStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CoverImage" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="CoverIamgeFadeOutStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CoverImage" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Image.Resources>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource CoverIamgeFadeOutStoryboard}" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource CoverIamgeFadeInStoryboard}" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Image>
</Grid>
<ui:Effects.Shadow>
<media:AttachedCardShadow
BlurRadius="32"
CornerRadius="{x:Bind ViewModel.CoverImageGridCornerRadius, Mode=OneWay, Converter={StaticResource CornerRadiusToDoubleConverter}}"
InnerContentClipMode="CompositionMaskBrush"
Opacity="0.1" />
</ui:Effects.Shadow>
</Grid>
</Grid>
<!-- Title and artist -->
@@ -179,7 +187,7 @@
x:Name="TitleTextBlock"
Behavior="Bouncing"
FontSize="{StaticResource TitleTextBlockFontSize}"
FontWeight="SemiBold"
FontWeight="Bold"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Text="{x:Bind ViewModel.SongInfo.Title, Mode=OneWay}" />
</controls:OpacityMaskView>
@@ -222,7 +230,6 @@
<labs:MarqueeText
Behavior="Bouncing"
FontSize="{StaticResource SubtitleTextBlockFontSize}"
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Opacity="0.5"
Text="{x:Bind ViewModel.SongInfo.Artist, Mode=OneWay}" />
@@ -343,6 +350,8 @@
IsOpen="{x:Bind ViewModel.IsWelcomeTeachingTipOpen, Mode=OneWay}"
Target="{x:Bind SettingsButton}" />
<uc:SystemTray />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LayoutStates">

View File

@@ -1,13 +1,13 @@
using System;
using BetterLyrics.WinUI3.Helper;
// 2025/6/23 by Zhe Fang
using System;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
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.
@@ -15,12 +15,15 @@ using WinUIEx;
namespace BetterLyrics.WinUI3.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// An empty page that can be used on its own or navigated to within a Frame
/// </summary>
public sealed partial class LyricsPage : Page
{
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LyricsPage"/> class.
/// </summary>
public LyricsPage()
{
this.InitializeComponent();
@@ -28,19 +31,24 @@ namespace BetterLyrics.WinUI3.Views
DataContext = Ioc.Default.GetService<LyricsPageViewModel>();
}
private void WelcomeTeachingTip_Closed(TeachingTip sender, TeachingTipClosedEventArgs args)
{
ViewModel.IsFirstRun = false;
}
#endregion
private void CoverArea_SizeChanged(object sender, SizeChangedEventArgs e)
{
CoverImageGrid.Width = CoverImageGrid.Height = Math.Min(
CoverArea.ActualWidth,
CoverArea.ActualHeight
);
}
#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
@@ -50,6 +58,11 @@ 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
@@ -59,14 +72,49 @@ namespace BetterLyrics.WinUI3.Views
BottomCommandGrid.Opacity = 0;
}
private void LyricsPlaceholderGrid_SizeChanged(object sender, SizeChangedEventArgs e)
/// <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)
{
ViewModel.LimitedLineWidth = e.NewSize.Width;
CoverImageGrid.Width = CoverImageGrid.Height = Math.Min(
CoverArea.ActualWidth,
CoverArea.ActualHeight
);
}
/// <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
}
}

View File

@@ -9,6 +9,7 @@
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:vm="using:BetterLyrics.WinUI3.ViewModels"
mc:Ignorable="d">
@@ -418,12 +419,6 @@
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon Glyph=&#xE727;}">
<controls:SettingsCard.Description>
<StackPanel>
<TextBlock x:Uid="SettingsPageLyricsBlurHighGPUUsage" Foreground="{ThemeResource SystemFillColorCautionBrush}" />
<TextBlock x:Uid="SettingsPageLyricsBlurAmountSideEffect" />
</StackPanel>
</controls:SettingsCard.Description>
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
@@ -450,7 +445,6 @@
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLyricsGlowEffectScope" IsEnabled="{x:Bind LyricsSettingsControlViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind LyricsSettingsControlViewModel.LyricsGlowEffectScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsGlowEffectScopeWholeLyrics" />
<ComboBoxItem x:Uid="SettingsPageLyricsGlowEffectScopeCurrentLine" />
<ComboBoxItem x:Uid="SettingsPageLyricsGlowEffectScopeCurrentChar" />
</ComboBox>
@@ -480,6 +474,9 @@
Command="{x:Bind ViewModel.LaunchProjectGitHubPageCommand}"
HeaderIcon="{ui:FontIcon Glyph=&#xE943;}"
IsClickEnabled="True" />
<uc:DependenciesSettingsExpander />
</StackPanel>
</controls:Case>
@@ -488,8 +485,11 @@
<controls:SettingsCard x:Uid="SettingsPageMockMusicPlaying">
<Button x:Uid="SettingsPagePlayingMockMusicButton" Command="{x:Bind ViewModel.PlayTestingMusicTaskCommand}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLog">
<Button x:Uid="SettingsPageOpenLogFolderButton" Command="{x:Bind ViewModel.OpenLogFolderCommand}" />
<controls:SettingsCard x:Uid="SettingsPageCache">
<Button x:Uid="SettingsPageOpenLogFolderButton" Command="{x:Bind ViewModel.OpenCacheFolderCommand}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDebugOverlay">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDebugOverlayEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
</controls:Case>

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
// 2025/6/23 by Zhe Fang
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
@@ -7,6 +8,10 @@ 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.
@@ -14,44 +19,74 @@ using Microsoft.UI.Xaml.Controls;
namespace BetterLyrics.WinUI3.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// An empty page that can be used on its own or navigated to within a Frame
/// </summary>
public sealed partial class SettingsPage : Page
{
public SettingsViewModel ViewModel => (SettingsViewModel)DataContext;
public LyricsSettingsControlViewModel LyricsSettingsControlViewModel =>
Ioc.Default.GetRequiredService<LyricsSettingsControlViewModel>();
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SettingsPage"/> class.
/// </summary>
public SettingsPage()
{
this.InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<SettingsViewModel>();
}
private void SettingsPageOpenPathButton_Click(
object sender,
Microsoft.UI.Xaml.RoutedEventArgs e
)
#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;
#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)
{
ViewModel.OpenMusicFolder((LocalLyricsFolder)(sender as HyperlinkButton)!.Tag);
if (sender is ToggleSwitch toggleSwitch)
{
if (toggleSwitch.DataContext is LocalLyricsFolder localLyricsFolder)
{
ViewModel.ToggleLocalLyricsFolder(localLyricsFolder);
}
}
}
private void SettingsPageRemovePathButton_Click(
object sender,
Microsoft.UI.Xaml.RoutedEventArgs e
/// <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
)
{
ViewModel.RemoveFolderAsync((LocalLyricsFolder)(sender as HyperlinkButton)!.Tag);
}
private void NavView_SelectionChanged(
NavigationView sender,
NavigationViewSelectionChangedEventArgs args
)
{
ViewModel.NavViewSelectedItemTag = (args.SelectedItem as NavigationViewItem)!.Tag;
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)
@@ -63,23 +98,45 @@ namespace BetterLyrics.WinUI3.Views
}
}
private void LyricsSearchProvidersListView_DragItemsCompleted(
ListViewBase sender,
DragItemsCompletedEventArgs args
/// <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
)
{
ViewModel.OnLyricsSearchProvidersReordered();
ViewModel.NavViewSelectedItemTag = (args.SelectedItem as NavigationViewItem)!.Tag;
}
private void LocalLyricsFolderToggleSwitch_Toggled(object sender, RoutedEventArgs e)
/// <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
)
{
if (sender is ToggleSwitch toggleSwitch)
{
if (toggleSwitch.DataContext is LocalLyricsFolder localLyricsFolder)
{
ViewModel.ToggleLocalLyricsFolder(localLyricsFolder);
}
}
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
)
{
ViewModel.RemoveFolderAsync((LocalLyricsFolder)(sender as HyperlinkButton)!.Tag);
}
#endregion
}
}