mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-13 03:34:55 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c50c180aa0 | ||
|
|
2f99d44b86 | ||
|
|
03386e72b2 | ||
|
|
54ba0a0c85 | ||
|
|
7bf8b2894d | ||
|
|
875da76e6b | ||
|
|
547ca6d631 | ||
|
|
60fb088bea | ||
|
|
3a89236af0 | ||
|
|
7d16bdbc88 | ||
|
|
812d23a101 | ||
|
|
4381a34191 | ||
|
|
6e21e5636b | ||
|
|
5e74468194 | ||
|
|
ff65429b16 | ||
|
|
ab03870b6a | ||
|
|
23bafc4d75 | ||
|
|
3bdce0d975 | ||
|
|
454edbeaba | ||
|
|
1e7e63032a |
@@ -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.7.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
@@ -90,6 +91,8 @@
|
||||
|
||||
<!-- Dimensions -->
|
||||
|
||||
<!-- Fonts -->
|
||||
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.Services.BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -14,29 +18,18 @@ using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
using Serilog;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
/// <summary>
|
||||
/// 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; }
|
||||
public static ResourceLoader? ResourceLoader { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
@@ -57,30 +50,23 @@ namespace BetterLyrics.WinUI3
|
||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||
}
|
||||
|
||||
private void CurrentDomain_FirstChanceException(
|
||||
object? sender,
|
||||
System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e
|
||||
)
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
}
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindow>();
|
||||
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
|
||||
private void TaskScheduler_UnobservedTaskException(
|
||||
object? sender,
|
||||
UnobservedTaskExceptionEventArgs e
|
||||
)
|
||||
{
|
||||
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
string[] commandLineArguments = Environment.GetCommandLineArgs();
|
||||
if (commandLineArguments.Length > 1)
|
||||
{
|
||||
commandLineArguments = commandLineArguments.Skip(1).ToArray();
|
||||
if (commandLineArguments.First() == AppInfo.UnlockWindowTag)
|
||||
{
|
||||
lyricsWindow.AutoSelectLyricsMode(AutoStartWindowType.DesktopMode, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
lyricsWindow.AutoSelectLyricsMode();
|
||||
}
|
||||
|
||||
private void CurrentDomain_UnhandledException(
|
||||
object sender,
|
||||
System.UnhandledExceptionEventArgs e
|
||||
)
|
||||
{
|
||||
_logger.LogError(e.ExceptionObject.ToString(), "CurrentDomain_UnhandledException");
|
||||
}
|
||||
|
||||
private static void ConfigureServices()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
@@ -102,8 +88,10 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<IMusicSearchService, MusicSearchService>()
|
||||
.AddSingleton<ILibWatcherService, LibWatcherService>()
|
||||
// ViewModels
|
||||
.AddTransient<HostWindowViewModel>()
|
||||
.AddSingleton<SettingsViewModel>()
|
||||
.AddSingleton<LyricsWindowViewModel>()
|
||||
.AddSingleton<SettingsWindowViewModel>()
|
||||
.AddSingleton<SystemTrayViewModel>()
|
||||
.AddSingleton<SettingsPageViewModel>()
|
||||
.AddSingleton<LyricsPageViewModel>()
|
||||
.AddSingleton<LyricsRendererViewModel>()
|
||||
.AddSingleton<LyricsSettingsControlViewModel>()
|
||||
@@ -111,22 +99,25 @@ namespace BetterLyrics.WinUI3
|
||||
);
|
||||
}
|
||||
|
||||
private void App_UnhandledException(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.UnhandledExceptionEventArgs e
|
||||
)
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
_logger.LogError(e.Exception, "App_UnhandledException");
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
private void CurrentDomain_FirstChanceException(object? sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
|
||||
{
|
||||
WindowHelper.OpenLyricsWindow();
|
||||
//_logger.LogError(e.Exception, "CurrentDomain_FirstChanceException");
|
||||
}
|
||||
|
||||
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
_logger.LogError(e.ExceptionObject.ToString(), "CurrentDomain_UnhandledException");
|
||||
}
|
||||
|
||||
private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
//_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -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\SystemTray.xaml" />
|
||||
<None Remove="Views\SettingsWindow.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" />
|
||||
@@ -42,17 +50,16 @@
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
<PackageReference Include="WinUIEx" Version="2.5.1" />
|
||||
<PackageReference Include="z440.atl.core" Version="6.26.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\AI - 甜度爆表.mp3">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
|
||||
<PackageReference Include="WinUIEx" Version="2.6.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\InAppLyricsRenderer.xaml">
|
||||
@@ -64,14 +71,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="Views\SettingsWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\SystemTray.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.SystemTray"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tb="using:H.NotifyIcon"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<tb:TaskbarIcon
|
||||
x:Name="TrayIcon"
|
||||
x:FieldModifier="public"
|
||||
ContextMenuMode="SecondWindow"
|
||||
IconSource="ms-appx:///Assets/Logo.ico"
|
||||
NoLeftClickDelay="True"
|
||||
ToolTipText="{x:Bind ViewModel.ToolTipText, Mode=OneWay}">
|
||||
<tb:TaskbarIcon.ContextFlyout>
|
||||
<MenuFlyout
|
||||
AreOpenCloseAnimationsEnabled="True"
|
||||
LightDismissOverlayMode="On"
|
||||
ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||
<MenuFlyoutItem x:Uid="SystemTraySettings" Command="{x:Bind ViewModel.OpenSettingsCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTrayExit" Command="{x:Bind ViewModel.ExitAppCommand}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SystemTrayUnlock"
|
||||
Command="{x:Bind ViewModel.UnlockWindowCommand}"
|
||||
Visibility="{x:Bind ViewModel.IsLyricsWindowLocked, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</MenuFlyout>
|
||||
</tb:TaskbarIcon.ContextFlyout>
|
||||
</tb:TaskbarIcon>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,17 @@
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
public sealed partial class SystemTray : UserControl
|
||||
{
|
||||
public SystemTrayViewModel ViewModel => (SystemTrayViewModel)DataContext;
|
||||
|
||||
public SystemTray()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetService<SystemTrayViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
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
|
||||
public partial class ColorToBrushConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
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)
|
||||
{
|
||||
return (double)cornerRadius.TopLeft;
|
||||
}
|
||||
return .0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
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
|
||||
internal partial class EnumToIntConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
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
|
||||
public partial class IntToCornerRadius : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
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
|
||||
public partial class LyricsSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
@@ -16,22 +14,16 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLocalLrcFile"
|
||||
),
|
||||
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLocalMusicFile"
|
||||
),
|
||||
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLrcLib"
|
||||
),
|
||||
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderEslrcFile"
|
||||
),
|
||||
LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderTtmlFile"
|
||||
),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null),
|
||||
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString("LyricsSearchProviderLrcLib"),
|
||||
LyricsSearchProvider.QQ => App.ResourceLoader!.GetString("LyricsSearchProviderQQ"),
|
||||
LyricsSearchProvider.Netease => App.ResourceLoader!.GetString("LyricsSearchProviderNetease"),
|
||||
LyricsSearchProvider.Kugou => App.ResourceLoader!.GetString("LyricsSearchProviderKugou"),
|
||||
LyricsSearchProvider.AmllTtmlDb => App.ResourceLoader!.GetString("LyricsSearchProviderAmllTtmlDb"),
|
||||
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
|
||||
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
|
||||
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),
|
||||
LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString("LyricsSearchProviderTtmlFile"),
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
return "";
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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
|
||||
public partial class MatchedLocalFilesPathToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
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
|
||||
{
|
||||
@@ -10,5 +6,6 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
StandardMode,
|
||||
DockMode,
|
||||
DesktopMode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
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
|
||||
{
|
||||
|
||||
15
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/EasingType.cs
Normal file
15
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/EasingType.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum EasingType
|
||||
{
|
||||
EaseInOutQuad,
|
||||
EaseInQuad,
|
||||
EaseOutQuad,
|
||||
EaseInOutExpo,
|
||||
Linear,
|
||||
SmoothStep,
|
||||
SmootherStep,
|
||||
}
|
||||
}
|
||||
@@ -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 +6,9 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsType
|
||||
public enum LineMaskType
|
||||
{
|
||||
InAppLyrics,
|
||||
DesktopLyrics,
|
||||
Glow,
|
||||
Highlight,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LineRenderingType
|
||||
{
|
||||
UntilCurrentChar,
|
||||
CurrentCharOnly,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
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
|
||||
{
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
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
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsDisplayType
|
||||
{
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
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
|
||||
{
|
||||
public enum LyricsFontColorType
|
||||
{
|
||||
Default,
|
||||
Dominant,
|
||||
AdaptiveColored,
|
||||
AdaptiveGrayed,
|
||||
Custom,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
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.Text;
|
||||
using Windows.UI.Text;
|
||||
|
||||
@@ -40,11 +38,7 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
LyricsFontWeight.ExtraBold => FontWeights.ExtraBold,
|
||||
LyricsFontWeight.Black => FontWeights.Black,
|
||||
LyricsFontWeight.ExtraBlack => FontWeights.ExtraBlack,
|
||||
LyricsFontWeight _ => throw new ArgumentOutOfRangeException(
|
||||
nameof(weight),
|
||||
weight,
|
||||
null
|
||||
),
|
||||
LyricsFontWeight _ => throw new ArgumentOutOfRangeException(nameof(weight), weight, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
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
|
||||
{
|
||||
@@ -11,38 +7,22 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
Lrc,
|
||||
Eslrc,
|
||||
Ttml,
|
||||
Qrc,
|
||||
Krc,
|
||||
NotSpecified,
|
||||
}
|
||||
|
||||
public static class LyricsFormatExtensions
|
||||
{
|
||||
public static string ToFileExtension(this LyricsFormat format)
|
||||
public static LyricsFormat? DetectFormat(this string content)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
LyricsFormat.Lrc => ".lrc",
|
||||
LyricsFormat.Eslrc => ".eslrc",
|
||||
LyricsFormat.Ttml => ".ttml",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null),
|
||||
};
|
||||
}
|
||||
|
||||
public static LyricsFormat? Detect(string content)
|
||||
{
|
||||
if (
|
||||
content.StartsWith("<?xml")
|
||||
&& System.Text.RegularExpressions.Regex.IsMatch(content, @"<tt(:\w+)?\b")
|
||||
)
|
||||
if (content.StartsWith("<?xml") && System.Text.RegularExpressions.Regex.IsMatch(content, @"<tt(:\w+)?\b"))
|
||||
{
|
||||
return LyricsFormat.Ttml;
|
||||
}
|
||||
// 检测标准LRC和增强型LRC
|
||||
else if (
|
||||
System.Text.RegularExpressions.Regex.IsMatch(content, @"\[\d{1,2}:\d{2}")
|
||||
|| System.Text.RegularExpressions.Regex.IsMatch(
|
||||
content,
|
||||
@"<\d{1,2}:\d{2}\.\d{2,3}>"
|
||||
)
|
||||
)
|
||||
else if (System.Text.RegularExpressions.Regex.IsMatch(content, @"\[\d{1,2}:\d{2}")
|
||||
|| System.Text.RegularExpressions.Regex.IsMatch(content, @"<\d{1,2}:\d{2}\.\d{2,3}>"))
|
||||
{
|
||||
return LyricsFormat.Lrc;
|
||||
}
|
||||
@@ -51,5 +31,18 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
_ => ".*",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsGlowEffectScope
|
||||
{
|
||||
WholeLyrics,
|
||||
CurrentLine,
|
||||
CurrentChar,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsHighlightType
|
||||
{
|
||||
LineByLine,
|
||||
CharByChar,
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsPlayingState
|
||||
{
|
||||
/// <summary>
|
||||
/// Not played yet, will be playing in the future
|
||||
/// </summary>
|
||||
NotPlayed,
|
||||
|
||||
/// <summary>
|
||||
/// Playing
|
||||
/// </summary>
|
||||
Playing,
|
||||
|
||||
/// <summary>
|
||||
/// Has already played
|
||||
/// </summary>
|
||||
Played,
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,65 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsSearchProvider
|
||||
{
|
||||
QQ,
|
||||
Kugou,
|
||||
Netease,
|
||||
LrcLib,
|
||||
AmllTtmlDb,
|
||||
LocalMusicFile,
|
||||
LocalLrcFile,
|
||||
LocalEslrcFile,
|
||||
LocalTtmlFile,
|
||||
}
|
||||
|
||||
public static class LyricsSearchProviderExtensions
|
||||
{
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsStatus
|
||||
{
|
||||
NotFound,
|
||||
Found,
|
||||
Loading,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum MusicSearchMatchMode
|
||||
{
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum TitleBarType
|
||||
{
|
||||
Compact,
|
||||
Extended,
|
||||
}
|
||||
|
||||
public static class TitleBarTypeExtensions
|
||||
{
|
||||
public static double GetHeight(this TitleBarType titleBarType)
|
||||
{
|
||||
return titleBarType switch
|
||||
{
|
||||
TitleBarType.Compact => 32.0,
|
||||
TitleBarType.Extended => 48.0,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(titleBarType),
|
||||
titleBarType,
|
||||
null
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum WindowColorSampleMode
|
||||
{
|
||||
BelowWindow,
|
||||
WindowArea,
|
||||
WindowEdge,
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
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
|
||||
{
|
||||
public class IsPlayingChangedEventArgs(bool isPlaying) : EventArgs
|
||||
{
|
||||
public bool IsPlaying { get; set; } = isPlaying;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,10 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class LibChangedEventArgs : EventArgs
|
||||
public class LibChangedEventArgs(string folder, string filePath, WatcherChangeTypes changeType) : EventArgs
|
||||
{
|
||||
public string Folder { get; }
|
||||
public string FilePath { get; }
|
||||
public WatcherChangeTypes ChangeType { get; }
|
||||
|
||||
public LibChangedEventArgs(string folder, string filePath, WatcherChangeTypes changeType)
|
||||
{
|
||||
Folder = folder;
|
||||
FilePath = filePath;
|
||||
ChangeType = changeType;
|
||||
}
|
||||
public WatcherChangeTypes ChangeType { get; } = changeType;
|
||||
public string FilePath { get; } = filePath;
|
||||
public string Folder { get; } = folder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
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
|
||||
{
|
||||
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
|
||||
{
|
||||
public TimeSpan Position { get; set; } = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
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.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
@@ -10,5 +8,5 @@ namespace BetterLyrics.WinUI3.Events
|
||||
public class SongInfoChangedEventArgs(SongInfo? songInfo) : EventArgs
|
||||
{
|
||||
public SongInfo? SongInfo { get; set; } = songInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class AnimationHelper
|
||||
{
|
||||
public const int DebounceDefaultDuration = 200;
|
||||
public const int StackedNotificationsShowingDuration = 3900;
|
||||
public const int StoryboardDefaultDuration = 200;
|
||||
public const int DebounceDefaultDuration = 200;
|
||||
}
|
||||
|
||||
public class ValueTransition<T>
|
||||
where T : struct
|
||||
{
|
||||
private T _currentValue;
|
||||
private float _durationSeconds;
|
||||
private readonly EasingType? _easingType;
|
||||
private Func<T, T, float, T> _interpolator;
|
||||
private bool _isTransitioning;
|
||||
private float _progress;
|
||||
private T _startValue;
|
||||
private T _targetValue;
|
||||
private float _progress;
|
||||
private float _durationSeconds;
|
||||
private bool _isTransitioning;
|
||||
private Func<T, T, float, T> _interpolator;
|
||||
|
||||
public ValueTransition(
|
||||
T initialValue,
|
||||
float durationSeconds,
|
||||
Func<T, T, float, T> interpolator
|
||||
)
|
||||
public bool IsTransitioning => _isTransitioning;
|
||||
public T Value => _currentValue;
|
||||
|
||||
public ValueTransition(T initialValue, float durationSeconds, Func<T, T, float, T>? interpolator = null, EasingType? easingType = null)
|
||||
{
|
||||
_currentValue = initialValue;
|
||||
_startValue = initialValue;
|
||||
@@ -32,11 +35,41 @@ 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;
|
||||
public bool IsTransitioning => _isTransitioning;
|
||||
public void JumpTo(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 1f;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
public void Reset(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 0f;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
public void StartTransition(T targetValue)
|
||||
{
|
||||
@@ -51,8 +84,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public void Update(TimeSpan elapsedTime)
|
||||
{
|
||||
if (!_isTransitioning)
|
||||
return;
|
||||
if (!_isTransitioning) return;
|
||||
|
||||
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
|
||||
if (_progress >= 1f)
|
||||
@@ -67,13 +99,45 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset(T value)
|
||||
private Func<T, T, float, T> GetInterpolatorByEasingType(EasingType type)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 0f;
|
||||
_isTransitioning = false;
|
||||
if (typeof(T) == typeof(float))
|
||||
{
|
||||
return (start, end, progress) =>
|
||||
{
|
||||
float s = (float)(object)start;
|
||||
float e = (float)(object)end;
|
||||
float t = progress;
|
||||
switch (type)
|
||||
{
|
||||
case EasingType.EaseInOutExpo:
|
||||
t = EasingHelper.EaseInOutExpo(t);
|
||||
break;
|
||||
case EasingType.EaseInOutQuad:
|
||||
t = EasingHelper.EaseInOutQuad(t);
|
||||
break;
|
||||
case EasingType.EaseInQuad:
|
||||
t = EasingHelper.EaseInQuad(t);
|
||||
break;
|
||||
case EasingType.EaseOutQuad:
|
||||
t = EasingHelper.EaseOutQuad(t);
|
||||
break;
|
||||
case EasingType.Linear:
|
||||
t = EasingHelper.Linear(t);
|
||||
break;
|
||||
case EasingType.SmoothStep:
|
||||
t = EasingHelper.SmoothStep(t);
|
||||
break;
|
||||
case EasingType.SmootherStep:
|
||||
t = EasingHelper.SmootherStep(t);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (T)(object)(s + (e - s) * t);
|
||||
};
|
||||
}
|
||||
throw new NotSupportedException($"Easing type {type} is not supported for type {typeof(T)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
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 System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
|
||||
public static class AppInfo
|
||||
{
|
||||
// App Metadata
|
||||
public const string AppName = "BetterLyrics";
|
||||
public const string AppDisplayName = "Better Lyrics";
|
||||
public const string AppAuthor = "Zhe Fang";
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
public const string AppDisplayName = "Better Lyrics";
|
||||
public const string AppName = "BetterLyrics";
|
||||
public static string AppVersion
|
||||
{
|
||||
get
|
||||
@@ -27,35 +24,47 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
// Environment Info
|
||||
public static bool IsDebug =>
|
||||
#if DEBUG
|
||||
true;
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
|
||||
|
||||
public const string UnlockWindowTag = "UnlockWindow";
|
||||
|
||||
public static string AmllTtmlDbIndexPath => Path.Combine(CacheFolder, "amll-ttml-db-index.json");
|
||||
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
|
||||
|
||||
// Base Folders
|
||||
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
|
||||
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
|
||||
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
|
||||
|
||||
// Data Files
|
||||
|
||||
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
|
||||
public static string KugouLyricsCacheDirectory => Path.Combine(CacheFolder, "kugou-lyrics");
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
public static string OnlineLyricsCacheDirectory =>
|
||||
Path.Combine(CacheFolder, "online-lyrics");
|
||||
|
||||
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
|
||||
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
|
||||
public static string LrcLibLyricsCacheDirectory => Path.Combine(CacheFolder, "lrclib-lyrics");
|
||||
public static string NeteaseLyricsCacheDirectory => Path.Combine(CacheFolder, "netease-lyrics");
|
||||
public static string QQLyricsCacheDirectory => Path.Combine(CacheFolder, "qq-lyrics");
|
||||
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static async Task<DateTime> GetBuildDate()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var filePath = assembly.Location;
|
||||
if (!File.Exists(filePath))
|
||||
return DateTime.MinValue;
|
||||
|
||||
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
|
||||
// 获取文件基本属性
|
||||
BasicProperties props = await file.GetBasicPropertiesAsync();
|
||||
// 返回修改日期
|
||||
return props.DateModified.DateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
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.Helper
|
||||
{
|
||||
@@ -10,8 +8,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static T? SafeGet<T>(this IList<T> list, int index)
|
||||
{
|
||||
if (list == null || index < 0 || index >= list.Count)
|
||||
return default;
|
||||
if (list == null || index < 0 || index >= list.Count) return default;
|
||||
return list[index];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,51 @@
|
||||
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;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class ColorHelper
|
||||
{
|
||||
public static Windows.UI.Color ToWindowsUIColor(this System.Drawing.Color color)
|
||||
public static ElementTheme GetElementThemeFromBackgroundColor(Color backgroundColor)
|
||||
{
|
||||
return Windows.UI.Color.FromArgb(color.A, color.R, color.G, color.B);
|
||||
// 计算亮度(YIQ公式)
|
||||
double yiq =
|
||||
((backgroundColor.R * 299) + (backgroundColor.G * 587) + (backgroundColor.B * 114))
|
||||
/ 1000.0;
|
||||
return yiq >= 128 ? ElementTheme.Light : ElementTheme.Dark;
|
||||
}
|
||||
|
||||
public static Color GetInterpolatedColor(
|
||||
float progress,
|
||||
Color startColor,
|
||||
Color targetColor
|
||||
)
|
||||
public static Color GetForegroundColor(Color background)
|
||||
{
|
||||
// 转为 HSL
|
||||
var hsl = CommunityToolkit.WinUI.Helpers.ColorHelper.ToHsl(background);
|
||||
double h = hsl.H;
|
||||
double s = hsl.S;
|
||||
double l = hsl.L;
|
||||
|
||||
// 目标亮度与背景错开,但不极端
|
||||
double targetL;
|
||||
if (l >= 0.7)
|
||||
targetL = 0.35; // 背景很亮,前景适中偏暗
|
||||
else if (l <= 0.3)
|
||||
targetL = 0.75; // 背景很暗,前景适中偏亮
|
||||
else
|
||||
targetL = l > 0.5 ? l - 0.35 : l + 0.35; // 其余情况适度错开
|
||||
|
||||
// 保持色相,适当提升饱和度
|
||||
double targetS = Math.Min(1.0, s + 0.2);
|
||||
|
||||
// 转回 Color
|
||||
var fg = CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, targetS, targetL);
|
||||
|
||||
// 保持不透明
|
||||
return Color.FromArgb(255, fg.R, fg.G, fg.B);
|
||||
}
|
||||
|
||||
public static Color GetInterpolatedColor(float progress, Color startColor, Color targetColor)
|
||||
{
|
||||
byte Lerp(byte a, byte b) => (byte)(a + (progress * (b - a)));
|
||||
return Color.FromArgb(
|
||||
@@ -28,5 +55,31 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
Lerp(startColor.B, targetColor.B)
|
||||
);
|
||||
}
|
||||
|
||||
public static Color ToColor(this int argb)
|
||||
{
|
||||
byte a = (byte)(argb >> 24);
|
||||
byte r = (byte)(argb >> 16);
|
||||
byte g = (byte)(argb >> 8);
|
||||
byte b = (byte)argb;
|
||||
|
||||
// 还原非预乘分量
|
||||
if (a == 0)
|
||||
return Color.FromArgb(0, 0, 0, 0);
|
||||
|
||||
// 预乘解码
|
||||
// 这里 a+1 是编码时的分母
|
||||
int ap1 = a + 1;
|
||||
r = (byte)Math.Min(255, (r * 255 + (ap1 / 2)) / ap1);
|
||||
g = (byte)Math.Min(255, (g * 255 + (ap1 / 2)) / ap1);
|
||||
b = (byte)Math.Min(255, (b * 255 + (ap1 / 2)) / ap1);
|
||||
|
||||
return Color.FromArgb(a, r, g, b);
|
||||
}
|
||||
|
||||
public static Color ToColor(this System.Drawing.Color color)
|
||||
{
|
||||
return Color.FromArgb(color.A, color.R, color.G, color.B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,146 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Vanara.PInvoke;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class DesktopModeHelper
|
||||
{
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
private static readonly Dictionary<IntPtr, bool> _clickThroughStates = [];
|
||||
private static readonly Dictionary<IntPtr, bool> _originalTopmostStates = [];
|
||||
private static readonly Dictionary<IntPtr, (double X, double Y, double Width, double Height)> _originalWindowBounds = [];
|
||||
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
|
||||
|
||||
private delegate nint WndProcDelegate(nint hWnd, uint msg, nint wParam, nint lParam);
|
||||
|
||||
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 Enable(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <20><>¼ԭʼ<D4AD><CABC><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
|
||||
var windowManager = WindowManager.Get(window);
|
||||
if (!_originalWindowBounds.ContainsKey(hwnd))
|
||||
{
|
||||
_originalWindowBounds[hwnd] = (
|
||||
windowManager.AppWindow.Position.X,
|
||||
windowManager.AppWindow.Position.Y,
|
||||
windowManager.Width,
|
||||
windowManager.Height
|
||||
);
|
||||
}
|
||||
|
||||
// <20>Ӵ洢<D3B4><E6B4A2><EFBFBD><EFBFBD>ȡĿ<C8A1><C4BF><EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD>λ<EFBFBD><CEBB>
|
||||
int targetWidth = _settingsService.DesktopWindowWidth;
|
||||
int targetHeight = _settingsService.DesktopWindowHeight;
|
||||
int targetX = _settingsService.DesktopWindowLeft;
|
||||
int targetY = _settingsService.DesktopWindowTop;
|
||||
|
||||
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
|
||||
windowManager.AppWindow.MoveAndResize(
|
||||
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
|
||||
);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
|
||||
if (!_originalWindowStyles.ContainsKey(hwnd))
|
||||
_originalWindowStyles[hwnd] = window.GetWindowStyle();
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
|
||||
if (!_originalTopmostStates.ContainsKey(hwnd))
|
||||
_originalTopmostStates[hwnd] = window.GetIsAlwaysOnTop();
|
||||
|
||||
// <20><><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD>ö<EFBFBD>
|
||||
window.SetIsAlwaysOnTop(true);
|
||||
|
||||
window.SetIsShownInSwitchers(false);
|
||||
}
|
||||
|
||||
public static void Lock(Window window)
|
||||
{
|
||||
window.SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ޱ߿<DEB1><DFBF><EFBFBD><EFBFBD><CDB8>
|
||||
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
|
||||
SetClickThrough(window, true);
|
||||
}
|
||||
|
||||
public static void SetClickThrough(Window window, bool enable)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
int exStyle = User32.GetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE);
|
||||
if (enable)
|
||||
{
|
||||
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle | (int)User32.WindowStylesEx.WS_EX_TRANSPARENT | (int)User32.WindowStylesEx.WS_EX_LAYERED);
|
||||
_clickThroughStates[hwnd] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle & ~(int)User32.WindowStylesEx.WS_EX_TRANSPARENT);
|
||||
_clickThroughStates[hwnd] = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unlock(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <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);
|
||||
|
||||
// To recover the system backdrop, we need to reopen the window
|
||||
WindowHelper.RestartApp(AppInfo.UnlockWindowTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class DockHelper
|
||||
{
|
||||
private static readonly HashSet<IntPtr> _registered = [];
|
||||
|
||||
private static readonly Dictionary<IntPtr, RECT> _originalPositions = [];
|
||||
|
||||
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyle = [];
|
||||
|
||||
public static void Disable(Window window)
|
||||
{
|
||||
window.SetIsShownInSwitchers(true);
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
window.SetIsAlwaysOnTop(false);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
window.SetWindowStyle(_originalWindowStyle[hwnd]);
|
||||
_originalWindowStyle.Remove(hwnd);
|
||||
|
||||
if (_originalPositions.TryGetValue(hwnd, out var rect))
|
||||
{
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
|
||||
);
|
||||
_originalPositions.Remove(hwnd);
|
||||
}
|
||||
|
||||
UnregisterAppBar(hwnd);
|
||||
}
|
||||
|
||||
public static void Enable(Window window, int appBarHeight)
|
||||
{
|
||||
window.SetIsShownInSwitchers(false);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
window.SetIsAlwaysOnTop(true);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
if (!_originalWindowStyle.ContainsKey(hwnd))
|
||||
{
|
||||
_originalWindowStyle[hwnd] = window.GetWindowStyle();
|
||||
}
|
||||
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
|
||||
|
||||
if (!_originalPositions.ContainsKey(hwnd))
|
||||
{
|
||||
if (GetWindowRect(hwnd, out var rect))
|
||||
{
|
||||
_originalPositions[hwnd] = rect;
|
||||
}
|
||||
}
|
||||
|
||||
RegisterAppBar(hwnd, appBarHeight);
|
||||
|
||||
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
|
||||
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
screenWidth,
|
||||
appBarHeight,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
|
||||
);
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||
|
||||
#region AppBar registration
|
||||
private const uint ABM_NEW = 0x00000000;
|
||||
private const uint ABM_REMOVE = 0x00000001;
|
||||
private const uint ABM_SETPOS = 0x00000003;
|
||||
private const int ABE_TOP = 1;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct APPBARDATA
|
||||
{
|
||||
public int cbSize;
|
||||
public IntPtr hWnd;
|
||||
public uint uCallbackMessage;
|
||||
public uint uEdge;
|
||||
public RECT rc;
|
||||
public int lParam;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct RECT
|
||||
{
|
||||
public int left,
|
||||
top,
|
||||
right,
|
||||
bottom;
|
||||
}
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true)]
|
||||
private static extern uint SHAppBarMessage(uint dwMessage, ref APPBARDATA pData);
|
||||
|
||||
private static void RegisterAppBar(IntPtr hwnd, int height)
|
||||
{
|
||||
if (_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = Marshal.SizeOf<APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = ABE_TOP,
|
||||
rc = new RECT
|
||||
{
|
||||
left = 0,
|
||||
top = 0,
|
||||
right = GetSystemMetrics(SM_CXSCREEN),
|
||||
bottom = height,
|
||||
},
|
||||
};
|
||||
|
||||
SHAppBarMessage(ABM_NEW, ref abd);
|
||||
SHAppBarMessage(ABM_SETPOS, ref abd);
|
||||
|
||||
_registered.Add(hwnd);
|
||||
}
|
||||
|
||||
private static void UnregisterAppBar(IntPtr hwnd)
|
||||
{
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
APPBARDATA abd = new() { cbSize = Marshal.SizeOf<APPBARDATA>(), hWnd = hwnd };
|
||||
|
||||
SHAppBarMessage(ABM_REMOVE, ref abd);
|
||||
_registered.Remove(hwnd);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Win32 Helper and Constants
|
||||
|
||||
private const int SWP_NOACTIVATE = 0x0010;
|
||||
private const int SWP_NOOWNERZORDER = 0x0200;
|
||||
private const int SWP_SHOWWINDOW = 0x0040;
|
||||
|
||||
private const int SM_CXSCREEN = 0;
|
||||
private const int SM_CYSCREEN = 0;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetSystemMetrics(int nIndex);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool SetWindowPos(
|
||||
IntPtr hWnd,
|
||||
IntPtr hWndInsertAfter,
|
||||
int X,
|
||||
int Y,
|
||||
int cx,
|
||||
int cy,
|
||||
uint uFlags
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 更改已注册 AppBar 的高度。
|
||||
/// </summary>
|
||||
/// <param name="window">目标窗口</param>
|
||||
/// <param name="newHeight">新的高度</param>
|
||||
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight)
|
||||
{
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = Marshal.SizeOf<APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = ABE_TOP,
|
||||
rc = new RECT
|
||||
{
|
||||
left = 0,
|
||||
top = 0,
|
||||
right = GetSystemMetrics(SM_CXSCREEN),
|
||||
bottom = newHeight,
|
||||
},
|
||||
};
|
||||
|
||||
SHAppBarMessage(ABM_SETPOS, ref abd);
|
||||
|
||||
// 同步窗口实际高度
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
GetSystemMetrics(SM_CXSCREEN),
|
||||
newHeight,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
158
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockModeHelper.cs
Normal file
158
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/DockModeHelper.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class DockModeHelper
|
||||
{
|
||||
private static readonly HashSet<IntPtr> _registered = [];
|
||||
private static readonly Dictionary<IntPtr, RECT> _originalPositions = [];
|
||||
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyle = [];
|
||||
|
||||
public static void Disable(Window window)
|
||||
{
|
||||
window.SetIsShownInSwitchers(true);
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
window.SetIsAlwaysOnTop(false);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
window.SetWindowStyle(_originalWindowStyle[hwnd]);
|
||||
_originalWindowStyle.Remove(hwnd);
|
||||
|
||||
if (_originalPositions.TryGetValue(hwnd, out var rect))
|
||||
{
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
rect.Left,
|
||||
rect.Top,
|
||||
rect.Right - rect.Left,
|
||||
rect.Bottom - rect.Top,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
_originalPositions.Remove(hwnd);
|
||||
}
|
||||
|
||||
UnregisterAppBar(hwnd);
|
||||
}
|
||||
|
||||
public static void Enable(Window window, int appBarHeight)
|
||||
{
|
||||
window.SetIsShownInSwitchers(false);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
window.SetIsAlwaysOnTop(true);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
if (!_originalWindowStyle.ContainsKey(hwnd))
|
||||
{
|
||||
_originalWindowStyle[hwnd] = window.GetWindowStyle();
|
||||
}
|
||||
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
|
||||
|
||||
if (!_originalPositions.ContainsKey(hwnd))
|
||||
{
|
||||
if (User32.GetWindowRect(hwnd, out var rect))
|
||||
{
|
||||
_originalPositions[hwnd] = rect;
|
||||
}
|
||||
}
|
||||
|
||||
RegisterAppBar(hwnd, appBarHeight);
|
||||
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
screenWidth,
|
||||
appBarHeight,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
}
|
||||
|
||||
private static void RegisterAppBar(IntPtr hwnd, int height)
|
||||
{
|
||||
if (_registered.Contains(hwnd)) return;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = Shell32.ABE.ABE_TOP,
|
||||
rc = new RECT
|
||||
{
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = height,
|
||||
},
|
||||
};
|
||||
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_NEW, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
|
||||
_registered.Add(hwnd);
|
||||
}
|
||||
|
||||
private static void UnregisterAppBar(IntPtr hwnd)
|
||||
{
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd
|
||||
};
|
||||
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
|
||||
_registered.Remove(hwnd);
|
||||
}
|
||||
|
||||
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight)
|
||||
{
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = Shell32.ABE.ABE_TOP,
|
||||
rc = new RECT
|
||||
{
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = newHeight,
|
||||
},
|
||||
};
|
||||
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
|
||||
// 同步窗口实际高度
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
newHeight,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -8,43 +10,35 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class EasingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// No easing
|
||||
/// </summary>
|
||||
public static float Linear(float t) => t;
|
||||
public static float EaseInOutExpo(float t)
|
||||
{
|
||||
return t == 0
|
||||
? 0
|
||||
: t == 1
|
||||
? 1
|
||||
: t < 0.5 ? MathF.Pow(2, 20 * t - 10) / 2
|
||||
: (2 - MathF.Pow(2, -20 * t + 10)) / 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
|
||||
/// <summary>
|
||||
/// Acceleration until halfway then deceleration
|
||||
/// </summary>
|
||||
public static float EaseInOutQuad(float t)
|
||||
{
|
||||
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smoother transition than linear
|
||||
/// </summary>
|
||||
public static float SmoothStep(float t)
|
||||
{
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
public static float EaseInQuad(float t) => t * t;
|
||||
|
||||
public static float EaseOutQuad(float t) => t * (2 - t);
|
||||
|
||||
public static float Linear(float t) => 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);
|
||||
}
|
||||
|
||||
public static float SmoothStep(float t)
|
||||
{
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ude;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
@@ -5,32 +5,29 @@ using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ForegroundWindowWatcherHelper
|
||||
{
|
||||
private readonly WinEventDelegate _winEventDelegate;
|
||||
private readonly List<IntPtr> _hooks = new();
|
||||
private IntPtr _currentForeground = IntPtr.Zero;
|
||||
private readonly User32.WinEventProc _winEventDelegate;
|
||||
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
|
||||
private HWND _currentForeground = HWND.NULL;
|
||||
private readonly IntPtr _selfHwnd;
|
||||
private readonly DispatcherTimer _pollingTimer;
|
||||
private DateTime _lastEventTime = DateTime.MinValue;
|
||||
private const int ThrottleIntervalMs = 100;
|
||||
private const int ThrottleIntervalMs = 1000;
|
||||
|
||||
public delegate void WindowChangedHandler(IntPtr hwnd);
|
||||
public delegate void WindowChangedHandler(HWND hwnd);
|
||||
private readonly WindowChangedHandler _onWindowChanged;
|
||||
|
||||
private const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
|
||||
private const uint EVENT_SYSTEM_MINIMIZEEND = 0x0017;
|
||||
private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
|
||||
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
|
||||
|
||||
public ForegroundWindowWatcherHelper(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
|
||||
{
|
||||
_selfHwnd = selfHwnd;
|
||||
_onWindowChanged = onWindowChanged;
|
||||
_winEventDelegate = new WinEventDelegate(WinEventProc);
|
||||
_winEventDelegate = new User32.WinEventProc(WinEventProc);
|
||||
|
||||
_pollingTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
|
||||
_pollingTimer.Tick += (_, _) =>
|
||||
@@ -44,27 +41,27 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
// Hook: foreground changes and minimize end
|
||||
_hooks.Add(
|
||||
SetWinEventHook(
|
||||
EVENT_SYSTEM_FOREGROUND,
|
||||
EVENT_SYSTEM_MINIMIZEEND,
|
||||
IntPtr.Zero,
|
||||
User32.SetWinEventHook(
|
||||
User32.EventConstants.EVENT_SYSTEM_FOREGROUND,
|
||||
User32.EventConstants.EVENT_SYSTEM_MINIMIZEEND,
|
||||
HINSTANCE.NULL,
|
||||
_winEventDelegate,
|
||||
0,
|
||||
0,
|
||||
WINEVENT_OUTOFCONTEXT
|
||||
User32.WINEVENT.WINEVENT_OUTOFCONTEXT
|
||||
)
|
||||
);
|
||||
|
||||
// Hook: window move/resize (location change)
|
||||
_hooks.Add(
|
||||
SetWinEventHook(
|
||||
EVENT_OBJECT_LOCATIONCHANGE,
|
||||
EVENT_OBJECT_LOCATIONCHANGE,
|
||||
IntPtr.Zero,
|
||||
User32.SetWinEventHook(
|
||||
User32.EventConstants.EVENT_OBJECT_LOCATIONCHANGE,
|
||||
User32.EventConstants.EVENT_OBJECT_LOCATIONCHANGE,
|
||||
HINSTANCE.NULL,
|
||||
_winEventDelegate,
|
||||
0,
|
||||
0,
|
||||
WINEVENT_OUTOFCONTEXT
|
||||
User32.WINEVENT.WINEVENT_OUTOFCONTEXT
|
||||
)
|
||||
);
|
||||
|
||||
@@ -74,16 +71,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public void Stop()
|
||||
{
|
||||
foreach (var hook in _hooks)
|
||||
UnhookWinEvent(hook);
|
||||
User32.UnhookWinEvent(hook);
|
||||
|
||||
_hooks.Clear();
|
||||
_pollingTimer.Stop();
|
||||
}
|
||||
|
||||
private void WinEventProc(
|
||||
IntPtr hWinEventHook,
|
||||
User32.HWINEVENTHOOK hWinEventHook,
|
||||
uint eventType,
|
||||
IntPtr hwnd,
|
||||
HWND hwnd,
|
||||
int idObject,
|
||||
int idChild,
|
||||
uint dwEventThread,
|
||||
@@ -99,44 +96,15 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
_lastEventTime = now;
|
||||
|
||||
if (eventType == EVENT_SYSTEM_FOREGROUND)
|
||||
if (eventType == User32.EventConstants.EVENT_SYSTEM_FOREGROUND)
|
||||
{
|
||||
_currentForeground = hwnd;
|
||||
_onWindowChanged?.Invoke(hwnd);
|
||||
}
|
||||
else if (
|
||||
(eventType == EVENT_OBJECT_LOCATIONCHANGE || eventType == EVENT_SYSTEM_MINIMIZEEND)
|
||||
&& hwnd == _currentForeground
|
||||
)
|
||||
else if ((eventType == User32.EventConstants.EVENT_OBJECT_LOCATIONCHANGE || eventType == User32.EventConstants.EVENT_SYSTEM_MINIMIZEEND) && hwnd == _currentForeground)
|
||||
{
|
||||
_onWindowChanged?.Invoke(hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
#region WinAPI
|
||||
private delegate void WinEventDelegate(
|
||||
IntPtr hWinEventHook,
|
||||
uint eventType,
|
||||
IntPtr hwnd,
|
||||
int idObject,
|
||||
int idChild,
|
||||
uint dwEventThread,
|
||||
uint dwmsEventTime
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SetWinEventHook(
|
||||
uint eventMin,
|
||||
uint eventMax,
|
||||
IntPtr hmodWinEventProc,
|
||||
WinEventDelegate lpfnWinEventProc,
|
||||
uint idProcess,
|
||||
uint idThread,
|
||||
uint dwFlags
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,11 +10,8 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
|
||||
@@ -20,34 +19,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ImageHelper
|
||||
{
|
||||
private static readonly ColorThief _colorThief = new();
|
||||
public const int AccentColorCount = 3;
|
||||
|
||||
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(
|
||||
byte[] imageBytes
|
||||
)
|
||||
{
|
||||
if (imageBytes == null || imageBytes.Length == 0)
|
||||
return null;
|
||||
|
||||
InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
@@ -57,31 +30,7 @@ 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));
|
||||
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(
|
||||
string text,
|
||||
int width,
|
||||
int height
|
||||
)
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(string text, int width, int height)
|
||||
{
|
||||
var device = CanvasDevice.GetSharedDevice();
|
||||
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
|
||||
@@ -152,5 +101,75 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes)
|
||||
{
|
||||
// 使用 ImageSharp 读取图片
|
||||
using var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(bytes);
|
||||
|
||||
// 简单聚类法:统计所有像素出现频率,取出现最多的前 AccentColorCount 个颜色
|
||||
var colorCount = new Dictionary<SixLabors.ImageSharp.PixelFormats.Rgba32, int>();
|
||||
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < image.Width; x++)
|
||||
{
|
||||
var color = image[x, y];
|
||||
// 可选:忽略透明像素
|
||||
if (color.A < 32) continue;
|
||||
if (colorCount.ContainsKey(color))
|
||||
colorCount[color]++;
|
||||
else
|
||||
colorCount[color] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 按出现次数排序,取前 AccentColorCount 个
|
||||
var topColors = colorCount
|
||||
.OrderByDescending(kv => kv.Value)
|
||||
.Take(AccentColorCount)
|
||||
.Select(kv => kv.Key)
|
||||
.ToList();
|
||||
|
||||
// 转换为 Windows.UI.Color
|
||||
return topColors
|
||||
.Select(c => Windows.UI.Color.FromArgb(c.A, c.R, c.G, c.B))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
await bitmapImage.SetSourceAsync(stream);
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
public static async Task<BitmapDecoder> GetDecoderFromByte(byte[] bytes) =>
|
||||
await BitmapDecoder.CreateAsync(await ByteArrayToStream(bytes));
|
||||
|
||||
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
|
||||
{
|
||||
if (imageBytes == null || imageBytes.Length == 0)
|
||||
return null;
|
||||
|
||||
InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(imageBytes.AsBuffer());
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
|
||||
{
|
||||
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
|
||||
using var memoryStream = new MemoryStream();
|
||||
await stream.AsStreamForRead().CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Lyricify.Lyrics.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Windows.Globalization.Fonts;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
@@ -13,50 +16,51 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
private List<List<LyricsLine>> _multiLangLyricsLines = [];
|
||||
|
||||
public List<List<LyricsLine>> Parse(
|
||||
string raw,
|
||||
LyricsFormat? lyricsFormat = null,
|
||||
string? title = null,
|
||||
string? artist = null,
|
||||
int durationMs = 0
|
||||
)
|
||||
public List<List<LyricsLine>> Parse(string? raw, LyricsFormat? lyricsFormat = null, string? title = null, string? artist = null, int durationMs = 0)
|
||||
{
|
||||
_multiLangLyricsLines = [];
|
||||
switch (lyricsFormat)
|
||||
if (raw == null)
|
||||
{
|
||||
case LyricsFormat.Lrc:
|
||||
case LyricsFormat.Eslrc:
|
||||
ParseLrc(raw, durationMs);
|
||||
break;
|
||||
case LyricsFormat.Ttml:
|
||||
ParseTtml(raw, durationMs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
_multiLangLyricsLines.Add(
|
||||
[
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = durationMs,
|
||||
Text = App.ResourceLoader!.GetString("LyricsNotFound"),
|
||||
CharTimings = [],
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (lyricsFormat)
|
||||
{
|
||||
case LyricsFormat.Lrc:
|
||||
case LyricsFormat.Eslrc:
|
||||
ParseLrc(raw);
|
||||
break;
|
||||
case LyricsFormat.Qrc:
|
||||
ParseUsingLyricify(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Krc:
|
||||
ParseUsingLyricify(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Ttml:
|
||||
ParseTtml(raw);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
PostProcessLyricsLines(durationMs);
|
||||
return _multiLangLyricsLines;
|
||||
}
|
||||
|
||||
private void PostProcessLyricsLines(List<LyricsLine> lines)
|
||||
private void ParseLrc(string raw)
|
||||
{
|
||||
if (lines.Count > 0 && lines[0].StartMs > 0)
|
||||
{
|
||||
lines.Insert(
|
||||
0,
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = lines[0].StartMs,
|
||||
Text = "",
|
||||
CharTimings = [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseLrc(string raw, int durationMs)
|
||||
{
|
||||
var lines = raw.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = raw.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
|
||||
var lrcLines =
|
||||
new List<(int time, string text, List<(int time, string text)> syllables)>();
|
||||
|
||||
@@ -104,7 +108,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,56 +141,36 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 修正 EndMs
|
||||
for (int langIdx = 0; langIdx < languageCount; langIdx++)
|
||||
{
|
||||
var linesInSingleLang = _multiLangLyricsLines[langIdx];
|
||||
for (int i = 0; i < linesInSingleLang.Count; i++)
|
||||
{
|
||||
if (i + 1 < linesInSingleLang.Count)
|
||||
linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs;
|
||||
else
|
||||
linesInSingleLang[i].EndMs = durationMs;
|
||||
|
||||
// 修正 CharTimings 的最后一个 EndMs
|
||||
var timings = linesInSingleLang[i].CharTimings;
|
||||
if (timings.Count > 0)
|
||||
{
|
||||
for (int j = 0; j < timings.Count; j++)
|
||||
{
|
||||
if (j + 1 < timings.Count)
|
||||
timings[j].EndMs = timings[j + 1].StartMs;
|
||||
else
|
||||
timings[j].EndMs = linesInSingleLang[i].EndMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
PostProcessLyricsLines(linesInSingleLang);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseTtml(string raw, int durationMs)
|
||||
private void ParseTtml(string raw)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<LyricsLine> singleLangLyricsLine = [];
|
||||
var xdoc = XDocument.Parse(raw);
|
||||
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
|
||||
if (body == null)
|
||||
return;
|
||||
if (body == null) return;
|
||||
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
|
||||
foreach (var p in ps)
|
||||
{
|
||||
@@ -197,17 +181,13 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
int pEndMs = ParseTtmlTime(pEnd);
|
||||
|
||||
// 处理分词分时
|
||||
var spans = p.Elements()
|
||||
.Where(s =>
|
||||
s.Name.LocalName == "span"
|
||||
&& s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))
|
||||
== null
|
||||
)
|
||||
.ToList();
|
||||
var spans = p.Elements().Where(s => s.Name.LocalName == "span").ToList();
|
||||
|
||||
string text = string.Concat(spans.Select(s => s.Value));
|
||||
var charTimings = new List<CharTiming>();
|
||||
|
||||
int startIndex = 0;
|
||||
|
||||
for (int i = 0; i < spans.Count; i++)
|
||||
{
|
||||
var span = spans[i];
|
||||
@@ -225,23 +205,23 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
? ParseTtmlTime(spans[i + 1].Attribute("begin")?.Value)
|
||||
: pEndMs;
|
||||
|
||||
charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = sEndMs });
|
||||
charTimings.Add(new CharTiming { StartMs = sStartMs, EndMs = 0, StartIndex = startIndex, Text = span.Value });
|
||||
startIndex += span.Value.Length;
|
||||
}
|
||||
|
||||
if (spans.Count == 0)
|
||||
text = p.Value.Trim();
|
||||
text = p.Value;
|
||||
|
||||
singleLangLyricsLine.Add(
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = pEndMs,
|
||||
EndMs = 0,
|
||||
Text = text,
|
||||
CharTimings = charTimings,
|
||||
}
|
||||
);
|
||||
}
|
||||
PostProcessLyricsLines(singleLangLyricsLine);
|
||||
_multiLangLyricsLines.Add(singleLangLyricsLine);
|
||||
}
|
||||
catch
|
||||
@@ -310,5 +290,111 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void ParseUsingLyricify(List<ILineInfo>? lines)
|
||||
{
|
||||
lines = lines?.Where(x => x.Text != string.Empty).ToList();
|
||||
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,
|
||||
EndMs = 0,
|
||||
Text = lineRead.Text,
|
||||
CharTimings = [],
|
||||
};
|
||||
|
||||
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
|
||||
if (syllables != null)
|
||||
{
|
||||
int startIndex = 0;
|
||||
for (
|
||||
int syllableIndex = 0;
|
||||
syllableIndex < syllables.Count;
|
||||
syllableIndex++
|
||||
)
|
||||
{
|
||||
var syllable = syllables[syllableIndex];
|
||||
var charTiming = new CharTiming
|
||||
{
|
||||
StartMs = syllable.StartTime,
|
||||
EndMs = 0,
|
||||
Text = syllable.Text,
|
||||
StartIndex = startIndex,
|
||||
};
|
||||
if (syllableIndex + 1 < syllables.Count)
|
||||
{
|
||||
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
charTiming.EndMs = lineWrite.EndMs;
|
||||
}
|
||||
lineWrite.CharTimings.Add(charTiming);
|
||||
startIndex += syllable.Text.Length;
|
||||
}
|
||||
}
|
||||
|
||||
lyricsLines.Add(lineWrite);
|
||||
}
|
||||
}
|
||||
|
||||
_multiLangLyricsLines.Add(lyricsLines);
|
||||
}
|
||||
|
||||
private void PostProcessLyricsLines(int durationMs)
|
||||
{
|
||||
for (int langIdx = 0; langIdx < _multiLangLyricsLines.Count; langIdx++)
|
||||
{
|
||||
var linesInSingleLang = _multiLangLyricsLines[langIdx];
|
||||
for (int i = 0; i < linesInSingleLang.Count; i++)
|
||||
{
|
||||
if (i + 1 < linesInSingleLang.Count)
|
||||
{
|
||||
linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
linesInSingleLang[i].EndMs = durationMs;
|
||||
}
|
||||
|
||||
// 修正 CharTimings 的 EndMs
|
||||
var timings = linesInSingleLang[i].CharTimings;
|
||||
if (timings.Count > 0)
|
||||
{
|
||||
for (int j = 0; j < timings.Count; j++)
|
||||
{
|
||||
if (j + 1 < timings.Count)
|
||||
{
|
||||
timings[j].EndMs = timings[j + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
timings[j].EndMs = linesInSingleLang[i].EndMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (linesInSingleLang.Count > 0 && linesInSingleLang[0].StartMs > 0)
|
||||
{
|
||||
linesInSingleLang.Insert(
|
||||
0,
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = linesInSingleLang[0].StartMs,
|
||||
Text = "● ● ●",
|
||||
CharTimings = [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
|
||||
@@ -1,22 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class WindowColorHelper
|
||||
{
|
||||
public static Color GetDominantColorBelow(IntPtr myHwnd)
|
||||
public static Color GetDominantColor(IntPtr myHwnd, WindowColorSampleMode mode)
|
||||
{
|
||||
if (!GetWindowRect(myHwnd, out RECT myRect))
|
||||
return Color.Transparent;
|
||||
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return Color.Transparent;
|
||||
|
||||
int screenWidth = GetSystemMetrics(SystemMetric.SM_CXSCREEN);
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
switch (mode)
|
||||
{
|
||||
case WindowColorSampleMode.BelowWindow:
|
||||
{
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowColorSampleMode.WindowArea:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return Color.Transparent;
|
||||
// 采集窗口区域的平均色
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top, width, height);
|
||||
}
|
||||
case WindowColorSampleMode.WindowEdge:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
int height = myRect.Bottom - myRect.Top;
|
||||
if (width <= 0 || height <= 0)
|
||||
return Color.Transparent;
|
||||
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
var edgeThickness = new Thickness(36, 0, 36, 0);
|
||||
List<Color> edgeColors = [];
|
||||
|
||||
// Top edge
|
||||
if (edgeThickness.Top > 0 && edgeThickness.Top < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Top,
|
||||
width,
|
||||
(int)edgeThickness.Top
|
||||
)
|
||||
);
|
||||
// Bottom edge
|
||||
if (edgeThickness.Bottom > 0 && edgeThickness.Bottom < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Bottom - (int)edgeThickness.Bottom,
|
||||
width,
|
||||
(int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
// Left edge
|
||||
if (edgeThickness.Left > 0 && edgeThickness.Left < width)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Top + (int)edgeThickness.Top,
|
||||
(int)edgeThickness.Left,
|
||||
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
// Right edge
|
||||
if (edgeThickness.Right > 0 && edgeThickness.Right < width)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Right - (int)edgeThickness.Right,
|
||||
myRect.Top + (int)edgeThickness.Top,
|
||||
(int)edgeThickness.Right,
|
||||
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
|
||||
// 合并四边平均色
|
||||
if (edgeColors.Count == 0)
|
||||
return Color.Transparent;
|
||||
long r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
foreach (var c in edgeColors)
|
||||
{
|
||||
r += c.R;
|
||||
g += c.G;
|
||||
b += c.B;
|
||||
}
|
||||
return Color.FromArgb(
|
||||
255,
|
||||
(int)(r / edgeColors.Count),
|
||||
(int)(g / edgeColors.Count),
|
||||
(int)(b / edgeColors.Count)
|
||||
);
|
||||
}
|
||||
default:
|
||||
return Color.Transparent;
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
|
||||
@@ -25,21 +114,19 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
using Graphics gDest = Graphics.FromImage(bmp);
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = GetDC(IntPtr.Zero); // Entire screen
|
||||
IntPtr hdcSrc = (nint)User32.GetDC(IntPtr.Zero); // Entire screen
|
||||
|
||||
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, SRCCOPY);
|
||||
Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, Gdi32.RasterOperationMode.SRCCOPY);
|
||||
|
||||
gDest.ReleaseHdc(hdcDest);
|
||||
ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
User32.ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
private static Color ComputeAverageColor(Bitmap bmp)
|
||||
{
|
||||
long r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
long r = 0, g = 0, b = 0;
|
||||
int count = 0;
|
||||
|
||||
for (int y = 0; y < bmp.Height; y++)
|
||||
@@ -54,53 +141,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
return Color.Transparent;
|
||||
if (count == 0) return Color.Transparent;
|
||||
return Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
|
||||
}
|
||||
|
||||
#region Win32 Imports & Structs
|
||||
private const int SRCCOPY = 0x00CC0020;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetDC(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern bool BitBlt(
|
||||
IntPtr hdcDest,
|
||||
int nXDest,
|
||||
int nYDest,
|
||||
int nWidth,
|
||||
int nHeight,
|
||||
IntPtr hdcSrc,
|
||||
int nXSrc,
|
||||
int nYSrc,
|
||||
int dwRop
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetSystemMetrics(SystemMetric smIndex);
|
||||
|
||||
private enum SystemMetric
|
||||
{
|
||||
SM_CXSCREEN = 0,
|
||||
SM_CYSCREEN = 1,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
@@ -10,124 +13,92 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class WindowHelper
|
||||
{
|
||||
private static readonly Dictionary<Type, Window> _windowCache = new();
|
||||
private static List<object> _activeWindows = [];
|
||||
|
||||
public static void HideSystemTitleBar(this Window window)
|
||||
public static void CloseWindow<T>()
|
||||
{
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
window.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
}
|
||||
|
||||
public static void HideSystemTitleBarAndSetCustomTitleBar(
|
||||
this Window window,
|
||||
UIElement titleBar
|
||||
)
|
||||
{
|
||||
window.HideSystemTitleBar();
|
||||
window.SetTitleBar(titleBar);
|
||||
}
|
||||
|
||||
public static void OpenSettingsWindow()
|
||||
{
|
||||
OpenOrShowWindow(typeof(SettingsPage));
|
||||
}
|
||||
|
||||
public static void OpenLyricsWindow()
|
||||
{
|
||||
OpenOrShowWindow(typeof(LyricsPage));
|
||||
}
|
||||
|
||||
private static void OpenOrShowWindow(Type pageType)
|
||||
{
|
||||
if (_windowCache.TryGetValue(pageType, out var window))
|
||||
var window = _activeWindows.Find(w => w is T);
|
||||
if (window is Window w)
|
||||
{
|
||||
window.TryShow();
|
||||
w.Close();
|
||||
_activeWindows.Remove(w);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExitAllWindows()
|
||||
{
|
||||
while (_activeWindows.Count > 0)
|
||||
{
|
||||
var window = _activeWindows[0];
|
||||
((Window)window).Close();
|
||||
_activeWindows.Remove(window);
|
||||
}
|
||||
App.Current.Exit();
|
||||
}
|
||||
|
||||
public static T GetWindowByWindowType<T>()
|
||||
{
|
||||
foreach (var window in _activeWindows)
|
||||
{
|
||||
if (window is T castedWindow)
|
||||
{
|
||||
return castedWindow;
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException($"No window of type {typeof(T).Name} found.");
|
||||
}
|
||||
public static void OpenOrShowWindow<T>()
|
||||
{
|
||||
var window = _activeWindows.Find(w => w is T);
|
||||
if (window != null)
|
||||
{
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Restore();
|
||||
}
|
||||
else
|
||||
{
|
||||
var newWindow = new HostWindow();
|
||||
TrackWindow(newWindow, pageType);
|
||||
newWindow.ViewModel.FramePageType = pageType;
|
||||
newWindow.Navigate(pageType);
|
||||
newWindow.Activate();
|
||||
object newWindow;
|
||||
if (typeof(T) == typeof(LyricsWindow))
|
||||
{
|
||||
newWindow = new LyricsWindow();
|
||||
}
|
||||
else if (typeof(T) == typeof(SettingsWindow))
|
||||
{
|
||||
newWindow = new SettingsWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Unsupported window type", nameof(T));
|
||||
}
|
||||
((Window)newWindow).Activate();
|
||||
TrackWindow(newWindow);
|
||||
}
|
||||
}
|
||||
|
||||
public static void TrackWindow(Window window, Type pageType = null)
|
||||
public static void RestartApp(string args = "")
|
||||
{
|
||||
if (pageType != null)
|
||||
{
|
||||
_windowCache[pageType] = window;
|
||||
}
|
||||
// The restart will be executed immediately.
|
||||
AppRestartFailureReason failureReason =
|
||||
Microsoft.Windows.AppLifecycle.AppInstance.Restart(args);
|
||||
|
||||
// If the restart fails, handle it here.
|
||||
switch (failureReason)
|
||||
{
|
||||
case AppRestartFailureReason.RestartPending:
|
||||
break;
|
||||
case AppRestartFailureReason.NotInForeground:
|
||||
break;
|
||||
case AppRestartFailureReason.InvalidUser:
|
||||
break;
|
||||
default: //AppRestartFailureReason.Other
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void TrackWindow(object window)
|
||||
{
|
||||
if (!_activeWindows.Contains(window))
|
||||
_activeWindows.Add(window);
|
||||
}
|
||||
|
||||
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
|
||||
static public double GetRasterizationScaleForElement(UIElement element)
|
||||
{
|
||||
if (element.XamlRoot != null)
|
||||
{
|
||||
foreach (Window window in _activeWindows)
|
||||
{
|
||||
if (element.XamlRoot == window.Content.XamlRoot)
|
||||
{
|
||||
return element.XamlRoot.RasterizationScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
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;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Messages
|
||||
{
|
||||
public class ShowNotificatonMessage(Notification value)
|
||||
: ValueChangedMessage<Notification>(value)
|
||||
{ }
|
||||
public class ShowNotificatonMessage(Notification value) : ValueChangedMessage<Notification>(value) { }
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
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.Models
|
||||
{
|
||||
public class CharTiming
|
||||
{
|
||||
public int StartMs { get; set; }
|
||||
public int EndMs { get; set; }
|
||||
public int StartIndex { get; set; }
|
||||
public int StartMs { get; set; }
|
||||
public string Text { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
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
|
||||
@@ -10,10 +7,10 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public partial class LocalLyricsFolder : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial string Path { get; set; }
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
public partial string Path { get; set; }
|
||||
|
||||
public LocalLyricsFolder() { }
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
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
|
||||
{
|
||||
@@ -11,6 +9,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public int LanguageIndex { get; set; } = 0;
|
||||
|
||||
public List<LyricsLine> LyricsLines => MultiLangLyricsLines[LanguageIndex];
|
||||
|
||||
public List<List<LyricsLine>> MultiLangLyricsLines { get; set; } = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,33 @@
|
||||
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
|
||||
{
|
||||
public class LyricsLine
|
||||
{
|
||||
public string Text { get; set; } = "";
|
||||
public ValueTransition<float> AngleTransition { get; set; } = new(initialValue: 0f, durationSeconds: 0.3f);
|
||||
public ValueTransition<float> BlurAmountTransition { get; set; } = new(initialValue: 0f, durationSeconds: 0.3f);
|
||||
public ValueTransition<float> HighlightOpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: 0.3f);
|
||||
public ValueTransition<float> OpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: 0.3f);
|
||||
public ValueTransition<float> ScaleTransition { get; set; } = new(initialValue: 0.95f, durationSeconds: 0.3f);
|
||||
|
||||
public CanvasTextLayout? CanvasTextLayout { get; set; }
|
||||
|
||||
public Vector2 CenterPosition { get; set; }
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public List<CharTiming> CharTimings { get; set; } = [];
|
||||
|
||||
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 int EndMs { get; set; }
|
||||
|
||||
public float ExitingProgress { get; set; }
|
||||
public int StartMs { get; set; }
|
||||
|
||||
public float PlayingProgress { get; set; }
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public Vector2 CenterPosition { get; set; }
|
||||
|
||||
public float Scale { get; set; }
|
||||
|
||||
public float Opacity { get; set; }
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
public string Text { get; set; } = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
@@ -6,10 +8,10 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public partial class LyricsSearchProviderInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial LyricsSearchProvider Provider { get; set; }
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
public partial LyricsSearchProvider Provider { get; set; }
|
||||
|
||||
public LyricsSearchProviderInfo() { }
|
||||
|
||||
@@ -18,5 +20,6 @@ namespace BetterLyrics.WinUI3.Models
|
||||
Provider = provider;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
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;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
@@ -12,26 +9,21 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public partial class Notification : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial InfoBarSeverity Severity { get; set; }
|
||||
public partial bool IsForeverDismissable { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? Message { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsForeverDismissable { get; set; }
|
||||
public partial string? RelatedSettingsKeyName { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial InfoBarSeverity Severity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Visibility Visibility { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? RelatedSettingsKeyName { get; set; }
|
||||
|
||||
public Notification(
|
||||
string? message = null,
|
||||
InfoBarSeverity severity = InfoBarSeverity.Informational,
|
||||
bool isForeverDismissable = false,
|
||||
string? relatedSettingsKeyName = null
|
||||
)
|
||||
public Notification(string? message = null, InfoBarSeverity severity = InfoBarSeverity.Informational, bool isForeverDismissable = false, string? relatedSettingsKeyName = null)
|
||||
{
|
||||
Message = message;
|
||||
Severity = severity;
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class SongInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial string Title { get; set; }
|
||||
public partial string? Album { get; set; }
|
||||
|
||||
public byte[]? AlbumArt { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Artist { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? Album { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In milliseconds
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial double? DurationMs { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? SourceAppUserModelId { get; set; } = null;
|
||||
|
||||
public byte[]? AlbumArt { get; set; } = null;
|
||||
[ObservableProperty]
|
||||
public partial string Title { get; set; }
|
||||
|
||||
public SongInfo() { }
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<canvas:CanvasAnimatedControl
|
||||
x:Name="LyricsCanvas"
|
||||
Draw="LyricsCanvas_Draw"
|
||||
Loaded="LyricsCanvas_Loaded"
|
||||
Update="LyricsCanvas_Update" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System.Diagnostics;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Renderer
|
||||
{
|
||||
public sealed partial class LyricsRenderer : UserControl
|
||||
@@ -19,25 +16,14 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
ViewModel = Ioc.Default.GetRequiredService<LyricsRendererViewModel>();
|
||||
}
|
||||
|
||||
private void LyricsCanvas_Draw(
|
||||
Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender,
|
||||
Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args
|
||||
)
|
||||
private void LyricsCanvas_Draw(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
|
||||
{
|
||||
ViewModel.Draw(sender, args.DrawingSession);
|
||||
}
|
||||
|
||||
private void LyricsCanvas_Update(
|
||||
Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender,
|
||||
Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args
|
||||
)
|
||||
private void LyricsCanvas_Update(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args)
|
||||
{
|
||||
ViewModel.Update(sender, args);
|
||||
}
|
||||
|
||||
private void LyricsCanvas_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.RequestRelayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
[JsonSerializable(typeof(List<LyricsSearchProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<LocalLyricsFolder>))]
|
||||
[JsonSerializable(typeof(List<string>))]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -11,6 +13,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
public interface ILibWatcherService
|
||||
{
|
||||
event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
|
||||
public void UpdateWatchers(List<LocalLyricsFolder> folders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
@@ -6,6 +8,8 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface IMusicSearchService
|
||||
{
|
||||
byte[]? SearchAlbumArtAsync(string title, string artist);
|
||||
|
||||
Task<(string?, LyricsFormat?)> SearchLyricsAsync(
|
||||
string title,
|
||||
string artist,
|
||||
@@ -13,7 +17,5 @@ namespace BetterLyrics.WinUI3.Services
|
||||
double durationMs = 0.0,
|
||||
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
|
||||
);
|
||||
|
||||
byte[]? SearchAlbumArtAsync(string title, string artist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
@@ -6,12 +8,16 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface IPlaybackService
|
||||
{
|
||||
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
|
||||
event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
|
||||
SongInfo? SongInfo { get; }
|
||||
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
|
||||
bool IsPlaying { get; }
|
||||
|
||||
TimeSpan Position { get; }
|
||||
|
||||
SongInfo? SongInfo { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,61 @@
|
||||
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;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
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; }
|
||||
|
||||
// App behavior
|
||||
|
||||
AutoStartWindowType AutoStartWindowType { get; set; }
|
||||
|
||||
// Album art background
|
||||
|
||||
bool IsCoverOverlayEnabled { get; set; }
|
||||
bool IsDynamicCoverOverlayEnabled { get; set; }
|
||||
int CoverOverlayOpacity { get; set; }
|
||||
int CoverOverlayBlurAmount { get; set; }
|
||||
|
||||
// Album art cover style
|
||||
int CoverImageRadius { get; set; }
|
||||
|
||||
// Lyrics style and effetc
|
||||
LyricsAlignmentType LyricsAlignmentType { get; set; }
|
||||
LyricsFontWeight LyricsFontWeight { get; set; }
|
||||
int LyricsBlurAmount { get; set; }
|
||||
int LyricsVerticalEdgeOpacity { get; set; }
|
||||
float LyricsLineSpacingFactor { get; set; }
|
||||
int LyricsFontSize { get; set; }
|
||||
int CoverOverlayBlurAmount { get; set; }
|
||||
int CoverOverlayOpacity { get; set; }
|
||||
bool IsDynamicCoverOverlayEnabled { get; set; }
|
||||
bool IsFanLyricsEnabled { get; set; }
|
||||
bool IsFirstRun { get; set; }
|
||||
bool IsLyricsGlowEffectEnabled { get; set; }
|
||||
LyricsGlowEffectScope LyricsGlowEffectScope { get; set; }
|
||||
Language Language { get; set; }
|
||||
int DesktopWindowLeft { get; set; }
|
||||
int DesktopWindowTop { get; set; }
|
||||
int DesktopWindowWidth { get; set; }
|
||||
int DesktopWindowHeight { get; set; }
|
||||
bool AutoLockOnDesktopMode { get; set; }
|
||||
|
||||
// Lyrics lib
|
||||
|
||||
List<LocalLyricsFolder> LocalLyricsFolders { get; set; }
|
||||
|
||||
// Lyrics style and effetc
|
||||
|
||||
LyricsAlignmentType LyricsAlignmentType { get; set; }
|
||||
|
||||
int LyricsBlurAmount { get; set; }
|
||||
|
||||
Color LyricsCustomFontColor { get; set; }
|
||||
|
||||
LyricsFontColorType LyricsFontColorType { get; set; }
|
||||
|
||||
int LyricsFontSize { get; set; }
|
||||
|
||||
LyricsFontWeight LyricsFontWeight { get; set; }
|
||||
|
||||
LineRenderingType LyricsGlowEffectScope { get; set; }
|
||||
|
||||
float LyricsLineSpacingFactor { get; set; }
|
||||
|
||||
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
|
||||
int LyricsVerticalEdgeOpacity { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +1,84 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
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
|
||||
public class LibWatcherService : IDisposable, ILibWatcherService
|
||||
{
|
||||
public class LibWatcherService : IDisposable, ILibWatcherService
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
private readonly Dictionary<string, FileSystemWatcher> _watchers = [];
|
||||
|
||||
public LibWatcherService(ISettingsService settingsService)
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly Dictionary<string, FileSystemWatcher> _watchers = [];
|
||||
_settingsService = settingsService;
|
||||
UpdateWatchers(_settingsService.LocalLyricsFolders);
|
||||
}
|
||||
|
||||
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
|
||||
public LibWatcherService(ISettingsService settingsService)
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var watcher in _watchers.Values)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
UpdateWatchers(_settingsService.LocalLyricsFolders);
|
||||
watcher.Dispose();
|
||||
}
|
||||
_watchers.Clear();
|
||||
}
|
||||
|
||||
public void UpdateWatchers(List<LocalLyricsFolder> folders)
|
||||
public void UpdateWatchers(List<LocalLyricsFolder> folders)
|
||||
{
|
||||
// 移除不再监听的
|
||||
foreach (var key in _watchers.Keys.ToList())
|
||||
{
|
||||
// 移除不再监听的
|
||||
foreach (var key in _watchers.Keys.ToList())
|
||||
if (!folders.Any(x => x.Path == key && x.IsEnabled))
|
||||
{
|
||||
if (!folders.Any(x => x.Path == key && x.IsEnabled))
|
||||
{
|
||||
_watchers[key].Dispose();
|
||||
_watchers.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加新的监听
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
if (
|
||||
!_watchers.ContainsKey(folder.Path)
|
||||
&& Directory.Exists(folder.Path)
|
||||
&& folder.IsEnabled
|
||||
)
|
||||
{
|
||||
var watcher = new FileSystemWatcher(folder.Path)
|
||||
{
|
||||
IncludeSubdirectories = true,
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
watcher.Created += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Changed += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Deleted += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Renamed += (s, e) => OnChanged(folder.Path, e);
|
||||
_watchers[folder.Path] = watcher;
|
||||
}
|
||||
_watchers[key].Dispose();
|
||||
_watchers.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChanged(string folder, FileSystemEventArgs e)
|
||||
// 添加新的监听
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
App.DispatcherQueue!.TryEnqueue(
|
||||
Microsoft.UI.Dispatching.DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
MusicLibraryFilesChanged?.Invoke(
|
||||
this,
|
||||
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var watcher in _watchers.Values)
|
||||
if (
|
||||
!_watchers.ContainsKey(folder.Path)
|
||||
&& Directory.Exists(folder.Path)
|
||||
&& folder.IsEnabled
|
||||
)
|
||||
{
|
||||
watcher.Dispose();
|
||||
var watcher = new FileSystemWatcher(folder.Path)
|
||||
{
|
||||
IncludeSubdirectories = true,
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
watcher.Created += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Changed += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Deleted += (s, e) => OnChanged(folder.Path, e);
|
||||
watcher.Renamed += (s, e) => OnChanged(folder.Path, e);
|
||||
_watchers[folder.Path] = watcher;
|
||||
}
|
||||
_watchers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChanged(string folder, FileSystemEventArgs e)
|
||||
{
|
||||
App.DispatcherQueue!.TryEnqueue(
|
||||
Microsoft.UI.Dispatching.DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
MusicLibraryFilesChanged?.Invoke(
|
||||
this,
|
||||
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@@ -8,24 +10,53 @@ using System.Threading.Tasks;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.FileProperties;
|
||||
using Lyricify.Lyrics.Providers.Web.Kugou;
|
||||
using Lyricify.Lyrics.Searchers;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class MusicSearchService : IMusicSearchService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly HttpClient _amllTtmlDbHttpClient;
|
||||
|
||||
private readonly HttpClient _lrcLibHttpClient;
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[]? SearchAlbumArtAsync(string title, string artist)
|
||||
@@ -34,15 +65,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (
|
||||
var file in Directory.GetFiles(
|
||||
folder.Path,
|
||||
$"*.*",
|
||||
SearchOption.AllDirectories
|
||||
)
|
||||
)
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
Track track = new(file);
|
||||
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
@@ -59,11 +84,8 @@ namespace BetterLyrics.WinUI3.Services
|
||||
}
|
||||
|
||||
public async Task<(string?, LyricsFormat?)> SearchLyricsAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album = "",
|
||||
double durationMs = 0.0,
|
||||
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
|
||||
string title, string artist, string album = "", double durationMs = 0.0,
|
||||
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleArtistAlbumAndDuration
|
||||
)
|
||||
{
|
||||
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
|
||||
@@ -73,150 +95,118 @@ 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:
|
||||
searchedLyrics = await LocalLyricsSearchInLyricsFiles(
|
||||
title,
|
||||
artist,
|
||||
LyricsFormat.Lrc
|
||||
);
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
searchedLyrics = await LocalLyricsSearchInLyricsFiles(title, artist, lyricsFormat);
|
||||
}
|
||||
}
|
||||
|
||||
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 SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, matchMode, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, matchMode, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
searchedLyrics = await SearchUsingLyricifyAsync(title, artist, album, (int)durationMs, matchMode, Searchers.Netease);
|
||||
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)
|
||||
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 async Task<string?> LocalLyricsSearchInLyricsFiles(string title, string artist, LyricsFormat format)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*{format.ToFileExtension()}", SearchOption.AllDirectories))
|
||||
{
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
|
||||
if (raw != null)
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? LocalLyricsSearchInMusicFiles(string title, string artist)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (
|
||||
var file in Directory.GetFiles(
|
||||
folder.Path,
|
||||
$"*.*",
|
||||
SearchOption.AllDirectories
|
||||
)
|
||||
)
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
//Track track = new(file);
|
||||
//var plain = track.Lyrics.UnsynchronizedLyrics;
|
||||
|
||||
try
|
||||
{
|
||||
var plain = TagLib.File.Create(file).Tag.Lyrics;
|
||||
@@ -234,48 +224,86 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> LocalLyricsSearchInLyricsFiles(
|
||||
string title,
|
||||
string artist,
|
||||
LyricsFormat format
|
||||
)
|
||||
private string? ReadCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(cacheFolderPath, $"{safeArtist} - {safeTitle}{format.ToFileExtension()}");
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (
|
||||
var file in Directory.GetFiles(
|
||||
folder.Path,
|
||||
$"*{format.ToFileExtension()}",
|
||||
SearchOption.AllDirectories
|
||||
)
|
||||
)
|
||||
{
|
||||
if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
string? raw = await File.ReadAllTextAsync(
|
||||
file,
|
||||
FileHelper.GetEncoding(file)
|
||||
);
|
||||
if (raw != null)
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return File.ReadAllText(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchLrcLib(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int duration,
|
||||
MusicSearchMatchMode matchMode
|
||||
)
|
||||
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;
|
||||
await foreach (var line in File.ReadLinesAsync(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;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string?> SearchLrcLibAsync(string title, string artist, string album, int duration, MusicSearchMatchMode matchMode)
|
||||
{
|
||||
// Build API query URL
|
||||
var url =
|
||||
@@ -290,7 +318,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 +342,67 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
private void WriteCache(string title, string artist, string lyrics, LyricsFormat format)
|
||||
private async Task<string?> SearchUsingLyricifyAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int durationMs,
|
||||
MusicSearchMatchMode matchMode,
|
||||
Searchers searchers
|
||||
)
|
||||
{
|
||||
var result = await SearchersHelper.GetSearcher(searchers).SearchForResult(
|
||||
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
{
|
||||
DurationMs = matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration ? durationMs : null,
|
||||
Album = matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration ? album : null,
|
||||
AlbumArtists = [artist],
|
||||
Artists = [artist],
|
||||
Title = title,
|
||||
}
|
||||
);
|
||||
|
||||
if (result is QQMusicSearchResult qqResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Decrypter.Qrc.Helper.GetLyricsAsync(qqResult.Id);
|
||||
var original = response?.Lyrics;
|
||||
return original;
|
||||
}
|
||||
else if (result is NeteaseSearchResult neteaseResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
|
||||
return response?.Lrc.Lyric;
|
||||
}
|
||||
else if (result is KugouSearchResult kugouResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(kugouResult.Hash);
|
||||
if (response?.Candidates.FirstOrDefault() is SearchLyricsResponse.Candidate candidate)
|
||||
{
|
||||
return Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyrics(
|
||||
candidate.Id,
|
||||
candidate.AccessKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
@@ -20,45 +17,102 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
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; }
|
||||
|
||||
private readonly IMusicSearchService _musicSearchService;
|
||||
|
||||
public PlaybackService(
|
||||
ISettingsService settingsService,
|
||||
IMusicSearchService musicSearchService
|
||||
)
|
||||
private GlobalSystemMediaTransportControlsSession? _currentSession = null;
|
||||
|
||||
private GlobalSystemMediaTransportControlsSessionManager? _sessionManager = null;
|
||||
|
||||
public PlaybackService(ISettingsService settingsService, IMusicSearchService musicSearchService)
|
||||
{
|
||||
_musicSearchService = musicSearchService;
|
||||
InitMediaManager().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async Task InitMediaManager()
|
||||
{
|
||||
_sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
|
||||
_sessionManager.CurrentSessionChanged += SessionManager_CurrentSessionChanged;
|
||||
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
|
||||
SessionManager_CurrentSessionChanged(_sessionManager, null);
|
||||
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
|
||||
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
|
||||
public bool IsPlaying { get; private set; }
|
||||
|
||||
public TimeSpan Position { get; private set; }
|
||||
|
||||
public SongInfo? SongInfo { get; private set; }
|
||||
|
||||
private void CurrentSession_MediaPropertiesChanged(GlobalSystemMediaTransportControlsSession? sender, MediaPropertiesChangedEventArgs? args)
|
||||
{
|
||||
App.DispatcherQueueTimer!.Debounce(
|
||||
async () =>
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
|
||||
if (sender == null)
|
||||
{
|
||||
SongInfo = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
mediaProps = await sender.TryGetMediaPropertiesAsync();
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
if (mediaProps == null)
|
||||
{
|
||||
SongInfo = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProps.Title,
|
||||
Artist = mediaProps.Artist,
|
||||
Album = mediaProps?.AlbumTitle ?? string.Empty,
|
||||
DurationMs = _currentSession
|
||||
?.GetTimelineProperties()
|
||||
.EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = _currentSession?.SourceAppUserModelId,
|
||||
};
|
||||
|
||||
if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
SongInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(
|
||||
streamReference
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
SongInfo.AlbumArt = _musicSearchService.SearchAlbumArtAsync(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist
|
||||
);
|
||||
|
||||
if (SongInfo.AlbumArt == null)
|
||||
{
|
||||
SongInfo.AlbumArt =
|
||||
await ImageHelper.CreateTextPlaceholderBytesAsync(
|
||||
$"{SongInfo.Artist} - {SongInfo.Title}",
|
||||
400,
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
|
||||
}
|
||||
);
|
||||
},
|
||||
TimeSpan.FromMilliseconds(1000)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: Non-UI thread
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
private void CurrentSession_PlaybackInfoChanged(
|
||||
GlobalSystemMediaTransportControlsSession? sender,
|
||||
PlaybackInfoChangedEventArgs? args
|
||||
)
|
||||
private void CurrentSession_PlaybackInfoChanged(GlobalSystemMediaTransportControlsSession? sender, PlaybackInfoChangedEventArgs? args)
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
@@ -85,8 +139,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
break;
|
||||
}
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(IsPlaying));
|
||||
@@ -94,6 +147,33 @@ namespace BetterLyrics.WinUI3.Services
|
||||
);
|
||||
}
|
||||
|
||||
private void CurrentSession_TimelinePropertiesChanged(GlobalSystemMediaTransportControlsSession? sender, TimelinePropertiesChangedEventArgs? args)
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
Position = TimeSpan.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
Position = sender.GetTimelineProperties().Position;
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
PositionChanged?.Invoke(this, new PositionChangedEventArgs(Position));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async Task InitMediaManager()
|
||||
{
|
||||
_sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
|
||||
_sessionManager.CurrentSessionChanged += SessionManager_CurrentSessionChanged;
|
||||
|
||||
SessionManager_CurrentSessionChanged(_sessionManager, null);
|
||||
}
|
||||
|
||||
private void SessionManager_CurrentSessionChanged(
|
||||
GlobalSystemMediaTransportControlsSessionManager sender,
|
||||
CurrentSessionChangedEventArgs? args
|
||||
@@ -124,108 +204,5 @@ namespace BetterLyrics.WinUI3.Services
|
||||
CurrentSession_PlaybackInfoChanged(_currentSession, null);
|
||||
CurrentSession_TimelinePropertiesChanged(_currentSession, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: this func is invoked by non-UI thread
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
private async void CurrentSession_MediaPropertiesChanged(
|
||||
GlobalSystemMediaTransportControlsSession? sender,
|
||||
MediaPropertiesChangedEventArgs? args
|
||||
)
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
|
||||
if (sender == null)
|
||||
{
|
||||
SongInfo = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
mediaProps = await sender.TryGetMediaPropertiesAsync();
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
if (mediaProps == null)
|
||||
{
|
||||
SongInfo = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProps.Title,
|
||||
Artist = mediaProps.Artist,
|
||||
Album = mediaProps?.AlbumTitle ?? string.Empty,
|
||||
DurationMs = _currentSession
|
||||
?.GetTimelineProperties()
|
||||
.EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = _currentSession?.SourceAppUserModelId,
|
||||
};
|
||||
|
||||
if (
|
||||
SongInfo.SourceAppUserModelId?.Contains(Package.Current.Id.FamilyName)
|
||||
?? false
|
||||
)
|
||||
{
|
||||
SongInfo.Title = "甜度爆表";
|
||||
SongInfo.Artist = "AI";
|
||||
}
|
||||
|
||||
if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
SongInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(streamReference);
|
||||
}
|
||||
else
|
||||
{
|
||||
SongInfo.AlbumArt = _musicSearchService.SearchAlbumArtAsync(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist
|
||||
);
|
||||
|
||||
if (SongInfo.AlbumArt == null)
|
||||
{
|
||||
SongInfo.AlbumArt = await ImageHelper.CreateTextPlaceholderBytesAsync(
|
||||
$"{SongInfo.Artist} - {SongInfo.Title}",
|
||||
400,
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void CurrentSession_TimelinePropertiesChanged(
|
||||
GlobalSystemMediaTransportControlsSession? sender,
|
||||
TimelinePropertiesChangedEventArgs? args
|
||||
)
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
Position = TimeSpan.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
Position = sender.GetTimelineProperties().Position;
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
PositionChanged?.Invoke(this, new PositionChangedEventArgs(Position));
|
||||
}
|
||||
);
|
||||
// _logger.LogDebug(_currentTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,202 +1,57 @@
|
||||
using System;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Storage;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
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";
|
||||
public const string LyricsCustomFontColorKey = "LyricsCustomFontColor";
|
||||
|
||||
// App behavior
|
||||
|
||||
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";
|
||||
private const string CoverImageRadiusKey = "CoverImageRadius";
|
||||
private const string CoverOverlayBlurAmountKey = "CoverOverlayBlurAmount";
|
||||
private const string CoverOverlayOpacityKey = "CoverOverlayOpacity";
|
||||
private const string IsCoverOverlayEnabledKey = "IsCoverOverlayEnabled";
|
||||
|
||||
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";
|
||||
private const string DesktopWindowLeftKey = "DesktopWindowLeft";
|
||||
private const string DesktopWindowTopKey = "DesktopWindowTop";
|
||||
private const string DesktopWindowWidthKey = "DesktopWindowWidth";
|
||||
private const string DesktopWindowHeightKey = "DesktopWindowHeight";
|
||||
private const string AutoLockOnDesktopModeKey = "AutoLockOnDesktopMode";
|
||||
|
||||
private const string IsDynamicCoverOverlayEnabledKey = "IsDynamicCoverOverlayEnabled";
|
||||
private const string IsFanLyricsEnabledKey = "IsFanLyricsEnabled";
|
||||
private const string IsFirstRunKey = "IsFirstRun";
|
||||
private const string IsLyricsGlowEffectEnabledKey = "IsLyricsGlowEffectEnabled";
|
||||
private const string LanguageKey = "Language";
|
||||
|
||||
private const string LocalLyricsFoldersKey = "LocalLyricsFolders";
|
||||
private const string LyricsAlignmentTypeKey = "LyricsAlignmentType";
|
||||
private const string LyricsBlurAmountKey = "LyricsBlurAmount";
|
||||
private const string LyricsFontColorTypeKey = "LyricsFontColorType";
|
||||
private const string LyricsFontSizeKey = "LyricsFontSize";
|
||||
private const string LyricsFontWeightKey = "LyricsFontWeightKey";
|
||||
private const string LyricsGlowEffectScopeKey = "LyricsGlowEffectScope";
|
||||
private const string LyricsLineSpacingFactorKey = "LyricsLineSpacingFactor";
|
||||
private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo";
|
||||
private const string LyricsVerticalEdgeOpacityKey = "LyricsVerticalEdgeOpacity";
|
||||
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public ElementTheme ThemeType
|
||||
{
|
||||
get => (ElementTheme)GetValue<int>(ThemeTypeKey);
|
||||
set => SetValue(ThemeTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public Language Language
|
||||
{
|
||||
get => (Language)GetValue<int>(LanguageKey);
|
||||
set => SetValue(LanguageKey, (int)value);
|
||||
}
|
||||
|
||||
public BackdropType BackdropType
|
||||
{
|
||||
get => (BackdropType)GetValue<int>(BackdropTypeKey);
|
||||
set => SetValue(BackdropTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public AutoStartWindowType AutoStartWindowType
|
||||
{
|
||||
get => (AutoStartWindowType)GetValue<int>(AutoStartWindowTypeKey);
|
||||
set => SetValue(AutoStartWindowTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public bool IsCoverOverlayEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsCoverOverlayEnabledKey);
|
||||
set => SetValue(IsCoverOverlayEnabledKey, value);
|
||||
}
|
||||
|
||||
public bool IsDynamicCoverOverlayEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsDynamicCoverOverlayEnabledKey);
|
||||
set => SetValue(IsDynamicCoverOverlayEnabledKey, value);
|
||||
}
|
||||
|
||||
public int CoverOverlayOpacity
|
||||
{
|
||||
get => GetValue<int>(CoverOverlayOpacityKey);
|
||||
set => SetValue(CoverOverlayOpacityKey, value);
|
||||
}
|
||||
|
||||
public int CoverOverlayBlurAmount
|
||||
{
|
||||
get => GetValue<int>(CoverOverlayBlurAmountKey);
|
||||
set => SetValue(CoverOverlayBlurAmountKey, value);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
private readonly ApplicationDataContainer _localSettings;
|
||||
|
||||
public SettingsService()
|
||||
{
|
||||
@@ -227,9 +82,12 @@ namespace BetterLyrics.WinUI3.Services
|
||||
.ToList();
|
||||
}
|
||||
// App appearance
|
||||
SetDefault(ThemeTypeKey, (int)ElementTheme.Default);
|
||||
SetDefault(LanguageKey, (int)Language.FollowSystem);
|
||||
SetDefault(BackdropTypeKey, (int)BackdropType.DesktopAcrylic);
|
||||
SetDefault(DesktopWindowHeightKey, 400);
|
||||
SetDefault(DesktopWindowLeftKey, 0);
|
||||
SetDefault(DesktopWindowTopKey, 0);
|
||||
SetDefault(DesktopWindowWidthKey, 600);
|
||||
SetDefault(AutoLockOnDesktopModeKey, false);
|
||||
// App behavior
|
||||
SetDefault(AutoStartWindowTypeKey, (int)AutoStartWindowType.StandardMode);
|
||||
// Album art
|
||||
@@ -237,18 +95,190 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(IsDynamicCoverOverlayEnabledKey, true);
|
||||
SetDefault(CoverOverlayOpacityKey, 75); // 100 % = 1.0
|
||||
SetDefault(CoverOverlayBlurAmountKey, 200);
|
||||
SetDefault(TitleBarTypeKey, (int)TitleBarType.Compact);
|
||||
SetDefault(CoverImageRadiusKey, 24); // 24 %
|
||||
// Lyrics
|
||||
SetDefault(LyricsAlignmentTypeKey, (int)LyricsAlignmentType.Center);
|
||||
SetDefault(LyricsFontWeightKey, (int)LyricsFontWeight.Bold);
|
||||
SetDefault(LyricsBlurAmountKey, 0);
|
||||
SetDefault(LyricsFontColorTypeKey, (int)LyricsFontColorType.Default);
|
||||
SetDefault(LyricsBlurAmountKey, 5);
|
||||
SetDefault(LyricsFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
|
||||
SetDefault(LyricsCustomFontColorKey, Colors.White.ToInt());
|
||||
SetDefault(LyricsFontSizeKey, 28);
|
||||
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
|
||||
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
|
||||
SetDefault(IsLyricsGlowEffectEnabledKey, true);
|
||||
SetDefault(LyricsGlowEffectScopeKey, (int)LyricsGlowEffectScope.CurrentChar);
|
||||
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.CurrentCharOnly);
|
||||
SetDefault(IsFanLyricsEnabledKey, false);
|
||||
}
|
||||
|
||||
public AutoStartWindowType AutoStartWindowType
|
||||
{
|
||||
get => (AutoStartWindowType)GetValue<int>(AutoStartWindowTypeKey);
|
||||
set => SetValue(AutoStartWindowTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public int DesktopWindowLeft
|
||||
{
|
||||
get => GetValue<int>(DesktopWindowLeftKey);
|
||||
set => SetValue(DesktopWindowLeftKey, value);
|
||||
}
|
||||
|
||||
public int DesktopWindowTop
|
||||
{
|
||||
get => GetValue<int>(DesktopWindowTopKey);
|
||||
set => SetValue(DesktopWindowTopKey, value);
|
||||
}
|
||||
|
||||
public int DesktopWindowWidth
|
||||
{
|
||||
get => GetValue<int>(DesktopWindowWidthKey);
|
||||
set => SetValue(DesktopWindowWidthKey, value);
|
||||
}
|
||||
|
||||
public int DesktopWindowHeight
|
||||
{
|
||||
get => GetValue<int>(DesktopWindowHeightKey);
|
||||
set => SetValue(DesktopWindowHeightKey, value);
|
||||
}
|
||||
public bool AutoLockOnDesktopMode
|
||||
{
|
||||
get => GetValue<bool>(AutoLockOnDesktopModeKey);
|
||||
set => SetValue(AutoLockOnDesktopModeKey, value);
|
||||
}
|
||||
|
||||
public int CoverImageRadius
|
||||
{
|
||||
get => GetValue<int>(CoverImageRadiusKey);
|
||||
set => SetValue(CoverImageRadiusKey, value);
|
||||
}
|
||||
|
||||
public int CoverOverlayBlurAmount
|
||||
{
|
||||
get => GetValue<int>(CoverOverlayBlurAmountKey);
|
||||
set => SetValue(CoverOverlayBlurAmountKey, value);
|
||||
}
|
||||
|
||||
public int CoverOverlayOpacity
|
||||
{
|
||||
get => GetValue<int>(CoverOverlayOpacityKey);
|
||||
set => SetValue(CoverOverlayOpacityKey, value);
|
||||
}
|
||||
|
||||
public bool IsDynamicCoverOverlayEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsDynamicCoverOverlayEnabledKey);
|
||||
set => SetValue(IsDynamicCoverOverlayEnabledKey, value);
|
||||
}
|
||||
|
||||
public bool IsFanLyricsEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsFanLyricsEnabledKey);
|
||||
set => SetValue(IsFanLyricsEnabledKey, value);
|
||||
}
|
||||
|
||||
public bool IsFirstRun
|
||||
{
|
||||
get => GetValue<bool>(IsFirstRunKey);
|
||||
set => SetValue(IsFirstRunKey, value);
|
||||
}
|
||||
|
||||
public bool IsLyricsGlowEffectEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsLyricsGlowEffectEnabledKey);
|
||||
set => SetValue(IsLyricsGlowEffectEnabledKey, value);
|
||||
}
|
||||
|
||||
public Language Language
|
||||
{
|
||||
get => (Language)GetValue<int>(LanguageKey);
|
||||
set => SetValue(LanguageKey, (int)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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public LyricsAlignmentType LyricsAlignmentType
|
||||
{
|
||||
get => (LyricsAlignmentType)GetValue<int>(LyricsAlignmentTypeKey);
|
||||
set => SetValue(LyricsAlignmentTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public int LyricsBlurAmount
|
||||
{
|
||||
get => GetValue<int>(LyricsBlurAmountKey);
|
||||
set => SetValue(LyricsBlurAmountKey, value);
|
||||
}
|
||||
|
||||
public Color LyricsCustomFontColor
|
||||
{
|
||||
get => GetValue<int>(LyricsCustomFontColorKey)!.ToColor();
|
||||
set => SetValue(LyricsCustomFontColorKey, value.ToInt());
|
||||
}
|
||||
|
||||
public LyricsFontColorType LyricsFontColorType
|
||||
{
|
||||
get => (LyricsFontColorType)GetValue<int>(LyricsFontColorTypeKey);
|
||||
set => SetValue(LyricsFontColorTypeKey, (int)value);
|
||||
}
|
||||
|
||||
public int LyricsFontSize
|
||||
{
|
||||
get => GetValue<int>(LyricsFontSizeKey);
|
||||
set => SetValue(LyricsFontSizeKey, value);
|
||||
}
|
||||
|
||||
public LyricsFontWeight LyricsFontWeight
|
||||
{
|
||||
get => (LyricsFontWeight)GetValue<int>(LyricsFontWeightKey);
|
||||
set => SetValue(LyricsFontWeightKey, (int)value);
|
||||
}
|
||||
|
||||
public LineRenderingType LyricsGlowEffectScope
|
||||
{
|
||||
get => (LineRenderingType)GetValue<int>(LyricsGlowEffectScopeKey);
|
||||
set => SetValue(LyricsGlowEffectScopeKey, (int)value);
|
||||
}
|
||||
|
||||
public float LyricsLineSpacingFactor
|
||||
{
|
||||
get => GetValue<float>(LyricsLineSpacingFactorKey);
|
||||
set => SetValue(LyricsLineSpacingFactorKey, value);
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public int LyricsVerticalEdgeOpacity
|
||||
{
|
||||
get => GetValue<int>(LyricsVerticalEdgeOpacityKey);
|
||||
set => SetValue(LyricsVerticalEdgeOpacityKey, value);
|
||||
}
|
||||
|
||||
private T? GetValue<T>(string key)
|
||||
@@ -260,16 +290,16 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return default;
|
||||
}
|
||||
|
||||
private void SetValue<T>(string key, T value)
|
||||
{
|
||||
_localSettings.Values[key] = value;
|
||||
}
|
||||
|
||||
private void SetDefault<T>(string key, T value)
|
||||
{
|
||||
if (_localSettings.Values.ContainsKey(key) && _localSettings.Values[key] is T)
|
||||
return;
|
||||
_localSettings.Values[key] = value;
|
||||
}
|
||||
|
||||
private void SetValue<T>(string key, T value)
|
||||
{
|
||||
_localSettings.Values[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,13 +139,13 @@
|
||||
<value>Add a folder</value>
|
||||
</data>
|
||||
<data name="SettingsPageTheme.Header" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
<value>Lyrics window theme</value>
|
||||
</data>
|
||||
<data name="SettingsPageLanguage.Header" xml:space="preserve">
|
||||
<value>Language</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>Follow system</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>Adaptive to lyrics background (Colored)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLight.Content" xml:space="preserve">
|
||||
<value>Light</value>
|
||||
@@ -196,7 +196,7 @@
|
||||
<value>Transparent</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
|
||||
<value>Backdrop</value>
|
||||
<value>Lyrics backdrop</value>
|
||||
</data>
|
||||
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
|
||||
<value>Default</value>
|
||||
@@ -210,14 +210,14 @@
|
||||
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
|
||||
<value>The folder has been added. Please do not add it again.</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
|
||||
<value>Overlay album art background</value>
|
||||
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
|
||||
<value>Lyrics background</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
|
||||
<value>Dynamic album art background</value>
|
||||
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
|
||||
<value>Dynamic lyrics background</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
|
||||
<value>Album art background opacity</value>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>Lyrics background opacity</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>Settings - BetterLyrics</value>
|
||||
@@ -237,8 +237,8 @@
|
||||
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
|
||||
<value>Right</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
|
||||
<value>Album art background blur amount</value>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>Lyrics background blur amount</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
|
||||
<value>Blur amount</value>
|
||||
@@ -252,9 +252,6 @@
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>Significantly higher GPU usage when blur is enabled (> 0)</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
|
||||
<value>Enabling this feature will slightly increase GPU utilization</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
|
||||
<value>Top and bottom edge opacity</value>
|
||||
</data>
|
||||
@@ -273,8 +270,8 @@
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Immersive mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>Album background</value>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>Lyrics background</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>About</value>
|
||||
@@ -282,7 +279,7 @@
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>Lyrics library</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>App appearance</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
@@ -294,6 +291,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>
|
||||
@@ -313,19 +313,19 @@
|
||||
<value>Play test music</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>Play using system player</value>
|
||||
<value>Play "Cut To The Feeling" on "soundcloud.com"</value>
|
||||
</data>
|
||||
<data name="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>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
|
||||
<value>Default</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>Album art accent color</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>Adaptive to lyrics background (Grayed)</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>Album art style</value>
|
||||
@@ -378,19 +378,19 @@
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Exit picture-in-picture mode</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<data name="LyricsNotFound" xml:space="preserve">
|
||||
<value>Lyrics not found</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>Lyrics effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>Lyrics style</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>This folder is already included in the existing folder and does not need to be added again</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
|
||||
<value>App behavior</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
@@ -400,10 +400,10 @@
|
||||
<value>Activate standard mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>Activate dock mode</value>
|
||||
<value>Activate desktop mode</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>Lyrics not found</value>
|
||||
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
|
||||
<value>Activate dock mode</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>System tray - BetterLyrics</value>
|
||||
@@ -411,6 +411,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>
|
||||
@@ -459,7 +462,7 @@
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<data name="LyricsLoading" xml:space="preserve">
|
||||
<value>Loading lyrics...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
@@ -486,4 +489,55 @@
|
||||
<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 (Restart needed)</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>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>Fan lyrics</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
|
||||
<value>Custom</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyrics.Content" xml:space="preserve">
|
||||
<value>Lyrics style and effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageApp.Content" xml:space="preserve">
|
||||
<value>App appearance and behavior</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
|
||||
<value>Auto-lock when activating desktop mode</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -139,13 +139,13 @@
|
||||
<value>フォルダーを追加します</value>
|
||||
</data>
|
||||
<data name="SettingsPageTheme.Header" xml:space="preserve">
|
||||
<value>テーマ</value>
|
||||
<value>歌詞ウィンドウのテーマ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLanguage.Header" xml:space="preserve">
|
||||
<value>言語</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>システムをフォローします</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>歌詞の背景に適応する(色付き)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLight.Content" xml:space="preserve">
|
||||
<value>ライト</value>
|
||||
@@ -196,7 +196,7 @@
|
||||
<value>透明</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
|
||||
<value>背景</value>
|
||||
<value>歌詞の背景素材</value>
|
||||
</data>
|
||||
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
|
||||
<value>デフォルト</value>
|
||||
@@ -210,14 +210,14 @@
|
||||
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
|
||||
<value>フォルダーが追加されました。二度と追加しないでください。</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
|
||||
<value>オーバーレイアルバムアートの背景</value>
|
||||
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
|
||||
<value>歌詞の背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
|
||||
<value>ダイナミックアルバムアートの背景</value>
|
||||
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
|
||||
<value>ダイナミックな歌詞の背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
|
||||
<value>アルバムアートの背景不透明</value>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>歌詞の背景不透明</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>設定 - BetterLyrics</value>
|
||||
@@ -237,8 +237,8 @@
|
||||
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
|
||||
<value>右</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
|
||||
<value>アルバムアートバックグラウンドブラー量</value>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>歌詞の背景ぼやけ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
|
||||
<value>ぼやけの量</value>
|
||||
@@ -252,9 +252,6 @@
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>ぼかしが有効になっている場合のGPU使用量が大幅に高くなります(> 0)</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
|
||||
<value>この機能を有効にすると、GPUの使用率がわずかに増加します</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
|
||||
<value>上端と下端の不透明度</value>
|
||||
</data>
|
||||
@@ -273,8 +270,8 @@
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>没入モード</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>アルバムの背景</value>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>歌詞の背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>について</value>
|
||||
@@ -282,7 +279,7 @@
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>歌詞ライブラリ</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>アプリの外観</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
@@ -294,6 +291,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>
|
||||
@@ -313,19 +313,19 @@
|
||||
<value>テスト音楽を再生します</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>システムプレーヤーを使用して再生します</value>
|
||||
<value>「SoundCloud.com」で「Cut to the Feeling」を再生する</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>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
|
||||
<value>デフォルト</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>アルバムアートアクセントカラー</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>歌詞の背景に適応する(灰色)</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>アルバムアートスタイル</value>
|
||||
@@ -378,19 +378,19 @@
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ピクチャーインピクチャーモードを終了します</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<data name="LyricsNotFound" xml:space="preserve">
|
||||
<value>歌詞が見つかりません</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>歌詞効果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>歌詞スタイル</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>このフォルダーは既存のフォルダーに既に含まれており、再度追加する必要はありません</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
|
||||
<value>アプリの動作</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
@@ -400,10 +400,10 @@
|
||||
<value>標準モードをアクティブにします</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>ドックモードをアクティブにします</value>
|
||||
<value>デスクトップモードを開始します</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>歌詞が見つかりません</value>
|
||||
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
|
||||
<value>ドックモードをアクティブにします</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>システムトレイ - BetterLyrics</value>
|
||||
@@ -411,6 +411,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>
|
||||
@@ -459,7 +462,7 @@
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<data name="LyricsLoading" xml:space="preserve">
|
||||
<value>歌詞の読み込み...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
@@ -486,4 +489,55 @@
|
||||
<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>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>ファンの歌詞</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
|
||||
<value>カスタマイズ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyrics.Content" xml:space="preserve">
|
||||
<value>歌詞のスタイルと効果</value>
|
||||
</data>
|
||||
<data name="SettingsPageApp.Content" xml:space="preserve">
|
||||
<value>アプリの外観と動作</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
|
||||
<value>デスクトップモードをアクティブにするときの自動ロック</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -139,13 +139,13 @@
|
||||
<value>폴더를 추가하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageTheme.Header" xml:space="preserve">
|
||||
<value>주제</value>
|
||||
<value>가사 창 테마</value>
|
||||
</data>
|
||||
<data name="SettingsPageLanguage.Header" xml:space="preserve">
|
||||
<value>언어</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>시스템을 따르십시오</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>가사 배경 (색상)에 적응</value>
|
||||
</data>
|
||||
<data name="SettingsPageLight.Content" xml:space="preserve">
|
||||
<value>빛</value>
|
||||
@@ -196,7 +196,7 @@
|
||||
<value>투명한</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
|
||||
<value>배경</value>
|
||||
<value>가사 배경 자료</value>
|
||||
</data>
|
||||
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
|
||||
<value>기본</value>
|
||||
@@ -210,14 +210,14 @@
|
||||
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
|
||||
<value>폴더가 추가되었습니다. 다시 추가하지 마십시오.</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
|
||||
<value>오버레이 앨범 아트 배경</value>
|
||||
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
|
||||
<value>가사 배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
|
||||
<value>동적 앨범 아트 배경</value>
|
||||
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
|
||||
<value>역동적 인 가사 배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
|
||||
<value>앨범 아트 배경 불투명도</value>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>가사 배경 불투명도</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>설정 - BetterLyrics</value>
|
||||
@@ -237,8 +237,8 @@
|
||||
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
|
||||
<value>오른쪽</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
|
||||
<value>앨범 아트 배경 흐림 금액</value>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>가사 배경 블러</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
|
||||
<value>흐림 금액</value>
|
||||
@@ -252,9 +252,6 @@
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>Blur가 활성화 될 때 상당히 높은 GPU 사용량 (> 0)</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
|
||||
<value>이 기능을 활성화하면 GPU 사용률이 약간 증가합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
|
||||
<value>상단 및 하단 가장자리 불투명도</value>
|
||||
</data>
|
||||
@@ -273,8 +270,8 @@
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>몰입 형 모드</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>앨범 배경</value>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>가사 배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>에 대한</value>
|
||||
@@ -282,7 +279,7 @@
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>가사 도서관</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>앱 모양</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
@@ -294,6 +291,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>
|
||||
@@ -313,19 +313,19 @@
|
||||
<value>테스트 음악을 재생하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>시스템 플레이어를 사용하여 재생하십시오</value>
|
||||
<value>"soundcloud.com"에서 "Fut to the Feeling"을 재생하십시오.</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>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
|
||||
<value>기본</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>앨범 아트 악센트 색상</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>가사 배경 (회색)에 적응</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>앨범 아트 스타일</value>
|
||||
@@ -378,19 +378,19 @@
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Picture-in-Picture 모드 종료</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<data name="LyricsNotFound" xml:space="preserve">
|
||||
<value>가사를 찾을 수 없습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>가사 효과</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>가사 스타일</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>이 폴더는 이미 기존 폴더에 포함되어 있으며 다시 추가 할 필요가 없습니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
|
||||
<value>앱 동작</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
@@ -400,10 +400,10 @@
|
||||
<value>표준 모드를 활성화합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>도크 모드를 활성화하십시오</value>
|
||||
<value>데스크탑 모드를 시작하십시오</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>가사를 찾을 수 없습니다</value>
|
||||
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
|
||||
<value>도크 모드를 활성화하십시오</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>시스템 트레이 - BetterLyrics</value>
|
||||
@@ -411,6 +411,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>
|
||||
@@ -459,7 +462,7 @@
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>설정</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<data name="LyricsLoading" xml:space="preserve">
|
||||
<value>가사로드 ...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
@@ -486,4 +489,55 @@
|
||||
<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>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>팬 가사</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
|
||||
<value>사용자 정의하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyrics.Content" xml:space="preserve">
|
||||
<value>가사 스타일과 효과</value>
|
||||
</data>
|
||||
<data name="SettingsPageApp.Content" xml:space="preserve">
|
||||
<value>앱 외관과 행동</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
|
||||
<value>데스크탑 모드를 활성화 할 때 자동 잠금</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -139,13 +139,13 @@
|
||||
<value>添加文件夹</value>
|
||||
</data>
|
||||
<data name="SettingsPageTheme.Header" xml:space="preserve">
|
||||
<value>主题</value>
|
||||
<value>歌词窗口主题</value>
|
||||
</data>
|
||||
<data name="SettingsPageLanguage.Header" xml:space="preserve">
|
||||
<value>语言</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>跟随系统</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>适应歌词背景(彩色)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLight.Content" xml:space="preserve">
|
||||
<value>浅色</value>
|
||||
@@ -196,7 +196,7 @@
|
||||
<value>透明</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
|
||||
<value>背景材质</value>
|
||||
<value>歌词背景材质</value>
|
||||
</data>
|
||||
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
|
||||
<value>默认</value>
|
||||
@@ -210,14 +210,14 @@
|
||||
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
|
||||
<value>已添加过该文件夹,请勿重复添加</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
|
||||
<value>叠加专辑图片背景</value>
|
||||
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
|
||||
<value>歌词背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
|
||||
<value>动态专辑图片背景</value>
|
||||
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
|
||||
<value>动态歌词背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
|
||||
<value>专辑图片背景不透明度</value>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>歌词背景不透明度</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>设置 - BetterLyrics</value>
|
||||
@@ -237,8 +237,8 @@
|
||||
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
|
||||
<value>靠右</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
|
||||
<value>专辑图片背景模糊度</value>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>歌词背景模糊度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
|
||||
<value>模糊度</value>
|
||||
@@ -252,9 +252,6 @@
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>启用模糊(> 0)时将显著提升 GPU 占用率</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
|
||||
<value>启用该功能将略微提升 GPU 占用率</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
|
||||
<value>上下边缘不透明度</value>
|
||||
</data>
|
||||
@@ -273,8 +270,8 @@
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>沉浸模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>专辑背景</value>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>歌词背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>关于</value>
|
||||
@@ -282,7 +279,7 @@
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>歌词库</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>应用外观</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
@@ -294,6 +291,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>
|
||||
@@ -313,19 +313,19 @@
|
||||
<value>播放测试音乐</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>使用系统播放器播放</value>
|
||||
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</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>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
|
||||
<value>默认</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>专辑强调色</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>适应歌词背景(灰色)</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>专辑封面样式</value>
|
||||
@@ -378,19 +378,19 @@
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>退出画中画模式</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<data name="LyricsNotFound" xml:space="preserve">
|
||||
<value>未找到歌词</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>歌词动效</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>歌词样式</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>该文件夹已包含在已有文件夹中,无需再次添加</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
|
||||
<value>应用行为</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
@@ -400,10 +400,10 @@
|
||||
<value>启动标准模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>启动停靠模式</value>
|
||||
<value>启动桌面模式</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>未找到歌词</value>
|
||||
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
|
||||
<value>启动停靠模式</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>系统托盘 - BetterLyrics</value>
|
||||
@@ -411,6 +411,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>
|
||||
@@ -459,7 +462,7 @@
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>设置</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<data name="LyricsLoading" xml:space="preserve">
|
||||
<value>加载歌词中...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
@@ -486,4 +489,55 @@
|
||||
<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>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>扇形歌词</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
|
||||
<value>自定义</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyrics.Content" xml:space="preserve">
|
||||
<value>歌词样式与动效</value>
|
||||
</data>
|
||||
<data name="SettingsPageApp.Content" xml:space="preserve">
|
||||
<value>应用外观与行为</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
|
||||
<value>启动桌面模式时随即锁定窗口</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -139,13 +139,13 @@
|
||||
<value>新增資料夾</value>
|
||||
</data>
|
||||
<data name="SettingsPageTheme.Header" xml:space="preserve">
|
||||
<value>主題</value>
|
||||
<value>歌詞窗口主題</value>
|
||||
</data>
|
||||
<data name="SettingsPageLanguage.Header" xml:space="preserve">
|
||||
<value>語言</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>跟隨系統</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
|
||||
<value>適應歌詞背景(彩色)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLight.Content" xml:space="preserve">
|
||||
<value>淺色</value>
|
||||
@@ -196,7 +196,7 @@
|
||||
<value>透明</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackdrop.Header" xml:space="preserve">
|
||||
<value>背景材質</value>
|
||||
<value>歌詞背景材質</value>
|
||||
</data>
|
||||
<data name="SettingsPageSystemLanguage.Content" xml:space="preserve">
|
||||
<value>預設</value>
|
||||
@@ -210,14 +210,14 @@
|
||||
<data name="SettingsPagePathExistedInfo" xml:space="preserve">
|
||||
<value>已新增過該資料夾,請勿重複新增</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlay.Header" xml:space="preserve">
|
||||
<value>疊加專輯圖片背景</value>
|
||||
<data name="SettingsPageLyricsBackground.Header" xml:space="preserve">
|
||||
<value>歌詞背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicCoverOverlay.Header" xml:space="preserve">
|
||||
<value>動態專輯圖片背景</value>
|
||||
<data name="SettingsPageDynamicLyricsBackground.Header" xml:space="preserve">
|
||||
<value>動態歌詞背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayOpacity.Header" xml:space="preserve">
|
||||
<value>專輯圖片背景不透明度</value>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>歌詞背景不透明度</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitle" xml:space="preserve">
|
||||
<value>設定 - BetterLyrics</value>
|
||||
@@ -237,8 +237,8 @@
|
||||
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
|
||||
<value>靠右</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayBlurAmount.Header" xml:space="preserve">
|
||||
<value>專輯圖片背景模糊度</value>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>歌詞背景模糊度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurAmount.Header" xml:space="preserve">
|
||||
<value>模糊度</value>
|
||||
@@ -252,9 +252,6 @@
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>啟用模糊(> 0)時將顯著提升 GPU 佔用率</value>
|
||||
</data>
|
||||
<data name="SettingsPageCoverOverlayGPUUsage.Text" xml:space="preserve">
|
||||
<value>啟用此功能將略微提升 GPU 佔用率</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
|
||||
<value>上下邊緣不透明度</value>
|
||||
</data>
|
||||
@@ -273,8 +270,8 @@
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>沉浸模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumOverlay.Content" xml:space="preserve">
|
||||
<value>專輯背景</value>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>歌詞背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageAbout.Content" xml:space="preserve">
|
||||
<value>關於</value>
|
||||
@@ -282,7 +279,7 @@
|
||||
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
|
||||
<value>歌詞庫</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppAppearance.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
|
||||
<value>應用外觀</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
@@ -294,6 +291,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>
|
||||
@@ -313,19 +313,19 @@
|
||||
<value>播放測試音樂</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayingMockMusicButton.Content" xml:space="preserve">
|
||||
<value>使用系統播放器播放</value>
|
||||
<value>在 “soundcloud.com” 上播放 “Cut to the Feeling”</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>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDefault.Content" xml:space="preserve">
|
||||
<value>預設</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorDominant.Content" xml:space="preserve">
|
||||
<value>專輯強調色</value>
|
||||
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
|
||||
<value>適應歌詞背景(灰色)</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>專輯封面樣式</value>
|
||||
@@ -378,19 +378,19 @@
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>退出畫中畫模式</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<data name="LyricsNotFound" xml:space="preserve">
|
||||
<value>找不到歌詞</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>歌詞動效</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Content" xml:space="preserve">
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>歌詞樣式</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>該資料夾已包含在已有資料夾中,無需再次添加</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppBehavior.Content" xml:space="preserve">
|
||||
<data name="SettingsPageAppBehavior.Text" xml:space="preserve">
|
||||
<value>應用行為</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
@@ -400,10 +400,10 @@
|
||||
<value>啟動標準模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartDesktopLyrics.Content" xml:space="preserve">
|
||||
<value>啟動停靠模式</value>
|
||||
<value>啟動桌面模式</value>
|
||||
</data>
|
||||
<data name="DesktopLyricsRendererPageLyricsNotFound.Text" xml:space="preserve">
|
||||
<value>找不到歌詞</value>
|
||||
<data name="SettingsPageAutoStartDockLyrics.Content" xml:space="preserve">
|
||||
<value>啟動停靠模式</value>
|
||||
</data>
|
||||
<data name="SystemTrayPageTitle" xml:space="preserve">
|
||||
<value>系統托盤 - BetterLyrics</value>
|
||||
@@ -411,6 +411,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>
|
||||
@@ -459,7 +462,7 @@
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<data name="LyricsLoading" xml:space="preserve">
|
||||
<value>載入歌詞中...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
@@ -486,4 +489,55 @@
|
||||
<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>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>扇形歌詞</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
|
||||
<value>自定義</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyrics.Content" xml:space="preserve">
|
||||
<value>歌詞樣式與動效</value>
|
||||
</data>
|
||||
<data name="SettingsPageApp.Content" xml:space="preserve">
|
||||
<value>應用外觀與行為</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoLock.Header" xml:space="preserve">
|
||||
<value>啟動桌面模式時隨即鎖定窗口</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Dispatching;
|
||||
@@ -8,11 +9,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class BaseViewModel : ObservableRecipient, IDisposable
|
||||
{
|
||||
private protected readonly ISettingsService _settingsService;
|
||||
|
||||
private protected readonly DispatcherQueue _dispatcherQueue =
|
||||
DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
private protected readonly ISettingsService _settingsService;
|
||||
|
||||
public BaseViewModel(ISettingsService settingsService)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class BaseWindowViewModel : BaseViewModel
|
||||
{
|
||||
public BaseWindowViewModel(ISettingsService settingsService) : base(settingsService) { }
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
public partial class HostWindowViewModel
|
||||
: BaseViewModel,
|
||||
IRecipient<PropertyChangedMessage<TitleBarType>>,
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<BackdropType>>,
|
||||
IRecipient<PropertyChangedMessage<int>>
|
||||
{
|
||||
private ForegroundWindowWatcherHelper? _watcherHelper = null;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Type FramePageType { get; set; }
|
||||
|
||||
[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; }
|
||||
|
||||
public HostWindowViewModel(ISettingsService settingsService)
|
||||
: base(settingsService)
|
||||
{
|
||||
TitleBarType = _settingsService.TitleBarType;
|
||||
ThemeType = _settingsService.ThemeType;
|
||||
OnTitleBarTypeChanged(TitleBarType);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<ShowNotificatonMessage>(
|
||||
this,
|
||||
async (r, m) =>
|
||||
{
|
||||
Notification = m.Value;
|
||||
if (
|
||||
!Notification.IsForeverDismissable
|
||||
|| AlreadyForeverDismissedThisMessage() == false
|
||||
)
|
||||
{
|
||||
Notification.Visibility = Notification.IsForeverDismissable
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
ShowInfoBar = true;
|
||||
await Task.Delay(AnimationHelper.StackedNotificationsShowingDuration);
|
||||
ShowInfoBar = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void StartWatchWindowColorChange()
|
||||
{
|
||||
var hwnd = WindowNative.GetWindowHandle(
|
||||
WindowHelper.GetWindowByFramePageType(FramePageType)
|
||||
);
|
||||
_watcherHelper = new ForegroundWindowWatcherHelper(
|
||||
hwnd,
|
||||
onWindowChanged =>
|
||||
{
|
||||
UpdateAccentColor(hwnd);
|
||||
}
|
||||
);
|
||||
_watcherHelper.Start();
|
||||
UpdateAccentColor(hwnd);
|
||||
}
|
||||
|
||||
private void StopWatchWindowColorChange()
|
||||
{
|
||||
_watcherHelper?.Stop();
|
||||
_watcherHelper = null;
|
||||
}
|
||||
|
||||
partial void OnFramePageTypeChanged(Type value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var window = WindowHelper.GetWindowByFramePageType(FramePageType);
|
||||
window.SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(
|
||||
_settingsService.BackdropType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateAccentColor(nint hwnd)
|
||||
{
|
||||
ActivatedWindowAccentColor = WindowColorHelper
|
||||
.GetDominantColorBelow(hwnd)
|
||||
.ToWindowsUIColor();
|
||||
}
|
||||
|
||||
partial void OnTitleBarTypeChanged(TitleBarType value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case TitleBarType.Compact:
|
||||
AppLogoImageIconHeight = 18;
|
||||
TitleBarFontSize = 11;
|
||||
break;
|
||||
case TitleBarType.Extended:
|
||||
AppLogoImageIconHeight = 20;
|
||||
TitleBarFontSize = 14;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
TitleBarHeight = value.GetHeight();
|
||||
}
|
||||
|
||||
[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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
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 BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -18,67 +19,13 @@ using WinUIEx.Messaging;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsPageViewModel
|
||||
: BaseViewModel,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsStatus>>
|
||||
public partial class LyricsPageViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<int>>, IRecipient<PropertyChangedMessage<bool>>
|
||||
{
|
||||
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;
|
||||
|
||||
private readonly IPlaybackService _playbackService;
|
||||
|
||||
public LyricsPageViewModel(
|
||||
ISettingsService settingsService,
|
||||
IPlaybackService playbackService
|
||||
)
|
||||
: base(settingsService)
|
||||
private LyricsDisplayType? _preferredDisplayTypeBeforeSwitchToNonStandardMode;
|
||||
|
||||
public LyricsPageViewModel(ISettingsService settingsService, IPlaybackService playbackService) : base(settingsService)
|
||||
{
|
||||
LyricsFontSize = _settingsService.LyricsFontSize;
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
@@ -92,42 +39,93 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
UpdateSongInfoUI(_playbackService.SongInfo).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
partial void OnCoverImageRadiusChanged(int value)
|
||||
{
|
||||
if (double.IsNaN(CoverImageGridActualHeight))
|
||||
return;
|
||||
[ObservableProperty]
|
||||
public partial bool AboutToUpdateUI { get; set; }
|
||||
|
||||
CoverImageGridCornerRadius = new CornerRadius(
|
||||
value / 100f * CoverImageGridActualHeight / 2
|
||||
[ObservableProperty]
|
||||
public partial BitmapImage? CoverImage { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double CoverImageGridActualHeight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial CornerRadius CoverImageGridCornerRadius { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int CoverImageRadius { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsDisplayType DisplayType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsFirstRun { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsNotDockMode { get; set; } = true;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsWelcomeTeachingTipOpen { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int LyricsFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial double MaxLyricsWidth { get; set; } = 0.0;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsDisplayType? PreferredDisplayType { get; set; } = LyricsDisplayType.SplitView;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial SongInfo? SongInfo { get; set; } = null;
|
||||
|
||||
public void OpenMatchedFileFolderInFileExplorer(string path)
|
||||
{
|
||||
Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "explorer.exe",
|
||||
Arguments = $"/select,\"{path}\"",
|
||||
UseShellExecute = true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
partial void OnCoverImageGridActualHeightChanged(double value)
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (double.IsNaN(value))
|
||||
return;
|
||||
|
||||
CoverImageGridCornerRadius = new CornerRadius(CoverImageRadius / 100f * value / 2);
|
||||
if (message.Sender is LyricsWindowViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
|
||||
{
|
||||
IsNotDockMode = !message.NewValue;
|
||||
SetNonStandardModePreferredDisplayType(message.NewValue);
|
||||
TrySwitchToPreferredDisplayType(SongInfo);
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
|
||||
{
|
||||
SetNonStandardModePreferredDisplayType(message.NewValue);
|
||||
TrySwitchToPreferredDisplayType(SongInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnIsFirstRunChanged(bool value)
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
IsWelcomeTeachingTipOpen = value;
|
||||
_settingsService.IsFirstRun = false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OnDisplayTypeChanged(object value)
|
||||
{
|
||||
int index = Convert.ToInt32(value);
|
||||
PreferredDisplayType = (LyricsDisplayType)index;
|
||||
DisplayType = (LyricsDisplayType)index;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenSettingsWindow()
|
||||
{
|
||||
WindowHelper.OpenSettingsWindow();
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.CoverImageRadius))
|
||||
{
|
||||
CoverImageRadius = message.NewValue;
|
||||
}
|
||||
}
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize))
|
||||
{
|
||||
LyricsFontSize = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateSongInfoUI(SongInfo? songInfo)
|
||||
@@ -147,6 +145,25 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
AboutToUpdateUI = false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenSettingsWindow()
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
private void SetNonStandardModePreferredDisplayType(bool isEnabled)
|
||||
{
|
||||
if (isEnabled)
|
||||
{
|
||||
_preferredDisplayTypeBeforeSwitchToNonStandardMode = PreferredDisplayType;
|
||||
PreferredDisplayType = LyricsDisplayType.LyricsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode;
|
||||
}
|
||||
}
|
||||
|
||||
private void TrySwitchToPreferredDisplayType(SongInfo? songInfo)
|
||||
{
|
||||
LyricsDisplayType displayType;
|
||||
@@ -165,68 +182,31 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
|
||||
DisplayType = displayType;
|
||||
|
||||
}
|
||||
|
||||
public void OpenMatchedFileFolderInFileExplorer(string path)
|
||||
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);
|
||||
}
|
||||
|
||||
partial void OnCoverImageRadiusChanged(int value)
|
||||
{
|
||||
if (double.IsNaN(CoverImageGridActualHeight))
|
||||
return;
|
||||
|
||||
CoverImageGridCornerRadius = new CornerRadius(
|
||||
value / 100f * CoverImageGridActualHeight / 2
|
||||
);
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
IsWelcomeTeachingTipOpen = value;
|
||||
_settingsService.IsFirstRun = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,537 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Brushes;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
using var combined = new CanvasCommandList(control);
|
||||
using var combinedDs = combined.CreateDrawingSession();
|
||||
|
||||
DrawAlbumArtBackground(control, combinedDs);
|
||||
|
||||
if (_isDockMode)
|
||||
{
|
||||
DrawImmersiveBackground(control, combinedDs);
|
||||
}
|
||||
|
||||
combinedDs.DrawImage(blurredLyrics);
|
||||
|
||||
if (_isDesktopMode)
|
||||
{
|
||||
ds.DrawImage(blurredLyrics);
|
||||
}
|
||||
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: "
|
||||
+ $"Cur playing {currentPlayingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n"
|
||||
+ $"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n"
|
||||
+ $"Cur time {TotalTime}\n" +
|
||||
$"Lang size: {_multiLangLyrics.Count}\n" +
|
||||
$"{_lyricsOpacityTransition.Value}",
|
||||
new Vector2(10, 10),
|
||||
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
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 (_lastAlbumArtBitmap != null)
|
||||
{
|
||||
DrawImgae(
|
||||
control,
|
||||
overlappedCoversDs,
|
||||
_lastAlbumArtBitmap,
|
||||
1 - _albumArtBgTransition.Value
|
||||
);
|
||||
}
|
||||
if (_albumArtBitmap != null)
|
||||
{
|
||||
DrawImgae(
|
||||
control,
|
||||
overlappedCoversDs,
|
||||
_albumArtBitmap,
|
||||
_albumArtBgTransition.Value
|
||||
);
|
||||
}
|
||||
|
||||
using var coverOverlayEffect = new OpacityEffect
|
||||
{
|
||||
Opacity = CoverOverlayOpacity / 100f,
|
||||
Source = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = CoverOverlayBlurAmount,
|
||||
Source = overlappedCovers,
|
||||
},
|
||||
};
|
||||
ds.DrawImage(coverOverlayEffect);
|
||||
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
private void DrawBlurredLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
||||
|
||||
var currentPlayingLine = _multiLangLyrics
|
||||
.SafeGet(_langIndex)
|
||||
?.SafeGet(currentPlayingLineIndex);
|
||||
|
||||
if (currentPlayingLine == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
|
||||
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;
|
||||
|
||||
// 组合变换:缩放 -> 旋转 -> 平移
|
||||
ds.Transform =
|
||||
Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
|
||||
* Matrix3x2.CreateRotation(
|
||||
line.AngleTransition.Value,
|
||||
currentPlayingLine.Position
|
||||
)
|
||||
* Matrix3x2.CreateTranslation(
|
||||
offsetToLeft,
|
||||
_canvasYScrollTransition.Value + (float)(control.Size.Height / 2)
|
||||
);
|
||||
|
||||
// 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 = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
|
||||
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,
|
||||
}
|
||||
: new CanvasCommandList(control.Device),
|
||||
Foreground = new AlphaMaskEffect
|
||||
{
|
||||
Source = lyrics,
|
||||
AlphaMask = mask,
|
||||
},
|
||||
},
|
||||
Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset scale
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawImmersiveBackground(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
bool withGradient = true
|
||||
)
|
||||
{
|
||||
ds.FillRectangle(
|
||||
new Rect(0, 0, control.Size.Width, control.Size.Height),
|
||||
new CanvasLinearGradientBrush(
|
||||
control,
|
||||
[
|
||||
new CanvasGradientStop
|
||||
{
|
||||
Position = 0f,
|
||||
Color = withGradient
|
||||
? Color.FromArgb(
|
||||
211,
|
||||
_immersiveBgTransition.Value.R,
|
||||
_immersiveBgTransition.Value.G,
|
||||
_immersiveBgTransition.Value.B
|
||||
)
|
||||
: _immersiveBgTransition.Value,
|
||||
},
|
||||
new CanvasGradientStop
|
||||
{
|
||||
Position = 1,
|
||||
Color = _immersiveBgTransition.Value,
|
||||
},
|
||||
]
|
||||
)
|
||||
{
|
||||
StartPoint = new Vector2(0, 0),
|
||||
EndPoint = new Vector2(0, (float)control.Size.Height),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private CanvasLinearGradientBrush GetHorizontalFillBrush(
|
||||
ICanvasAnimatedControl control,
|
||||
List<(float position, float opacity)> stops,
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
void DrawShenGuang(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
float w = (float)control.Size.Width;
|
||||
float h = (float)control.Size.Height;
|
||||
|
||||
float beamLength = h; // 光束长度等于画布高度
|
||||
float beamAngle = (float)(Math.PI / 6); // 30°
|
||||
float centerX = w / 2;
|
||||
float centerY = h;
|
||||
float angle = _shenGuangAngleTransition.Value;
|
||||
|
||||
var p0 = new Vector2(centerX, centerY);
|
||||
var p1 = new Vector2(
|
||||
centerX + beamLength * (float)Math.Cos(angle - beamAngle / 2),
|
||||
centerY + beamLength * (float)Math.Sin(angle - beamAngle / 2)
|
||||
);
|
||||
var p2 = new Vector2(
|
||||
centerX + beamLength * (float)Math.Cos(angle + beamAngle / 2),
|
||||
centerY + beamLength * (float)Math.Sin(angle + beamAngle / 2)
|
||||
);
|
||||
|
||||
using var path = new CanvasPathBuilder(control);
|
||||
path.BeginFigure(p0);
|
||||
path.AddLine(p1);
|
||||
path.AddArc(
|
||||
p2,
|
||||
beamLength,
|
||||
beamLength,
|
||||
0,
|
||||
CanvasSweepDirection.Clockwise,
|
||||
CanvasArcSize.Small
|
||||
);
|
||||
path.EndFigure(CanvasFigureLoop.Closed);
|
||||
|
||||
using var geometry = CanvasGeometry.CreatePath(path);
|
||||
|
||||
// 渐变为白色,透明度递减
|
||||
using var brush = new CanvasRadialGradientBrush(
|
||||
control,
|
||||
new[]
|
||||
{
|
||||
new CanvasGradientStop
|
||||
{
|
||||
Position = 0f,
|
||||
Color = Color.FromArgb(180, 255, 255, 255),
|
||||
},
|
||||
new CanvasGradientStop
|
||||
{
|
||||
Position = 0.5f,
|
||||
Color = Color.FromArgb(60, 255, 255, 255),
|
||||
},
|
||||
new CanvasGradientStop
|
||||
{
|
||||
Position = 1f,
|
||||
Color = Color.FromArgb(0, 255, 255, 255),
|
||||
},
|
||||
}
|
||||
)
|
||||
{
|
||||
Center = p0,
|
||||
OriginOffset = new Vector2(0, 0),
|
||||
RadiusX = beamLength * 0.8f,
|
||||
RadiusY = beamLength * 0.8f,
|
||||
};
|
||||
|
||||
using var beamCmd = new CanvasCommandList(control);
|
||||
using (var beamDs = beamCmd.CreateDrawingSession())
|
||||
{
|
||||
beamDs.FillGeometry(geometry, brush);
|
||||
}
|
||||
|
||||
var blur = new GaussianBlurEffect
|
||||
{
|
||||
Source = beamCmd,
|
||||
BlurAmount = 36f,
|
||||
Optimization = EffectOptimization.Quality,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
};
|
||||
|
||||
ds.DrawImage(blur);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
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<LyricsFontWeight>>,
|
||||
IRecipient<PropertyChangedMessage<LineRenderingType>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>>>
|
||||
{
|
||||
public async void Receive(
|
||||
PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>> message
|
||||
)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LocalLyricsFolders))
|
||||
{
|
||||
// Music lib changed, re-fetch lyrics
|
||||
await RefreshLyricsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void Receive(
|
||||
PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>> message
|
||||
)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsSearchProvidersInfo))
|
||||
{
|
||||
// Lyrics search providers info changed, re-fetch lyrics
|
||||
await RefreshLyricsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Receive methods for handling messages from other view models
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(SettingsPageViewModel.IsDynamicCoverOverlayEnabled)
|
||||
)
|
||||
{
|
||||
IsDynamicCoverOverlayEnabled = message.NewValue;
|
||||
}
|
||||
else if (
|
||||
message.PropertyName == nameof(SettingsPageViewModel.IsDebugOverlayEnabled)
|
||||
)
|
||||
{
|
||||
_isDebugOverlayEnabled = message.NewValue;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.IsLyricsGlowEffectEnabled)
|
||||
)
|
||||
{
|
||||
IsLyricsGlowEffectEnabled = message.NewValue;
|
||||
}
|
||||
else if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.IsFanLyricsEnabled)
|
||||
)
|
||||
{
|
||||
_isFanLyricsEnabled = message.NewValue;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsWindowViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
|
||||
{
|
||||
_isDockMode = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
|
||||
{
|
||||
_isDesktopMode = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<Color> message)
|
||||
{
|
||||
if (message.Sender is LyricsWindowViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.ActivatedWindowAccentColor))
|
||||
{
|
||||
_immersiveBgTransition.StartTransition(message.NewValue);
|
||||
_lyricsWindowBgColor = message.NewValue;
|
||||
_adaptiveFontColor = Helper.ColorHelper.GetForegroundColor(_lyricsWindowBgColor);
|
||||
UpdateFontColor();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsCustomFontColor))
|
||||
{
|
||||
_customFontColor = message.NewValue;
|
||||
UpdateFontColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<double> message)
|
||||
{
|
||||
if (message.Sender is LyricsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsPageViewModel.MaxLyricsWidth))
|
||||
{
|
||||
_maxLyricsWidthTransition.StartTransition((float)message.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<float> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.LyricsLineSpacingFactor)
|
||||
)
|
||||
{
|
||||
LyricsLineSpacingFactor = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.CoverImageRadius))
|
||||
{
|
||||
CoverImageRadius = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayOpacity))
|
||||
{
|
||||
CoverOverlayOpacity = message.NewValue;
|
||||
}
|
||||
else if (
|
||||
message.PropertyName == nameof(SettingsPageViewModel.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LineRenderingType> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.LyricsGlowEffectScope)
|
||||
)
|
||||
{
|
||||
LyricsGlowEffectScope = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsAlignmentType> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.LyricsAlignmentType)
|
||||
)
|
||||
{
|
||||
LyricsAlignmentType = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
|
||||
{
|
||||
DisplayType = message.NewValue;
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.LyricsFontColorType)
|
||||
)
|
||||
{
|
||||
LyricsFontColorType = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontWeight))
|
||||
{
|
||||
LyricsFontWeight = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnLyricsFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
UpdateFontColor();
|
||||
}
|
||||
|
||||
partial void OnLyricsFontSizeChanged(int value)
|
||||
{
|
||||
_isRelayoutNeeded = true;
|
||||
}
|
||||
|
||||
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
|
||||
{
|
||||
_textFormat.FontWeight = value.ToFontWeight();
|
||||
_isRelayoutNeeded = true;
|
||||
}
|
||||
|
||||
partial void OnLyricsLineSpacingFactorChanged(float value)
|
||||
{
|
||||
_isRelayoutNeeded = true;
|
||||
}
|
||||
|
||||
async partial void OnSongInfoChanged(SongInfo? oldValue, SongInfo? newValue)
|
||||
{
|
||||
TotalTime = TimeSpan.Zero;
|
||||
|
||||
SoftwareBitmap? newalbumArtBitmap;
|
||||
Color? newAlbumArtAccentColor;
|
||||
|
||||
if (newValue?.AlbumArt is byte[] bytes)
|
||||
{
|
||||
var decoder = await ImageHelper.GetDecoderFromByte(bytes);
|
||||
newalbumArtBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
|
||||
newAlbumArtAccentColor = (ImageHelper.GetAccentColorsFromByte(bytes)).SafeGet(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
newalbumArtBitmap = null;
|
||||
newAlbumArtAccentColor = null;
|
||||
}
|
||||
|
||||
_lastAlbumArtBitmap = _albumArtBitmap;
|
||||
_albumArtBitmap = newalbumArtBitmap;
|
||||
|
||||
_albumArtBgTransition.Reset(0f);
|
||||
_albumArtBgTransition.StartTransition(1f);
|
||||
|
||||
_albumArtAccentColor = newAlbumArtAccentColor;
|
||||
_lyricsWindowBgColor = _albumArtAccentColor ?? Colors.Gray;
|
||||
|
||||
if (!_isDesktopMode && !_isDockMode) _adaptiveFontColor = Helper.ColorHelper.GetForegroundColor(_lyricsWindowBgColor);
|
||||
|
||||
UpdateFontColor();
|
||||
|
||||
await RefreshLyricsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
switch (DisplayType)
|
||||
{
|
||||
case Enums.LyricsDisplayType.AlbumArtOnly:
|
||||
_lyricsOpacityTransition.StartTransition(0f);
|
||||
break;
|
||||
case Enums.LyricsDisplayType.LyricsOnly:
|
||||
case Enums.LyricsDisplayType.SplitView:
|
||||
_lyricsOpacityTransition.StartTransition(1f);
|
||||
break;
|
||||
case Enums.LyricsDisplayType.PlaceholderOnly:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (_lyricsOpacityTransition.IsTransitioning)
|
||||
{
|
||||
_lyricsOpacityTransition.Update(ElapsedTime);
|
||||
}
|
||||
|
||||
// 神光角度目标值(左右±15度摆动,周期约4秒)
|
||||
double t = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0;
|
||||
float targetAngle = (float)(-Math.PI / 2 + Math.Sin(t * Math.PI / 2) * (Math.PI / 12)); // -90°为正上,±15°摆动
|
||||
_shenGuangAngleTransition.StartTransition(targetAngle);
|
||||
|
||||
if (_shenGuangAngleTransition.IsTransitioning)
|
||||
{
|
||||
_shenGuangAngleTransition.Update(ElapsedTime);
|
||||
}
|
||||
|
||||
if (_isRelayoutNeeded)
|
||||
{
|
||||
ReLayout(control);
|
||||
_isRelayoutNeeded = false;
|
||||
UpdateCanvasYScrollOffset(control, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateCanvasYScrollOffset(control, true);
|
||||
}
|
||||
|
||||
UpdateLinesProps();
|
||||
}
|
||||
|
||||
private void ReLayout(ICanvasAnimatedControl control)
|
||||
{
|
||||
if (control == null)
|
||||
return;
|
||||
|
||||
_textFormat.FontSize = LyricsFontSize;
|
||||
|
||||
float y = _topMargin;
|
||||
|
||||
// Init Positions
|
||||
for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
|
||||
{
|
||||
var line = _multiLangLyrics[_langIndex].SafeGet(i);
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.CanvasTextLayout != null)
|
||||
{
|
||||
line.CanvasTextLayout.Dispose();
|
||||
line.CanvasTextLayout = null;
|
||||
}
|
||||
|
||||
// Calculate layout bounds
|
||||
line.CanvasTextLayout = new CanvasTextLayout(
|
||||
control,
|
||||
line.Text,
|
||||
_textFormat,
|
||||
(float)_maxLyricsWidthTransition.Value,
|
||||
(float)control.Size.Height
|
||||
);
|
||||
|
||||
line.Position = new Vector2(0, y);
|
||||
|
||||
y +=
|
||||
(float)line.CanvasTextLayout.LayoutBounds.Height
|
||||
/ line.CanvasTextLayout.LineCount
|
||||
* (line.CanvasTextLayout.LineCount + LyricsLineSpacingFactor);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCanvasYScrollOffset(ICanvasAnimatedControl control, bool withAnimation)
|
||||
{
|
||||
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private protected void UpdateFontColor()
|
||||
{
|
||||
ThemeTypeSent =
|
||||
Helper.ColorHelper.GetElementThemeFromBackgroundColor(_lyricsWindowBgColor);
|
||||
|
||||
Color fallbackFg = Colors.Transparent;
|
||||
switch (ThemeTypeSent)
|
||||
{
|
||||
case ElementTheme.Light:
|
||||
fallbackFg = _darkFontColor;
|
||||
break;
|
||||
case ElementTheme.Dark:
|
||||
fallbackFg = _lightFontColor;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (LyricsFontColorType)
|
||||
{
|
||||
case Enums.LyricsFontColorType.AdaptiveGrayed:
|
||||
_fontColor = fallbackFg;
|
||||
break;
|
||||
case Enums.LyricsFontColorType.AdaptiveColored:
|
||||
_fontColor = _adaptiveFontColor ?? fallbackFg;
|
||||
break;
|
||||
case Enums.LyricsFontColorType.Custom:
|
||||
_fontColor = _customFontColor ?? fallbackFg;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLinesProps()
|
||||
{
|
||||
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
||||
|
||||
int halfVisibleLineCount =
|
||||
Math.Max(1, Math.Max(
|
||||
currentPlayingLineIndex - _startVisibleLineIndex,
|
||||
_endVisibleLineIndex - currentPlayingLineIndex
|
||||
));
|
||||
|
||||
if (halfVisibleLineCount < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int distanceFromPlayingLine = Math.Abs(i - currentPlayingLineIndex);
|
||||
if (distanceFromPlayingLine > halfVisibleLineCount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float distanceFactor = distanceFromPlayingLine / (float)halfVisibleLineCount;
|
||||
|
||||
line.AngleTransition.StartTransition(
|
||||
_isFanLyricsEnabled
|
||||
? (float)Math.PI
|
||||
* (30f / 180f)
|
||||
* distanceFactor
|
||||
* (i - currentPlayingLineIndex > 0 ? 1 : -1)
|
||||
: 0
|
||||
);
|
||||
line.BlurAmountTransition.StartTransition(LyricsBlurAmount * distanceFactor);
|
||||
line.ScaleTransition.StartTransition(
|
||||
_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale)
|
||||
);
|
||||
line.OpacityTransition.StartTransition(_defaultOpacity - distanceFactor * _defaultOpacity * (1 - LyricsVerticalEdgeOpacity / 100f));
|
||||
|
||||
// Only calculate highlight opacity for the current line and the two lines around it
|
||||
// to avoid unnecessary calculations
|
||||
if (distanceFromPlayingLine <= 1)
|
||||
{
|
||||
line.HighlightOpacityTransition.StartTransition(
|
||||
distanceFromPlayingLine == 0 ? 1 : 0
|
||||
);
|
||||
}
|
||||
|
||||
if (line.AngleTransition.IsTransitioning)
|
||||
{
|
||||
line.AngleTransition.Update(ElapsedTime);
|
||||
}
|
||||
if (line.ScaleTransition.IsTransitioning)
|
||||
{
|
||||
line.ScaleTransition.Update(ElapsedTime);
|
||||
}
|
||||
if (line.BlurAmountTransition.IsTransitioning)
|
||||
{
|
||||
line.BlurAmountTransition.Update(ElapsedTime);
|
||||
}
|
||||
if (line.OpacityTransition.IsTransitioning)
|
||||
{
|
||||
line.OpacityTransition.Update(ElapsedTime);
|
||||
}
|
||||
// Only update highlight opacity for the current line and the two lines around it
|
||||
if (distanceFromPlayingLine <= 1)
|
||||
{
|
||||
if (line.HighlightOpacityTransition.IsTransitioning)
|
||||
{
|
||||
line.HighlightOpacityTransition.Update(ElapsedTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,48 +1,15 @@
|
||||
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;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterInAppLyrics.WinUI3.ViewModels
|
||||
{
|
||||
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; }
|
||||
|
||||
public LyricsSettingsControlViewModel(ISettingsService settingsService)
|
||||
: base(settingsService)
|
||||
{
|
||||
@@ -56,37 +23,58 @@ namespace BetterInAppLyrics.WinUI3.ViewModels
|
||||
LyricsFontSize = _settingsService.LyricsFontSize;
|
||||
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
|
||||
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
|
||||
IsFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
|
||||
LyricsFontColorType = _settingsService.LyricsFontColorType;
|
||||
LyricsCustomFontColor = _settingsService.LyricsCustomFontColor;
|
||||
}
|
||||
|
||||
partial void OnLyricsAlignmentTypeChanged(LyricsAlignmentType value)
|
||||
{
|
||||
_settingsService.LyricsAlignmentType = value;
|
||||
}
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsFanLyricsEnabled { get; set; }
|
||||
|
||||
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
|
||||
{
|
||||
_settingsService.LyricsFontWeight = value;
|
||||
}
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsGlowEffectEnabled { get; set; }
|
||||
|
||||
partial void OnLyricsBlurAmountChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsBlurAmount = value;
|
||||
}
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsAlignmentType LyricsAlignmentType { get; set; }
|
||||
|
||||
partial void OnLyricsVerticalEdgeOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsVerticalEdgeOpacity = value;
|
||||
}
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsBlurAmount { get; set; }
|
||||
|
||||
partial void OnLyricsLineSpacingFactorChanged(float value)
|
||||
{
|
||||
_settingsService.LyricsLineSpacingFactor = value;
|
||||
}
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial Color LyricsCustomFontColor { get; set; }
|
||||
|
||||
partial void OnLyricsFontSizeChanged(int value)
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontColorType LyricsFontColorType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontWeight LyricsFontWeight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LineRenderingType LyricsGlowEffectScope { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial float LyricsLineSpacingFactor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsVerticalEdgeOpacity { get; set; }
|
||||
|
||||
partial void OnIsFanLyricsEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.LyricsFontSize = value;
|
||||
_settingsService.IsFanLyricsEnabled = value;
|
||||
}
|
||||
|
||||
partial void OnIsLyricsGlowEffectEnabledChanged(bool value)
|
||||
@@ -94,14 +82,49 @@ namespace BetterInAppLyrics.WinUI3.ViewModels
|
||||
_settingsService.IsLyricsGlowEffectEnabled = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsAlignmentTypeChanged(LyricsAlignmentType value)
|
||||
{
|
||||
_settingsService.LyricsAlignmentType = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsBlurAmountChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsBlurAmount = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsCustomFontColorChanged(Color value)
|
||||
{
|
||||
_settingsService.LyricsCustomFontColor = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
_settingsService.LyricsFontColorType = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsGlowEffectScopeChanged(LyricsGlowEffectScope value)
|
||||
partial void OnLyricsFontSizeChanged(int value)
|
||||
{
|
||||
_settingsService?.LyricsGlowEffectScope = value;
|
||||
_settingsService.LyricsFontSize = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
|
||||
{
|
||||
_settingsService.LyricsFontWeight = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsGlowEffectScopeChanged(LineRenderingType value)
|
||||
{
|
||||
_settingsService.LyricsGlowEffectScope = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsLineSpacingFactorChanged(float value)
|
||||
{
|
||||
_settingsService.LyricsLineSpacingFactor = value;
|
||||
}
|
||||
|
||||
partial void OnLyricsVerticalEdgeOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsVerticalEdgeOpacity = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
public partial class LyricsWindowViewModel
|
||||
: BaseWindowViewModel,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>
|
||||
{
|
||||
private ForegroundWindowWatcherHelper? _watcherHelper = null;
|
||||
|
||||
public LyricsWindowViewModel(ISettingsService settingsService)
|
||||
: base(settingsService)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<ShowNotificatonMessage>(
|
||||
this,
|
||||
async (r, m) =>
|
||||
{
|
||||
Notification = m.Value;
|
||||
if (!Notification.IsForeverDismissable)
|
||||
{
|
||||
Notification.Visibility = Notification.IsForeverDismissable ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowInfoBar = true;
|
||||
await Task.Delay(AnimationHelper.StackedNotificationsShowingDuration);
|
||||
ShowInfoBar = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial Color ActivatedWindowAccentColor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDesktopMode { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDockMode { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsWindowLocked { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Notification Notification { get; set; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowInfoBar { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ElementTheme ThemeType { get; set; } = ElementTheme.Default;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double TitleBarFontSize { get; set; } = 11;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double TitleBarHeight { get; set; } = 36;
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is SystemTrayViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SystemTrayViewModel.IsLyricsWindowLocked))
|
||||
{
|
||||
if (IsLyricsWindowLocked != message.NewValue)
|
||||
{
|
||||
IsLyricsWindowLocked = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ElementTheme> message)
|
||||
{
|
||||
if (message.Sender is LyricsRendererViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.ThemeTypeSent))
|
||||
{
|
||||
ThemeType = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize))
|
||||
{
|
||||
if (IsDockMode)
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
DockModeHelper.UpdateAppBarHeight(
|
||||
WindowNative.GetWindowHandle(window),
|
||||
message.NewValue * 3
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StartWatchWindowColorChange(WindowColorSampleMode mode)
|
||||
{
|
||||
var hwnd = WindowNative.GetWindowHandle(
|
||||
WindowHelper.GetWindowByWindowType<LyricsWindow>()
|
||||
);
|
||||
_watcherHelper = new ForegroundWindowWatcherHelper(
|
||||
hwnd,
|
||||
onWindowChanged =>
|
||||
{
|
||||
UpdateAccentColor(hwnd, mode);
|
||||
}
|
||||
);
|
||||
_watcherHelper.Start();
|
||||
UpdateAccentColor(hwnd, mode);
|
||||
}
|
||||
|
||||
public void UpdateAccentColor(nint hwnd, WindowColorSampleMode mode)
|
||||
{
|
||||
ActivatedWindowAccentColor = WindowColorHelper.GetDominantColor(hwnd, mode).ToColor();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void LockWindow()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
DesktopModeHelper.Lock(window);
|
||||
IsLyricsWindowLocked = true;
|
||||
}
|
||||
|
||||
private void StopWatchWindowColorChange()
|
||||
{
|
||||
_watcherHelper?.Stop();
|
||||
_watcherHelper = null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ToggleDesktopMode()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
StopWatchWindowColorChange();
|
||||
|
||||
IsDesktopMode = !IsDesktopMode;
|
||||
if (IsDesktopMode)
|
||||
{
|
||||
StartWatchWindowColorChange(WindowColorSampleMode.WindowEdge);
|
||||
DesktopModeHelper.Enable(window);
|
||||
}
|
||||
else
|
||||
{
|
||||
DesktopModeHelper.Disable(window);
|
||||
StopWatchWindowColorChange();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ToggleDockMode()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
StopWatchWindowColorChange();
|
||||
|
||||
IsDockMode = !IsDockMode;
|
||||
if (IsDockMode)
|
||||
{
|
||||
StartWatchWindowColorChange(WindowColorSampleMode.BelowWindow);
|
||||
DockModeHelper.Enable(window, _settingsService.LyricsFontSize * 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartWatchWindowColorChange(WindowColorSampleMode.WindowEdge);
|
||||
DockModeHelper.Disable(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -10,37 +11,76 @@ using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Globalization;
|
||||
using Windows.Media;
|
||||
using Windows.Media.Playback;
|
||||
using Windows.System;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class SettingsViewModel : ObservableRecipient
|
||||
public partial class SettingsPageViewModel : ObservableRecipient
|
||||
{
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ElementTheme ThemeType { get; set; }
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial BackdropType BackdropType { get; set; }
|
||||
public SettingsPageViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_libWatcherService = libWatcherService;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial TitleBarType TitleBarType { get; set; }
|
||||
LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
|
||||
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
|
||||
|
||||
Language = _settingsService.Language;
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
|
||||
AutoStartWindowType = _settingsService.AutoStartWindowType;
|
||||
AutoLockOnDesktopMode = _settingsService.AutoLockOnDesktopMode;
|
||||
|
||||
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
|
||||
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
BuildDate = (await AppInfo.GetBuildDate()).ToString("(yyyy/MM/dd HH:mm:ss)");
|
||||
});
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AutoStartWindowType AutoStartWindowType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool AutoLockOnDesktopMode { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverImageRadius { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverOverlayBlurAmount { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverOverlayOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDebugOverlayEnabled { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDynamicCoverOverlayEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Enums.Language Language { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<LocalLyricsFolder> LocalLyricsFolders { get; set; }
|
||||
|
||||
@@ -49,151 +89,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverImageRadius { get; set; }
|
||||
public partial object NavViewSelectedItemTag { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsCoverOverlayEnabled { get; set; }
|
||||
public string Version { get; set; } = Helper.AppInfo.AppVersion;
|
||||
|
||||
[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;
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
private readonly IPlaybackService _playbackService;
|
||||
|
||||
public SettingsViewModel(
|
||||
ISettingsService settingsService,
|
||||
ILibWatcherService libWatcherService,
|
||||
IPlaybackService playbackService
|
||||
)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_libWatcherService = libWatcherService;
|
||||
_playbackService = playbackService;
|
||||
|
||||
RootGridMargin = new Thickness(0, _settingsService.TitleBarType.GetHeight(), 0, 0);
|
||||
|
||||
LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
|
||||
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
|
||||
|
||||
Language = _settingsService.Language;
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
ThemeType = _settingsService.ThemeType;
|
||||
BackdropType = _settingsService.BackdropType;
|
||||
TitleBarType = _settingsService.TitleBarType;
|
||||
|
||||
AutoStartWindowType = _settingsService.AutoStartWindowType;
|
||||
|
||||
IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
|
||||
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
|
||||
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;
|
||||
}
|
||||
|
||||
partial void OnThemeTypeChanged(ElementTheme value)
|
||||
{
|
||||
_settingsService.ThemeType = value;
|
||||
}
|
||||
|
||||
partial void OnBackdropTypeChanged(BackdropType value)
|
||||
{
|
||||
_settingsService.BackdropType = value;
|
||||
}
|
||||
|
||||
partial void OnTitleBarTypeChanged(TitleBarType value)
|
||||
{
|
||||
_settingsService.TitleBarType = value;
|
||||
RootGridMargin = new Thickness(0, value.GetHeight(), 0, 0);
|
||||
}
|
||||
|
||||
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
|
||||
{
|
||||
_settingsService.AutoStartWindowType = value;
|
||||
}
|
||||
|
||||
partial void OnCoverImageRadiusChanged(int value)
|
||||
{
|
||||
_settingsService.CoverImageRadius = value;
|
||||
}
|
||||
|
||||
partial void OnIsCoverOverlayEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsCoverOverlayEnabled = value;
|
||||
}
|
||||
|
||||
partial void OnIsDynamicCoverOverlayEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsDynamicCoverOverlayEnabled = value;
|
||||
}
|
||||
|
||||
partial void OnCoverOverlayOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.CoverOverlayOpacity = value;
|
||||
}
|
||||
|
||||
partial void OnCoverOverlayBlurAmountChanged(int value)
|
||||
{
|
||||
_settingsService.CoverOverlayBlurAmount = value;
|
||||
}
|
||||
|
||||
public void RemoveFolderAsync(LocalLyricsFolder folder)
|
||||
{
|
||||
LocalLyricsFolders.Remove(folder);
|
||||
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
|
||||
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
|
||||
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
|
||||
}
|
||||
public string BuildDate { get; set; }
|
||||
|
||||
public void OnLyricsSearchProvidersReordered()
|
||||
{
|
||||
@@ -205,22 +105,33 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SelectAndAddFolderAsync(UIElement sender)
|
||||
public void OpenMusicFolder(LocalLyricsFolder folder)
|
||||
{
|
||||
var picker = new Windows.Storage.Pickers.FolderPicker();
|
||||
OpenFolderInFileExplorer(folder.Path);
|
||||
}
|
||||
|
||||
picker.FileTypeFilter.Add("*");
|
||||
public void RemoveFolderAsync(LocalLyricsFolder folder)
|
||||
{
|
||||
LocalLyricsFolders.Remove(folder);
|
||||
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
|
||||
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
|
||||
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
|
||||
}
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(WindowHelper.GetWindowForElement(sender));
|
||||
InitializeWithWindow.Initialize(picker, hwnd);
|
||||
public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
|
||||
{
|
||||
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
|
||||
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
|
||||
}
|
||||
|
||||
var folder = await picker.PickSingleFolderAsync();
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
AddFolderAsync(folder.Path);
|
||||
}
|
||||
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
|
||||
{
|
||||
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
|
||||
Broadcast(
|
||||
LyricsSearchProvidersInfo,
|
||||
LyricsSearchProvidersInfo,
|
||||
nameof(LyricsSearchProvidersInfo)
|
||||
);
|
||||
}
|
||||
|
||||
private void AddFolderAsync(string path)
|
||||
@@ -296,7 +207,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[RelayCommand]
|
||||
private async Task LaunchProjectGitHubPageAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri(AppInfo.GithubUrl));
|
||||
await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenCacheFolder()
|
||||
{
|
||||
OpenFolderInFileExplorer(Helper.AppInfo.CacheFolder);
|
||||
}
|
||||
|
||||
private void OpenFolderInFileExplorer(string path)
|
||||
@@ -311,60 +228,92 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
);
|
||||
}
|
||||
|
||||
public void OpenMusicFolder(LocalLyricsFolder folder)
|
||||
[RelayCommand]
|
||||
private void PlayTestingMusicTask()
|
||||
{
|
||||
OpenFolderInFileExplorer(folder.Path);
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindow>();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RestartApp()
|
||||
{
|
||||
// The restart will be executed immediately.
|
||||
AppRestartFailureReason failureReason =
|
||||
Microsoft.Windows.AppLifecycle.AppInstance.Restart("");
|
||||
WindowHelper.RestartApp();
|
||||
}
|
||||
|
||||
// If the restart fails, handle it here.
|
||||
switch (failureReason)
|
||||
[RelayCommand]
|
||||
private async Task SelectAndAddFolderAsync(UIElement sender)
|
||||
{
|
||||
var picker = new Windows.Storage.Pickers.FolderPicker();
|
||||
|
||||
picker.FileTypeFilter.Add("*");
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(WindowHelper.GetWindowByWindowType<SettingsWindow>());
|
||||
InitializeWithWindow.Initialize(picker, hwnd);
|
||||
|
||||
var folder = await picker.PickSingleFolderAsync();
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
case AppRestartFailureReason.RestartPending:
|
||||
break;
|
||||
case AppRestartFailureReason.NotInForeground:
|
||||
break;
|
||||
case AppRestartFailureReason.InvalidUser:
|
||||
break;
|
||||
default: //AppRestartFailureReason.Other
|
||||
break;
|
||||
AddFolderAsync(folder.Path);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void PlayTestingMusicTask()
|
||||
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
|
||||
{
|
||||
AddFolderAsync(AppInfo.AssetsFolder);
|
||||
_mediaPlayer.SetUriSource(new Uri(AppInfo.TestMusicPath));
|
||||
_mediaPlayer.Play();
|
||||
_settingsService.AutoStartWindowType = value;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenLogFolder()
|
||||
partial void OnAutoLockOnDesktopModeChanged(bool value)
|
||||
{
|
||||
OpenFolderInFileExplorer(AppInfo.LogDirectory);
|
||||
_settingsService.AutoLockOnDesktopMode = value;
|
||||
}
|
||||
|
||||
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
|
||||
partial void OnCoverImageRadiusChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
|
||||
Broadcast(
|
||||
LyricsSearchProvidersInfo,
|
||||
LyricsSearchProvidersInfo,
|
||||
nameof(LyricsSearchProvidersInfo)
|
||||
);
|
||||
_settingsService.CoverImageRadius = value;
|
||||
}
|
||||
|
||||
public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
|
||||
partial void OnCoverOverlayBlurAmountChanged(int value)
|
||||
{
|
||||
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
|
||||
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
|
||||
_settingsService.CoverOverlayBlurAmount = value;
|
||||
}
|
||||
|
||||
partial void OnCoverOverlayOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.CoverOverlayOpacity = value;
|
||||
}
|
||||
|
||||
partial void OnIsDynamicCoverOverlayEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsDynamicCoverOverlayEnabled = value;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public class SettingsWindowViewModel : BaseWindowViewModel
|
||||
{
|
||||
public SettingsWindowViewModel(ISettingsService settingsService) : base(settingsService) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class SystemTrayViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<bool>>
|
||||
{
|
||||
public SystemTrayViewModel(ISettingsService settingsService) : base(settingsService) { }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsWindowLocked { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string ToolTipText { get; set; } = AppInfo.AppName;
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is LyricsWindowViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.IsLyricsWindowLocked))
|
||||
{
|
||||
if (IsLyricsWindowLocked != message.NewValue)
|
||||
{
|
||||
IsLyricsWindowLocked = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ExitApp()
|
||||
{
|
||||
WindowHelper.ExitAllWindows();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenSettings()
|
||||
{
|
||||
// 打开设置窗口
|
||||
WindowHelper.OpenOrShowWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void UnlockWindow()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
DesktopModeHelper.Unlock(window);
|
||||
IsLyricsWindowLocked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -44,30 +45,6 @@
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<StackPanel
|
||||
x:Name="LyricsNotFoundPlaceholder"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Opacity="0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
<TextBlock x:Uid="MainPageLyricsNotFound" FontSize="{x:Bind ViewModel.LyricsFontSize, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
x:Name="LyricsLoadingPlaceholder"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Opacity="0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
<TextBlock x:Uid="MainPageLyricsLoading" FontSize="{x:Bind ViewModel.LyricsFontSize, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
@@ -93,45 +70,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 +163,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 +206,6 @@
|
||||
<labs:MarqueeText
|
||||
Behavior="Bouncing"
|
||||
FontSize="{StaticResource SubtitleTextBlockFontSize}"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
Opacity="0.5"
|
||||
Text="{x:Bind ViewModel.SongInfo.Artist, Mode=OneWay}" />
|
||||
@@ -258,17 +241,54 @@
|
||||
Opacity="0"
|
||||
PointerEntered="BottomCommandGrid_PointerEntered"
|
||||
PointerExited="BottomCommandGrid_PointerExited"
|
||||
Visibility="{x:Bind ViewModel.IsNotMockMode, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
Visibility="{x:Bind ViewModel.IsNotDockMode, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" Spacing="4">
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
|
||||
<Button Style="{StaticResource GhostButtonStyle}" Visibility="Collapsed">
|
||||
<Grid>
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontWeight="Bold"
|
||||
Glyph="" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="11"
|
||||
FontWeight="Bold"
|
||||
Text="0.1" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Style="{StaticResource GhostButtonStyle}" Visibility="Collapsed">
|
||||
<Grid>
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontWeight="Bold"
|
||||
Glyph=""
|
||||
RenderTransformOrigin="0.5,0.5">
|
||||
<FontIcon.RenderTransform>
|
||||
<ScaleTransform ScaleX="-1" />
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="11"
|
||||
FontWeight="Bold"
|
||||
Text="0.1" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
x:Name="DisplayTypeSwitchButton"
|
||||
x:Uid="MainPageDisplayTypeSwitcher"
|
||||
Content="{ui:FontIcon Glyph=}"
|
||||
Content="{ui:FontIcon FontWeight=Bold,
|
||||
FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<Button.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
@@ -282,21 +302,9 @@
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<RadioButtons MaxColumns="1" SelectedIndex="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<RadioButton
|
||||
x:Uid="MainPageAlbumArtOnly"
|
||||
Command="{x:Bind ViewModel.DisplayTypeChangedCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}"
|
||||
Tag="0" />
|
||||
<RadioButton
|
||||
x:Uid="MainPageLyriscOnly"
|
||||
Command="{x:Bind ViewModel.DisplayTypeChangedCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}"
|
||||
Tag="1" />
|
||||
<RadioButton
|
||||
x:Uid="MainPageSplitView"
|
||||
Command="{x:Bind ViewModel.DisplayTypeChangedCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}"
|
||||
Tag="2" />
|
||||
<RadioButton x:Uid="MainPageAlbumArtOnly" Click="AlbumArtOnlyRadioButton_Click" />
|
||||
<RadioButton x:Uid="MainPageLyriscOnly" Click="LyricsOnlyRadioButton_Click" />
|
||||
<RadioButton x:Uid="MainPageSplitView" Click="SplitViewRadioButton_Click" />
|
||||
</RadioButtons>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
@@ -304,21 +312,23 @@
|
||||
|
||||
<Button
|
||||
x:Name="MusicInfoButton"
|
||||
Content="{ui:FontIcon Glyph=}"
|
||||
Content="{ui:FontIcon FontWeight=Bold,
|
||||
FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel Spacing="16">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="" />
|
||||
<TextBlock Text="{x:Bind ViewModel.SongInfo.SourceAppUserModelId, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="" />
|
||||
<TextBlock Text="{x:Bind ViewModel.SongInfo.Title, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="" />
|
||||
<TextBlock Text="{x:Bind ViewModel.SongInfo.Artist, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
@@ -329,7 +339,9 @@
|
||||
<Button
|
||||
x:Name="SettingsButton"
|
||||
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
|
||||
Content="{ui:FontIcon Glyph=}"
|
||||
Content="{ui:FontIcon FontWeight=Bold,
|
||||
FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
|
||||
</StackPanel>
|
||||
@@ -343,116 +355,9 @@
|
||||
IsOpen="{x:Bind ViewModel.IsWelcomeTeachingTipOpen, Mode=OneWay}"
|
||||
Target="{x:Bind SettingsButton}" />
|
||||
|
||||
<uc:SystemTray />
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="LayoutStates">
|
||||
|
||||
<!-- Album art only -->
|
||||
<VisualState x:Name="AlbumArtOnly">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="Equal"
|
||||
Value="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
|
||||
To="0" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="LyricsGrid.Opacity" Value="1" />
|
||||
<Setter Target="LyricsPlaceholderGrid.Opacity" Value="0" />
|
||||
<Setter Target="LyricsPlaceholderGrid.(Grid.Column)" Value="0" />
|
||||
<Setter Target="LyricsPlaceholderGrid.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="SongInfoInnerGrid.(Grid.Column)" Value="0" />
|
||||
<Setter Target="SongInfoInnerGrid.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="SongInfoInnerGrid.Opacity" Value="1" />
|
||||
<Setter Target="MainPageNoMusicPlayingTextBlock.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<!-- Lyrics only -->
|
||||
<VisualState x:Name="LyricsOnly">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="Equal"
|
||||
Value="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
|
||||
To="1" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SongInfoInnerGrid.Opacity" Value="0" />
|
||||
<Setter Target="SongInfoInnerGrid.(Grid.Column)" Value="0" />
|
||||
<Setter Target="SongInfoInnerGrid.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="LyricsGrid.Opacity" Value="1" />
|
||||
<Setter Target="LyricsPlaceholderGrid.(Grid.Column)" Value="0" />
|
||||
<Setter Target="LyricsPlaceholderGrid.(Grid.ColumnSpan)" Value="3" />
|
||||
<Setter Target="LyricsPlaceholderGrid.Opacity" Value="1" />
|
||||
<Setter Target="MainPageNoMusicPlayingTextBlock.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<!-- Split view -->
|
||||
<VisualState x:Name="SplitView">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="Equal"
|
||||
Value="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
|
||||
To="2" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SongInfoInnerGrid.(Grid.Column)" Value="0" />
|
||||
<Setter Target="SongInfoInnerGrid.(Grid.ColumnSpan)" Value="1" />
|
||||
<Setter Target="SongInfoInnerGrid.Opacity" Value="1" />
|
||||
<Setter Target="LyricsGrid.Opacity" Value="1" />
|
||||
<Setter Target="LyricsPlaceholderGrid.(Grid.Column)" Value="2" />
|
||||
<Setter Target="LyricsPlaceholderGrid.(Grid.ColumnSpan)" Value="1" />
|
||||
<Setter Target="LyricsPlaceholderGrid.Opacity" Value="1" />
|
||||
<Setter Target="MainPageNoMusicPlayingTextBlock.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<!-- Placeholder only -->
|
||||
<VisualState x:Name="PlaceholderOnly">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="Equal"
|
||||
Value="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
|
||||
To="3" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="SongInfoInnerGrid.Opacity" Value="0" />
|
||||
<Setter Target="LyricsGrid.Opacity" Value="0" />
|
||||
<Setter Target="LyricsPlaceholderGrid.Opacity" Value="0" />
|
||||
<Setter Target="MainPageNoMusicPlayingTextBlock.Opacity" Value="1" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<VisualStateGroup x:Name="LyricsStatus">
|
||||
<VisualState x:Name="Loading">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="2" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="LyricsNotFoundPlaceholder.Opacity" Value="0" />
|
||||
<Setter Target="LyricsLoadingPlaceholder.Opacity" Value=".5" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Found">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="1" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="LyricsNotFoundPlaceholder.Opacity" Value="0" />
|
||||
<Setter Target="LyricsLoadingPlaceholder.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NotFound">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="0" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="LyricsNotFoundPlaceholder.Opacity" Value=".5" />
|
||||
<Setter Target="LyricsLoadingPlaceholder.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<VisualStateGroup x:Name="MusicPlayingStates">
|
||||
<VisualState x:Name="MusicPlaying">
|
||||
<VisualState.StateTriggers>
|
||||
|
||||
@@ -1,46 +1,60 @@
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
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.
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using WinUIEx.Messaging;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class LyricsPage : Page
|
||||
{
|
||||
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
|
||||
|
||||
public LyricsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
DataContext = Ioc.Default.GetService<LyricsPageViewModel>();
|
||||
}
|
||||
|
||||
private void WelcomeTeachingTip_Closed(TeachingTip sender, TeachingTipClosedEventArgs args)
|
||||
{
|
||||
ViewModel.IsFirstRun = false;
|
||||
}
|
||||
|
||||
private void CoverArea_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
CoverImageGrid.Width = CoverImageGrid.Height = Math.Min(
|
||||
CoverArea.ActualWidth,
|
||||
CoverArea.ActualHeight
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsDisplayType>>(
|
||||
this,
|
||||
async (r, m) =>
|
||||
{
|
||||
if (m.Sender is LyricsPageViewModel)
|
||||
{
|
||||
if (m.PropertyName == nameof(LyricsPageViewModel.DisplayType))
|
||||
{
|
||||
switch (m.NewValue)
|
||||
{
|
||||
case LyricsDisplayType.AlbumArtOnly:
|
||||
await SwitchToAlbumArtOnlyDisplayTypeAsync();
|
||||
break;
|
||||
case LyricsDisplayType.LyricsOnly:
|
||||
await SwitchToLyricsOnlyDisplayTypeAsync();
|
||||
break;
|
||||
case LyricsDisplayType.SplitView:
|
||||
await SwitchToSplitViewDisplayTypeAsync();
|
||||
break;
|
||||
case LyricsDisplayType.PlaceholderOnly:
|
||||
await SwitchToPlaceholderOnlyDisplayTypeAsync();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
|
||||
|
||||
private void BottomCommandGrid_PointerEntered(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e
|
||||
@@ -59,14 +73,101 @@ namespace BetterLyrics.WinUI3.Views
|
||||
BottomCommandGrid.Opacity = 0;
|
||||
}
|
||||
|
||||
private void LyricsPlaceholderGrid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
private void CoverArea_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
ViewModel.LimitedLineWidth = e.NewSize.Width;
|
||||
CoverImageGrid.Width = CoverImageGrid.Height = Math.Min(
|
||||
CoverArea.ActualWidth,
|
||||
CoverArea.ActualHeight
|
||||
);
|
||||
}
|
||||
|
||||
private void CoverImageGrid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
ViewModel.CoverImageGridActualHeight = e.NewSize.Height;
|
||||
}
|
||||
|
||||
private void LyricsPlaceholderGrid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
ViewModel.MaxLyricsWidth = e.NewSize.Width;
|
||||
}
|
||||
|
||||
private void WelcomeTeachingTip_Closed(TeachingTip sender, TeachingTipClosedEventArgs args)
|
||||
{
|
||||
ViewModel.IsFirstRun = false;
|
||||
}
|
||||
|
||||
private async void LyricsOnlyRadioButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.LyricsOnly;
|
||||
await SwitchToLyricsOnlyDisplayTypeAsync();
|
||||
}
|
||||
|
||||
private async void AlbumArtOnlyRadioButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.AlbumArtOnly;
|
||||
await SwitchToAlbumArtOnlyDisplayTypeAsync();
|
||||
}
|
||||
|
||||
private async void SplitViewRadioButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.SplitView;
|
||||
await SwitchToSplitViewDisplayTypeAsync();
|
||||
}
|
||||
|
||||
private async Task SwitchToLyricsOnlyDisplayTypeAsync()
|
||||
{
|
||||
await BeforeSwitchDisplayTypeAsync();
|
||||
|
||||
Grid.SetColumn(LyricsPlaceholderGrid, 0);
|
||||
Grid.SetColumnSpan(LyricsPlaceholderGrid, 3);
|
||||
|
||||
LyricsPlaceholderGrid.Opacity = 1;
|
||||
LyricsGrid.Opacity = 1;
|
||||
}
|
||||
|
||||
|
||||
private async Task SwitchToAlbumArtOnlyDisplayTypeAsync()
|
||||
{
|
||||
await BeforeSwitchDisplayTypeAsync();
|
||||
|
||||
Grid.SetColumn(SongInfoInnerGrid, 0);
|
||||
Grid.SetColumnSpan(SongInfoInnerGrid, 3);
|
||||
|
||||
SongInfoInnerGrid.Opacity = 1;
|
||||
LyricsGrid.Opacity = 1;
|
||||
}
|
||||
|
||||
|
||||
private async Task BeforeSwitchDisplayTypeAsync()
|
||||
{
|
||||
SongInfoInnerGrid.Opacity = 0;
|
||||
LyricsPlaceholderGrid.Opacity = 0;
|
||||
//LyricsGrid.Opacity = 0;
|
||||
MainPageNoMusicPlayingTextBlock.Opacity = 0;
|
||||
|
||||
await Task.Delay(300);
|
||||
}
|
||||
|
||||
private async Task SwitchToSplitViewDisplayTypeAsync()
|
||||
{
|
||||
await BeforeSwitchDisplayTypeAsync();
|
||||
|
||||
Grid.SetColumn(SongInfoInnerGrid, 0);
|
||||
Grid.SetColumnSpan(SongInfoInnerGrid, 1);
|
||||
|
||||
Grid.SetColumn(LyricsPlaceholderGrid, 2);
|
||||
Grid.SetColumnSpan(LyricsPlaceholderGrid, 1);
|
||||
|
||||
SongInfoInnerGrid.Opacity = 1;
|
||||
LyricsPlaceholderGrid.Opacity = 1;
|
||||
LyricsGrid.Opacity = 1;
|
||||
}
|
||||
|
||||
private async Task SwitchToPlaceholderOnlyDisplayTypeAsync()
|
||||
{
|
||||
await BeforeSwitchDisplayTypeAsync();
|
||||
|
||||
MainPageNoMusicPlayingTextBlock.Opacity = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Window
|
||||
x:Class="BetterLyrics.WinUI3.Views.HostWindow"
|
||||
x:Class="BetterLyrics.WinUI3.Views.LyricsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
|
||||
@@ -11,62 +11,75 @@
|
||||
xmlns:media="using:CommunityToolkit.WinUI.Media"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
|
||||
<Grid
|
||||
x:Name="RootGrid"
|
||||
PointerMoved="RootGrid_PointerMoved"
|
||||
RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
|
||||
|
||||
<Frame
|
||||
x:Name="RootFrame"
|
||||
Navigated="RootFrame_Navigated"
|
||||
NavigationFailed="RootFrame_NavigationFailed" />
|
||||
<local:LyricsPage />
|
||||
|
||||
<Grid
|
||||
x:Name="TopCommandGrid"
|
||||
Height="{x:Bind ViewModel.TitleBarHeight, Mode=OneWay}"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
Opacity="0"
|
||||
PointerMoved="TopCommandGrid_PointerMoved">
|
||||
Opacity="0">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
|
||||
<ImageIcon
|
||||
x:Name="AppLogoImageIcon"
|
||||
Height="{x:Bind ViewModel.AppLogoImageIconHeight, Mode=OneWay}"
|
||||
Margin="16,0"
|
||||
Source="ms-appx:///Assets/Logo.png" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="AppTitleTextBlock"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
FontWeight="SemiBold"
|
||||
Text="{x:Bind Title, Mode=OneWay}" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
|
||||
<Button
|
||||
x:Name="ClickThroughButton"
|
||||
Command="{x:Bind ViewModel.LockWindowCommand}"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
FontWeight="Bold"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
<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
|
||||
Margin="0,0,0,8"
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
FontWeight="ExtraBold"
|
||||
Glyph="" />
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
FontWeight="ExtraBold"
|
||||
Glyph="" />
|
||||
<FontIcon
|
||||
Margin="0,8,0,0"
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
FontWeight="ExtraBold"
|
||||
Glyph="" />
|
||||
@@ -86,6 +99,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"
|
||||
@@ -104,7 +122,7 @@
|
||||
Click="MinimiseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
@@ -114,7 +132,7 @@
|
||||
Click="MaximiseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
@@ -125,7 +143,7 @@
|
||||
Style="{StaticResource TitleBarButtonStyle}"
|
||||
Visibility="Collapsed">
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
@@ -135,7 +153,7 @@
|
||||
Click="CloseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
@@ -1,195 +1,87 @@
|
||||
using System;
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class HostWindow : Window
|
||||
public sealed partial class LyricsWindow : Window
|
||||
{
|
||||
public HostWindowViewModel ViewModel { get; private set; } =
|
||||
Ioc.Default.GetRequiredService<HostWindowViewModel>();
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
private readonly ISettingsService _settingsService =
|
||||
Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
public HostWindow(bool alwaysOnTop = false, bool clickThrough = false)
|
||||
public LyricsWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
AppWindow.Changed += AppWindow_Changed;
|
||||
|
||||
this.HideSystemTitleBarAndSetCustomTitleBar(TopCommandGrid);
|
||||
|
||||
if (clickThrough)
|
||||
this.SetExtendedWindowStyle(
|
||||
ExtendedWindowStyle.Transparent | ExtendedWindowStyle.Layered
|
||||
);
|
||||
|
||||
if (alwaysOnTop)
|
||||
((OverlappedPresenter)AppWindow.Presenter).IsAlwaysOnTop = true;
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
Title = App.ResourceLoader!.GetString("LyricsPageTitle");
|
||||
SetTitleBar(TopCommandGrid);
|
||||
}
|
||||
|
||||
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
|
||||
{
|
||||
if (args.DidPresenterChange)
|
||||
UpdateTitleBarWindowButtonsVisibility();
|
||||
}
|
||||
public LyricsWindowViewModel ViewModel { get; private set; } = Ioc.Default.GetRequiredService<LyricsWindowViewModel>();
|
||||
|
||||
public void Navigate(Type type)
|
||||
public void AutoSelectLyricsMode(AutoStartWindowType? type = null, bool? autoLook = null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (RootFrame.SourcePageType == typeof(LyricsPage))
|
||||
type ??= _settingsService.AutoStartWindowType;
|
||||
switch (type!)
|
||||
{
|
||||
Application.Current.Exit();
|
||||
}
|
||||
else
|
||||
{
|
||||
AppWindow.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.Maximize();
|
||||
}
|
||||
}
|
||||
|
||||
private void MinimiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
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:
|
||||
case AutoStartWindowType.StandardMode:
|
||||
break;
|
||||
case AppWindowPresenterKind.CompactOverlay:
|
||||
MinimiseButton.Visibility =
|
||||
MaximiseButton.Visibility =
|
||||
RestoreButton.Visibility =
|
||||
AOTFlyoutItem.Visibility =
|
||||
FullScreenFlyoutItem.Visibility =
|
||||
DockFlyoutItem.Visibility =
|
||||
Visibility.Collapsed;
|
||||
case AutoStartWindowType.DockMode:
|
||||
DockFlyoutItem.IsChecked = true;
|
||||
ViewModel.ToggleDockModeCommand.Execute(null);
|
||||
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)
|
||||
case AutoStartWindowType.DesktopMode:
|
||||
DesktopFlyoutItem.IsChecked = true;
|
||||
ViewModel.ToggleDesktopModeCommand.Execute(null);
|
||||
if (autoLook == null && _settingsService.AutoLockOnDesktopMode)
|
||||
{
|
||||
MinimiseButton.Visibility =
|
||||
MaximiseButton.Visibility =
|
||||
RestoreButton.Visibility =
|
||||
AOTFlyoutItem.Visibility =
|
||||
FullScreenFlyoutItem.Visibility =
|
||||
MiniFlyoutItem.Visibility =
|
||||
Visibility.Collapsed;
|
||||
ViewModel.LockWindowCommand.Execute(null);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AOTFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var overlappedPresenter = (OverlappedPresenter)AppWindow.Presenter;
|
||||
overlappedPresenter.IsAlwaysOnTop = !overlappedPresenter.IsAlwaysOnTop;
|
||||
}
|
||||
|
||||
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
|
||||
{
|
||||
if (args.DidPresenterChange)
|
||||
UpdateTitleBarWindowButtonsVisibility();
|
||||
if (ViewModel.IsDesktopMode && (args.DidPositionChange || args.DidSizeChange))
|
||||
OnPosOrSizeChanged();
|
||||
}
|
||||
|
||||
private void OnPosOrSizeChanged()
|
||||
{
|
||||
var rect = AppWindow.Position;
|
||||
var size = AppWindow.Size;
|
||||
|
||||
_settingsService.DesktopWindowLeft = rect.X;
|
||||
_settingsService.DesktopWindowTop = rect.Y;
|
||||
_settingsService.DesktopWindowWidth = size.Width;
|
||||
_settingsService.DesktopWindowHeight = size.Height;
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowHelper.ExitAllWindows();
|
||||
}
|
||||
|
||||
private void FullScreenFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
switch (AppWindow.Presenter.Kind)
|
||||
@@ -209,6 +101,14 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.Maximize();
|
||||
}
|
||||
}
|
||||
|
||||
private void MiniFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (MiniFlyoutItem.IsChecked)
|
||||
@@ -221,12 +121,21 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void SettingsMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
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) { }
|
||||
private void RestoreButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.Restore();
|
||||
}
|
||||
}
|
||||
|
||||
private void RootGrid_PointerMoved(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
@@ -248,5 +157,98 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SettingsMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,58 +9,57 @@
|
||||
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">
|
||||
|
||||
<Grid x:Name="RootGrid" Margin="{x:Bind ViewModel.RootGridMargin, Mode=OneWay}">
|
||||
<Grid x:Name="RootGrid">
|
||||
|
||||
<NavigationView
|
||||
x:Name="NavView"
|
||||
IsBackButtonVisible="Collapsed"
|
||||
IsPaneToggleButtonVisible="False"
|
||||
IsSettingsVisible="False"
|
||||
PaneDisplayMode="Auto"
|
||||
SelectionChanged="NavView_SelectionChanged">
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageLyricsLib"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
x:Uid="SettingsPageApp"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsSelected="True"
|
||||
Tag="LyricsLib" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageAppAppearance"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="AppAppearance" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageAppBehavior"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="AppBehavior" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageAlbumOverlay"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="AlbumArtOverlay" />
|
||||
Tag="App" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageAlbumStyle"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Tag="AlbumArtStyle" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageLyricsStyle"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="LyricsStyle" />
|
||||
x:Uid="SettingsPageLyricsLib"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Tag="LyricsLib" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageLyricsEffect"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="LyricsEffect" />
|
||||
x:Uid="SettingsPageBackgroundOverlay"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Tag="Background" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageLyrics"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Tag="Lyrics" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageAbout"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Tag="About" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsPageDev"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Tag="Dev" />
|
||||
</NavigationView.MenuItems>
|
||||
|
||||
<ScrollViewer Padding="36,0">
|
||||
<Grid Margin="0,36">
|
||||
<controls:SwitchPresenter Value="{x:Bind ViewModel.NavViewSelectedItemTag, Mode=OneWay}">
|
||||
@@ -69,21 +68,22 @@
|
||||
<PopupThemeTransition />
|
||||
</TransitionCollection>
|
||||
</controls:SwitchPresenter.ContentTransitions>
|
||||
|
||||
<!-- Lyrics lib -->
|
||||
|
||||
<controls:Case Value="LyricsLib">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageMusicLib"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="True"
|
||||
ItemsSource="{x:Bind ViewModel.LocalLyricsFolders, Mode=OneWay}">
|
||||
<controls:SettingsExpander.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:SettingsCard>
|
||||
<controls:SettingsCard.Header>
|
||||
<HyperlinkButton
|
||||
Click="SettingsPageOpenPathButton_Click"
|
||||
Content="{Binding Path, Mode=OneWay}"
|
||||
Tag="{Binding}" />
|
||||
<HyperlinkButton Content="{Binding Path, Mode=OneWay}" NavigateUri="{Binding Path, Mode=OneWay}" />
|
||||
</controls:SettingsCard.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<HyperlinkButton
|
||||
@@ -139,7 +139,8 @@
|
||||
<controls:SettingsCard
|
||||
x:Name="LyricsSearchProvidersSettingsExpander"
|
||||
x:Uid="SettingsPageLyricsSearchProvidersConfig"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}" />
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}" />
|
||||
|
||||
<ListView
|
||||
x:Name="LyricsSearchProvidersListView"
|
||||
@@ -173,36 +174,19 @@
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<controls:Case Value="AppAppearance">
|
||||
<!-- App appearance and behavior -->
|
||||
|
||||
<controls:Case Value="App">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageTheme" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox x:Name="ThemeComboBox" SelectedIndex="{x:Bind ViewModel.ThemeType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageFollowSystem" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLight" />
|
||||
<ComboBoxItem x:Uid="SettingsPageDark" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageBackdrop" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox x:Name="BackdropComboBox" SelectedIndex="{x:Bind ViewModel.BackdropType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageNoBackdrop" />
|
||||
<ComboBoxItem x:Uid="SettingsPageMica" />
|
||||
<ComboBoxItem x:Uid="SettingsPageMicaAlt" />
|
||||
<ComboBoxItem x:Uid="SettingsPageDesktopAcrylic" />
|
||||
<ComboBoxItem x:Uid="SettingsPageTransparent" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
<!-- App appearance -->
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageTitleBarType" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox x:Name="TitleBarTypeComboBox" SelectedIndex="{x:Bind ViewModel.TitleBarType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageCompactTitleBar" />
|
||||
<ComboBoxItem x:Uid="SettingsPageExtendedTitleBar" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
<TextBlock x:Uid="SettingsPageAppAppearance" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageLanguage"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="True">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.Language, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageSystemLanguage" />
|
||||
@@ -219,84 +203,81 @@
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<!-- App behavior -->
|
||||
|
||||
<TextBlock x:Uid="SettingsPageAppBehavior" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageAutoStartWindow">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.AutoStartWindowType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageAutoStartInAppLyrics" />
|
||||
<ComboBoxItem x:Uid="SettingsPageAutoStartDockLyrics" />
|
||||
<ComboBoxItem x:Uid="SettingsPageAutoStartDesktopLyrics" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageAutoLock">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AutoLockOnDesktopMode, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<controls:Case Value="AppBehavior">
|
||||
<!-- Lyrics background -->
|
||||
|
||||
<controls:Case Value="Background">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageAutoStartWindow">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.AutoStartWindowType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageAutoStartInAppLyrics" />
|
||||
<ComboBoxItem x:Uid="SettingsPageAutoStartDesktopLyrics" />
|
||||
</ComboBox>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageDynamicLyricsBackground">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDynamicCoverOverlayEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<controls:Case Value="AlbumArtOverlay">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageCoverOverlay"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<controls:SettingsExpander.Description>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="SettingsPageCoverOverlayGPUUsage" />
|
||||
</StackPanel>
|
||||
</controls:SettingsExpander.Description>
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsCoverOverlayEnabled, Mode=TwoWay}" />
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundOpacity">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.CoverOverlayOpacity, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
Margin="0,0,14,0"
|
||||
VerticalAlignment="Center"
|
||||
Text=" %" />
|
||||
<Slider
|
||||
Maximum="100"
|
||||
Minimum="1"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="1"
|
||||
TickFrequency="1"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.CoverOverlayOpacity, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageDynamicCoverOverlay" IsEnabled="{x:Bind ViewModel.IsCoverOverlayEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDynamicCoverOverlayEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageCoverOverlayOpacity" IsEnabled="{x:Bind ViewModel.IsCoverOverlayEnabled, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.CoverOverlayOpacity, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
Margin="0,0,14,0"
|
||||
VerticalAlignment="Center"
|
||||
Text=" %" />
|
||||
<Slider
|
||||
Maximum="100"
|
||||
Minimum="1"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="1"
|
||||
TickFrequency="1"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.CoverOverlayOpacity, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageCoverOverlayBlurAmount" IsEnabled="{x:Bind ViewModel.IsCoverOverlayEnabled, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock
|
||||
Margin="0,0,14,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind ViewModel.CoverOverlayBlurAmount, Mode=OneWay}" />
|
||||
<Slider
|
||||
Maximum="200"
|
||||
Minimum="50"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="10"
|
||||
TickFrequency="10"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.CoverOverlayBlurAmount, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundBlurAmount">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock
|
||||
Margin="0,0,14,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind ViewModel.CoverOverlayBlurAmount, Mode=OneWay}" />
|
||||
<Slider
|
||||
Maximum="200"
|
||||
Minimum="50"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="10"
|
||||
TickFrequency="10"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.CoverOverlayBlurAmount, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<!-- Album art style -->
|
||||
|
||||
<controls:Case Value="AlbumArtStyle">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.CoverImageRadius, Mode=OneWay}" />
|
||||
@@ -318,10 +299,16 @@
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<controls:Case Value="LyricsStyle">
|
||||
<!-- Lyrics style and effect -->
|
||||
|
||||
<controls:Case Value="Lyrics">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<!-- Lyrics style -->
|
||||
|
||||
<TextBlock x:Uid="SettingsPageLyricsStyle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox SelectedIndex="{x:Bind LyricsSettingsControlViewModel.LyricsAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsLeft" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsCenter" />
|
||||
@@ -329,7 +316,7 @@
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFontWeight" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFontWeight" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox SelectedIndex="{x:Bind LyricsSettingsControlViewModel.LyricsFontWeight, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsThin" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsExtraLight" />
|
||||
@@ -345,14 +332,41 @@
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFontColor" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFontColor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox SelectedIndex="{x:Bind LyricsSettingsControlViewModel.LyricsFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDefault" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDominant" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveColored" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveGrayed" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorCustom" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ColorPicker
|
||||
ColorSpectrumShape="Box"
|
||||
IsAlphaEnabled="True"
|
||||
IsAlphaSliderVisible="True"
|
||||
IsAlphaTextInputVisible="True"
|
||||
IsColorChannelTextInputVisible="True"
|
||||
IsColorSliderVisible="True"
|
||||
IsHexInputVisible="True"
|
||||
IsMoreButtonVisible="True"
|
||||
Color="{x:Bind LyricsSettingsControlViewModel.LyricsCustomFontColor, Mode=TwoWay}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind LyricsSettingsControlViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="2">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind LyricsSettingsControlViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="2">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</ColorPicker>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock
|
||||
@@ -371,7 +385,7 @@
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=LyricsLineSpacingFactorSlider, Path=Value, Mode=OneWay}" />
|
||||
@@ -391,13 +405,14 @@
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
<!-- Effect -->
|
||||
|
||||
<controls:Case Value="LyricsEffect">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<TextBlock
|
||||
x:Uid="SettingsPageLyricsEffect"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Text="Effect" />
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsVerticalEdgeOpacity" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsVerticalEdgeOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=LyricsVerticalEdgeOpacitySlider, Path=Value, Mode=OneWay}" />
|
||||
@@ -417,13 +432,7 @@
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<controls:SettingsCard.Description>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="SettingsPageLyricsBlurHighGPUUsage" Foreground="{ThemeResource SystemFillColorCautionBrush}" />
|
||||
<TextBlock x:Uid="SettingsPageLyricsBlurAmountSideEffect" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard.Description>
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock
|
||||
@@ -444,13 +453,13 @@
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageLyricsGlowEffect"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="{x:Bind LyricsSettingsControlViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsSettingsControlViewModel.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
|
||||
<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>
|
||||
@@ -458,9 +467,15 @@
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageFan" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsSettingsControlViewModel.IsFanLyricsEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<!-- About -->
|
||||
|
||||
<controls:Case Value="About">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsCard Header="BetterLyrics" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/Logo.png}">
|
||||
@@ -469,6 +484,7 @@
|
||||
<Paragraph>
|
||||
<Run x:Uid="SettingsPageVersion" />
|
||||
<Run Text="{x:Bind ViewModel.Version, Mode=OneWay}" />
|
||||
<Run Text="{x:Bind ViewModel.BuildDate, Mode=OneWay}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
</controls:SettingsCard.Description>
|
||||
@@ -476,20 +492,29 @@
|
||||
|
||||
<controls:SettingsCard
|
||||
x:Uid="SettingsPageGitHub"
|
||||
ActionIcon="{ui:FontIcon Glyph=}"
|
||||
ActionIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Command="{x:Bind ViewModel.LaunchProjectGitHubPageCommand}"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsClickEnabled="True" />
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
<controls:Case Value="Dev">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageMockMusicPlaying">
|
||||
<Button x:Uid="SettingsPagePlayingMockMusicButton" Command="{x:Bind ViewModel.PlayTestingMusicTaskCommand}" />
|
||||
<HyperlinkButton
|
||||
x:Uid="SettingsPagePlayingMockMusicButton"
|
||||
Command="{x:Bind ViewModel.PlayTestingMusicTaskCommand}"
|
||||
NavigateUri="https://soundcloud.com/carlyraejepsen/cut-to-the-feeling" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="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>
|
||||
|
||||
@@ -1,55 +1,44 @@
|
||||
using System.Threading.Tasks;
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class SettingsPage : Page
|
||||
{
|
||||
public SettingsViewModel ViewModel => (SettingsViewModel)DataContext;
|
||||
public LyricsSettingsControlViewModel LyricsSettingsControlViewModel =>
|
||||
Ioc.Default.GetRequiredService<LyricsSettingsControlViewModel>();
|
||||
|
||||
public SettingsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<SettingsViewModel>();
|
||||
DataContext = Ioc.Default.GetRequiredService<SettingsPageViewModel>();
|
||||
}
|
||||
|
||||
private void SettingsPageOpenPathButton_Click(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.RoutedEventArgs e
|
||||
)
|
||||
public LyricsSettingsControlViewModel LyricsSettingsControlViewModel =>
|
||||
Ioc.Default.GetRequiredService<LyricsSettingsControlViewModel>();
|
||||
|
||||
public SettingsPageViewModel ViewModel => (SettingsPageViewModel)DataContext;
|
||||
|
||||
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
|
||||
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();
|
||||
}
|
||||
|
||||
private void LyricsSearchProviderToggleSwitch_Toggled(object sender, RoutedEventArgs e)
|
||||
@@ -63,23 +52,20 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void LyricsSearchProvidersListView_DragItemsCompleted(
|
||||
ListViewBase sender,
|
||||
DragItemsCompletedEventArgs args
|
||||
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)
|
||||
private void SettingsPageRemovePathButton_Click(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.RoutedEventArgs e
|
||||
)
|
||||
{
|
||||
if (sender is ToggleSwitch toggleSwitch)
|
||||
{
|
||||
if (toggleSwitch.DataContext is LocalLyricsFolder localLyricsFolder)
|
||||
{
|
||||
ViewModel.ToggleLocalLyricsFolder(localLyricsFolder);
|
||||
}
|
||||
}
|
||||
ViewModel.RemoveFolderAsync((LocalLyricsFolder)(sender as HyperlinkButton)!.Tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Window
|
||||
x:Class="BetterLyrics.WinUI3.Views.SettingsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
|
||||
<Grid>
|
||||
<local:SettingsPage />
|
||||
</Grid>
|
||||
|
||||
</Window>
|
||||
@@ -0,0 +1,33 @@
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using H.NotifyIcon;
|
||||
using Microsoft.UI.Xaml;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
public sealed partial class SettingsWindow : Window
|
||||
{
|
||||
public SettingsWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Title = App.ResourceLoader!.GetString("SettingsPageTitle");
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
|
||||
AppWindow.Closing += AppWindow_Closing;
|
||||
}
|
||||
|
||||
public SettingsWindowViewModel ViewModel { get; set; } =
|
||||
Ioc.Default.GetRequiredService<SettingsWindowViewModel>();
|
||||
|
||||
private void AppWindow_Closing(
|
||||
Microsoft.UI.Windowing.AppWindow sender,
|
||||
Microsoft.UI.Windowing.AppWindowClosingEventArgs args
|
||||
)
|
||||
{
|
||||
args.Cancel = true; // Prevent the window from closing
|
||||
this.Hide(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
201
README.CN.md
201
README.CN.md
@@ -1,188 +1,127 @@
|
||||
<a href="https://github.com/jayfunc/BetterLyrics/blob/dev/README.md">_**Click here to see the English version**_</a>
|
||||
|
||||
<div align="center">
|
||||
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="64"/>
|
||||
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="64"/>
|
||||
</div>
|
||||
|
||||
<h2 align="center">
|
||||
BetterLyrics
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<h3 align="center">
|
||||
基于 WinUI 3 打造的流畅动态本地歌词显示工具
|
||||
</h3>
|
||||
使用 WinUI 3 构建的流畅动态歌词显示工具
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 亮点
|
||||
## 亮点功能
|
||||
|
||||
- 支持将模糊专辑封面设为背景
|
||||
- 歌词淡入淡出、缩放等动画流畅自然
|
||||
- 切换歌曲时界面无缝过渡
|
||||
- 支持每个字符的渐变卡拉OK(发光)效果
|
||||
- 沉浸式桌面歌词(Dock 模式)
|
||||
- 动态模糊专辑封面作为背景
|
||||
- 流畅的歌词淡入/淡出、放大/缩小效果
|
||||
- 流畅的用户界面随歌曲切换
|
||||
- 每个字符均支持渐变卡拉 OK(带光晕)效果
|
||||
- 沉浸式桌面歌词(停靠模式)
|
||||
|
||||
> 项目仍在开发中,`dev` 分支可能存在 bug。
|
||||
> 该项目目前仍在开发中,最新的开发分支中可能存在错误和意外行为。
|
||||
|
||||
---
|
||||
## 支持的歌词来源
|
||||
|
||||
## 支持的歌词源
|
||||
- 来自您的本地存储
|
||||
- 音乐文件(内嵌歌词)
|
||||
- [.lrc](https://en.wikipedia.org/wiki/LRC_(file_format)) 文件(包含核心格式和增强格式)
|
||||
- [.eslrc](https://github.com/ESLyric/release) 文件
|
||||
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) 文件
|
||||
|
||||
- 本地歌词:
|
||||
- 音乐文件内嵌歌词(通过 [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet) 读取和解析)
|
||||
- `.lrc` 文件
|
||||
(歌词下载,您可以使用 [LDDC](https://github.com/chenmozhijin/LDDC))
|
||||
|
||||
- 在线歌词源:
|
||||
- [LRCLIB](https://lrclib.net/)
|
||||
- QQ 音乐(通过 [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper) 获取和解码)
|
||||
- 来自在线歌词提供商
|
||||
- QQ 音乐
|
||||
- 网易云音乐
|
||||
- 酷狗音乐
|
||||
- [amll-ttml-db](https://github.com/Steve-xmh/amll-ttml-db)
|
||||
- [LRCLIB](https://lrclib.net/)
|
||||
|
||||
---
|
||||
## 截图
|
||||
|
||||
## 多种个性化设置选项
|
||||

|
||||
|
||||
提供了丰富的自定义项:
|
||||

|
||||
|
||||
- 主题模式(浅色、深色、跟随系统)
|
||||
- 背景样式(无、Mica 云母、Acrylic 亚克力、透明)
|
||||
- 专辑封面背景(动态显示、模糊程度、透明度)
|
||||
- 歌词样式(对齐方式、字体大小、颜色 **(从专辑封面中提取主题色)**、行间距、透明度、模糊强度、动态**发光**特效)
|
||||
- 语言(英文、简体中文、繁体中文)
|
||||

|
||||
|
||||
---
|
||||

|
||||
|
||||
## 软件截图
|
||||

|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
---
|
||||

|
||||
|
||||
## 演示视频
|
||||

|
||||
|
||||
观看我们的介绍视频「BetterLyrics 阶段性开发成果展示」(上传于 2025 年 5 月 31 日):
|
||||
[点此观看 B 站视频](https://b23.tv/QjKkYmL)
|
||||

|
||||
|
||||
---
|
||||
## 演示
|
||||
|
||||
在 Bilibili 上观看我们的介绍视频(上传于 2025 年 5 月 31 日) [此处](https://b23.tv/QjKkYmL)。
|
||||
|
||||
## 立即体验
|
||||
|
||||
### 稳定版本
|
||||
- 稳定版本
|
||||
|
||||
<a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct">
|
||||
<img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/>
|
||||
<img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/>
|
||||
</a>
|
||||
|
||||
> **推荐方式**,**永久免费试用或购买**(免费与付费功能上无差别,若喜欢可购买支持作者)
|
||||
> **最简单**的获取方式。 **无限**免费试用或购买(免费版和付费版**没有区别**,如果您喜欢,可以购买来支持我)
|
||||
|
||||
也可从 Google Drive 下载(详见 [release 页面](https://github.com/jayfunc/BetterLyrics/releases/latest))
|
||||
或者您也可以从 Google Drive 获取(链接见 [release](https://github.com/jayfunc/BetterLyrics/releases/latest) 页面)
|
||||
|
||||
> 注意:这是一个 `.zip` 压缩包,请参考[安装指南](How2Install/How2Install.md)进行安装。
|
||||
> 请注意,您正在下载“.zip”文件,有关安装指南,请参考[此文档](How2Install/How2Install.md)。
|
||||
|
||||
### 最新开发版本
|
||||
- 最新开发版本
|
||||
|
||||
可通过 `git clone` 克隆本仓库后自行构建运行。
|
||||
您可以使用 `git clone` 命令克隆此项目并自行构建。
|
||||
|
||||
---
|
||||
## 已知不支持的音乐播放器
|
||||
|
||||
## 播放器适配说明
|
||||
- 网易云音乐
|
||||
|
||||
本项目通过监听 [SMTC](https://learn.microsoft.com/en-ca/windows/uwp/audio-video-camera/integrate-with-systemmediatransportcontrols) 获取当前播放歌曲信息。
|
||||
理论上,**只要你的播放器支持 SMTC 控件**,加载本地音乐或歌词后即可使用。
|
||||
|
||||
兼容性良好的播放器包括但不限于:
|
||||
|
||||
- Spotify
|
||||
- Groove 音乐
|
||||
- Apple Music
|
||||
- Windows 媒体播放器
|
||||
- VLC
|
||||
- QQ 音乐
|
||||
- 酷狗音乐
|
||||
- 酷我音乐
|
||||
|
||||
>(注:未测试全部播放器,如有异常欢迎反馈 issue)
|
||||
|
||||
---
|
||||
|
||||
## 后续工作
|
||||
|
||||
敬请期待。
|
||||
|
||||
---
|
||||
|
||||
## 特别感谢
|
||||
## 非常感谢
|
||||
|
||||
- [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper)
|
||||
- 提供 QQ、网易、酷狗等平台歌词的获取、解密和解析功能
|
||||
- [LRCLIB](https://lrclib.net/)
|
||||
- 在线歌词 API 提供源
|
||||
- LRCLIB 歌词 API 提供程序
|
||||
- [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet)
|
||||
- 本地音频元信息读取
|
||||
- 用于提取音乐文件中的图片
|
||||
- [WinUIEx](https://github.com/dotMorten/WinUIEx)
|
||||
- 简化 Win32 窗口操作
|
||||
- 提供访问 Win32 窗口 API 的便捷方法
|
||||
- [TagLib#](https://github.com/mono/taglib-sharp)
|
||||
- 曾用作元信息解析库
|
||||
- [Stackoverflow - WPF 动画 Margin 属性](https://stackoverflow.com/a/21542882/11048731)
|
||||
- [DevWinUI](https://github.com/ghost1372/DevWinUI)
|
||||
- [Bilibili -【WinUI3】SystemBackdropController 教程](https://www.bilibili.com/video/BV1PY4FevEkS)
|
||||
- [博客园 - .NET App 与 SMTC 交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
|
||||
- [Win2D 游戏循环教程](https://www.cnblogs.com/walterlv/p/10236395.html)
|
||||
- [Win2D Iris Blur 示例](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
|
||||
- [CommunityToolkit - 教程合集](https://mvvm.coldwind.top/)
|
||||
- 用于读取原版歌词内容
|
||||
- [Stackoverflow - 如何在 WPF 中为 Margin 属性设置动画](https://stackoverflow.com/a/21542882/11048731)
|
||||
- [DevWinUI](https://github.com/ghost1372/DevWinUI)
|
||||
- [Bilibili -【WinUI3】SystemBackdropController:定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS)
|
||||
- [cnblogs - .NET App 与 Windows 系统媒体控制(SMTC)交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
|
||||
- [Win2D中的游戏循环:CanvasAnimatedControl](https://www.cnblogs.com/walterlv/p/10236395.html)
|
||||
- [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
|
||||
- [CommunityToolkit - 从入门到精通](https://mvvm.coldwind.top/)
|
||||
|
||||
---
|
||||
## 灵感来自
|
||||
|
||||
## 灵感来源
|
||||
|
||||
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
|
||||
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
|
||||
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
|
||||
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
|
||||
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
|
||||
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
|
||||
- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)
|
||||
|
||||
---
|
||||
|
||||
## 使用的第三方库
|
||||
|
||||
```xml
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper" Version="0.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
<PackageReference Include="WinUIEx" Version="2.5.1" />
|
||||
<PackageReference Include="z440.atl.core" Version="6.25.0" />
|
||||
```
|
||||
|
||||
## Star 历史
|
||||
|
||||
[](https://www.star-history.com/#jayfunc/BetterLyrics&Date)
|
||||
[](https://www.star-history.com/#jayfunc/BetterLyrics&Date)
|
||||
|
||||
## 欢迎提出反馈或建议
|
||||
|
||||
感谢。
|
||||
## 欢迎提出任何问题和 PR
|
||||
|
||||
如果您发现错误,请提交至 issues;如果您有任何想法,请随时在此处分享。
|
||||
|
||||
或者,您也可以加入群聊,分享您的宝贵反馈:
|
||||
- QQ[「BetterLyrics」反馈交流群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388)
|
||||
- Discord [「BetterLyrics」反馈交流群](https://discord.gg/rbnF556r)
|
||||
98
README.md
98
README.md
@@ -9,7 +9,7 @@ BetterLyrics
|
||||
</div>
|
||||
|
||||
<h3 align="center">
|
||||
Your smooth dynamic local lyrics display built with WinUI 3
|
||||
Your smooth dynamic lyrics display tool built with WinUI 3
|
||||
</div>
|
||||
|
||||
---
|
||||
@@ -32,52 +32,15 @@ Your smooth dynamic local lyrics display built with WinUI 3
|
||||
- [.eslrc](https://github.com/ESLyric/release) files
|
||||
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) files
|
||||
|
||||
(For lyrics downloading, you can use [LDDC](https://github.com/chenmozhijin/LDDC))
|
||||
|
||||
- From online lyrics providers
|
||||
- QQ Music
|
||||
- 网易云音乐 NetEase Cloud Music
|
||||
- 酷狗音乐 Kugou Music
|
||||
- [amll-ttml-db](https://github.com/Steve-xmh/amll-ttml-db)
|
||||
- [LRCLIB](https://lrclib.net/)
|
||||
|
||||
## Customize in your way
|
||||
|
||||
We provide more than one setting item to better align with your preference
|
||||
|
||||
- Theme
|
||||
- Light
|
||||
- Dark
|
||||
- Follow system
|
||||
|
||||
- Backdrop
|
||||
- None
|
||||
- Mica
|
||||
- Acrylic
|
||||
- Transparent
|
||||
|
||||
- Album art as background
|
||||
- Dynamic
|
||||
- Blur amount
|
||||
- Opacity
|
||||
|
||||
- Album art as cover
|
||||
- Corner radius
|
||||
|
||||
- Lyrics
|
||||
- Alignment
|
||||
- Font size
|
||||
- Font color **(from album art accent color)**
|
||||
- Line spacing
|
||||
- Opacity
|
||||
- Blur amount
|
||||
- Dynamic **glow** effect
|
||||
- Whole lyrics
|
||||
- Line by line
|
||||
- Word by word
|
||||
|
||||
- Language
|
||||
- English
|
||||
- Simplified Chinese
|
||||
- Traditional Chinese
|
||||
- Japanese
|
||||
- Korean
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
@@ -104,7 +67,7 @@ Watch our introduction video (uploaded on 31 May 2025) on Bilibili [here](https:
|
||||
|
||||
## Try it now
|
||||
|
||||
### Stable version
|
||||
- Stable version
|
||||
|
||||
<a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct">
|
||||
<img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/>
|
||||
@@ -116,26 +79,20 @@ Or alternatively get it from Google Drive (see [release](https://github.com/jayf
|
||||
|
||||
> Please note you are downloading ".zip" file, for guide on how to install it, please kindly follow [this doc](How2Install/How2Install.md).
|
||||
|
||||
### Latest dev version
|
||||
- Latest dev version
|
||||
|
||||
You can `git clone` this project and build it yourself.
|
||||
|
||||
## Setup your app
|
||||
## Known unsupported music player
|
||||
|
||||
This project relies on listening messages from [SMTC](https://learn.microsoft.com/en-ca/windows/uwp/audio-video-camera/integrate-with-systemmediatransportcontrols), so most of the music players will work.
|
||||
|
||||
### About lyrics
|
||||
|
||||
For a better experience, you can use [LDDC](https://github.com/chenmozhijin/LDDC) to download lyrics.
|
||||
|
||||
## Future work
|
||||
|
||||
To be added later.
|
||||
- 网易云音乐 NetEase Cloud Music
|
||||
|
||||
## Many thanks to
|
||||
|
||||
- [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper)
|
||||
- Provide lyrics fetch, decryption, and parse for QQ, Netease, Kugou sources
|
||||
- [LRCLIB](https://lrclib.net/)
|
||||
- Online lyrics API provider
|
||||
- LRCLIB lyrics API provider
|
||||
- [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet)
|
||||
- Used for extracting pictures in music files
|
||||
- [WinUIEx](https://github.com/dotMorten/WinUIEx)
|
||||
@@ -157,29 +114,6 @@ To be added later.
|
||||
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
|
||||
- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)
|
||||
|
||||
## Third-party libraries that this project uses
|
||||
|
||||
```
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
<PackageReference Include="WinUIEx" Version="2.5.1" />
|
||||
<PackageReference Include="z440.atl.core" Version="6.26.0" />S
|
||||
```
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#jayfunc/BetterLyrics&Date)
|
||||
@@ -187,3 +121,7 @@ To be added later.
|
||||
## Any issues and PRs are welcomed
|
||||
|
||||
If you find a bug please file it in issues or if you have any ideas feel free to share it here.
|
||||
|
||||
Or alternatively join group chat to share your valuable feedback:
|
||||
- [「BetterLyrics」反馈交流群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388) on QQ
|
||||
- [「BetterLyrics」Feedback Chat Group](https://discord.gg/rbnF556r) on Discord
|
||||
|
||||
Reference in New Issue
Block a user