Compare commits

...

83 Commits

Author SHA1 Message Date
Zhe Fang
7238713ff5 chores: bump to 1.1.167.0 2025-12-04 12:53:45 -05:00
Zhe Fang
66c42f81f2 fix: song timeline offset, lyrics scroll overlay offset 2025-12-04 12:34:46 -05:00
Zhe Fang
3e6ba725d2 Update README.CN.md 2025-12-03 18:45:19 -05:00
Zhe Fang
ffb0c58a58 Update README.md 2025-12-03 18:41:37 -05:00
Zhe Fang
3b61f568d0 Update README.md 2025-12-03 18:31:26 -05:00
Zhe Fang
b925a10d69 Update README.CN.md 2025-12-03 18:31:11 -05:00
Zhe Fang
720f4b311e Update README.md 2025-12-03 18:30:20 -05:00
Zhe Fang
6c03002051 Delete LICENSE.txt 2025-12-03 18:28:44 -05:00
Zhe Fang
9cebd56bd5 Change LICENSE 2025-12-03 18:27:59 -05:00
Zhe Fang
f0a4c1251d chores: bump to 1.1.166.0 2025-12-03 16:38:22 -05:00
Zhe Fang
a8418d4234 feat: album art switch animation 2025-12-03 16:31:21 -05:00
Zhe Fang
53abc4526c fix: album art shadow 2025-12-03 14:31:57 -05:00
Zhe Fang
4d3b982904 feat: add settings for changing playing line top offset; fix: scroll bar visibility for lyrics placeholder 2025-12-03 11:44:50 -05:00
Zhe Fang
5faace562d feat: enable/disable blur effect; auto/manually resize album art height 2025-12-03 09:41:59 -05:00
Zhe Fang
290b7f38b4 fix: songinfo display issue 2025-12-02 21:33:39 -05:00
Zhe Fang
6f02a1a46c fix: font selector 2025-12-02 19:19:15 -05:00
Zhe Fang
4f74e48cfb chores: bump version code to 1.1.164.0 2025-12-02 17:33:23 -05:00
Zhe Fang
06f08558cc fix: i18n 2025-12-02 17:24:57 -05:00
Zhe Fang
a2b21ed3d5 chores: improve font family selection ux 2025-12-02 16:59:36 -05:00
Zhe Fang
4cb1ca0bb3 chores: improve layout 2025-12-02 15:10:18 -05:00
Zhe Fang
81ed341e47 fix: phonetic opacity 2025-12-02 14:04:53 -05:00
Zhe Fang
a9f92f4cb7 fix: local file lyrics read algo;
feat: support lyrics scrolling and clicking
2025-12-02 13:47:21 -05:00
Zhe Fang
62719ed513 chores: bump version code to 1.1.161.0 2025-12-01 15:18:21 -05:00
Zhe Fang
c9bd7725d0 fix: lyrics style not refreshed when switching lyrics window status 2025-12-01 14:54:32 -05:00
Zhe Fang
b60952916c chores: improve layout 2025-12-01 13:11:12 -05:00
Zhe Fang
ec0917b6c8 chores: delete unused code 2025-12-01 10:27:23 -05:00
Zhe Fang
583fa106ce chores: code cleanup 2025-12-01 09:22:30 -05:00
Zhe Fang
88488e4813 chores: improve layout 2025-12-01 08:16:22 -05:00
Zhe Fang
6e65310b6d chores: add minimize button and bump version code to 1.1.158.0 2025-11-30 21:00:42 -05:00
Zhe Fang
22bd7c2252 chores: add comments 2025-11-30 20:15:43 -05:00
Zhe Fang
5c50bd569a chores: fix floating animation; improve layout 2025-11-30 20:14:47 -05:00
Zhe Fang
401c33003c fix: stroke not drawn on current playing lyrics line 2025-11-30 14:42:00 -05:00
Zhe Fang
664451c530 chores: add translation for spectrum 2025-11-30 13:19:44 -05:00
Zhe Fang
657c81add5 chores: improve layout 2025-11-30 13:13:51 -05:00
Zhe Fang
1dd63ab9ba fix: overlay on album art 2025-11-30 12:21:43 -05:00
Zhe Fang
0a9b9bf484 fix: theme type listener 2025-11-30 12:19:19 -05:00
Zhe Fang
794079f20b fix: album art corner radius load delay 2025-11-30 11:00:05 -05:00
Zhe Fang
be9a67f57d chores: improve layout 2025-11-30 10:23:31 -05:00
Zhe Fang
5b5d62d688 fix: syllable glow effect 2025-11-30 09:06:50 -05:00
Zhe Fang
dfe428645e chores: increase IsLongDuration threshold 2025-11-29 21:57:15 -05:00
Zhe Fang
8e2c977a44 refactor 2025-11-29 21:46:24 -05:00
Zhe Fang
47806c924d refactor: improve layout custom ssytem 2025-11-29 11:11:19 -05:00
Zhe Fang
bbda1cd797 refactor 2025-11-28 18:25:20 -05:00
Zhe Fang
2099332f02 refactor 2025-11-27 14:36:10 -05:00
Zhe Fang
016d9a626f Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-11-26 12:02:23 -05:00
Zhe Fang
cff6f202d6 refactor: lyrics render, album art render; delete album art background; change some features 2025-11-26 12:02:19 -05:00
Zhe Fang
f643f86567 更新 README.CN.md 2025-11-24 22:11:53 -05:00
Zhe Fang
e9b50c84ac 更新 README.md 2025-11-24 22:09:12 -05:00
Zhe Fang
4c590bcf6f fix: improve floating animation and add word-by-word scale animation 2025-11-24 21:19:22 -05:00
Zhe Fang
68a1c6a465 chores: delete surplus args 2025-11-24 16:19:56 -05:00
Zhe Fang
f46364a491 chores: remove unused code 2025-11-24 16:19:13 -05:00
Zhe Fang
75f047e389 Update releases-to-discord.yml 2025-11-24 10:43:43 -05:00
Zhe Fang
1cb21b1373 Update release event types for Telegram notification 2025-11-24 10:43:35 -05:00
Zhe Fang
4592be10e8 chores: bump version code to 149 2025-11-24 10:43:07 -05:00
Zhe Fang
c0217150c1 Add Chinese translations to index.md
Updated the index.md file to include Chinese translations for section titles and descriptions.
2025-11-24 09:47:14 -05:00
Zhe Fang
0705bde0e2 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-11-24 09:30:25 -05:00
Zhe Fang
3f5122c93f chores: replace parse error placeholde with no lyrics found 2025-11-24 09:30:24 -05:00
Zhe Fang
97f061d887 Update links and headings in index.md 2025-11-24 09:24:05 -05:00
Zhe Fang
77525a62ff chores: ui 2025-11-24 09:23:54 -05:00
Zhe Fang
ad473bbd1d fix: ncm return exception when roman is null 2025-11-24 08:42:57 -05:00
Zhe Fang
b1126026c2 Update Discord notification workflow for releases 2025-11-23 17:25:13 -05:00
Zhe Fang
2f84155e6e Update release event types for Telegram notification 2025-11-23 17:24:38 -05:00
Zhe Fang
1769167811 Add workflow_dispatch trigger to Telegram release workflow 2025-11-23 17:14:53 -05:00
Zhe Fang
551da8c0b0 Add workflow_dispatch trigger to releases workflow 2025-11-23 17:14:36 -05:00
Zhe Fang
a5b3671ce3 chores: bump version code to 147 2025-11-23 14:59:36 -05:00
Zhe Fang
181a06c932 add: support 0 duration for lyrics animation 2025-11-23 09:46:18 -05:00
Zhe Fang
93d567e21d Delete .github/workflows/jekyll-gh-pages.yml 2025-11-22 20:04:09 -05:00
Zhe Fang
9da8510de6 Remove remote_theme from Jekyll GitHub Pages workflow
Removed remote_theme configuration from the workflow.
2025-11-22 20:01:41 -05:00
Zhe Fang
3ed9e599be Add remote theme for Jekyll GitBook deployment 2025-11-22 19:59:24 -05:00
Zhe Fang
e277faea9e Update links in README.CN.md for better navigation 2025-11-22 17:15:38 -05:00
Zhe Fang
21dcd7de4b Update README.md 2025-11-22 17:12:31 -05:00
Zhe Fang
31540beaa0 chores: bump version code to 1.0.146.0 2025-11-22 16:36:27 -05:00
Zhe Fang
63ffe6b661 fix: lyrics matching rule 2025-11-22 14:46:17 -05:00
Zhe Fang
5cb880021c fix: add error parser placeholder 2025-11-22 09:38:49 -05:00
Zhe Fang
b00f2b5865 fix: parse lrc for ncm 2025-11-22 09:27:55 -05:00
Zhe Fang
90a75f1b96 add: globally set lyrics matching threshold 2025-11-21 20:58:28 -05:00
Zhe Fang
735f03542f add: support adjusting matching threshold 2025-11-21 19:53:18 -05:00
Zhe Fang
b7853ded26 fix: ncm transation wrongly tagged with roman 2025-11-21 12:13:10 -05:00
Zhe Fang
88fc0adbec fix: AM album title 2025-11-21 12:08:08 -05:00
Zhe Fang
a3366422a2 chores: code cleanup 2025-11-20 17:35:36 -05:00
Zhe Fang
8f6e106282 chores: bump version code 2025-11-20 13:02:33 -05:00
Zhe Fang
08d4f4ce90 fix: clear all media props when switching tracks (local player) 2025-11-20 12:30:06 -05:00
Zhe Fang
212041a509 fix: can not find local .lrc lyrics file when metadata is missing in the music file 2025-11-20 10:09:31 -05:00
136 changed files with 7601 additions and 5575 deletions

View File

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

View File

@@ -69,6 +69,11 @@
<converter:IndexToDisplayConverter x:Key="IndexToDisplayConverter" />
<converter:IntToDoubleConverter x:Key="IntToDoubleConverter" />
<converter:MillisecondsToSecondsConverter x:Key="MillisecondsToSecondsConverter" />
<converter:PictureInfosToImageSourceConverter x:Key="PictureInfosToImageSourceConverter" />
<converter:LyricsFontWeightToFontWeightConverter x:Key="LyricsFontWeightToFontWeightConverter" />
<converter:TextAlignmentTypeToHorizontalAlignmentConverter x:Key="TextAlignmentTypeToHorizontalAlignmentConverter" />
<converter:LyricsLayoutOrientationToOrientationConverter x:Key="LyricsLayoutOrientationToOrientationConverter" />
<converter:LyricsLayoutOrientationNegationToOrientationConverter x:Key="LyricsLayoutOrientationNegationToOrientationConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />

View File

@@ -13,7 +13,6 @@ using BetterLyrics.WinUI3.Services.ResourceService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
@@ -26,6 +25,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
using WinUIEx;
namespace BetterLyrics.WinUI3
{
@@ -69,7 +69,11 @@ namespace BetterLyrics.WinUI3
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
WindowHook.OpenOrShowWindow<LyricsWindow>();
// 设置托盘
WindowHook.OpenOrShowWindow<SystemTrayWindow>();
WindowHook.HideWindow<SystemTrayWindow>();
WindowHook.OpenOrShowWindow<NowPlayingWindow>();
if (Ioc.Default.GetRequiredService<ISettingsService>().AppSettings.MusicGallerySettings.AutoOpen)
{
WindowHook.OpenOrShowWindow<MusicGalleryWindow>();
@@ -110,13 +114,14 @@ namespace BetterLyrics.WinUI3
.AddSingleton<LyricsWindowSettingsControlViewModel>()
.AddSingleton<LyricsWindowSwitchControlViewModel>()
.AddSingleton<LyricsWindowSwitchWindowViewModel>()
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<NowPlayingWindowViewModel>()
.AddSingleton<NowPlayingPageViewModel>()
.AddSingleton<SettingsWindowViewModel>()
.AddSingleton<SystemTrayViewModel>()
.AddSingleton<SettingsPageViewModel>()
.AddSingleton<LyricsPageViewModel>()
.AddSingleton<MusicGalleryViewModel>()
.AddSingleton<LyricsRendererViewModel>()
.AddSingleton<AboutControlViewModel>()
.BuildServiceProvider()
);

View File

@@ -23,11 +23,12 @@
<None Remove="Assets\Segoe Fluent Icons.ttf" />
<None Remove="Assets\Wiki82.profile.xml" />
<None Remove="Controls\AboutControl.xaml" />
<None Remove="Controls\AlbumArtLayoutSettingsControl.xaml" />
<None Remove="Controls\AlbumArtAreaEffectSettingsControl.xaml" />
<None Remove="Controls\AppSettingsControl.xaml" />
<None Remove="Controls\DemoWindowGrid.xaml" />
<None Remove="Controls\ExtendedSlider.xaml" />
<None Remove="Controls\FontFamilyAutoSuggestBox.xaml" />
<None Remove="Controls\ImageSwitcher.xaml" />
<None Remove="Controls\LyricsSearchControl.xaml" />
<None Remove="Controls\LyricsStyleSettingsControl.xaml" />
<None Remove="Controls\LyricsWindowSettingsControl.xaml" />
@@ -37,11 +38,13 @@
<None Remove="Controls\PropertyRow.xaml" />
<None Remove="Controls\ShortcutTextBox.xaml" />
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Controls\WindowSettingsControl.xaml" />
<None Remove="Views\LyricsSearchWindow.xaml" />
<None Remove="Views\LyricsWindowSwitchWindow.xaml" />
<None Remove="Views\MusicGalleryPage.xaml" />
<None Remove="Views\MusicGalleryWindow.xaml" />
<None Remove="Views\SettingsWindow.xaml" />
<None Remove="Views\SystemTrayWindow.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Logo.ico" />
@@ -65,10 +68,10 @@
<PackageReference Include="ComputeSharp.D2D1.WinUI" Version="3.2.0" />
<PackageReference Include="csharp-kana" Version="1.0.2" />
<PackageReference Include="csharp-pinyin" Version="1.0.1" />
<PackageReference Include="DevWinUI.Controls" Version="9.5.0" />
<PackageReference Include="DevWinUI.Controls" Version="9.7.0" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="F23.StringSimilarity" Version="7.0.0" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.2" />
<PackageReference Include="F23.StringSimilarity" Version="7.0.1" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.4.1" />
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
@@ -80,7 +83,7 @@
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="NTextCat" Version="0.3.65" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.3-dev-02320" />
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="System.Drawing.Common" Version="10.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.0" />
@@ -93,7 +96,7 @@
<PackageReference Include="Vanara.Windows.Shell" Version="4.2.1" />
<PackageReference Include="VCollab.DiscordRichPresence" Version="1.7.0" />
<PackageReference Include="WinUIEx" Version="2.9.0" />
<PackageReference Include="z440.atl.core" Version="7.8.0" />
<PackageReference Include="z440.atl.core" Version="7.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ColorThief.WinUI3\ColorThief.WinUI3.csproj" />
@@ -292,7 +295,7 @@
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AlbumArtLayoutSettingsControl.xaml">
<Page Update="Controls\AlbumArtAreaStyleSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
@@ -334,6 +337,31 @@
<ItemGroup>
<Folder Include="TemplateSelector\" />
</ItemGroup>
<ItemGroup>
<Page Update="Views\SystemTrayWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\WindowSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AlbumArtAreaEffectSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ImageSwitcher.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ShadowImage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\PropertyRow.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -3,6 +3,7 @@
public class ExtendedGenreFiled
{
public const string NetEaseCloudMusicTrackID = "NCM-";
public const string QQMusicTrackID = "QQ-";
public const string FileName = "FILENAME-";
}
}

View File

@@ -2,16 +2,17 @@
{
public static class Link
{
public const string MicrosoftStoreUrl = "https://apps.microsoft.com/detail/9p1wcd1p597r";
public const string GitHubUrl = "https://github.com/jayfunc/BetterLyrics";
public const string ShareHubUrl = $"{GitHubUrl}/blob/dev/ShareHub/index.md";
public const string TermsOfServiceUrl = $"{GitHubUrl}/blob/dev/TermsofService.md";
public const string PrivacyPolicy = $"{GitHubUrl}/blob/dev/PrivacyPolicy.md";
public const string WikiUrl = "https://jayfunc.blog/work/betterlyrics";
public const string AppleMusicCfgUrl = $"{WikiUrl}#lyrics-sources-configuration";
public const string FAQUrl = $"{WikiUrl}#faq";
public const string QQGroupUrl = "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";
public const string DiscordUrl = "https://discord.gg/5yAQPnyCKv";
public const string TelegramUrl = "https://t.me/+svhSLZ7awPsxNGY1";
public const string MicrosoftStore = "https://apps.microsoft.com/detail/9p1wcd1p597r";
public const string GitHub = "https://github.com/jayfunc/BetterLyrics";
public const string ShareHub = $"{GitHub}/blob/dev/ShareHub/index.md";
public const string TermsOfService = $"{GitHub}/blob/dev/TermsofService.md";
public const string PrivacyPolicy = $"{GitHub}/blob/dev/PrivacyPolicy.md";
public const string UserGuide = $"{GitHub}/wiki/User-Guide";
public const string AppleMusicCfg = $"{UserGuide}#lyrics-source-configuration";
public const string QQGroup = "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";
public const string Discord = "https://discord.gg/5yAQPnyCKv";
public const string Telegram = "https://t.me/+svhSLZ7awPsxNGY1";
}
}

View File

@@ -20,7 +20,8 @@
public const string Edge = "MSEdge";
public const string BetterLyrics = "37412.BetterLyrics_rd1g0rsrrtxw8!App";
public const string BetterLyricsDebug = "37412.BetterLyrics_c8mj3v9sysxb4!App";
public const string SaltPlayerForWindows = "Sakawish.SaltPlayerforWindows_q65q631pyh094!SaltPlayerforWindows";
public const string SaltPlayerForWindowsMS = "Sakawish.SaltPlayerforWindows_q65q631pyh094!SaltPlayerforWindows";
public const string SaltPlayerForWindowsSteam = "Salt Player for Windows.exe";
public const string MoeKoeMusic = "cn.MoeKoe.Music";
public const string MoeKoeMusicAlternative = "electron.app.MoeKoe Music";
public const string Listen1 = "com.listen1.listen1";

View File

@@ -20,7 +20,8 @@
public const string Edge = "Microsoft Edge";
public const string BetterLyrics = "BetterLyrics";
public const string BetterLyricsDebug = "BetterLyrics (Debug)";
public const string SaltPlayerForWindows = "Salt Player for Windows";
public const string SaltPlayerForWindowsMS = "Salt Player for Windows (Microsoft Store)";
public const string SaltPlayerForWindowsSteam = "Salt Player for Windows (Steam)";
public const string MoeKoeMusic = "MoeKoe Music";
public const string Listen1 = "Listen 1";
}

View File

@@ -4,6 +4,7 @@ namespace BetterLyrics.WinUI3.Constants
{
public static class Time
{
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(300);
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(250);
public static readonly TimeSpan AnimationDuration = TimeSpan.FromMilliseconds(350);
}
}

View File

@@ -51,11 +51,10 @@
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.GitHubUrl}" />
<HyperlinkButton Content="Wiki" NavigateUri="{x:Bind const:Link.WikiUrl}" />
<HyperlinkButton Content="FAQ" NavigateUri="{x:Bind const:Link.FAQUrl}" />
<HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.GitHub}" />
<HyperlinkButton x:Uid="UserGuide" NavigateUri="{x:Bind const:Link.UserGuide}" />
<HyperlinkButton x:Uid="PrivacyPolicy" NavigateUri="{x:Bind const:Link.PrivacyPolicy}" />
<HyperlinkButton x:Uid="TermsOfService" NavigateUri="{x:Bind const:Link.TermsOfServiceUrl}" />
<HyperlinkButton x:Uid="TermsOfService" NavigateUri="{x:Bind const:Link.TermsOfService}" />
</StackPanel>
</StackPanel>
@@ -65,9 +64,9 @@
<StackPanel Spacing="6">
<TextBlock x:Uid="SetingsPageFeedback" />
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton x:Uid="SettingsPageQQGroup" NavigateUri="{x:Bind const:Link.QQGroupUrl}" />
<HyperlinkButton x:Uid="SettingsPageDiscord" NavigateUri="{x:Bind const:Link.DiscordUrl}" />
<HyperlinkButton x:Uid="SettingsPageTelegram" NavigateUri="{x:Bind const:Link.TelegramUrl}" />
<HyperlinkButton x:Uid="SettingsPageQQGroup" NavigateUri="{x:Bind const:Link.QQGroup}" />
<HyperlinkButton x:Uid="SettingsPageDiscord" NavigateUri="{x:Bind const:Link.Discord}" />
<HyperlinkButton x:Uid="SettingsPageTelegram" NavigateUri="{x:Bind const:Link.Telegram}" />
</StackPanel>
</StackPanel>
</dev:SettingsCard>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.AlbumArtAreaEffectSettingsControl"
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:dev="using:DevWinUI"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="SettingsPageAlbumEffect" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageImageSwitchType" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8EB;}">
<ComboBox SelectedIndex="{x:Bind AlbumArtAreaEffectSettings.ImageSwitchType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageCrossfade" />
<ComboBoxItem x:Uid="SettingsPageSlide" />
</ComboBox>
</dev:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,37 @@
using BetterLyrics.WinUI3.Models.Settings;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls;
public sealed partial class AlbumArtAreaEffectSettingsControl : UserControl
{
public static readonly DependencyProperty AlbumArtAreaEffectSettingsProperty =
DependencyProperty.Register(nameof(AlbumArtAreaEffectSettings), typeof(AlbumArtAreaEffectSettings), typeof(AlbumArtAreaEffectSettingsControl), new PropertyMetadata(default));
public AlbumArtAreaEffectSettings AlbumArtAreaEffectSettings
{
get => (AlbumArtAreaEffectSettings)GetValue(AlbumArtAreaEffectSettingsProperty);
set => SetValue(AlbumArtAreaEffectSettingsProperty, value);
}
public AlbumArtAreaEffectSettingsControl()
{
InitializeComponent();
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.AlbumArtLayoutSettingsControl"
x:Class="BetterLyrics.WinUI3.Controls.AlbumArtAreaStyleSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
@@ -16,29 +16,40 @@
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="SettingsPageAlbumStyle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<!-- 整体对齐 -->
<dev:SettingsCard x:Uid="SettingsPageAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLeft" />
<ComboBoxItem x:Uid="SettingsPageCenter" />
<ComboBoxItem x:Uid="SettingsPageRight" />
</ComboBox>
</dev:SettingsCard>
<TextBlock x:Uid="SettingsPageAlbumArt" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<!-- 专辑高度 -->
<dev:SettingsExpander
x:Uid="SettingsPageAlbumArtSize"
x:Uid="SettingsPageAlbumArtHeight"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE744;}"
Glyph=&#xE784;}"
IsExpanded="True">
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageAutoAdjust">
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.AutoAlbumArtSize, Mode=TwoWay}" />
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.IsAutoCoverImageHeight, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard IsEnabled="{x:Bind AlbumArtLayoutSettings.AutoAlbumArtSize, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<dev:SettingsCard IsEnabled="{x:Bind AlbumArtLayoutSettings.IsAutoCoverImageHeight, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Frequency="2"
Maximum="800"
Minimum="10"
ResetButtonVisibility="Collapsed"
Maximum="512"
Minimum="0"
Unit="px"
Value="{x:Bind AlbumArtLayoutSettings.AlbumArtSize, Mode=TwoWay}" />
Value="{x:Bind AlbumArtLayoutSettings.CoverImageHeight, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 专辑圆角 -->
<dev:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEA3A;}">
<local:ExtendedSlider
Default="12"
@@ -48,6 +59,7 @@
Value="{x:Bind AlbumArtLayoutSettings.CoverImageRadius, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- 专辑阴影 -->
<dev:SettingsCard x:Uid="SettingsPageAlbumShadowAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF5EF;}">
<local:ExtendedSlider
Default="12"
@@ -58,14 +70,6 @@
<TextBlock x:Uid="SettingsPageSongInfo" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageSongInfoAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageSongInfoLeft" />
<ComboBoxItem x:Uid="SettingsPageSongInfoCenter" />
<ComboBoxItem x:Uid="SettingsPageSongInfoRight" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsExpander
x:Uid="SettingsPageLyricsFontSize"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
@@ -87,21 +91,15 @@
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageShowTitle"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEF3D;}"
IsExpanded="True">
<dev:SettingsCard x:Uid="SettingsPageShowTitle" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEF3D;}">
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.ShowTitle, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageShowArtists">
<ToggleSwitch IsEnabled="{x:Bind AlbumArtLayoutSettings.ShowTitle, Mode=OneWay}" IsOn="{x:Bind AlbumArtLayoutSettings.ShowArtists, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageShowAlbum">
<ToggleSwitch IsEnabled="{x:Bind AlbumArtLayoutSettings.ShowTitle, Mode=OneWay}" IsOn="{x:Bind AlbumArtLayoutSettings.ShowAlbum, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageShowArtists" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEF3D;}">
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.ShowArtists, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageShowAlbum" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEF3D;}">
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.ShowAlbum, Mode=TwoWay}" />
</dev:SettingsCard>
</StackPanel>
</Grid>

View File

@@ -7,18 +7,18 @@ using Microsoft.UI.Xaml.Controls;
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class AlbumArtLayoutSettingsControl : UserControl
public sealed partial class AlbumArtAreaStyleSettingsControl : UserControl
{
public static readonly DependencyProperty AlbumArtLayoutSettingsProperty =
DependencyProperty.Register(nameof(AlbumArtLayoutSettings), typeof(AlbumArtLayoutSettings), typeof(AlbumArtLayoutSettingsControl), new PropertyMetadata(default));
DependencyProperty.Register(nameof(AlbumArtLayoutSettings), typeof(AlbumArtAreaStyleSettings), typeof(AlbumArtAreaStyleSettingsControl), new PropertyMetadata(default));
public AlbumArtLayoutSettings AlbumArtLayoutSettings
public AlbumArtAreaStyleSettings AlbumArtLayoutSettings
{
get => (AlbumArtLayoutSettings)GetValue(AlbumArtLayoutSettingsProperty);
get => (AlbumArtAreaStyleSettings)GetValue(AlbumArtLayoutSettingsProperty);
set => SetValue(AlbumArtLayoutSettingsProperty, value);
}
public AlbumArtLayoutSettingsControl()
public AlbumArtAreaStyleSettingsControl()
{
InitializeComponent();
}

View File

@@ -6,25 +6,54 @@
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:models="using:BetterLyrics.WinUI3.Models"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock
x:Name="SelectedLocalizedFontFamilyTextBlock"
VerticalAlignment="Center"
Foreground="{StaticResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True" />
<AutoSuggestBox
x:Name="AutoSuggestBox"
Width="150"
MinWidth="180"
GotFocus="AutoSuggestBox_GotFocus"
LostFocus="AutoSuggestBox_LostFocus"
SuggestionChosen="AutoSuggestBox_SuggestionChosen"
Text="{x:Bind SelectedFontFamily, Mode=OneWay}"
TextChanged="AutoSuggestBox_TextChanged" />
TextChanged="AutoSuggestBox_TextChanged">
<!--<AutoSuggestBox.ItemTemplate>
<DataTemplate x:DataType="models:ExtendedFontFamily">
<StackPanel>
<TextBlock Text="{x:Bind LocalizedFontFamily}" TextWrapping="Wrap" />
<TextBlock
FontSize="12"
Foreground="{StaticResource TextFillColorSecondaryBrush}"
Text="{x:Bind FontFamily}"
TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>-->
</AutoSuggestBox>
<Button
Click="Button_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE72C;}"
Style="{StaticResource GhostButtonStyle}" />
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="SettingsPageRefreshDropdown" />
</ToolTipService.ToolTip>
</Button>
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FontSize=12, Glyph=&#xE74A;}" Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="SettingsPageCollapseDropdown" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -1,8 +1,15 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -11,11 +18,15 @@ namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class FontFamilyAutoSuggestBox : UserControl
{
private List<string> SystemFontNames { get; set; } = [.. FontHelper.SystemFontFamilies];
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
//private List<ExtendedFontFamily> FontFamilies { get; set; } = [];
private List<string> FontFamilies { get; set; } = [];
public FontFamilyAutoSuggestBox()
{
InitializeComponent();
RefreshFontFamilies();
}
public static readonly DependencyProperty SelectedFontFamilyProperty =
@@ -27,20 +38,47 @@ namespace BetterLyrics.WinUI3.Controls
set => SetValue(SelectedFontFamilyProperty, value);
}
private void RefreshFontFamilies()
{
//Task.Run(() =>
//{
// var fontFamilies = FontHelper.SystemFontFamilies.Select(x => new ExtendedFontFamily()
// {
// FontFamily = x,
// LocalizedFontFamily = FontHelper.GetLocalizedFontFamilyName(x, _settingsService.AppSettings.GeneralSettings.LanguageCode)
// }).OrderBy(x => x.LocalizedFontFamily).ToList();
// DispatcherQueue.TryEnqueue(() =>
// {
// FontFamilies = fontFamilies;
// });
//});
FontFamilies = FontHelper.SystemFontFamilies.OrderBy(x => x).ToList();
}
private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{
SelectedFontFamily = args.SelectedItem.ToString() ?? "";
if (args.SelectedItem is ExtendedFontFamily extendedFontFamily)
{
SelectedFontFamily = extendedFontFamily.FontFamily;
}
else
{
SelectedFontFamily = args.SelectedItem.ToString() ?? "";
}
}
private void UpdateAutoSuggestBoxItemsSource()
private void UpdateAutoSuggestBoxItemsSource(string? query = null)
{
query ??= AutoSuggestBox.Text;
//var suitableItems = new List<ExtendedFontFamily>();
var suitableItems = new List<string>();
var splitText = AutoSuggestBox.Text.ToLower().Split(" ");
foreach (var fontFamily in SystemFontNames)
var splitText = query.ToLower().Split(" ");
foreach (var fontFamily in FontFamilies)
{
var found = splitText.All((key) =>
bool found = splitText.All((key) =>
{
//return fontFamily.FontFamily.ToLower().Contains(key) || fontFamily.LocalizedFontFamily.ToLower().Contains(key);
return fontFamily.ToLower().Contains(key);
});
if (found)
@@ -50,9 +88,15 @@ namespace BetterLyrics.WinUI3.Controls
}
if (suitableItems.Count == 0)
{
//suitableItems.Add(new ExtendedFontFamily()
//{
// FontFamily = "",
// LocalizedFontFamily = "N/A"
//});
suitableItems.Add("N/A");
}
AutoSuggestBox.ItemsSource = suitableItems.Order();
//AutoSuggestBox.ItemsSource = suitableItems.OrderBy(x => x.LocalizedFontFamily);
AutoSuggestBox.ItemsSource = suitableItems.OrderBy(x => x);
}
private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
@@ -63,16 +107,18 @@ namespace BetterLyrics.WinUI3.Controls
{
UpdateAutoSuggestBoxItemsSource();
}
SelectedLocalizedFontFamilyTextBlock.Text = FontHelper.GetLocalizedFontFamilyName(SelectedFontFamily, _settingsService.AppSettings.GeneralSettings.LanguageCode);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SystemFontNames = [.. FontHelper.SystemFontFamilies];
RefreshFontFamilies();
}
private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e)
{
UpdateAutoSuggestBoxItemsSource();
UpdateAutoSuggestBoxItemsSource("");
}
private void AutoSuggestBox_LostFocus(object sender, RoutedEventArgs e)

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.ImageSwitcher"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:const="using:BetterLyrics.WinUI3.Constants"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</Grid.OpacityTransition>
<local:ShadowImage
x:Name="LastAlbumArtImage"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadiusAmount="{x:Bind CornerRadiusAmount, Mode=OneWay}"
ShadowAmount="{x:Bind ShadowAmount, Mode=OneWay}" />
<local:ShadowImage
x:Name="AlbumArtImage"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadiusAmount="{x:Bind CornerRadiusAmount, Mode=OneWay}"
ShadowAmount="{x:Bind ShadowAmount, Mode=OneWay}" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,145 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls;
public sealed partial class ImageSwitcher : UserControl
{
public int CornerRadiusAmount
{
get { return (int)GetValue(CornerRadiusAmountProperty); }
set { SetValue(CornerRadiusAmountProperty, value); }
}
public static readonly DependencyProperty CornerRadiusAmountProperty =
DependencyProperty.Register(nameof(CornerRadiusAmount), typeof(int), typeof(ImageSwitcher), new PropertyMetadata(0));
public int ShadowAmount
{
get { return (int)GetValue(ShadowAmountProperty); }
set { SetValue(ShadowAmountProperty, value); }
}
public static readonly DependencyProperty ShadowAmountProperty =
DependencyProperty.Register(nameof(ShadowAmount), typeof(int), typeof(ImageSwitcher), new PropertyMetadata(0));
public ImageSource? Source
{
get { return (ImageSource?)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(nameof(Source), typeof(ImageSource), typeof(ImageSwitcher), new PropertyMetadata(null, OnDependencyPropertyChanged));
public ImageSwitchType SwitchType
{
get { return (ImageSwitchType)GetValue(SwitchTypeProperty); }
set { SetValue(SwitchTypeProperty, value); }
}
public static readonly DependencyProperty SwitchTypeProperty =
DependencyProperty.Register(nameof(SwitchType), typeof(ImageSwitchType), typeof(ImageSwitcher), new PropertyMetadata(ImageSwitchType.Crossfade));
public ImageSwitcher()
{
InitializeComponent();
}
private void UpdateSource()
{
switch (SwitchType)
{
case ImageSwitchType.Crossfade:
UpdateSourceCrossfade();
break;
case ImageSwitchType.Slide:
UpdateSourceSlide();
break;
default:
break;
}
}
private void UpdateSourceCrossfade()
{
// Ϊ<><CEAA><EFBFBD><EFBFBD>ͼƬ<CDBC><C6AC><EFBFBD>þ<EFBFBD>Դ
LastAlbumArtImage.Source = AlbumArtImage.Source;
// ʹ<><CAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD>
LastAlbumArtImage.TranslationTransition = null;
LastAlbumArtImage.OpacityTransition = null;
LastAlbumArtImage.Translation = new();
LastAlbumArtImage.Opacity = 1;
LastAlbumArtImage.OpacityTransition = new ScalarTransition { Duration = Constants.Time.AnimationDuration };
// ʹǰ<CAB9><C7B0>ͼƬ<CDBC><C6AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD>
AlbumArtImage.TranslationTransition = null;
AlbumArtImage.OpacityTransition = null;
AlbumArtImage.Translation = new();
AlbumArtImage.Opacity = 0;
AlbumArtImage.OpacityTransition = new ScalarTransition { Duration = Constants.Time.AnimationDuration };
// ֮<><D6AE>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ
AlbumArtImage.Source = Source;
// <20><><EFBFBD><EFBFBD><E6B5AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
LastAlbumArtImage.Opacity = 0;
AlbumArtImage.Opacity = 1;
}
private void UpdateSourceSlide()
{
// Ϊ<><CEAA><EFBFBD><EFBFBD>ͼƬ<CDBC><C6AC><EFBFBD>þ<EFBFBD>Դ
LastAlbumArtImage.Source = AlbumArtImage.Source;
// ʹ<><CAB9><EFBFBD><EFBFBD>λ
LastAlbumArtImage.TranslationTransition = null;
LastAlbumArtImage.OpacityTransition = null;
LastAlbumArtImage.Translation = new();
LastAlbumArtImage.Opacity = 1;
LastAlbumArtImage.TranslationTransition = new Vector3Transition { Duration = Constants.Time.AnimationDuration };
LastAlbumArtImage.OpacityTransition = new ScalarTransition { Duration = Constants.Time.AnimationDuration };
// ʹǰ<CAB9><C7B0>ͼƬ<CDBC><C6AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD>
AlbumArtImage.TranslationTransition = null;
AlbumArtImage.OpacityTransition = null;
AlbumArtImage.Translation = new(-(float)ActualWidth, 0, 0);
AlbumArtImage.Opacity = 0;
AlbumArtImage.TranslationTransition = new Vector3Transition { Duration = Constants.Time.AnimationDuration };
AlbumArtImage.OpacityTransition = new ScalarTransition { Duration = Constants.Time.AnimationDuration };
// ֮<><D6AE>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ
AlbumArtImage.Source = Source;
// <20><><EFBFBD><EFBFBD>
LastAlbumArtImage.Opacity = 0;
AlbumArtImage.Opacity = 1;
LastAlbumArtImage.Translation = new(-(float)ActualWidth, 0, 0);
AlbumArtImage.Translation = new();
}
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageSwitcher imageSwitcher)
{
if (e.Property == SourceProperty)
{
imageSwitcher.UpdateSource();
}
}
}
}

View File

@@ -17,7 +17,7 @@
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<TextBlock x:Uid="SettingsPageBackgroundOverlay" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageTheme" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE790;}">
<ComboBox x:Name="ThemeComboBox" SelectedIndex="{x:Bind LyricsBackgroundSettings.LyricsBackgroundTheme, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
@@ -47,51 +47,6 @@
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageAlbumArtLayer"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE93C;}"
IsExpanded="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageOpacity" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayOpacity, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageSpeed" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="50"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlaySpeed, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageBlurAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Maximum="100"
Minimum="0"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayBlurAmount, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageBackgroundAcrylicEffectAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="0"
Maximum="10"
Minimum="0"
Value="{x:Bind LyricsBackgroundSettings.CoverAcrylicEffectAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageFluidLayer"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
@@ -162,6 +117,21 @@
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageSpectrumLayerStyle" IsEnabled="{x:Bind LyricsBackgroundSettings.IsSpectrumOverlayEnabled, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind LyricsBackgroundSettings.SpectrumStyle, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageSpectrumStyleCurve" />
<ComboBoxItem x:Uid="SettingsPageSpectrumStyleBar" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsSpectrumOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="128"
Maximum="1024"
Minimum="1"
Value="{x:Bind LyricsBackgroundSettings.SpectrumCount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>

View File

@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Renderer.LyricsRenderer"
x:Class="BetterLyrics.WinUI3.Controls.LyricsCanvas"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Renderer"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Unloaded="LyricsCanvas_Unloaded"
Unloaded="Canvas_Unloaded"
mc:Ignorable="d">
<Grid>
<canvas:CanvasAnimatedControl
x:Name="LyricsCanvas"
CreateResources="LyricsCanvas_CreateResources"
Draw="LyricsCanvas_Draw"
Update="LyricsCanvas_Update" />
x:Name="Canvas"
CreateResources="Canvas_CreateResources"
Draw="Canvas_Draw"
Update="Canvas_Update" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,863 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Logic;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Renderer;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Windows.Foundation;
using Windows.UI;
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class LyricsCanvas : UserControl,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<AlbumArtThemeColors>>,
IRecipient<PropertyChangedMessage<TimeSpan>>,
IRecipient<PropertyChangedMessage<LyricsData?>>,
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>,
IRecipient<PropertyChangedMessage<double>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<TextAlignmentType>>,
IRecipient<PropertyChangedMessage<SongInfo?>>,
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
IRecipient<PropertyChangedMessage<string>>
{
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
private readonly ILastFMService _lastFMService = Ioc.Default.GetRequiredService<ILastFMService>();
private readonly LyricsRenderer _lyricsRenderer = new();
private readonly FluidBackgroundRenderer _fluidRenderer = new();
private readonly PureColorBackgroundRenderer _pureColorRenderer = new();
private readonly SnowRenderer _snowRenderer = new();
private readonly FogRenderer _fogRenderer = new();
private readonly SpectrumRenderer _spectrumRenderer = new();
private readonly LyricsSynchronizer _synchronizer = new();
private readonly LyricsAnimator _animator = new();
private readonly SpectrumAnalyzer _spectrumAnalyzer = new();
private readonly ValueTransition<Color> _immersiveBgColorTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
private readonly ValueTransition<double> _immersiveBgOpacityTransition = new(
initialValue: 1f,
durationSeconds: 0.3f
);
private readonly ValueTransition<Color> _accentColor1Transition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
private readonly ValueTransition<Color> _accentColor2Transition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
private readonly ValueTransition<Color> _accentColor3Transition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
private readonly ValueTransition<Color> _accentColor4Transition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
private readonly ValueTransition<double> _canvasYScrollTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutSine
);
private readonly ValueTransition<double> _mouseYScrollTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutSine
);
private TimeSpan _songPositionWithOffset;
private TimeSpan _songPosition; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1>
private TimeSpan _totalPlayedTime; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ػ<EFBFBD><D8BB>ظ<EFBFBD><D8B8><EFBFBD><EFBFBD>ŵ<EFBFBD>ʱ<EFBFBD>
private bool _isLastFMTracked = false;
private double _renderLyricsStartX = 0;
private double _renderLyricsStartY = 0;
private double _renderLyricsWidth = 0;
private double _renderLyricsHeight = 0;
private double _renderLyricsOpacity = 0;
private Point _mousePosition = new(0, 0);
private int _mouseHoverLineIndex = -1;
private bool _isMouseInLyricsArea = false;
private bool _isMousePressing = false;
private bool _isMouseScrolling = false;
private List<RenderLyricsLine>? _renderLyricsLines = null;
private bool _isLayoutChanged = true;
private bool _isMouseScrollingChanged = false;
private int _playingLineIndex;
private (int Start, int End) _visibleRange;
private double _canvasTargetScrollOffset;
public TimeSpan SongPosition => _songPosition;
public double CurrentCanvasYScroll => _canvasYScrollTransition.Value;
public double ActualLyricsHeight => LyricsLayoutManager.CalculateActualHeight(_renderLyricsLines);
public int CurrentHoveringLineIndex => _mouseHoverLineIndex;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><CABC> X <20><><EFBFBD><EFBFBD>
public double LyricsStartX
{
get { return (double)GetValue(LyricsStartXProperty); }
set { SetValue(LyricsStartXProperty, value); }
}
public static readonly DependencyProperty LyricsStartXProperty =
DependencyProperty.Register(nameof(LyricsStartX), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ Y <20><><EFBFBD><EFBFBD>
public double LyricsStartY
{
get { return (double)GetValue(LyricsStartYProperty); }
set { SetValue(LyricsStartYProperty, value); }
}
public static readonly DependencyProperty LyricsStartYProperty =
DependencyProperty.Register(nameof(LyricsStartY), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
public double LyricsWidth
{
get { return (double)GetValue(LyricsWidthProperty); }
set { SetValue(LyricsWidthProperty, value); }
}
public static readonly DependencyProperty LyricsWidthProperty =
DependencyProperty.Register(nameof(LyricsWidth), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߶<EFBFBD>
public double LyricsHeight
{
get { return (double)GetValue(LyricsHeightProperty); }
set { SetValue(LyricsHeightProperty, value); }
}
public static readonly DependencyProperty LyricsHeightProperty =
DependencyProperty.Register(nameof(LyricsHeight), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͸<EFBFBD><CDB8><EFBFBD><EFBFBD>
public double LyricsOpacity
{
get { return (double)GetValue(LyricsOpacityProperty); }
set { SetValue(LyricsOpacityProperty, value); }
}
public static readonly DependencyProperty LyricsOpacityProperty =
DependencyProperty.Register(nameof(LyricsOpacity), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
/// <summary>
/// <20>û<EFBFBD><C3BB>ٿ<EFBFBD><D9BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD>ľ<EFBFBD><C4BE><EFBFBD><EBA3A8> 0 <20><>ʼ<EFBFBD>
/// </summary>
public double MouseScrollOffset
{
get { return (double)GetValue(MouseScrollOffsetProperty); }
set { SetValue(MouseScrollOffsetProperty, value); }
}
public static readonly DependencyProperty MouseScrollOffsetProperty =
DependencyProperty.Register(nameof(MouseScrollOffset), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
/// <summary>
/// <20>û<EFBFBD><C3BB><EFBFBD><EFBFBD>굱ǰ<EAB5B1><C7B0>λ<EFBFBD>ã<EFBFBD><C3A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڸ<EFBFBD><DAB8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ͻǣ<CFBD>
/// </summary>
public Point MousePosition
{
get { return (Point)GetValue(MousePositionProperty); }
set { SetValue(MousePositionProperty, value); }
}
public static readonly DependencyProperty MousePositionProperty =
DependencyProperty.Register(nameof(MousePosition), typeof(Point), typeof(LyricsCanvas), new PropertyMetadata(new Point(0, 0), OnLayoutPropChanged));
public bool IsMouseInLyricsArea
{
get { return (bool)GetValue(IsMouseInLyricsAreaProperty); }
set { SetValue(IsMouseInLyricsAreaProperty, value); }
}
public static readonly DependencyProperty IsMouseInLyricsAreaProperty =
DependencyProperty.Register(nameof(IsMouseInLyricsArea), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnLayoutPropChanged));
public bool IsMousePressing
{
get { return (bool)GetValue(IsMousePressingProperty); }
set { SetValue(IsMousePressingProperty, value); }
}
public static readonly DependencyProperty IsMousePressingProperty =
DependencyProperty.Register(nameof(IsMousePressing), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnLayoutPropChanged));
public bool IsMouseScrolling
{
get { return (bool)GetValue(IsMouseScrollingProperty); }
set { SetValue(IsMouseScrollingProperty, value); }
}
public static readonly DependencyProperty IsMouseScrollingProperty =
DependencyProperty.Register(nameof(IsMouseScrolling), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnLayoutPropChanged));
public LyricsCanvas()
{
InitializeComponent();
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<int>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<AlbumArtThemeColors>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<TimeSpan>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsData?>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsWindowStatus>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<double>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<bool>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<TextAlignmentType>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<SongInfo?>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsFontWeight>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<string>>(this);
}
private static void OnLayoutPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is LyricsCanvas canvas)
{
if (e.Property == LyricsStartXProperty)
{
canvas._renderLyricsStartX = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
}
else if (e.Property == LyricsStartYProperty)
{
canvas._renderLyricsStartY = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
}
else if (e.Property == LyricsWidthProperty)
{
canvas._renderLyricsWidth = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
}
else if (e.Property == LyricsHeightProperty)
{
canvas._renderLyricsHeight = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
}
else if (e.Property == LyricsOpacityProperty)
{
canvas._renderLyricsOpacity = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
}
else if (e.Property == MouseScrollOffsetProperty)
{
canvas._mouseYScrollTransition.StartTransition(Convert.ToDouble(e.NewValue));
}
else if (e.Property == MousePositionProperty)
{
canvas._mousePosition = (Point)e.NewValue;
}
else if (e.Property == IsMouseInLyricsAreaProperty)
{
canvas._isMouseInLyricsArea = (bool)e.NewValue;
}
else if (e.Property == IsMousePressingProperty)
{
canvas._isMousePressing = (bool)e.NewValue;
}
else if (e.Property == IsMouseScrollingProperty)
{
var value = (bool)e.NewValue;
if (canvas._isMouseScrolling != value)
{
canvas._isMouseScrollingChanged = true;
}
canvas._isMouseScrolling = value;
}
}
}
// ====
private void Canvas_Draw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
var bounds = new Rect(0, 0, sender.Size.Width, sender.Size.Height);
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
var albumArtLayout = status.AlbumArtLayoutSettings;
var lyricsBg = status.LyricsBackgroundSettings;
var lyricsStyle = status.LyricsStyleSettings;
var lyricsEffect = status.LyricsEffectSettings;
double songDuration = _mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0;
bool isForceWordByWord = _settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect;
var lyricsThemeColors = _mediaSessionsService.AlbumArtThemeColors;
Color overlayColor;
double finalOpacity;
if (status.IsAdaptToEnvironment)
{
// <20><><EFBFBD><EFBFBD>Ӧɫ
overlayColor = _immersiveBgColorTransition.Value;
finalOpacity = _immersiveBgOpacityTransition.Value * lyricsBg.PureColorOverlayOpacity / 100.0;
}
else
{
// ר<><D7A8>ɫ
overlayColor = _accentColor1Transition.Value;
finalOpacity = lyricsBg.PureColorOverlayOpacity / 100.0;
}
_pureColorRenderer.Draw(
args.DrawingSession,
bounds,
overlayColor,
finalOpacity,
lyricsBg.IsPureColorOverlayEnabled
);
_fluidRenderer.Opacity = lyricsBg.FluidOverlayOpacity;
_fluidRenderer.IsEnabled = lyricsBg.IsFluidOverlayEnabled;
_fluidRenderer.Draw(sender, args.DrawingSession);
_snowRenderer.Draw(sender, args.DrawingSession);
_fogRenderer.Draw(sender, args.DrawingSession);
_lyricsRenderer.Draw(
control: sender,
ds: args.DrawingSession,
lines: _renderLyricsLines,
playingLineIndex: _playingLineIndex,
mouseHoverLineIndex: _mouseHoverLineIndex,
isMousePressing: _isMousePressing,
startVisibleIndex: _visibleRange.Start,
endVisibleIndex: _visibleRange.End,
lyricsX: _renderLyricsStartX,
lyricsY: _renderLyricsStartY,
lyricsWidth: _renderLyricsWidth,
lyricsHeight: _renderLyricsHeight,
userScrollOffset: _mouseYScrollTransition.Value,
lyricsOpacity: _renderLyricsOpacity,
playingLineTopOffsetFactor: lyricsStyle.PlayingLineTopOffset / 100.0,
windowStatus: status,
strokeColor: lyricsThemeColors.StrokeFontColor,
bgColor: lyricsThemeColors.BgFontColor,
fgColor: lyricsThemeColors.FgFontColor,
getPlaybackState: (lineIndex) =>
{
if (_renderLyricsLines == null) return new LinePlaybackState();
var line = _renderLyricsLines.ElementAtOrDefault(lineIndex);
if (line == null) return new LinePlaybackState();
var nextLine = _renderLyricsLines.ElementAtOrDefault(lineIndex + 1);
return _synchronizer.GetLinePlayingProgress(
_songPositionWithOffset.TotalMilliseconds,
line,
nextLine,
songDuration,
isForceWordByWord
);
}
);
if (_spectrumAnalyzer.IsCapturing)
{
_spectrumRenderer.Draw(
resourceCreator: sender,
ds: args.DrawingSession,
spectrumData: _spectrumAnalyzer?.SmoothSpectrum,
barCount: _spectrumAnalyzer?.BarCount ?? 1,
isEnabled: lyricsBg.IsSpectrumOverlayEnabled,
placement: lyricsBg.SpectrumPlacement,
style: lyricsBg.SpectrumStyle,
canvasWidth: sender.Size.Width,
canvasHeight: sender.Size.Height,
fillColor: lyricsThemeColors.BgFontColor
);
}
#if DEBUG
args.DrawingSession.DrawText(
$"Lyrics render start pos: ({(int)_renderLyricsStartX}, {(int)_renderLyricsStartY})\n" +
$"Lyrics render size: [{(int)_renderLyricsWidth} x {(int)_renderLyricsHeight}]\n" +
$"Lyrics actual height: {LyricsLayoutManager.CalculateActualHeight(_renderLyricsLines)}\n" +
$"Playing line (idx): {_playingLineIndex}\n" +
$"Mouse hovering line (idx): {_mouseHoverLineIndex}\n" +
$"Visible lines range (idx): [{_visibleRange.Start}, {_visibleRange.End}]\n" +
$"Total line count: {LyricsLayoutManager.CalculateMaxRange(_renderLyricsLines).End + 1}\n" +
$"Played: {_songPosition} / {TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0)}\n" +
$"Y offset: {_canvasYScrollTransition.Value}\n" +
$"User scroll offset: {_mouseYScrollTransition.Value}",
new Vector2(0, 0), Colors.Red);
#endif
}
private void Canvas_Update(ICanvasAnimatedControl sender, CanvasAnimatedUpdateEventArgs args)
{
var lyricsBg = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings;
var lyricsStyle = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings;
var lyricsEffect = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings;
var albumArtThemeColors = _mediaSessionsService.AlbumArtThemeColors;
var lyricsData = _mediaSessionsService.CurrentLyricsData;
TimeSpan elapsedTime = args.Timing.ElapsedTime;
_accentColor1Transition.Update(elapsedTime);
_accentColor2Transition.Update(elapsedTime);
_accentColor3Transition.Update(elapsedTime);
_accentColor4Transition.Update(elapsedTime);
_immersiveBgOpacityTransition.Update(elapsedTime);
_immersiveBgColorTransition.Update(elapsedTime);
UpdatePlaybackState(elapsedTime);
TriggerRelayout();
#region UpdatePlayingLineIndex
int newPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPositionWithOffset.TotalMilliseconds, lyricsData);
bool isPlayingLineChanged = newPlayingIndex != _playingLineIndex;
_playingLineIndex = newPlayingIndex;
#endregion
#region UpdateTargetScrollOffset
if (isPlayingLineChanged || _isLayoutChanged)
{
var targetScroll = LyricsLayoutManager.CalculateTargetScrollOffset(_renderLyricsLines, _playingLineIndex);
if (targetScroll.HasValue) _canvasTargetScrollOffset = targetScroll.Value;
_canvasYScrollTransition.SetEasingType(lyricsEffect.LyricsScrollEasingType);
_canvasYScrollTransition.SetDuration(lyricsEffect.LyricsScrollDuration / 1000.0);
_canvasYScrollTransition.StartTransition(_canvasTargetScrollOffset, _isLayoutChanged);
}
_canvasYScrollTransition.Update(elapsedTime);
#endregion
_mouseYScrollTransition.Update(elapsedTime);
_mouseHoverLineIndex = LyricsLayoutManager.FindMouseHoverLineIndex(
_renderLyricsLines,
_isMouseInLyricsArea,
_mousePosition,
_canvasYScrollTransition.Value + _mouseYScrollTransition.Value,
_renderLyricsStartY,
_renderLyricsHeight,
lyricsStyle.PlayingLineTopOffset / 100.0
);
_visibleRange = LyricsLayoutManager.CalculateVisibleRange(
_renderLyricsLines,
_canvasYScrollTransition.Value + _mouseYScrollTransition.Value, // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>λ<EFBFBD><CEBB>
_renderLyricsStartY,
_renderLyricsHeight,
sender.Size.Height,
lyricsStyle.PlayingLineTopOffset / 100.0
);
var maxRange = LyricsLayoutManager.CalculateMaxRange(_renderLyricsLines);
_animator.UpdateLines(
_renderLyricsLines,
_isMouseScrolling ? maxRange.Start : _visibleRange.Start,
_isMouseScrolling ? maxRange.End : _visibleRange.End,
_playingLineIndex,
sender.Size.Height,
_canvasTargetScrollOffset,
lyricsStyle.PlayingLineTopOffset / 100.0,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings,
_canvasYScrollTransition,
albumArtThemeColors.BgFontColor,
albumArtThemeColors.FgFontColor,
elapsedTime,
_isMouseScrolling,
_isLayoutChanged,
isPlayingLineChanged,
_isMouseScrollingChanged
);
_isMouseScrollingChanged = false;
_lyricsRenderer.CalculateLyrics3DMatrix(
lyricsEffect: lyricsEffect,
lyricsX: _renderLyricsStartX,
lyricsY: _renderLyricsStartY,
lyricsWidth: _renderLyricsWidth,
canvasHeight: sender.Size.Height
);
_isLayoutChanged = false;
if (_fluidRenderer.IsEnabled)
{
_fluidRenderer.UpdateColors(
_accentColor1Transition.Value,
_accentColor2Transition.Value,
_accentColor3Transition.Value,
_accentColor4Transition.Value
);
_fluidRenderer.Update(elapsedTime);
}
_snowRenderer.IsEnabled = lyricsBg.IsSnowFlakeOverlayEnabled;
_snowRenderer.Amount = lyricsBg.SnowFlakeOverlayAmount / 100f;
_snowRenderer.Speed = lyricsBg.SnowFlakeOverlaySpeed;
_snowRenderer.Update(elapsedTime.TotalSeconds);
_fogRenderer.IsEnabled = lyricsBg.IsFogOverlayEnabled;
_fogRenderer.Update(elapsedTime.TotalSeconds);
if (lyricsBg.IsSpectrumOverlayEnabled && !_spectrumAnalyzer.IsCapturing)
{
_spectrumAnalyzer.BarCount = lyricsBg.SpectrumCount;
_spectrumAnalyzer.StartCapture();
}
else if (!lyricsBg.IsSpectrumOverlayEnabled && _spectrumAnalyzer.IsCapturing)
{
_spectrumAnalyzer.StopCapture();
}
if (_spectrumAnalyzer.IsCapturing)
{
_spectrumAnalyzer.UpdateSmoothSpectrum();
}
}
private void Canvas_Unloaded(object sender, RoutedEventArgs e)
{
Canvas.RemoveFromVisualTree();
Canvas = null;
_fluidRenderer.Dispose();
_snowRenderer.Dispose();
_fogRenderer.Dispose();
_spectrumRenderer.Dispose();
DisposeAnalyzer();
}
private async void Canvas_CreateResources(CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
args.TrackAsyncAction(_fluidRenderer.LoadResourcesAsync().AsAsyncAction());
_snowRenderer.LoadResources();
_fogRenderer.LoadResources();
_isLayoutChanged = true;
TriggerRelayout();
}
// ====
private void DisposeAnalyzer()
{
if (_spectrumAnalyzer.IsCapturing)
{
_spectrumAnalyzer.StopCapture();
}
_spectrumAnalyzer.Dispose();
}
private void TriggerRelayout()
{
if (_renderLyricsLines == null || !_isLayoutChanged) return;
LyricsLayoutManager.MeasureAndArrange(
resourceCreator: Canvas,
lines: _renderLyricsLines,
status: _liveStatesService.LiveStates.LyricsWindowStatus,
appSettings: _settingsService.AppSettings,
canvasWidth: Canvas.Size.Width,
canvasHeight: Canvas.Size.Height,
lyricsWidth: _renderLyricsWidth,
lyricsHeight: _renderLyricsHeight
);
}
private void UpdatePlaybackState(TimeSpan elapsedTime)
{
if (_mediaSessionsService.CurrentIsPlaying)
{
_songPosition += elapsedTime;
_totalPlayedTime += elapsedTime;
_songPositionWithOffset = _songPosition + TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
CheckAndScrobbleLastFM();
}
}
private void CheckAndScrobbleLastFM()
{
bool isEnabled = _mediaSessionsService.CurrentMediaSourceProviderInfo?.IsLastFMTrackEnabled ?? false;
if (!isEnabled || _isLastFMTracked) return;
var songInfo = _mediaSessionsService.CurrentSongInfo;
if (songInfo == null || songInfo.Duration <= 0) return;
if (_totalPlayedTime.TotalSeconds >= songInfo.Duration * 0.5)
{
_isLastFMTracked = true;
_lastFMService.TrackAsync(songInfo);
}
}
private void ResetPlaybackState()
{
_songPosition = TimeSpan.Zero;
_totalPlayedTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
public void Receive(PropertyChangedMessage<AlbumArtThemeColors> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtThemeColors))
{
var lyricsThemeColors = message.NewValue;
_immersiveBgColorTransition.StartTransition(lyricsThemeColors.EnvColor);
_accentColor1Transition.StartTransition(lyricsThemeColors.AccentColor1);
_accentColor2Transition.StartTransition(lyricsThemeColors.AccentColor2);
_accentColor3Transition.StartTransition(lyricsThemeColors.AccentColor3);
_accentColor4Transition.StartTransition(lyricsThemeColors.AccentColor4);
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<TimeSpan> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
{
var realPosition = message.NewValue;
var diff = Math.Abs(_songPosition.TotalMilliseconds - realPosition.TotalMilliseconds);
var timelineSyncThreshold = _mediaSessionsService.CurrentMediaSourceProviderInfo?.TimelineSyncThreshold ?? 0;
// ƫ<><C6AB> or seek
if (diff >= timelineSyncThreshold)
{
_songPosition = realPosition;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˿<EFBFBD>ͷ<EFBFBD><CDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD> LastFM ͳ<><CDB3>״̬
if (_songPosition.TotalSeconds <= 1)
{
_totalPlayedTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
}
// <20>϶<EFBFBD><CFB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȴ<EFBFBD><C8B4><EFBFBD><EFBFBD><EFBFBD>
if (diff >= timelineSyncThreshold + 5000)
{
_isLayoutChanged = true;
}
}
}
}
public void Receive(PropertyChangedMessage<LyricsData?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentLyricsData))
{
_renderLyricsLines = null;
if (_mediaSessionsService.CurrentLyricsData is LyricsData lyricsData)
{
_renderLyricsLines = lyricsData.LyricsLines.Select(x => new RenderLyricsLine()
{
LyricsSyllables = x.LyricsSyllables,
StartMs = x.StartMs,
EndMs = x.EndMs,
PhoneticText = x.PhoneticText,
OriginalText = x.OriginalText,
TranslatedText = x.TranslatedText
}).ToList();
}
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<LyricsWindowStatus> message)
{
if (message.Sender is LiveStates)
{
if (message.PropertyName == nameof(LiveStates.LyricsWindowStatus))
{
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.PhoneticLyricsFontSize))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.OriginalLyricsFontSize))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.TranslatedLyricsFontSize))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFontStrokeWidth))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.PlayingLineTopOffset))
{
_isLayoutChanged = true;
}
}
else if (message.Sender is LyricsEffectSettings)
{
if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollDuration))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollTopDuration))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollBottomDuration))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollTopDelay))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollBottomDelay))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.FanLyricsAngle))
{
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<double> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsLineSpacingFactor))
{
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is LyricsEffectSettings)
{
if (message.PropertyName == nameof(LyricsEffectSettings.IsFanLyricsEnabled))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.IsLyricsBlurEffectEnabled))
{
_isLayoutChanged = true;
}
}
else if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.IsDynamicLyricsFontSize))
{
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<TextAlignmentType> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsAlignmentType))
{
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<SongInfo?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
{
ResetPlaybackState();
}
}
}
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFontWeight))
{
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<string> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCJKFontFamily))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsWesternFontFamily))
{
_isLayoutChanged = true;
}
}
}
}
}

View File

@@ -18,147 +18,30 @@
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<!-- Effect -->
<TextBlock
x:Uid="SettingsPageLyricsEffect"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="Effect" />
<dev:SettingsCard x:Uid="SettingsPageLyricsVerticalEdgeOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB42;}">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsVerticalEdgeOpacitySlider"
Default="0"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsEffectSettings.LyricsVerticalEdgeOpacity, Mode=TwoWay}" />
<!-- 模糊效果 -->
<dev:SettingsCard x:Uid="SettingsPageLyricsBlurEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsBlurEffectEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsBlurAmountExtendedSlider"
Default="5"
Maximum="10"
Minimum="0"
Value="{x:Bind LyricsEffectSettings.LyricsBlurAmount, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsLineFade" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xED3A;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsLineFadeEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- 高亮 -->
<dev:SettingsExpander x:Uid="SettingsPageLyricsHighlight" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7E6;}">
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPagePhoneticText">
<local:ExtendedSlider
Default="60"
Frequency="5"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsEffectSettings.PhoneticLyricsHighlightAmount, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsHighlightScope">
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.OriginalLyricsHighlightScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentChar" />
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeLineStartToCurrentChar" />
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentLine" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageOriginalText">
<local:ExtendedSlider
Default="60"
Frequency="5"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsEffectSettings.OriginalLyricsHighlightAmount, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageTranslatedText">
<local:ExtendedSlider
Default="60"
Frequency="5"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsEffectSettings.TranslatedLyricsHighlightAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 阴影 -->
<dev:SettingsExpander
x:Uid="SettingsPageLyricsShadow"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF5EF;}"
IsExpanded="{x:Bind LyricsEffectSettings.IsLyricsShadowEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsShadowEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageScope" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsShadowEnabled, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsShadowScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentChar" />
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeLineStartToCurrentChar" />
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentLine" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsShadowEnabled, Mode=OneWay}">
<local:ExtendedSlider
Default="8"
Maximum="20"
Minimum="1"
Value="{x:Bind LyricsEffectSettings.LyricsShadowAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 辉光效果 -->
<dev:SettingsExpander
x:Uid="SettingsPageLyricsGlowEffect"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE9A9;}"
IsExpanded="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<dev:SettingsCard x:Uid="SettingsPageLyricsGlowEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE9A9;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageScope" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsGlowEffectScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentChar" />
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeLineStartToCurrentChar" />
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentLine" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<local:ExtendedSlider
Default="8"
Maximum="20"
Minimum="1"
Value="{x:Bind LyricsEffectSettings.LyricsGlowEffectAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
</dev:SettingsCard>
<!-- 缩放效果 -->
<dev:SettingsCard x:Uid="SettingsPageLyricsScaleEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8A3;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsScaleEffectEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- 浮动动画 -->
<dev:SettingsExpander
x:Uid="SettingsPageLyricsFloatAnimation"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8C5;}"
IsExpanded="True">
<dev:SettingsCard x:Uid="SettingsPageLyricsFloatAnimation" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8C5;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsFloatAnimationEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<local:ExtendedSlider
Default="1"
Maximum="4"
Minimum="1"
Value="{x:Bind LyricsEffectSettings.LyricsFloatAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
</dev:SettingsCard>
<!-- 扇形歌词 -->
<dev:SettingsExpander
@@ -224,7 +107,7 @@
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 滚动动画 -->
<!-- 歌词动画 -->
<dev:SettingsExpander x:Uid="SettingsPageScrollEasing" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xECE7;}">
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsScrollEasingType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEasingTypeLinear" />
@@ -246,7 +129,7 @@
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollTopDuration, Mode=TwoWay}" />
</dev:SettingsCard>
@@ -255,7 +138,7 @@
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollDuration, Mode=TwoWay}" />
</dev:SettingsCard>
@@ -264,7 +147,7 @@
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollBottomDuration, Mode=TwoWay}" />
</dev:SettingsCard>

View File

@@ -27,6 +27,7 @@
<Grid Grid.Column="0">
<ScrollViewer>
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="LyricsSearchControlSongInfoMapping" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Grid
@@ -34,22 +35,32 @@
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="4">
<StackPanel Spacing="6">
<TextBlock x:Uid="LyricsSearchControlTitle" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.MappedSongSearchQuery.OriginalTitle, Mode=OneWay}"
TextWrapping="Wrap" />
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedTitle, Mode=TwoWay}" TextWrapping="Wrap" />
<Button
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedTitleCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
<local:PropertyRow x:Uid="LyricsSearchControlTitle" Value="{x:Bind ViewModel.MappedSongSearchQuery.OriginalTitle, Mode=OneWay}" />
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LyricsSearchControlMappedAs"
Grid.Column="0"
VerticalAlignment="Center" />
<TextBox
Grid.Column="1"
Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedTitle, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button
Grid.Column="2"
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedTitleCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
</Grid>
</StackPanel>
</Grid>
@@ -60,33 +71,52 @@
CornerRadius="4">
<StackPanel Spacing="6">
<TextBlock x:Uid="LyricsSearchControlArtist" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.MappedSongSearchQuery.OriginalArtist, Mode=OneWay}"
TextWrapping="Wrap" />
<local:PropertyRow x:Uid="LyricsSearchControlArtist" Value="{x:Bind ViewModel.MappedSongSearchQuery.OriginalArtist, Mode=OneWay}" />
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedArtist, Mode=TwoWay}" TextWrapping="Wrap" />
<RichTextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" TextWrapping="Wrap">
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LyricsSearchControlMappedAs"
Grid.Column="0"
VerticalAlignment="Center" />
<TextBox
Grid.Column="1"
Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedArtist, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button
Grid.Column="2"
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedArtistCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
</Grid>
<RichTextBlock
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap">
<Paragraph>
<Run Text="*" />
<Run x:Uid="ArtistsSplitHint" />
<Run Text=";" />
<Run Text="," />
<Run Text="/" />
<Run Text="" />
<Run Text="、" />
<Run Text="" />
</Paragraph>
</RichTextBlock>
<Button
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedArtistCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
<RichTextBlock
FontSize="12"
FontWeight="Bold"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Loaded="ArtistsSplitHintRichTextBlock_Loaded"
TextWrapping="Wrap">
<Paragraph>
<Run Text="; , / " />
</Paragraph>
</RichTextBlock>
</StackPanel>
</Grid>
@@ -97,22 +127,32 @@
CornerRadius="4">
<StackPanel Spacing="6">
<TextBlock x:Uid="LyricsSearchControlAlbum" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind ViewModel.MappedSongSearchQuery.OriginalAlbum, Mode=OneWay}"
TextWrapping="Wrap" />
<local:PropertyRow x:Uid="LyricsSearchControlAlbum" Value="{x:Bind ViewModel.MappedSongSearchQuery.OriginalAlbum, Mode=OneWay}" />
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedAlbum, Mode=TwoWay}" TextWrapping="Wrap" />
<Button
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedAlbumCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LyricsSearchControlMappedAs"
Grid.Column="0"
VerticalAlignment="Center" />
<TextBox
Grid.Column="1"
Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedAlbum, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button
Grid.Column="2"
VerticalAlignment="Center"
Command="{x:Bind ViewModel.ResetMappedAlbumCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
</Grid>
</StackPanel>
</Grid>
@@ -138,7 +178,7 @@
<ListViewItem IsEnabled="{x:Bind IsFound}">
<StackPanel Padding="0,6" Opacity="{x:Bind IsFound, Converter={StaticResource BoolToPartialOpacityConverter}}">
<local:PropertyRow
Margin="-12,0,0,0"
Margin="-8,0,0,0"
Link="{x:Bind Reference, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind Reference, TargetNullValue=N/A, Mode=OneWay}"
Value="{x:Bind Provider, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
@@ -155,6 +195,10 @@
x:Uid="LyricsPageMatchPercentage"
Unit="%"
Value="{x:Bind MatchPercentage, Mode=OneWay}" />
<local:PropertyRow
x:Uid="LyricsPageCachePath"
Link="{x:Bind SelfPath, TargetNullValue=N/A, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind SelfPath, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel>
<!-- NOT FOUND -->
<TextBlock
@@ -279,31 +323,29 @@
</StackPanel>
</Grid>
</Grid>
<Grid Grid.Row="1">
<RelativePanel>
<TextBlock
x:Uid="LyricsSearchControlHelp"
Margin="0,0,24,0"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
RelativePanel.AlignVerticalCenterWithPanel="True"
RelativePanel.LeftOf="Reset"
TextWrapping="Wrap" />
<Button
x:Name="Reset"
x:Uid="LyricsSearchControlReset"
Margin="0,0,6,0"
Command="{x:Bind ViewModel.ResetCommand}"
RelativePanel.AlignVerticalCenterWithPanel="True"
RelativePanel.LeftOf="SaveChanges" />
<Button
x:Name="SaveChanges"
x:Uid="LyricsSearchControlSaveChanges"
Command="{x:Bind ViewModel.SaveCommand}"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Style="{StaticResource AccentButtonStyle}" />
</RelativePanel>
<Grid Grid.Row="1" ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LyricsSearchControlHelp"
Grid.Column="1"
VerticalAlignment="Center"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<Button
x:Uid="LyricsSearchControlReset"
Grid.Column="2"
Command="{x:Bind ViewModel.ResetCommand}" />
<Button
x:Uid="LyricsSearchControlSaveChanges"
Grid.Column="3"
Command="{x:Bind ViewModel.SaveCommand}"
Style="{StaticResource AccentButtonStyle}" />
</Grid>
</Grid>
</UserControl>

View File

@@ -2,6 +2,8 @@ using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -22,5 +24,26 @@ namespace BetterLyrics.WinUI3.Controls
{
ViewModel.SelectedLyricsLine = e.OriginalSource as LyricsLine;
}
private void ArtistsSplitHintRichTextBlock_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
if (sender is RichTextBlock richTextBlock)
{
TextHighlighter highlighter = new()
{
Background = App.Current.Resources["AccentTextFillColorPrimaryBrush"] as SolidColorBrush,
Ranges =
{
new() { StartIndex = 0, Length = 1 },
new() { StartIndex = 5, Length = 1 },
new() { StartIndex = 10, Length = 1 },
new() { StartIndex = 15, Length = 1 },
new() { StartIndex = 20, Length = 1 },
new() { StartIndex = 25, Length = 1 },
}
};
richTextBlock.TextHighlighters.Add(highlighter);
}
}
}
}

View File

@@ -23,12 +23,32 @@
<dev:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind LyricsStyleSettings.LyricsAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsLeft" />
<ComboBoxItem x:Uid="SettingsPageLyricsCenter" />
<ComboBoxItem x:Uid="SettingsPageLyricsRight" />
<ComboBoxItem x:Uid="SettingsPageLeft" />
<ComboBoxItem x:Uid="SettingsPageCenter" />
<ComboBoxItem x:Uid="SettingsPageRight" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsCenterTopOffset" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE78A;}">
<local:ExtendedSlider
Default="50"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsStyleSettings.PlayingLineTopOffset, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF579;}">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsLineSpacingFactorSlider"
Default="0.5"
Frequency="0.1"
Maximum="2"
Minimum="0"
Unit="x"
Value="{x:Bind LyricsStyleSettings.LyricsLineSpacingFactor, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsExpander x:Uid="SettingsPageLyricsFontFamily" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D2;}">
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageCJK">
@@ -56,15 +76,6 @@
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsBgFontOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB42;}">
<local:ExtendedSlider
Default="30"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsStyleSettings.LyricsBgFontOpacity, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsFontStrokeWidth" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC12;}">
<local:ExtendedSlider
Default="0"
@@ -216,24 +227,6 @@
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF579;}">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsLineSpacingFactorSlider"
Default="0.5"
Frequency="0.1"
Maximum="2"
Minimum="0"
Unit="x"
Value="{x:Bind LyricsStyleSettings.LyricsLineSpacingFactor, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsTranslationSeparator" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF464;}">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBox AcceptsReturn="True" Text="{x:Bind LyricsStyleSettings.LyricsTranslationSeparator, Mode=TwoWay}" />
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FontSize=12, Glyph=&#xE8FB;}" Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</dev:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>

View File

@@ -13,21 +13,32 @@
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid>
<Grid x:Name="DisplayGrid" SizeChanged="DisplayGrid_SizeChanged">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock
x:Uid="SettingsPageRecordedWindowStatus"
RelativePanel.AlignLeftWithPanel="True"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="SettingsPageRecordedWindowStatus"
Grid.Column="0"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Button
Grid.Column="2"
Margin="0,30,0,0"
Command="{x:Bind ViewModel.OpenConfigPanelCommand}"
Style="{StaticResource AccentButtonStyle}">
<TextBlock x:Uid="LyricsWindowSettingsControlCurrentLyricsWindowConfig" />
</Button>
</Grid>
<StackPanel Orientation="Horizontal" Spacing="3">
@@ -60,7 +71,7 @@
</Button>
<!-- Sharing hub -->
<HyperlinkButton x:Uid="SettingsPageShareHub" NavigateUri="{x:Bind constants:Link.ShareHubUrl}" />
<HyperlinkButton x:Uid="SettingsPageShareHub" NavigateUri="{x:Bind constants:Link.ShareHub}" />
</StackPanel>
@@ -114,16 +125,44 @@
</Grid>
</ScrollViewer>
</Grid>
<Grid Grid.Column="1">
<Grid
x:Name="ConfigGrid"
Background="{ThemeResource AcrylicBackgroundFillColorDefaultBrush}"
Opacity="{x:Bind ViewModel.IsConfigPanelOpened, Mode=OneWay, Converter={StaticResource BoolToOpacityConverter}}"
Translation="{x:Bind ViewModel.ConfigPanelTranslation, Mode=OneWay}">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<Grid.TranslationTransition>
<Vector3Transition />
</Grid.TranslationTransition>
<Grid Padding="36,0" Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="LyricsWindowSettingsControlCurrentLyricsWindowConfig" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LyricsWindowSettingsControlCurrentLyricsWindowConfig"
Grid.Column="0"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Button
Grid.Column="2"
Margin="0,30,0,0"
Command="{x:Bind ViewModel.CloseConfigPanelCommand}"
Content="{ui:FontIcon FontSize=16,
FontFamily={StaticResource IconFontFamily},
Glyph=&#xE711;}"
Style="{StaticResource AccentButtonStyle}" />
</Grid>
<Pivot SelectionChanged="Pivot_SelectionChanged">
<PivotItem Tag="General">
<PivotItem Tag="Window">
<PivotItem.Header>
<TextBlock
x:Uid="AppSettingsControlGeneral"
@@ -132,6 +171,15 @@
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="Layout">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageLayout"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="AlbumArtStyle">
<PivotItem.Header>
<TextBlock
@@ -141,6 +189,15 @@
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="AlbumArtEffect">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageAlbumEffect"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="LyricsStyle">
<PivotItem.Header>
<TextBlock
@@ -168,15 +225,6 @@
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="Advanced">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageAdvanced"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
</Pivot>
</StackPanel>
@@ -189,29 +237,20 @@
</TransitionCollection>
</controls:SwitchPresenter.ContentTransitions>
<!-- General -->
<controls:Case Value="General">
<!-- Window -->
<controls:Case Value="Window">
<uc:WindowSettingsControl LyricsWindowStatus="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=OneWay}" />
</controls:Case>
<!-- Layout -->
<controls:Case Value="Layout">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<TextBlock x:Uid="SettingsPageLayout" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageConfigName" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8AC;}">
<StackPanel
Margin="0,6,0,0"
Orientation="Horizontal"
Spacing="6">
<TextBox Text="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.Name, Mode=TwoWay}" TextWrapping="Wrap" />
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FontSize=12, Glyph=&#xE8FB;}" Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</dev:SettingsCard>
<dev:SettingsExpander
x:Uid="SettingsPageDisplayTypeSwitcher"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF246;}"
IsExpanded="True">
<dev:SettingsExpander x:Uid="SettingsPageDisplayTypeSwitcher" IsExpanded="True">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
<ComboBoxItem x:Uid="MainPageLyriscOnly" />
@@ -227,115 +266,6 @@
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageWorkArea"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE78B;}"
IsExpanded="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageWorkAreaHeight" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<uc:ExtendedSlider
Default="64"
Maximum="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorBounds.Height, Mode=OneWay}"
Minimum="64"
Unit="px"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.DockHeight, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDockPlacement" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.DockPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageDockPlacementTop" />
<ComboBoxItem x:Uid="SettingsPageDockPlacementBottom" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDockMonitor" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ComboBox ItemsSource="{x:Bind ViewModel.MonitorDeviceNames, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorDeviceName, Mode=TwoWay}" />
<Button
Command="{x:Bind ViewModel.RefreshMonitorDeviceNamesCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE72C;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageAdaptEnvColor"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE88F;}"
IsExpanded="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard
x:Uid="SettingsPageEnvColorSample"
Header="Environment color sample mode"
IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.EnvironmentSampleMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleBelow" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleAbove" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleInner" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleEdge" />
</ComboBox>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageWindowBounds"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF16B;}"
IsExpanded="True">
<dev:SettingsExpander.Items>
<dev:SettingsCard Header="X">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowX, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard Header="Y">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowY, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageWidth">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowWidth, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageHeight">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.WindowHeight, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageAOT"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE718;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAlwaysOnTop, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageForceAlwaysOnTop" IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAlwaysOnTop, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAlwaysOnTopPolling, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageHideWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xED1A;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow, Mode=TwoWay}" />
</dev:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
@@ -343,7 +273,12 @@
<!-- Album art area style -->
<controls:Case Value="AlbumArtStyle">
<uc:AlbumArtLayoutSettingsControl AlbumArtLayoutSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings, Mode=OneWay}" />
<uc:AlbumArtAreaStyleSettingsControl AlbumArtLayoutSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings, Mode=OneWay}" />
</controls:Case>
<!-- Album art area effect -->
<controls:Case Value="AlbumArtEffect">
<uc:AlbumArtAreaEffectSettingsControl AlbumArtAreaEffectSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtAreaEffectSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics style -->
@@ -361,39 +296,9 @@
<uc:LyricsBackgroundSettingsControl LyricsBackgroundSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings, Mode=OneWay}" />
</controls:Case>
<!-- Advanced -->
<controls:Case Value="Advanced">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<dev:SettingsCard x:Uid="SettingsPageShowInSwitchers" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C4;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsShownInSwitchers, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageClickThrough" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C9;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsClickThrough, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageBorderless" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8B2;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsBorderless, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDragArea" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB41;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.TitleBarArea, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaNone" />
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaTop" />
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaWhole" />
</ComboBox>
</dev:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</controls:Case>
</controls:SwitchPresenter>
</Grid>
</Grid>
</UserControl>

View File

@@ -73,7 +73,7 @@ namespace BetterLyrics.WinUI3.Controls
StorageFile? file;
if (this.Parent is FlyoutPresenter)
{
file = await PickerHelper.PickSaveFileAsync<LyricsWindow>(fileTypeChoices);
file = await PickerHelper.PickSaveFileAsync<NowPlayingWindow>(fileTypeChoices);
}
else
{
@@ -132,7 +132,7 @@ namespace BetterLyrics.WinUI3.Controls
StorageFile? file;
if (this.Parent is FlyoutPresenter)
{
file = await PickerHelper.PickSingleFileAsync<LyricsWindow>(fileTypeFilter);
file = await PickerHelper.PickSingleFileAsync<NowPlayingWindow>(fileTypeFilter);
}
else
{
@@ -149,5 +149,10 @@ namespace BetterLyrics.WinUI3.Controls
}
}
}
private void DisplayGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
ViewModel.DisplayPanelHeight = e.NewSize.Height;
}
}
}

View File

@@ -188,6 +188,14 @@
<ComboBoxItem x:Uid="SettingsPageLyricsSearchBestMatch" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageMatchingThreshold">
<local:ExtendedSlider
Default="0"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind ViewModel.SelectedMediaSourceProvider.MatchingThreshold, Mode=TwoWay}" />
</dev:SettingsCard>
<ListView
x:Name="LyricsSearchProvidersListView"
@@ -209,12 +217,48 @@
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:LyricsSearchProviderInfo">
<dev:SettingsCard Header="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}, Mode=OneWay}">
<dev:SettingsCard.HeaderIcon>
<FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x283F;" />
</dev:SettingsCard.HeaderIcon>
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<Grid>
<dev:SettingsExpander Header="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}, Mode=OneWay}" IsExpanded="{Binding IsMatchingThresholdOverwritten, Mode=OneWay}">
<dev:SettingsExpander.HeaderIcon>
<FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x283F;" />
</dev:SettingsExpander.HeaderIcon>
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageOverwriteMatchingThreshold">
<ToggleSwitch IsOn="{Binding IsMatchingThresholdOverwritten, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageMatchingThreshold" IsEnabled="{Binding IsMatchingThresholdOverwritten, Mode=OneWay}">
<local:ExtendedSlider
Default="0"
Maximum="100"
Minimum="0"
Unit="%"
Value="{Binding MatchingThreshold, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<Grid
Width="48"
HorizontalAlignment="Left"
Background="{ThemeResource ControlStrokeColorDefaultBrush}"
CornerRadius="4,0,0,4"
Opacity="0">
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<ToolTipService.ToolTip>
<ToolTip x:Uid="SettingsPageHoldDragSort" />
</ToolTipService.ToolTip>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
@@ -264,9 +308,7 @@
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left">
<StackPanel Spacing="6">
<!-- Playback source -->
<local:PropertyRow x:Uid="SettingsPagePlaybackSource" Value="{x:Bind ViewModel.MediaSessionsService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" />
<!-- Playback source ID -->
<local:PropertyRow x:Uid="SettingsPagePlaybackSourceID" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel>
</Expander>
@@ -277,13 +319,9 @@
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left">
<StackPanel Spacing="6">
<!-- Song title -->
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Title, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Song artists -->
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Song album -->
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Album, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Song duration -->
<local:PropertyRow
x:Uid="LyricsSearchControlDurauion"
Unit="s"
@@ -297,30 +335,27 @@
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left">
<StackPanel Spacing="6">
<!-- Searched title -->
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Title, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Searched artists -->
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Searched album -->
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Album, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Searched duration -->
<local:PropertyRow
x:Uid="LyricsSearchControlDurauion"
Unit="s"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Duration, TargetNullValue=N/A, Mode=OneWay}" />
<!-- Lyrics source -->
<local:PropertyRow
x:Uid="LyricsPageLyricsProviderPrefix"
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, TargetNullValue=N/A, Mode=OneWay}"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
<!-- Translation source -->
<local:PropertyRow x:Uid="LyricsPageTranslationProviderPrefix" Value="{x:Bind ViewModel.MediaSessionsService.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
<!-- Match percentage -->
<local:PropertyRow
x:Uid="LyricsPageMatchPercentage"
Unit="%"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.MatchPercentage, Mode=OneWay}" />
<local:PropertyRow
x:Uid="LyricsPageCachePath"
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay}" />
</StackPanel>
</Expander>
@@ -464,7 +499,7 @@
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE897;}"
NavigateUri="{x:Bind constants:Link.AppleMusicCfgUrl}" />
NavigateUri="{x:Bind constants:Link.AppleMusicCfg}" />
<Button
Grid.Column="2"
Command="{x:Bind ViewModel.SaveAppleMusicMediaUserTokenCommand}"

View File

@@ -9,7 +9,7 @@
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid HorizontalAlignment="Left" ColumnSpacing="12">
<Grid HorizontalAlignment="Left" ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
@@ -43,8 +43,9 @@
Padding="0"
HorizontalContentAlignment="Left"
Click="OnLinkClicked"
Content="{x:Bind Value, Mode=OneWay}"
Visibility="{x:Bind LinkVisibility, Mode=OneWay}" />
Visibility="{x:Bind LinkVisibility, Mode=OneWay}">
<TextBlock Text="{x:Bind Value, Mode=OneWay}" TextWrapping="Wrap" />
</HyperlinkButton>
</Grid>
<Button
@@ -56,7 +57,7 @@
Click="OnCopyClicked"
Opacity="0">
<ToolTipService.ToolTip>
<TextBlock x:Uid="Copy" />
<ToolTip x:Uid="Copy" />
</ToolTipService.ToolTip>
<Button.OpacityTransition>
<ScalarTransition />

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.ShadowImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:const="using:BetterLyrics.WinUI3.Constants"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<UserControl.OpacityTransition>
<ScalarTransition />
</UserControl.OpacityTransition>
<Grid Margin="-32" Padding="32">
<Grid
x:Name="ShadowCastGrid"
HorizontalAlignment="Center"
VerticalAlignment="Center"
SizeChanged="ShadowCastGrid_SizeChanged">
<Image Source="{x:Bind Source, Mode=OneWay}" Stretch="Uniform" />
</Grid>
<Border
x:Name="ShadowRect"
Loaded="ShadowRect_Loaded"
SizeChanged="ShadowRect_SizeChanged"
Translation="0,0,0">
<Border.Shadow>
<ThemeShadow x:Name="Shadow" />
</Border.Shadow>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,103 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class ShadowImage : UserControl
{
public int CornerRadiusAmount
{
get { return (int)GetValue(CornerRadiusAmountProperty); }
set { SetValue(CornerRadiusAmountProperty, value); }
}
public static readonly DependencyProperty CornerRadiusAmountProperty =
DependencyProperty.Register(nameof(CornerRadiusAmount), typeof(int), typeof(ShadowImage), new PropertyMetadata(0, OnDependencyPropertyChanged));
public int ShadowAmount
{
get { return (int)GetValue(ShadowAmountProperty); }
set { SetValue(ShadowAmountProperty, value); }
}
public static readonly DependencyProperty ShadowAmountProperty =
DependencyProperty.Register(nameof(ShadowAmount), typeof(int), typeof(ShadowImage), new PropertyMetadata(0, OnDependencyPropertyChanged));
public ImageSource? Source
{
get { return (ImageSource?)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(nameof(Source), typeof(ImageSource), typeof(ShadowImage), new PropertyMetadata(null));
public ShadowImage()
{
InitializeComponent();
}
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ShadowImage shadowImage)
{
if (e.Property == CornerRadiusAmountProperty)
{
shadowImage.UpdateShadowCastGridCornerRadius();
shadowImage.UpdateShadowRectCornerRadius();
}
else if (e.Property == ShadowAmountProperty)
{
shadowImage.UpdateShadowRectShadow();
}
}
}
private void UpdateShadowRectShadow()
{
ShadowRect.Translation = new(0, 0, ShadowAmount);
}
private void UpdateShadowCastGridCornerRadius()
{
var minSize = Math.Min(ShadowCastGrid.ActualHeight, ShadowCastGrid.ActualWidth);
ShadowCastGrid.CornerRadius = new(CornerRadiusAmount / 100.0 * minSize);
}
private void UpdateShadowRectCornerRadius()
{
var minSize = Math.Min(ShadowRect.ActualHeight, ShadowRect.ActualWidth);
ShadowRect.CornerRadius = new(CornerRadiusAmount / 100.0 * minSize);
}
private void ShadowCastGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateShadowCastGridCornerRadius();
}
private void ShadowRect_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateShadowRectCornerRadius();
}
private void ShadowRect_Loaded(object sender, RoutedEventArgs e)
{
Shadow.Receivers.Add(ShadowCastGrid);
}
}
}

View File

@@ -23,7 +23,11 @@
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE894;}"
Style="{StaticResource GhostButtonStyle}" />
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="SettingsPageClear" />
</ToolTipService.ToolTip>
</Button>
<Button
Margin="3,0,0,0"
HorizontalAlignment="Right"
@@ -31,7 +35,11 @@
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE721;}"
Style="{StaticResource GhostButtonStyle}" />
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="SettingsPageCheckShortcut" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -48,7 +48,7 @@
x:Uid="SystemTrayMusicGallery"
Command="{x:Bind ViewModel.OpenMusicGalleryCommand}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEA69;}" />
Glyph=&#xE8F1;}" />
<MenuFlyoutItem
x:Uid="SystemTraySettings"
Command="{x:Bind ViewModel.OpenSettingsCommand}"

View File

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.WindowSettingsControl"
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:dev="using:DevWinUI"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="AppSettingsControlGeneral" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageConfigName" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8AC;}">
<StackPanel
Margin="0,6,0,0"
Orientation="Horizontal"
Spacing="6">
<TextBox Text="{x:Bind LyricsWindowStatus.Name, Mode=TwoWay}" TextWrapping="Wrap" />
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FontSize=12, Glyph=&#xE8FB;}" Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</dev:SettingsCard>
<dev:SettingsExpander
x:Uid="SettingsPageWorkArea"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE78B;}"
IsExpanded="{x:Bind LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsWorkArea, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageWorkAreaHeight" IsEnabled="{x:Bind LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<local:ExtendedSlider
Default="64"
Maximum="{x:Bind LyricsWindowStatus.MonitorBounds.Height, Mode=OneWay}"
Minimum="64"
Unit="px"
Value="{x:Bind LyricsWindowStatus.DockHeight, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDockPlacement" IsEnabled="{x:Bind LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.DockPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageDockPlacementTop" />
<ComboBoxItem x:Uid="SettingsPageDockPlacementBottom" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDockMonitor" IsEnabled="{x:Bind LyricsWindowStatus.IsWorkArea, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ComboBox ItemsSource="{x:Bind MonitorDeviceNames, Mode=OneWay}" SelectedItem="{x:Bind LyricsWindowStatus.MonitorDeviceName, Mode=TwoWay}" />
<Button
Click="RefreshMonitorButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE72C;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageAdaptEnvColor"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE88F;}"
IsExpanded="{x:Bind LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsAdaptToEnvironment, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard
x:Uid="SettingsPageEnvColorSample"
Header="Environment color sample mode"
IsEnabled="{x:Bind LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.EnvironmentSampleMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleBelow" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleAbove" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleInner" />
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleEdge" />
</ComboBox>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageWindowBounds"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF16B;}"
IsExpanded="True">
<dev:SettingsExpander.Items>
<dev:SettingsCard Header="X">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind LyricsWindowStatus.WindowX, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard Header="Y">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind LyricsWindowStatus.WindowY, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageWidth">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind LyricsWindowStatus.WindowWidth, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageHeight">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind LyricsWindowStatus.WindowHeight, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageAOT"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE718;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsAlwaysOnTop, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageForceAlwaysOnTop" IsEnabled="{x:Bind LyricsWindowStatus.IsAlwaysOnTop, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsAlwaysOnTopPolling, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageHideWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xED1A;}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.AutoShowOrHideWindow, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageShowInSwitchers" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C4;}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsShownInSwitchers, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageClickThrough" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C9;}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsClickThrough, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageBorderless" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8B2;}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsBorderless, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDragArea" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB41;}">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.TitleBarArea, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaNone" />
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaTop" />
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaWhole" />
</ComboBox>
</dev:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,56 @@
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls;
public sealed partial class WindowSettingsControl : UserControl
{
public static readonly DependencyProperty LyricsWindowStatusProperty =
DependencyProperty.Register(nameof(LyricsWindowStatus), typeof(AlbumArtAreaEffectSettings), typeof(WindowSettingsControl), new PropertyMetadata(default));
public LyricsWindowStatus LyricsWindowStatus
{
get => (LyricsWindowStatus)GetValue(LyricsWindowStatusProperty);
set => SetValue(LyricsWindowStatusProperty, value);
}
public ObservableCollection<string> MonitorDeviceNames { get; set; } = [];
public WindowSettingsControl()
{
InitializeComponent();
MonitorDeviceNames = [.. MonitorHook.GetAllMonitorDeviceNames()];
}
private void RefreshMonitorDeviceNames()
{
MonitorDeviceNames = [.. MonitorHook.GetAllMonitorDeviceNames()];
LyricsWindowStatus.MonitorDeviceName = MonitorDeviceNames.FirstOrDefault() ?? "";
}
private void RefreshMonitorButton_Click(object sender, RoutedEventArgs e)
{
RefreshMonitorDeviceNames();
}
}

View File

@@ -0,0 +1,24 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml.Data;
using System;
namespace BetterLyrics.WinUI3.Converter
{
public partial class LyricsFontWeightToFontWeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is LyricsFontWeight weight)
{
return weight.ToFontWeight();
}
return FontWeights.Normal;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,25 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using System;
namespace BetterLyrics.WinUI3.Converter
{
public partial class LyricsLayoutOrientationNegationToOrientationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is LyricsLayoutOrientation orientation)
{
return orientation.ToOrientationInverse();
}
return Orientation.Horizontal;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,25 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using System;
namespace BetterLyrics.WinUI3.Converter
{
public partial class LyricsLayoutOrientationToOrientationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is LyricsLayoutOrientation orientation)
{
return orientation.ToOrientation();
}
return Orientation.Horizontal;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,32 @@
using ATL;
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.Linq;
namespace BetterLyrics.WinUI3.Converter
{
public partial class PictureInfosToImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
BitmapImage bitmapImage = new();
if (value is IList<PictureInfo> list && list.FirstOrDefault()?.PictureData is byte[] pictureData)
{
bitmapImage.SetSource(ImageHelper.ToIRandomAccessStream(pictureData));
}
else
{
bitmapImage.UriSource = new Uri(PathHelper.AlbumArtPlaceholderPath);
}
return bitmapImage;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,24 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
namespace BetterLyrics.WinUI3.Converter
{
public partial class TextAlignmentTypeToHorizontalAlignmentConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is TextAlignmentType type)
{
return type.ToHorizontalAlignment();
}
return HorizontalAlignment.Left;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Enums
{
public enum ImageSwitchType
{
Crossfade,
Slide
}
}

View File

@@ -0,0 +1,8 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum SpectrumStyle
{
Curve,
Bar
}
}

View File

@@ -1,6 +1,7 @@
// 2025/6/23 by Zhe Fang
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI.Xaml;
using System;
namespace BetterLyrics.WinUI3.Enums
@@ -14,6 +15,17 @@ namespace BetterLyrics.WinUI3.Enums
public static class LyricsAlignmentTypeExtensions
{
public static HorizontalAlignment ToHorizontalAlignment(this TextAlignmentType alignmentType)
{
return alignmentType switch
{
TextAlignmentType.Left => HorizontalAlignment.Left,
TextAlignmentType.Center => HorizontalAlignment.Center,
TextAlignmentType.Right => HorizontalAlignment.Right,
_ => throw new ArgumentOutOfRangeException(nameof(alignmentType), alignmentType, null),
};
}
public static CanvasHorizontalAlignment ToCanvasHorizontalAlignment(this TextAlignmentType alignmentType)
{
return alignmentType switch

View File

@@ -0,0 +1,26 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml.Controls;
using System;
namespace BetterLyrics.WinUI3.Extensions
{
public static class LyricsLayoutOrientationExtensions
{
extension(LyricsLayoutOrientation orientation)
{
public Orientation ToOrientation() => orientation switch
{
LyricsLayoutOrientation.Horizontal => Orientation.Horizontal,
LyricsLayoutOrientation.Vertical => Orientation.Vertical,
_ => throw new ArgumentOutOfRangeException(nameof(orientation)),
};
public Orientation ToOrientationInverse() => orientation switch
{
LyricsLayoutOrientation.Horizontal => Orientation.Vertical,
LyricsLayoutOrientation.Vertical => Orientation.Horizontal,
_ => throw new ArgumentOutOfRangeException(nameof(orientation)),
};
}
}
}

View File

@@ -16,6 +16,10 @@ namespace BetterLyrics.WinUI3.Extensions
LyricsSearchProvider.Kugou => PathHelper.KugouLyricsCacheDirectory,
LyricsSearchProvider.AmllTtmlDb => PathHelper.AmllTtmlDbLyricsCacheDirectory,
LyricsSearchProvider.AppleMusic => PathHelper.AppleMusicCacheDirectory,
LyricsSearchProvider.LocalMusicFile => PathHelper.LocalMusicCacheDirectory,
LyricsSearchProvider.LocalLrcFile => PathHelper.LocalLrcCacheDirectory,
LyricsSearchProvider.LocalEslrcFile => PathHelper.LocalEslrcCacheDirectory,
LyricsSearchProvider.LocalTtmlFile => PathHelper.LocalTtmlCacheDirectory,
_ => throw new ArgumentOutOfRangeException(nameof(provider)),
};

View File

@@ -70,7 +70,7 @@ namespace BetterLyrics.WinUI3.Extensions
{
Name = _resourceService.GetLocalizedString("FullscreenMode"),
IsBorderless = true,
IsAlwaysOnTop = false,
IsAlwaysOnTop = true,
TitleBarArea = TitleBarArea.None,
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
LyricsStyleSettings = new LyricsStyleSettings

View File

@@ -8,6 +8,12 @@ namespace BetterLyrics.WinUI3.Extensions
extension(Point point)
{
public PointInt32 ToPointInt32() => new((int)point.X, (int)point.Y);
public Point AddX(double deltaX) => new(point.X + deltaX, point.Y);
public Point AddY(double deltaY) => new(point.X, point.Y + deltaY);
public Point WithX(double x) => new(x, point.Y);
public Point WithY(double y) => new(point.X, y);
}
}
}

View File

@@ -41,6 +41,41 @@ namespace BetterLyrics.WinUI3.Extensions
rect.Width,
rect.Height
);
public Rect AddY(double y) => new(
rect.X,
rect.Y + y,
rect.Width,
rect.Height
);
public Rect Extend(double left, double top, double right, double bottom) => new(
rect.X - left,
rect.Y - top,
rect.Width + left + right,
rect.Height + top + bottom
);
public Rect Extend(double padding) => Extend(rect, padding, padding, padding, padding);
public Rect Scale(double scale)
{
double originalWidth = rect.Width;
double originalHeight = rect.Height;
double scaledWidth = originalWidth * scale;
double scaledHeight = originalHeight * scale;
double scaleOffsetX = (scaledWidth - originalWidth) / 2;
double scaleOffsetY = (scaledHeight - originalHeight) / 2;
return new Rect(
rect.X - scaleOffsetX,
rect.Y - scaleOffsetY,
scaledWidth,
scaledHeight
);
}
}
}
}

View File

@@ -15,7 +15,14 @@ namespace BetterLyrics.WinUI3.Extensions
{
if (track.Path is string path)
{
return TagLib.File.Create(path).Tag.Lyrics;
try
{
return TagLib.File.Create(path).Tag.Lyrics;
}
catch (System.Exception)
{
return "";
}
}
return "";
}

View File

@@ -1,14 +1,6 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Windows.Foundation;
using System;
using Windows.Graphics.Effects;
using Windows.UI;
@@ -16,351 +8,6 @@ namespace BetterLyrics.WinUI3.Helper
{
public class CanvasHelper
{
public static CanvasLinearGradientBrush CreateHorizontalFillBrush(
ICanvasAnimatedControl control,
List<(double position, double opacity)> stops,
double startX,
double width
)
{
return new CanvasLinearGradientBrush(control, stops.Select(stops => new CanvasGradientStop
{
Position = (float)stops.position,
Color = Color.FromArgb((byte)(stops.opacity * 255), 128, 128, 128),
}).ToArray())
{
StartPoint = new Vector2((float)startX, 0),
EndPoint = new Vector2((float)(startX + width), 0),
};
}
/// <summary>
/// 背景层
/// </summary>
/// <param name="lyricsLayerOpacity">_lyricsOpacityTransition.Value</param>
public static OpacityEffect CreateBackgroundEffect(LyricsLine lyricsLine, CanvasCommandList backgroundFontEffect, double lyricsLayerOpacity)
{
if (lyricsLine.BlurAmountTransition.Value == 0)
{
return new OpacityEffect
{
Source = backgroundFontEffect,
Opacity = (float)(lyricsLine.OpacityTransition.Value * lyricsLayerOpacity),
};
}
else
{
return new OpacityEffect
{
Source = new GaussianBlurEffect
{
Source = backgroundFontEffect,
BlurAmount = (float)lyricsLine.BlurAmountTransition.Value,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
},
Opacity = (float)(lyricsLine.OpacityTransition.Value * lyricsLayerOpacity),
};
}
}
public static CanvasCommandList CreateFontEffect(LyricsLine lyricsLine, ICanvasAnimatedControl control, Color strokeColor, int strokeWidth, Color fontColor)
{
CanvasCommandList list = new(control);
using var ds = list.CreateDrawingSession();
// 描边
if (strokeWidth > 0)
{
if (lyricsLine.PhoneticCanvasGeometry != null)
{
ds.DrawGeometry(lyricsLine.PhoneticCanvasGeometry, lyricsLine.PhoneticPosition, strokeColor, strokeWidth);
}
if (lyricsLine.OriginalCanvasGeometry != null)
{
ds.DrawGeometry(lyricsLine.OriginalCanvasGeometry, lyricsLine.OriginalPosition, strokeColor, strokeWidth);
}
if (lyricsLine.TranslatedCanvasGeometry != null)
{
ds.DrawGeometry(lyricsLine.TranslatedCanvasGeometry, lyricsLine.TranslatedPosition, strokeColor, strokeWidth);
}
}
// 绘制文本(填充)
if (lyricsLine.PhoneticCanvasTextLayout != null)
{
ds.DrawTextLayout(lyricsLine.PhoneticCanvasTextLayout, lyricsLine.PhoneticPosition, fontColor);
}
if (lyricsLine.OriginalCanvasTextLayout != null)
{
ds.DrawTextLayout(lyricsLine.OriginalCanvasTextLayout, lyricsLine.OriginalPosition, fontColor);
}
if (lyricsLine.TranslatedCanvasTextLayout != null)
{
ds.DrawTextLayout(lyricsLine.TranslatedCanvasTextLayout, lyricsLine.TranslatedPosition, fontColor);
}
return list;
}
/// <summary>
/// 创建辉光效果层
/// 仅需在布局重构 (Relayout) 时调用
/// </summary>
/// <param name="lineRenderingType">_lyricsGlowEffectScope</param>
/// <param name="glowEffectAmount">_lyricsGlowEffectAmount</param>
public static GaussianBlurEffect CreateForegroundBlurEffect(CanvasCommandList foregroundFontEffect, IGraphicsEffectSource mask, double glowEffectAmount)
{
return new GaussianBlurEffect
{
Source = new AlphaMaskEffect
{
Source = foregroundFontEffect,
AlphaMask = mask,
},
BlurAmount = (float)glowEffectAmount,
Optimization = EffectOptimization.Speed,
};
}
public static CanvasCommandList CreateCharMask(ICanvasAnimatedControl control, LyricsLine lyricsLine, int charStartIndex, int charLength, double charProgress)
{
var mask = new CanvasCommandList(control);
using var ds = mask.CreateDrawingSession();
if (lyricsLine.OriginalCanvasTextLayout == null)
{
return mask;
}
var highlightRegion = lyricsLine.OriginalCanvasTextLayout.GetCharacterRegions(charStartIndex, charLength).FirstOrDefault();
double highlightTotalWidth = (double)highlightRegion.LayoutBounds.Width;
// Draw the highlight for the current character
double highlightWidth = highlightTotalWidth * charProgress;
double fadingWidth = (double)highlightRegion.LayoutBounds.Height / 2;
// Rects
var highlightRect = new Rect(
highlightRegion.LayoutBounds.X,
highlightRegion.LayoutBounds.Y + lyricsLine.OriginalPosition.Y,
highlightWidth,
highlightRegion.LayoutBounds.Height
);
var fadeInRect = new Rect(
highlightRect.Right - fadingWidth,
highlightRegion.LayoutBounds.Y + lyricsLine.OriginalPosition.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
var fadeOutRect = new Rect(
highlightRect.Right,
highlightRegion.LayoutBounds.Y + lyricsLine.OriginalPosition.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
// Brushes
using var fadeInBrush = CanvasHelper.CreateHorizontalFillBrush(
control,
[(0f, 0f), (1f, 1f)],
(double)highlightRect.Right - fadingWidth,
fadingWidth
);
using var fadeOutBrush = CanvasHelper.CreateHorizontalFillBrush(
control,
[(0f, 1f), (1f, 0f)],
(double)highlightRect.Right,
fadingWidth
);
ds.FillRectangle(fadeInRect, fadeInBrush);
ds.FillRectangle(fadeOutRect, fadeOutBrush);
return mask;
}
public static CanvasCommandList CreateLineStartToCharMask(ICanvasAnimatedControl control, LyricsLine lyricsLine, int charStartIndex, int charLength, double charProgress, bool fade)
{
var mask = new CanvasCommandList(control);
if (lyricsLine.OriginalCanvasTextLayout == null)
{
return mask;
}
using var ds = mask.CreateDrawingSession();
var regions = lyricsLine.OriginalCanvasTextLayout.GetCharacterRegions(0, charStartIndex);
var highlightRegion = lyricsLine.OriginalCanvasTextLayout
.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 + lyricsLine.OriginalPosition.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
ds.FillRectangle(rect, Color.FromArgb(255, 128, 128, 128));
}
}
double highlightTotalWidth = (double)highlightRegion.LayoutBounds.Width;
// Draw the highlight for the current character
double highlightWidth = highlightTotalWidth * charProgress;
double fadingWidth = (double)highlightRegion.LayoutBounds.Height / 2;
// Rects
var highlightRect = new Rect(
highlightRegion.LayoutBounds.X,
highlightRegion.LayoutBounds.Y + lyricsLine.OriginalPosition.Y,
highlightWidth,
highlightRegion.LayoutBounds.Height
);
var fadeInRect = new Rect(
highlightRect.Right - fadingWidth,
highlightRegion.LayoutBounds.Y + lyricsLine.OriginalPosition.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
ds.FillRectangle(highlightRect, Color.FromArgb(255, 128, 128, 128));
if (fade)
{
var fadeOutRect = new Rect(
highlightRect.Right,
highlightRegion.LayoutBounds.Y + lyricsLine.OriginalPosition.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
using var fadeOutBrush = CreateHorizontalFillBrush(
control,
[(0f, 1f), (1f, 0f)],
(double)highlightRect.Right,
fadingWidth
);
ds.FillRectangle(fadeOutRect, fadeOutBrush);
}
return mask;
}
public static CanvasCommandList CreateLineMask(ICanvasAnimatedControl control, LyricsLine lyricsLine)
{
var mask = new CanvasCommandList(control);
using var ds = mask.CreateDrawingSession();
if (lyricsLine.OriginalCanvasTextLayout == null)
{
return mask;
}
var regions = lyricsLine.OriginalCanvasTextLayout.GetCharacterRegions(0, lyricsLine.OriginalText.Length);
if (regions.Length > 0)
{
for (int j = 0; j < regions.Length; j++)
{
var region = regions[j];
var rect = new Rect(
region.LayoutBounds.X,
region.LayoutBounds.Y + lyricsLine.OriginalPosition.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
ds.FillRectangle(rect, Colors.White);
}
}
return mask;
}
public static CanvasCommandList CreatePhoneticHighlightMask(ICanvasAnimatedControl control, LyricsLine lyricsLine)
{
var mask = new CanvasCommandList(control);
using var ds = mask.CreateDrawingSession();
if (lyricsLine.PhoneticCanvasTextLayout == null)
{
return mask;
}
var regions = lyricsLine.PhoneticCanvasTextLayout.GetCharacterRegions(0, lyricsLine.PhoneticText.Length);
if (regions.Length > 0)
{
for (int j = 0; j < regions.Length; j++)
{
var region = regions[j];
var rect = new Rect(
region.LayoutBounds.X,
region.LayoutBounds.Y + lyricsLine.PhoneticPosition.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
ds.FillRectangle(rect, Colors.White);
}
}
return mask;
}
public static CanvasCommandList CreateTranslatedHighlightMask(ICanvasAnimatedControl control, LyricsLine lyricsLine)
{
var mask = new CanvasCommandList(control);
using var ds = mask.CreateDrawingSession();
if (lyricsLine.TranslatedCanvasTextLayout == null)
{
return mask;
}
var regions = lyricsLine.TranslatedCanvasTextLayout.GetCharacterRegions(0, lyricsLine.TranslatedText.Length);
if (regions.Length > 0)
{
for (int j = 0; j < regions.Length; j++)
{
var region = regions[j];
var rect = new Rect(
region.LayoutBounds.X,
region.LayoutBounds.Y + lyricsLine.TranslatedPosition.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
ds.FillRectangle(rect, Colors.White);
}
}
return mask;
}
/// <summary>
/// 创建高亮效果层
/// </summary>
/// <param name="control"></param>
/// <param name="lineRenderingType"></param>
public static OpacityEffect CreateForegroundHighlightEffect(CanvasCommandList foregroundFontEffect, IGraphicsEffectSource mask, double opacity)
{
return new OpacityEffect
{
Source = new AlphaMaskEffect
{
Source = foregroundFontEffect,
AlphaMask = mask,
},
Opacity = (float)opacity,
};
}
public static ShadowEffect CreateForegroundShadowEffect(CanvasCommandList foregroundFontEffect, IGraphicsEffectSource mask, Color shadowColor, double shadowAmount)
{
return new ShadowEffect
@@ -371,34 +18,10 @@ namespace BetterLyrics.WinUI3.Helper
AlphaMask = mask,
},
ShadowColor = shadowColor,
BlurAmount = (float)shadowAmount,
BlurAmount = (float)Math.Clamp(shadowAmount, 0, 100),
Optimization = EffectOptimization.Speed,
};
}
public static OpacityEffect CreateForegroundTranslationEffect(CanvasCommandList foregroundFontEffect, IGraphicsEffectSource mask, double opacity)
{
return new OpacityEffect
{
Source = new AlphaMaskEffect
{
Source = foregroundFontEffect,
AlphaMask = mask,
},
Opacity = (float)opacity,
};
}
public static IGraphicsEffectSource GetAlphaMask(ICanvasAnimatedControl control, IGraphicsEffectSource charMask, IGraphicsEffectSource lineStartToCharMask, IGraphicsEffectSource lineMask, LineRenderingType lineRenderingType)
{
var result = lineRenderingType switch
{
LineRenderingType.CurrentChar => charMask,
LineRenderingType.LineStartToCurrentChar => lineStartToCharMask,
LineRenderingType.CurrentLine => lineMask,
_ => new CanvasCommandList(control),
};
return result;
}
}
}

View File

@@ -48,6 +48,7 @@ namespace BetterLyrics.WinUI3.Helper
{
var json = File.ReadAllText(cacheFilePath);
var data = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LyricsSearchResult);
data?.SelfPath = cacheFilePath;
return data;
}
return null;
@@ -68,6 +69,7 @@ namespace BetterLyrics.WinUI3.Helper
var cacheFilePath = Path.Combine(
lyricsSearchResult.Provider.GetCacheDirectory(),
SanitizeFileName($"{songInfo.ToFileName()}.json"));
lyricsSearchResult.SelfPath = cacheFilePath;
var json = System.Text.Json.JsonSerializer.Serialize(lyricsSearchResult, SourceGenerationContext.Default.LyricsSearchResult);
File.WriteAllText(cacheFilePath, json);
}

View File

@@ -1,10 +1,39 @@
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI.Xaml;
using System;
using System.Globalization;
using System.Linq;
using System.Windows.Markup;
using System.Windows.Media;
namespace BetterLyrics.WinUI3.Helper
{
public static class FontHelper
{
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies().Order().ToArray();
public static string GetLocalizedFontFamilyName(string sourceName, string langCode)
{
if (langCode == "")
{
langCode = CultureInfo.CurrentCulture.Name;
}
foreach (var font in Fonts.SystemFontFamilies)
{
if (font.FamilyNames.TryGetValue(XmlLanguage.GetLanguage("en-us"), out string englishFamilyName) && englishFamilyName == sourceName)
{
if (font.FamilyNames.ContainsKey(XmlLanguage.GetLanguage(langCode)))
{
if (font.FamilyNames.TryGetValue(XmlLanguage.GetLanguage(langCode), out string localizedFamilyName))
{
return localizedFamilyName;
}
}
}
}
return sourceName;
}
}
}

View File

@@ -137,7 +137,7 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public static async Task<byte[]?> GetImageBytesFromUrlAsync(string url)
public static async Task<byte[]?> GetImageByteArrayFromUrlAsync(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
@@ -185,5 +185,21 @@ namespace BetterLyrics.WinUI3.Helper
{
return buffer.AsStream().AsRandomAccessStream();
}
public static byte[] ToByteArray(IBuffer buffer)
{
using (var dataReader = DataReader.FromBuffer(buffer))
{
byte[] byteArray = new byte[buffer.Length];
dataReader.ReadBytes(byteArray);
return byteArray;
}
}
public static IRandomAccessStream ToIRandomAccessStream(byte[] arr)
{
MemoryStream stream = new MemoryStream(arr);
return stream.AsRandomAccessStream();
}
}
}

View File

@@ -0,0 +1,75 @@
using BetterLyrics.WinUI3.Models;
using System;
namespace BetterLyrics.WinUI3.Helper
{
public static class LyricsLayoutHelper
{
// 硬性限制
private const float BaseMinFontSize = 14f;
private const float BaseMaxFontSize = 80f;
private const float TargetMinVisibleLines = 5f;
private const float WidthPaddingRatio = 0.85f;
// 比例配置
private const float RatioSongTitle = 1f;
private const float RatioArtist = 0.85f;
private const float RatioAlbum = 0.75f;
private const float RatioTranslation = 0.7f;
private const float RatioTransliteration = 0.55f;
private const float AbsoluteMinReadableSize = 10f;
public static LyricsLayoutMetrics CalculateLayout(double width, double height)
{
float baseSize = CalculateBaseFontSize(width, height);
return new LyricsLayoutMetrics
{
MainLyricsSize = baseSize,
TranslationSize = ApplyRatio(baseSize, RatioTranslation),
TransliterationSize = ApplyRatio(baseSize, RatioTransliteration),
SongTitleSize = ApplyRatio(baseSize, RatioSongTitle),
ArtistNameSize = ApplyRatio(baseSize, RatioArtist),
AlbumNameSize = ApplyRatio(baseSize, RatioAlbum)
};
}
private static float CalculateBaseFontSize(double width, double height)
{
float usableWidth = (float)width * WidthPaddingRatio;
// 宽度 300~500px 时,除以 14 (字大)
// 宽度 >1000px 时,除以 30 (字适中,展示更多内容)
float targetCharsPerLine;
if (width < 500)
{
targetCharsPerLine = 14f;
}
else if (width > 1000)
{
targetCharsPerLine = 30f;
}
else
{
// 平滑过渡
float t = (float)(width - 500) / 500f;
targetCharsPerLine = 14f + 16f * t;
}
float sizeByWidth = usableWidth / targetCharsPerLine;
float sizeByHeight = (float)height / TargetMinVisibleLines;
float targetSize = Math.Min(sizeByWidth, sizeByHeight);
// 窄屏时底线设高一点 (16px),宽屏如果高度不够可能允许更小
float currentMinLimit = (width < 400) ? 16f : BaseMinFontSize;
return Math.Clamp(targetSize, currentMinLimit, BaseMaxFontSize);
}
private static float ApplyRatio(float baseSize, float ratio)
{
return Math.Max(baseSize * ratio, AbsoluteMinReadableSize);
}
}
}

View File

@@ -1,7 +1,6 @@
using BetterLyrics.WinUI3.Models;
using F23.StringSimilarity;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
@@ -16,48 +15,51 @@ namespace BetterLyrics.WinUI3.Helper
private const double WeightDuration = 0.10;
// JaroWinkler 适合短字符串匹配
private static readonly JaroWinkler _algo = new JaroWinkler();
private static readonly JaroWinkler _algo = new();
public static int CalculateScore(SongInfo local, LyricsSearchResult remote)
{
if (local == null || remote == null) return 0;
double titleScore = GetStringSimilarity(local.Title, remote.Title);
double artistScore = GetArtistSimilarity(local.Artists, remote.Artists);
double albumScore = GetStringSimilarity(local.Album, remote.Album);
double durationScore = GetDurationSimilarity(local.DurationMs, remote.Duration);
double totalScore = 0;
double totalScore = (titleScore * WeightTitle) +
(artistScore * WeightArtist) +
(albumScore * WeightAlbum) +
(durationScore * WeightDuration);
bool localHasMetadata = !string.IsNullOrWhiteSpace(local.Title);
bool remoteHasMetadata = !string.IsNullOrWhiteSpace(remote.Title);
if (localHasMetadata && remoteHasMetadata)
{
double titleScore = GetStringSimilarity(local.Title, remote.Title);
double artistScore = GetArtistSimilarity(local.Artists, remote.Artists);
double albumScore = GetStringSimilarity(local.Album, remote.Album);
double durationScore = GetDurationSimilarity(local.DurationMs, remote.Duration);
totalScore = (titleScore * WeightTitle) +
(artistScore * WeightArtist) +
(albumScore * WeightAlbum) +
(durationScore * WeightDuration);
}
else
{
string? localQuery = localHasMetadata
? $"{local.Title} {string.Join(" ", local.Artists ?? [])}"
: Path.GetFileNameWithoutExtension(local.LinkedFileName);
string remoteQuery = remoteHasMetadata
? $"{remote.Title} {string.Join(" ", remote.Artists ?? [])}"
: Path.GetFileNameWithoutExtension(remote.Reference);
string fp1 = CreateSortedFingerprint(localQuery);
string fp2 = CreateSortedFingerprint(remoteQuery);
if (string.IsNullOrWhiteSpace(fp1) || string.IsNullOrWhiteSpace(fp2))
totalScore = 0;
else
totalScore = _algo.Similarity(fp1, fp2);
}
return (int)Math.Round(totalScore * 100);
}
public static int CalculateScore(SongInfo songInfo, string filePathOrName)
{
if (songInfo == null || string.IsNullOrWhiteSpace(filePathOrName)) return 0;
string fileName = Path.GetFileNameWithoutExtension(filePathOrName);
string fileFingerprint = CreateSortedFingerprint(fileName);
var infoParts = new List<string>();
if (!string.IsNullOrEmpty(songInfo.Title))
infoParts.Add(songInfo.Title);
if (songInfo.Artists != null)
infoParts.AddRange(songInfo.Artists);
string infoRaw = string.Join(" ", infoParts);
string infoFingerprint = CreateSortedFingerprint(infoRaw);
double score = _algo.Similarity(infoFingerprint, fileFingerprint);
return (int)Math.Round(score * 100);
}
private static double GetStringSimilarity(string? s1, string? s2)
{
s1 = s1?.Trim().ToLowerInvariant() ?? "";
@@ -101,7 +103,7 @@ namespace BetterLyrics.WinUI3.Helper
return 1.0 - ((diff - PerfectTolerance) / (MaxTolerance - PerfectTolerance));
}
private static string CreateSortedFingerprint(string input)
private static string CreateSortedFingerprint(string? input)
{
if (string.IsNullOrWhiteSpace(input)) return "";

View File

@@ -45,6 +45,10 @@ namespace BetterLyrics.WinUI3.Helper
public static string KugouLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "kugou");
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(LyricsCacheDirectory, "amll-ttml-db");
public static string AppleMusicCacheDirectory => Path.Combine(LyricsCacheDirectory, "apple-music");
public static string LocalMusicCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-music");
public static string LocalLrcCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-lrc");
public static string LocalEslrcCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-eslrc");
public static string LocalTtmlCacheDirectory => Path.Combine(LyricsCacheDirectory, "local-ttml");
public static string AmllTtmlDbIndexPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-index.jsonl");
public static string AmllTtmlDbLastUpdatedPath => Path.Combine(LyricsCacheDirectory, "amll-ttml-db-last-updated.txt");
@@ -65,6 +69,10 @@ namespace BetterLyrics.WinUI3.Helper
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
Directory.CreateDirectory(AppleMusicCacheDirectory);
Directory.CreateDirectory(LocalMusicCacheDirectory);
Directory.CreateDirectory(LocalLrcCacheDirectory);
Directory.CreateDirectory(LocalEslrcCacheDirectory);
Directory.CreateDirectory(LocalTtmlCacheDirectory);
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
}

View File

@@ -48,7 +48,8 @@ namespace BetterLyrics.WinUI3.Helper
PlayerID.Edge => PlayerName.Edge,
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
PlayerID.SaltPlayerForWindows => PlayerName.SaltPlayerForWindows,
PlayerID.SaltPlayerForWindowsMS => PlayerName.SaltPlayerForWindowsMS,
PlayerID.SaltPlayerForWindowsSteam => PlayerName.SaltPlayerForWindowsSteam,
PlayerID.MoeKoeMusic => PlayerName.MoeKoeMusic,
PlayerID.MoeKoeMusicAlternative => PlayerName.MoeKoeMusic,
PlayerID.Listen1 => PlayerName.Listen1,
@@ -75,7 +76,8 @@ namespace BetterLyrics.WinUI3.Helper
PlayerID.Edge => PathHelper.EdgeLogoPath,
PlayerID.BetterLyrics => PathHelper.LogoPath,
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
PlayerID.SaltPlayerForWindows => PathHelper.SaltPlayerForWindowsLogoPath,
PlayerID.SaltPlayerForWindowsMS => PathHelper.SaltPlayerForWindowsLogoPath,
PlayerID.SaltPlayerForWindowsSteam => PathHelper.SaltPlayerForWindowsLogoPath,
PlayerID.MoeKoeMusic => PathHelper.MoeKoeMusicLogoPath,
PlayerID.MoeKoeMusicAlternative => PathHelper.MoeKoeMusicLogoPath,
PlayerID.Listen1 => PathHelper.Listen1LogoPath,

View File

@@ -6,6 +6,7 @@ namespace BetterLyrics.WinUI3.Helper
{
public partial class SpectrumAnalyzer : IDisposable
{
private readonly object _lock = new();
private WasapiLoopbackCapture? _capture;
private int _sampleRate = 48000;
@@ -28,7 +29,7 @@ namespace BetterLyrics.WinUI3.Helper
private float[]? _currentSpectrum;
public float[]? SmoothSpectrum { get; private set; }
public int BarCount { get; set; } = 16;
public int BarCount { get; set; } = 64;
public int Sensitivity { get; set; } = 100;
public float SmoothingFactor { get; set; } = 0.95f;
public bool IsCapturing { get; private set; } = false;
@@ -121,12 +122,15 @@ namespace BetterLyrics.WinUI3.Helper
Array.Copy(_spectrumRightData, 0, _spectrumData, _spectrumLeftData.Length, _spectrumRightData.Length);
}
for (int i = 0; i < BarCount; i++)
lock (_lock)
{
int index = (int)((float)i / BarCount * _spectrumData.Length);
if (index < _spectrumData.Length)
for (int i = 0; i < BarCount; i++)
{
_currentSpectrum[i] = _spectrumData[index] * 250f * Sensitivity;
int index = (int)((float)i / BarCount * _spectrumData.Length);
if (index < _spectrumData.Length)
{
_currentSpectrum[i] = _spectrumData[index] * 250f * Sensitivity;
}
}
}
@@ -139,10 +143,13 @@ namespace BetterLyrics.WinUI3.Helper
return;
}
for (int i = 0; i < BarCount; i++)
lock (_lock)
{
SmoothSpectrum[i] = SmoothSpectrum[i] * SmoothingFactor +
_currentSpectrum[i] * (1 - SmoothingFactor);
for (int i = 0; i < BarCount; i++)
{
SmoothSpectrum[i] = SmoothSpectrum[i] * SmoothingFactor +
_currentSpectrum[i] * (1 - SmoothingFactor);
}
}
}

View File

@@ -24,8 +24,10 @@ namespace BetterLyrics.WinUI3.Helper
public bool IsTransitioning => _isTransitioning;
public T Value => _currentValue;
public T StartValue => _startValue;
public T TargetValue => _targetValue;
public EasingType? EasingType => _easingType;
public double Progress => _progress;
public ValueTransition(T initialValue, double durationSeconds, Func<T, T, double, T>? interpolator = null, EasingType? easingType = null, double delaySeconds = 0)
{
@@ -57,7 +59,7 @@ namespace BetterLyrics.WinUI3.Helper
public void SetDuration(double seconds)
{
if (seconds <= 0)
if (seconds < 0)
throw new ArgumentOutOfRangeException(nameof(seconds), "Duration must be positive.");
_durationSeconds = seconds;
}
@@ -145,7 +147,7 @@ namespace BetterLyrics.WinUI3.Helper
}
}
private Func<T, T, double, T> GetInterpolatorByEasingType(EasingType type)
private Func<T, T, double, T> GetInterpolatorByEasingType(EasingType? type)
{
if (typeof(T) == typeof(double))
{
@@ -193,6 +195,7 @@ namespace BetterLyrics.WinUI3.Helper
t = EasingHelper.Linear(t);
break;
default:
t = EasingHelper.EaseInOutQuad(t);
break;
}
return (T)(object)(s + (e - s) * t);
@@ -201,7 +204,7 @@ namespace BetterLyrics.WinUI3.Helper
throw new NotSupportedException($"Easing type {type} is not supported for type {typeof(T)}.");
}
public void SetEasingType(EasingType easingType)
public void SetEasingType(EasingType? easingType)
{
_easingType = easingType;
_interpolator = GetInterpolatorByEasingType(easingType);

View File

@@ -43,7 +43,7 @@ namespace BetterLyrics.WinUI3.Hooks
public static void CloseWindow<T>()
{
if (typeof(T) == typeof(LyricsWindow))
if (typeof(T) == typeof(NowPlayingWindow))
{
EnsureDockModeReleased();
}
@@ -55,6 +55,15 @@ namespace BetterLyrics.WinUI3.Hooks
}
}
public static void MinimizeWindow<T>()
{
var window = _activeWindows.Find(w => w is T);
if (window is Window w)
{
w.Minimize();
}
}
public static T? GetWindow<T>()
{
foreach (var window in _activeWindows)
@@ -91,12 +100,13 @@ namespace BetterLyrics.WinUI3.Hooks
public static void OpenOrShowWindow<T>()
{
var window = _activeWindows.Find(w => w is T);
//window = null;
if (window == null)
{
if (typeof(T) == typeof(LyricsWindow))
if (typeof(T) == typeof(NowPlayingWindow))
{
window = new LyricsWindow();
((LyricsWindow)window).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
window = new NowPlayingWindow();
((NowPlayingWindow)window).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
}
else if (typeof(T) == typeof(SettingsWindow))
{
@@ -114,16 +124,21 @@ namespace BetterLyrics.WinUI3.Hooks
{
window = new LyricsWindowSwitchWindow();
}
else if (typeof(T) == typeof(SystemTrayWindow))
{
window = new SystemTrayWindow();
}
else
{
throw new ArgumentException("Unsupported window type", nameof(T));
}
TrackWindow(window);
var castedWindow = (Window)window;
castedWindow.Restore();
castedWindow.Activate();
if (typeof(T) == typeof(LyricsWindow))
if (typeof(T) == typeof(NowPlayingWindow))
{
_liveStatesService.InitLyricsWindowStatus();
@@ -131,7 +146,7 @@ namespace BetterLyrics.WinUI3.Hooks
_defaultWindowStyle.Add(hwnd, castedWindow.GetWindowStyle());
_defaultExtendedWindowStyle.Add(hwnd, castedWindow.GetExtendedWindowStyle());
var lyricsWindow = (LyricsWindow)window;
var lyricsWindow = (NowPlayingWindow)window;
lyricsWindow.ViewModel.InitShortcuts();
lyricsWindow.ViewModel.InitFgWindowWatcher();
@@ -185,7 +200,7 @@ namespace BetterLyrics.WinUI3.Hooks
private static void EnsureDockModeReleased()
{
SetIsWorkArea<LyricsWindow>(false);
SetIsWorkArea<NowPlayingWindow>(false);
}
private static void TrackWindow(object window)
@@ -291,9 +306,9 @@ namespace BetterLyrics.WinUI3.Hooks
public static void SetTitleBarArea<T>(TitleBarArea titleBarArea)
{
if (typeof(T) == typeof(LyricsWindow))
if (typeof(T) == typeof(NowPlayingWindow))
{
LyricsWindow? lyricsWindow = GetWindow<LyricsWindow>();
NowPlayingWindow? lyricsWindow = GetWindow<NowPlayingWindow>();
lyricsWindow?.SetTitleBarArea(titleBarArea);
}
else
@@ -394,7 +409,7 @@ namespace BetterLyrics.WinUI3.Hooks
_setLyricsWindowVisibilityByPlayingStatusTimer.Debounce(() =>
{
var window = GetWindow<LyricsWindow>();
var window = GetWindow<NowPlayingWindow>();
if (window == null) return;
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.CurrentIsPlaying)
@@ -402,23 +417,23 @@ namespace BetterLyrics.WinUI3.Hooks
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
_liveStatesService.LiveStates.IsLyricsWindowStatusRefreshing = true;
SetIsWorkArea<LyricsWindow>(false);
SetIsWorkArea<NowPlayingWindow>(false);
_liveStatesService.LiveStates.IsLyricsWindowStatusRefreshing = false;
}
HideWindow<LyricsWindow>();
HideWindow<NowPlayingWindow>();
}
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.CurrentIsPlaying)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
_liveStatesService.LiveStates.IsLyricsWindowStatusRefreshing = true;
SetIsWorkArea<LyricsWindow>(true);
SetIsWorkArea<NowPlayingWindow>(true);
_liveStatesService.LiveStates.IsLyricsWindowStatusRefreshing = false;
}
OpenOrShowWindow<LyricsWindow>();
OpenOrShowWindow<NowPlayingWindow>();
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
MoveAndResize<LyricsWindow>(_liveStatesService.LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
MoveAndResize<NowPlayingWindow>(_liveStatesService.LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
}
}
}, Constants.Time.DebounceTimeout);

View File

@@ -0,0 +1,138 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.UI;
namespace BetterLyrics.WinUI3.Logic
{
public class LyricsAnimator
{
private readonly double _defaultScale = 0.75f;
private readonly double _highlightedScale = 1.0f;
public void UpdateLines(
IList<RenderLyricsLine>? lines,
int startIndex,
int endIndex,
int playingLineIndex,
double canvasHeight,
double targetYScrollOffset,
double playingLineTopOffsetFactor,
LyricsEffectSettings lyricsEffect,
ValueTransition<double> canvasYScrollTransition,
Color bgColor,
Color fgColor,
TimeSpan elapsedTime,
bool isMouseScrolling,
bool isLayoutChanged,
bool isPlayingLineChanged,
bool isMouseScrollingChanged
)
{
if (lines == null) return;
var currentPlayingLine = lines.ElementAtOrDefault(playingLineIndex);
if (currentPlayingLine == null) return;
for (int i = startIndex; i <= endIndex + 1; i++)
{
var line = lines.ElementAtOrDefault(i);
if (line == null) continue;
if (isLayoutChanged || isPlayingLineChanged || isMouseScrollingChanged)
{
int lineCountDelta = i - playingLineIndex;
int absLineCountDelta = Math.Abs(lineCountDelta);
double distanceFromPlayingLine = Math.Abs(line.OriginalPosition.Y - currentPlayingLine.OriginalPosition.Y);
double distanceFactor = 0;
if (lineCountDelta < 0)
{
distanceFactor = Math.Clamp(distanceFromPlayingLine / (canvasHeight * playingLineTopOffsetFactor), 0, 1);
}
else
{
distanceFactor = Math.Clamp(distanceFromPlayingLine / (canvasHeight * (1 - playingLineTopOffsetFactor)), 0, 1);
}
double yScrollDuration;
double yScrollDelay;
if (lineCountDelta < 0)
{
yScrollDuration =
canvasYScrollTransition.DurationSeconds +
distanceFactor * (lyricsEffect.LyricsScrollTopDuration / 1000.0 - canvasYScrollTransition.DurationSeconds);
yScrollDelay = distanceFactor * lyricsEffect.LyricsScrollTopDelay / 1000.0;
}
else if (lineCountDelta == 0)
{
yScrollDuration = canvasYScrollTransition.DurationSeconds;
yScrollDelay = 0;
}
else
{
yScrollDuration =
canvasYScrollTransition.DurationSeconds +
distanceFactor * (lyricsEffect.LyricsScrollBottomDuration / 1000.0 - canvasYScrollTransition.DurationSeconds);
yScrollDelay = distanceFactor * lyricsEffect.LyricsScrollBottomDelay / 1000.0;
}
line.BlurAmountTransition.SetDuration(yScrollDuration);
line.BlurAmountTransition.SetDelay(yScrollDelay);
line.BlurAmountTransition.StartTransition(isMouseScrolling ? 0 : (lyricsEffect.IsLyricsBlurEffectEnabled ? (5 * distanceFactor) : 0));
line.ScaleTransition.SetDuration(yScrollDuration);
line.ScaleTransition.SetDelay(yScrollDelay);
line.ScaleTransition.StartTransition(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale));
line.PhoneticOpacityTransition.SetDuration(yScrollDuration);
line.PhoneticOpacityTransition.SetDelay(yScrollDelay);
line.PhoneticOpacityTransition.StartTransition(absLineCountDelta == 0 ? 0.6 : (isMouseScrolling ? 0.3 : (1 - distanceFactor) * 0.3));
line.PlayedOriginalOpacityTransition.SetDuration(yScrollDuration);
line.PlayedOriginalOpacityTransition.SetDelay(yScrollDelay);
line.PlayedOriginalOpacityTransition.StartTransition(absLineCountDelta == 0 ? 1 : (isMouseScrolling ? 0.3 : (1 - distanceFactor) * 0.3));
line.UnplayedOriginalOpacityTransition.SetDuration(yScrollDuration);
line.UnplayedOriginalOpacityTransition.SetDelay(yScrollDelay);
line.UnplayedOriginalOpacityTransition.StartTransition(absLineCountDelta == 0 ? 0.3 : (isMouseScrolling ? 0.3 : (1 - distanceFactor) * 0.3));
line.TranslatedOpacityTransition.SetDuration(yScrollDuration);
line.TranslatedOpacityTransition.SetDelay(yScrollDelay);
line.TranslatedOpacityTransition.StartTransition(absLineCountDelta == 0 ? 0.6 : (isMouseScrolling ? 0.3 : (1 - distanceFactor) * 0.3));
line.ColorTransition.SetDuration(yScrollDuration);
line.ColorTransition.SetDelay(yScrollDelay);
line.ColorTransition.StartTransition(absLineCountDelta == 0 ? fgColor : bgColor);
line.AngleTransition.SetEasingType(canvasYScrollTransition.EasingType);
line.AngleTransition.SetDuration(yScrollDuration);
line.AngleTransition.SetDelay(yScrollDelay);
line.AngleTransition.StartTransition(lyricsEffect.IsFanLyricsEnabled ?
Math.PI * (lyricsEffect.FanLyricsAngle / 180.0) * distanceFactor * (i > playingLineIndex ? 1 : -1) : 0);
line.YOffsetTransition.SetEasingType(canvasYScrollTransition.EasingType);
line.YOffsetTransition.SetDuration(yScrollDuration);
line.YOffsetTransition.SetDelay(yScrollDelay);
// 设计之初是当 isLayoutChanged 为真时 jumpTo
// 但考虑到动画视觉,强制使用动画
line.YOffsetTransition.StartTransition(targetYScrollOffset);
}
line.AngleTransition.Update(elapsedTime);
line.ScaleTransition.Update(elapsedTime);
line.BlurAmountTransition.Update(elapsedTime);
line.PhoneticOpacityTransition.Update(elapsedTime);
line.PlayedOriginalOpacityTransition.Update(elapsedTime);
line.UnplayedOriginalOpacityTransition.Update(elapsedTime);
line.TranslatedOpacityTransition.Update(elapsedTime);
line.YOffsetTransition.Update(elapsedTime);
line.ColorTransition.Update(elapsedTime);
}
}
}
}

View File

@@ -0,0 +1,253 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Logic
{
public class LyricsLayoutManager
{
/// <summary>
/// 重排歌词Y 轴从 0 刻度开始算
/// </summary>
/// <param name="resourceCreator"></param>
/// <param name="lyricsData"></param>
/// <param name="status"></param>
/// <param name="appSettings"></param>
/// <param name="canvasWidth"></param>
/// <param name="canvasHeight"></param>
/// <param name="lyricsWidth"></param>
/// <param name="lyricsHeight"></param>
public static void MeasureAndArrange(
ICanvasAnimatedControl resourceCreator,
IList<RenderLyricsLine>? lines,
LyricsWindowStatus status,
AppSettings appSettings,
double canvasWidth,
double canvasHeight,
double lyricsWidth,
double lyricsHeight)
{
if (lines == null || resourceCreator == null) return;
// 计算字体大小
int originalFontSize, phoneticFontSize, translatedFontSize;
var style = status.LyricsStyleSettings;
if (style.IsDynamicLyricsFontSize)
{
var lyricsLayoutMetrics = LyricsLayoutHelper.CalculateLayout(canvasWidth, canvasHeight);
phoneticFontSize = (int)lyricsLayoutMetrics.TransliterationSize;
originalFontSize = (int)lyricsLayoutMetrics.MainLyricsSize;
translatedFontSize = (int)lyricsLayoutMetrics.TranslationSize;
}
else
{
phoneticFontSize = style.PhoneticLyricsFontSize;
originalFontSize = style.OriginalLyricsFontSize;
translatedFontSize = style.TranslatedLyricsFontSize;
}
var fontWeight = style.LyricsFontWeight;
// 排版
double currentY = 0;
double actualWidth = 0;
foreach (var line in lines)
{
if (line == null) continue;
line.RecreateTextLayout(
resourceCreator,
appSettings.TranslationSettings.IsChineseRomanizationEnabled || appSettings.TranslationSettings.IsJapaneseRomanizationEnabled,
appSettings.TranslationSettings.IsTranslationEnabled,
phoneticFontSize, originalFontSize, translatedFontSize,
fontWeight,
style.LyricsCJKFontFamily, style.LyricsWesternFontFamily,
lyricsWidth, lyricsHeight, style.LyricsAlignmentType
);
line.RecreateTextGeometry();
// 左上角坐标
line.TopLeftPosition = new Vector2(0, (float)currentY);
// 注音层
line.PhoneticPosition = line.TopLeftPosition;
if (line.PhoneticCanvasTextLayout != null)
{
currentY += line.PhoneticCanvasTextLayout.LayoutBounds.Height;
// 间距
currentY += (line.PhoneticCanvasTextLayout.LayoutBounds.Height / line.PhoneticCanvasTextLayout.LineCount) * 0.1;
actualWidth = Math.Max(actualWidth, line.PhoneticCanvasTextLayout.LayoutBounds.Width);
}
// 原文层
line.OriginalPosition = new Vector2(0, (float)currentY);
if (line.OriginalCanvasTextLayout != null)
{
currentY += line.OriginalCanvasTextLayout.LayoutBounds.Height;
actualWidth = Math.Max(actualWidth, line.OriginalCanvasTextLayout.LayoutBounds.Width);
}
// 翻译层
if (line.TranslatedCanvasTextLayout != null)
{
// 间距
currentY += (line.TranslatedCanvasTextLayout.LayoutBounds.Height / line.TranslatedCanvasTextLayout.LineCount) * 0.1;
}
line.TranslatedPosition = new Vector2(0, (float)currentY);
if (line.TranslatedCanvasTextLayout != null)
{
currentY += line.TranslatedCanvasTextLayout.LayoutBounds.Height;
actualWidth = Math.Max(actualWidth, line.TranslatedCanvasTextLayout.LayoutBounds.Width);
}
// 右下角坐标
line.BottomRightPosition = new Vector2(0 + (float)actualWidth, (float)currentY);
// 行间距
if (line.OriginalCanvasTextLayout != null)
{
currentY += (line.OriginalCanvasTextLayout.LayoutBounds.Height / line.OriginalCanvasTextLayout.LineCount) * style.LyricsLineSpacingFactor;
}
// 更新中心点
line.UpdateCenterPosition(lyricsWidth, style.LyricsAlignmentType);
}
}
/// <summary>
/// 计算为了让当前歌词行的竖直几何中心点对齐到 0原点画布应该移动的距离从画布最初始状态计算的值
/// </summary>
public static double? CalculateTargetScrollOffset(
IList<RenderLyricsLine>? lines,
int playingLineIndex)
{
if (lines == null || lines.Count == 0) return null;
var currentLine = lines.ElementAtOrDefault(playingLineIndex);
var firstLine = lines.FirstOrDefault();
if (currentLine?.OriginalCanvasTextLayout == null || firstLine == null) return null;
return -currentLine.OriginalPosition.Y + firstLine.OriginalPosition.Y
- (currentLine.BottomRightPosition.Y - currentLine.TopLeftPosition.Y) / 2.0;
}
/// <summary>
/// 计算当前屏幕可见的行范围
/// 返回值: (StartVisibleIndex, EndVisibleIndex)
/// </summary>
public static (int Start, int End) CalculateVisibleRange(
IList<RenderLyricsLine>? lines,
double currentScrollOffset,
double lyricsY,
double lyricsHeight,
double canvasHeight,
double playingLineTopOffsetFactor
)
{
if (lines == null || lines.Count == 0) return (-1, -1);
double offset = currentScrollOffset + lyricsY + lyricsHeight * playingLineTopOffsetFactor;
int start = FindFirstVisibleLine(lines, offset, lyricsY);
int end = FindLastVisibleLine(lines, offset, lyricsY, lyricsHeight, canvasHeight);
// 修正边界情况
if (start != -1 && end == -1)
{
end = lines.Count - 1;
}
return (start, end);
}
public static (int Start, int End) CalculateMaxRange(IList<RenderLyricsLine>? lines)
{
if (lines == null || lines.Count == 0) return (-1, -1);
return (0, lines.Count - 1);
}
public static double CalculateActualHeight(IList<RenderLyricsLine>? lines)
{
if (lines == null || lines.Count == 0) return 0;
return lines.Last().BottomRightPosition.Y;
}
public static int FindMouseHoverLineIndex(
IList<RenderLyricsLine>? lines,
bool isMouseInLyricsArea,
Point mousePosition,
double currentScrollOffset,
double lyricsY,
double lyricsHeight,
double playingLineTopOffsetFactor
)
{
if (!isMouseInLyricsArea) return -1;
if (lines == null || lines.Count == 0) return -1;
double offset = currentScrollOffset + lyricsY + lyricsHeight * playingLineTopOffsetFactor;
int left = 0, right = lines.Count - 1, result = -1;
while (left <= right)
{
int mid = (left + right) / 2;
var line = lines[mid];
if (line.OriginalCanvasTextLayout == null) break;
double value = offset + line.BottomRightPosition.Y;
if (value >= mousePosition.Y) { result = mid; right = mid - 1; }
else { left = mid + 1; }
}
return result;
}
private static int FindFirstVisibleLine(IList<RenderLyricsLine> lines, double offset, double lyricsY)
{
int left = 0, right = lines.Count - 1, result = -1;
while (left <= right)
{
int mid = (left + right) / 2;
var line = lines[mid];
if (line.OriginalCanvasTextLayout == null) break;
double value = offset + line.BottomRightPosition.Y;
// 理论上说应该使用下面这一行来精确计算视野内的首个可见行,但是考虑到动画视觉效果,还是注释掉了
//if (value >= lyricsY) { result = mid; right = mid - 1; }
if (value >= 0) { result = mid; right = mid - 1; }
else { left = mid + 1; }
}
return result;
}
private static int FindLastVisibleLine(IList<RenderLyricsLine> lines, double offset, double lyricsY, double lyricsHeight, double canvasHeight)
{
int left = 0, right = lines.Count - 1, result = -1;
while (left <= right)
{
int mid = (left + right) / 2;
var line = lines[mid];
if (line.OriginalCanvasTextLayout == null) break;
double value = offset + line.BottomRightPosition.Y;
// 同理
//if (value >= lyricsY + lyricsHeight) { result = mid; right = mid - 1; }
if (value >= canvasHeight) { result = mid; right = mid - 1; }
else { left = mid + 1; }
}
return result;
}
}
}

View File

@@ -0,0 +1,153 @@
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
namespace BetterLyrics.WinUI3.Logic
{
public class LyricsSynchronizer
{
private int _lastFoundIndex = 0;
public void Reset()
{
_lastFoundIndex = 0;
}
public int GetCurrentLineIndex(double currentTimeMs, LyricsData? lyricsData)
{
if (lyricsData == null || lyricsData.LyricsLines.Count == 0) return 0;
var lines = lyricsData.LyricsLines;
// Cache hit
if (IsTimeInLine(currentTimeMs, lines, _lastFoundIndex)) return _lastFoundIndex;
if (_lastFoundIndex + 1 < lines.Count && IsTimeInLine(currentTimeMs, lines, _lastFoundIndex + 1))
{
_lastFoundIndex++;
return _lastFoundIndex;
}
// Cache miss
for (int i = 0; i < lines.Count; i++)
{
if (IsTimeInLine(currentTimeMs, lines, i))
{
_lastFoundIndex = i;
return i;
}
}
// Default
return Math.Min(_lastFoundIndex, lines.Count - 1);
}
public LinePlaybackState GetLinePlayingProgress(
double currentTimeMs,
LyricsLine line,
LyricsLine? nextLine,
double songDurationMs,
bool isForceWordByWord)
{
var state = new LinePlaybackState { SyllableStartIndex = 0, SyllableLength = 0, SyllableProgress = 0 };
if (line == null) return state;
double lineEndMs;
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
else if (nextLine != null) lineEndMs = nextLine.StartMs;
else lineEndMs = songDurationMs;
// 还没到
if (currentTimeMs < line.StartMs) return state;
// 过了
if (currentTimeMs > lineEndMs)
{
state.SyllableProgress = 1f;
state.SyllableStartIndex = Math.Max(0, line.OriginalText.Length - 1);
state.SyllableLength = 1;
return state;
}
// 逐字
if (line.LyricsSyllables != null && line.LyricsSyllables.Count > 1)
{
return CalculateSyllableProgress(currentTimeMs, line, lineEndMs);
}
// 强制逐字
if (isForceWordByWord && line.OriginalText.Length > 0)
{
return CalculateSimulatedProgress(currentTimeMs, line, lineEndMs);
}
else
{
// 普通行
state.SyllableStartIndex = line.OriginalText.Length;
state.SyllableProgress = 1f;
return state;
}
}
private LinePlaybackState CalculateSyllableProgress(double time, LyricsLine line, double lineEndMs)
{
var state = new LinePlaybackState();
int count = line.LyricsSyllables.Count;
for (int i = 0; i < count; i++)
{
var timing = line.LyricsSyllables[i];
var nextTiming = (i + 1 < count) ? line.LyricsSyllables[i + 1] : null;
double timingEndMs = timing.EndMs ?? nextTiming?.StartMs ?? lineEndMs;
// 在当前字范围内
if (time >= timing.StartMs && time <= timingEndMs)
{
state.SyllableStartIndex = timing.StartIndex;
state.SyllableLength = timing.Text.Length;
state.SyllableProgress = (timingEndMs > timing.StartMs)
? (time - timing.StartMs) / (timingEndMs - timing.StartMs)
: 0;
return state;
}
// 在空隙中 (已过当前字,未到下个字)
else if (time > timingEndMs && (nextTiming == null || time < nextTiming.StartMs))
{
state.SyllableProgress = 1f; // 保持上个字满进度
state.SyllableStartIndex = timing.StartIndex;
state.SyllableLength = timing.Text.Length;
return state;
}
}
return state;
}
private LinePlaybackState CalculateSimulatedProgress(double time, LyricsLine line, double lineEndMs)
{
var state = new LinePlaybackState();
int textLength = line.OriginalText.Length;
double progress = (time - line.StartMs) / (lineEndMs - line.StartMs);
progress = Math.Clamp(progress, 0, 1);
double charFloatIndex = progress * textLength;
int charIndex = (int)charFloatIndex;
state.SyllableStartIndex = Math.Clamp(charIndex, 0, textLength - 1);
state.SyllableLength = 1;
state.SyllableProgress = charFloatIndex - charIndex;
return state;
}
private bool IsTimeInLine(double time, IList<LyricsLine> lines, int index)
{
if (index < 0 || index >= lines.Count) return false;
var line = lines[index];
var nextLine = (index + 1 < lines.Count) ? lines[index + 1] : null;
if (time < line.StartMs) return false;
if (nextLine != null && time >= nextLine.StartMs) return false;
return true;
}
}
}

View File

@@ -0,0 +1,20 @@
using Microsoft.UI.Xaml;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models
{
public struct AlbumArtThemeColors
{
public Color BgFontColor;
public Color FgFontColor;
public Color StrokeFontColor;
public Color EnvColor;
public Color AccentColor1;
public Color AccentColor2;
public Color AccentColor3;
public Color AccentColor4;
public ElementTheme ThemeType;
}
}

View File

@@ -0,0 +1,8 @@
namespace BetterLyrics.WinUI3.Models
{
public class ExtendedFontFamily
{
public string FontFamily { get; set; } = "";
public string LocalizedFontFamily { get; set; } = "";
}
}

View File

@@ -0,0 +1,9 @@
namespace BetterLyrics.WinUI3.Models
{
public struct LinePlaybackState
{
public int SyllableStartIndex;
public int SyllableLength;
public double SyllableProgress;
}
}

View File

@@ -1,6 +1,7 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.ResourceService;
using CommunityToolkit.Mvvm.DependencyInjection;
using DevWinUI;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -19,7 +20,7 @@ namespace BetterLyrics.WinUI3.Models
}
public bool AutoGenerated { get; set; } = false;
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.OriginalText));
public bool IsWordByWord => LyricsLines.Any(x => x.LyricsChars.Count != 0);
public bool IsWordByWord => LyricsLines.Any(x => x.LyricsSyllables.Count != 0);
public LyricsData()
{
@@ -47,7 +48,7 @@ namespace BetterLyrics.WinUI3.Models
}
}
public void SetTranslatedText(LyricsData translationData, string separator, int toleranceMs = 0)
public void SetTranslatedText(LyricsData translationData, int toleranceMs = 0)
{
foreach (var line in LyricsLines)
{
@@ -68,7 +69,7 @@ namespace BetterLyrics.WinUI3.Models
}
}
public void SetPhoneticText(LyricsData phoneticData, string separator, int toleranceMs = 0)
public void SetPhoneticText(LyricsData phoneticData, int toleranceMs = 0)
{
foreach (var line in LyricsLines)
{
@@ -89,7 +90,7 @@ namespace BetterLyrics.WinUI3.Models
}
}
public void SetTranslation(string translation, string separator)
public void SetTranslation(string translation)
{
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
int i = 0;
@@ -131,14 +132,23 @@ namespace BetterLyrics.WinUI3.Models
return result;
}
public static LyricsData GetNotfoundPlaceholder(int durationMs)
public static LyricsData GetNotfoundPlaceholder()
{
return new LyricsData([new LyricsLine
{
StartMs = 0,
EndMs = durationMs,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = _resourceService.GetLocalizedString("LyricsNotFound"),
LyricsChars = [],
}]);
}
public static LyricsData GetParseErrorPlaceholder()
{
return new LyricsData([new LyricsLine
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = _resourceService.GetLocalizedString("LyricsParseError"),
}]);
}
@@ -149,10 +159,7 @@ namespace BetterLyrics.WinUI3.Models
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
PhoneticText = "",
OriginalText = "● ● ●",
TranslatedText = "",
LyricsChars = [],
},
]);
}
@@ -169,5 +176,6 @@ namespace BetterLyrics.WinUI3.Models
}
return null;
}
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.UI.Xaml;
namespace BetterLyrics.WinUI3.Models
{
public struct LyricsLayoutMetrics
{
public float MainLyricsSize;
public float TranslationSize;
public float TransliterationSize;
public float SongTitleSize;
public float ArtistNameSize;
public float AlbumNameSize;
public Thickness AlbumArtPadding;
}
}

View File

@@ -6,64 +6,17 @@ using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using System;
using System.Collections.Generic;
using System.Numerics;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models
{
public class LyricsLine
{
private const double _animationDuration = 0.3;
public ValueTransition<double> AngleTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutQuad
);
public ValueTransition<double> BlurAmountTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutQuad
);
public ValueTransition<double> HighlightOpacityTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutQuad
);
public ValueTransition<double> OpacityTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutQuad
);
public ValueTransition<double> ScaleTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutQuad
);
public ValueTransition<double> YOffsetTransition { get; set; } = new(
initialValue: 0,
durationSeconds: 0.5,
easingType: EasingType.EaseInOutQuad
);
public CanvasTextLayout? OriginalCanvasTextLayout { get; private set; }
public CanvasTextLayout? TranslatedCanvasTextLayout { get; private set; }
public CanvasTextLayout? PhoneticCanvasTextLayout { get; private set; }
public Vector2 CenterPosition { get; private set; }
/// <summary>
/// 原文位置
/// </summary>
public Vector2 OriginalPosition { get; set; }
/// <summary>
/// 译文位置
/// </summary>
public Vector2 TranslatedPosition { get; set; }
/// <summary>
/// 注音位置
/// </summary>
public Vector2 PhoneticPosition { get; set; }
public List<LyricsChar> LyricsChars { get; set; } = [];
public List<LyricsSyllable> LyricsSyllables { get; set; } = [];
public int? DurationMs => EndMs - StartMs;
public int? EndMs { get; set; }
@@ -82,123 +35,5 @@ namespace BetterLyrics.WinUI3.Models
/// </summary>
public string PhoneticText { get; set; } = "";
public CanvasGeometry? OriginalCanvasGeometry { get; private set; }
public CanvasGeometry? TranslatedCanvasGeometry { get; private set; }
public CanvasGeometry? PhoneticCanvasGeometry { get; private set; }
public void UpdateCenterPosition(double maxWidth, TextAlignmentType type)
{
if (OriginalCanvasTextLayout == null)
{
return;
}
double centerY = OriginalPosition.Y + (OriginalCanvasTextLayout?.LayoutBounds.Height ?? 0) / 2;
CenterPosition = type switch
{
TextAlignmentType.Left => new Vector2(OriginalPosition.X, (float)centerY),
TextAlignmentType.Center => new Vector2((float)(OriginalPosition.X + maxWidth / 2.0), (float)centerY),
TextAlignmentType.Right => new Vector2((float)(OriginalPosition.X + maxWidth), (float)centerY),
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
};
}
public void DisposeTextLayout()
{
PhoneticCanvasTextLayout?.Dispose();
PhoneticCanvasTextLayout = null;
OriginalCanvasTextLayout?.Dispose();
OriginalCanvasTextLayout = null;
TranslatedCanvasTextLayout?.Dispose();
TranslatedCanvasTextLayout = null;
}
public void RecreateTextLayout(
ICanvasAnimatedControl control,
bool createPhonetic, bool createTranslated,
int phoneticTextFontSize, int originalTextFontSize, int translatedTextFontSize,
LyricsFontWeight fontWeight,
string fontFamilyCJK, string fontFamilyWestern,
double maxWidth, double maxHeight, TextAlignmentType type)
{
DisposeTextLayout();
if (createPhonetic && PhoneticText != "")
{
PhoneticCanvasTextLayout = new CanvasTextLayout(control, PhoneticText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = phoneticTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment(),
};
PhoneticCanvasTextLayout.SetFontFamily(PhoneticText, fontFamilyCJK, fontFamilyWestern);
}
OriginalCanvasTextLayout = new CanvasTextLayout(control, OriginalText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = originalTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
};
OriginalCanvasTextLayout.SetFontFamily(OriginalText, fontFamilyCJK, fontFamilyWestern);
if (createTranslated && TranslatedText != "")
{
TranslatedCanvasTextLayout = new CanvasTextLayout(control, TranslatedText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = translatedTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
};
TranslatedCanvasTextLayout.SetFontFamily(TranslatedText, fontFamilyCJK, fontFamilyWestern);
}
}
public void DisposeTextGeometry()
{
PhoneticCanvasGeometry?.Dispose();
PhoneticCanvasGeometry = null;
OriginalCanvasGeometry?.Dispose();
OriginalCanvasGeometry = null;
TranslatedCanvasGeometry?.Dispose();
TranslatedCanvasGeometry = null;
}
public void RecreateTextGeometry()
{
DisposeTextGeometry();
if (PhoneticCanvasTextLayout != null)
{
PhoneticCanvasGeometry = CanvasGeometry.CreateText(PhoneticCanvasTextLayout);
}
if (OriginalCanvasTextLayout != null)
{
OriginalCanvasGeometry = CanvasGeometry.CreateText(OriginalCanvasTextLayout);
}
if (TranslatedCanvasTextLayout != null)
{
TranslatedCanvasGeometry = CanvasGeometry.CreateText(TranslatedCanvasTextLayout);
}
}
}
}

View File

@@ -8,8 +8,9 @@ namespace BetterLyrics.WinUI3.Models
public partial class LyricsSearchProviderInfo : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider Provider { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMatchingThresholdOverwritten { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 0;
public LyricsSearchProviderInfo() { }

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using CommunityToolkit.Mvvm.ComponentModel;
using NTextCat.Commons;
using System;
@@ -28,6 +29,8 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty] public partial int MatchPercentage { get; set; } = -1;
[ObservableProperty] public partial string Reference { get; set; } = "about:blank";
public string? SelfPath { get; set; }
public bool IsFound => !string.IsNullOrEmpty(Raw);
public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;

View File

@@ -2,11 +2,13 @@
namespace BetterLyrics.WinUI3.Models
{
public class LyricsChar
public class LyricsSyllable
{
public int? EndMs { get; set; }
public int StartIndex { get; set; }
public int StartMs { get; set; }
public string Text { get; set; } = string.Empty;
public int? DurationMs => EndMs - StartMs;
public bool IsLongDuration => DurationMs >= 700;
}
}

View File

@@ -30,11 +30,13 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty] public partial LyricsStyleSettings LyricsStyleSettings { get; set; } = new();
[ObservableProperty] public partial LyricsEffectSettings LyricsEffectSettings { get; set; } = new(500, 500, 500, EasingType.EaseInOutQuad);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsBackgroundSettings LyricsBackgroundSettings { get; set; } = new();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AlbumArtLayoutSettings AlbumArtLayoutSettings { get; set; } = new();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AlbumArtAreaStyleSettings AlbumArtLayoutSettings { get; set; } = new();
[ObservableProperty] public partial AlbumArtAreaEffectSettings AlbumArtAreaEffectSettings { get; set; } = new();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAdaptToEnvironment { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial WindowPixelSampleMode EnvironmentSampleMode { get; set; } = WindowPixelSampleMode.WindowEdge;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoShowOrHideWindow { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TitleBarArea TitleBarArea { get; set; } = TitleBarArea.Top;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowX { get; set; } = 100;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowY { get; set; } = 100;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowWidth { get; set; } = 800;
@@ -64,7 +66,7 @@ namespace BetterLyrics.WinUI3.Models
newValue.PropertyChanged += OldLyricsBackgroundSettings_PropertyChanged;
}
partial void OnAlbumArtLayoutSettingsChanged(AlbumArtLayoutSettings oldValue, AlbumArtLayoutSettings newValue)
partial void OnAlbumArtLayoutSettingsChanged(AlbumArtAreaStyleSettings oldValue, AlbumArtAreaStyleSettings newValue)
{
oldValue.PropertyChanged -= OldAlbumArtLayoutSettings_PropertyChanged;
newValue.PropertyChanged += OldAlbumArtLayoutSettings_PropertyChanged;
@@ -102,7 +104,7 @@ namespace BetterLyrics.WinUI3.Models
public void UpdateMonitorNameAndBounds()
{
var lyricsWindow = WindowHook.GetWindow<LyricsWindow>();
var lyricsWindow = WindowHook.GetWindow<NowPlayingWindow>();
if (lyricsWindow == null) return;
var mointor = MonitorHook.GetMonitorInfoExFromWindow(lyricsWindow);
@@ -183,19 +185,24 @@ namespace BetterLyrics.WinUI3.Models
MonitorBounds = this.MonitorBounds,
DemoMonitorBounds = this.DemoMonitorBounds,
DockPlacement = this.DockPlacement,
LyricsStyleSettings = (LyricsStyleSettings)this.LyricsStyleSettings.Clone(),
LyricsEffectSettings = (LyricsEffectSettings)this.LyricsEffectSettings.Clone(),
LyricsBackgroundSettings = (LyricsBackgroundSettings)this.LyricsBackgroundSettings.Clone(),
AlbumArtLayoutSettings = (AlbumArtLayoutSettings)this.AlbumArtLayoutSettings.Clone(),
AlbumArtLayoutSettings = (AlbumArtAreaStyleSettings)this.AlbumArtLayoutSettings.Clone(),
AlbumArtAreaEffectSettings = (AlbumArtAreaEffectSettings)this.AlbumArtAreaEffectSettings.Clone(),
IsAdaptToEnvironment = this.IsAdaptToEnvironment,
EnvironmentSampleMode = this.EnvironmentSampleMode,
AutoShowOrHideWindow = this.AutoShowOrHideWindow,
TitleBarArea = this.TitleBarArea,
WindowX = this.WindowX,
WindowY = this.WindowY,
WindowWidth = this.WindowWidth,
WindowHeight = this.WindowHeight,
};
}
}
}

View File

@@ -32,6 +32,7 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; } = [.. Enum.GetValues<AlbumArtSearchProvider>().Select(p => new AlbumArtSearchProviderInfo(p, true))];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchType LyricsSearchType { get; set; } = LyricsSearchType.Sequential;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 0;
public string LogoPath => PlayerIDHelper.GetLogoPath(Provider);

View File

@@ -0,0 +1,228 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models
{
public class RenderLyricsLine : LyricsLine
{
public double AnimationDuration { get; set; } = 0.3;
public ValueTransition<double> AngleTransition { get; set; }
public ValueTransition<double> BlurAmountTransition { get; set; }
public ValueTransition<double> PhoneticOpacityTransition { get; set; }
public ValueTransition<double> PlayedOriginalOpacityTransition { get; set; }
public ValueTransition<double> UnplayedOriginalOpacityTransition { get; set; }
public ValueTransition<double> TranslatedOpacityTransition { get; set; }
public ValueTransition<double> ScaleTransition { get; set; }
public ValueTransition<double> YOffsetTransition { get; set; }
public ValueTransition<Color> ColorTransition { get; set; }
public CanvasTextLayout? OriginalCanvasTextLayout { get; private set; }
public CanvasTextLayout? TranslatedCanvasTextLayout { get; private set; }
public CanvasTextLayout? PhoneticCanvasTextLayout { get; private set; }
/// <summary>
/// 原文坐标(相对于坐标原点)
/// </summary>
public Vector2 OriginalPosition { get; set; }
/// <summary>
/// 译文坐标(相对于坐标原点)
/// </summary>
public Vector2 TranslatedPosition { get; set; }
/// <summary>
/// 注音坐标(相对于坐标原点)
/// </summary>
public Vector2 PhoneticPosition { get; set; }
/// <summary>
/// 顶部坐标(相对于坐标原点)
/// </summary>
public Vector2 TopLeftPosition { get; set; }
/// <summary>
/// 中心坐标(相对于坐标原点)
/// </summary>
public Vector2 CenterPosition { get; private set; }
/// <summary>
/// 底部坐标(相对于坐标原点)
/// </summary>
public Vector2 BottomRightPosition { get; set; }
public CanvasGeometry? OriginalCanvasGeometry { get; private set; }
public CanvasGeometry? TranslatedCanvasGeometry { get; private set; }
public CanvasGeometry? PhoneticCanvasGeometry { get; private set; }
public RenderLyricsLine()
{
AngleTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
BlurAmountTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
PhoneticOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
PlayedOriginalOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
UnplayedOriginalOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
TranslatedOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
ScaleTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
YOffsetTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
ColorTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
}
public void UpdateCenterPosition(double maxWidth, TextAlignmentType type)
{
if (OriginalCanvasTextLayout == null)
{
return;
}
double centerY = (TopLeftPosition.Y + BottomRightPosition.Y) / 2;
CenterPosition = type switch
{
TextAlignmentType.Left => new Vector2(0, (float)centerY),
TextAlignmentType.Center => new Vector2((float)(0 + maxWidth / 2.0), (float)centerY),
TextAlignmentType.Right => new Vector2((float)(0 + maxWidth), (float)centerY),
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
};
}
public void DisposeTextLayout()
{
PhoneticCanvasTextLayout?.Dispose();
PhoneticCanvasTextLayout = null;
OriginalCanvasTextLayout?.Dispose();
OriginalCanvasTextLayout = null;
TranslatedCanvasTextLayout?.Dispose();
TranslatedCanvasTextLayout = null;
}
public void RecreateTextLayout(
ICanvasAnimatedControl control,
bool createPhonetic, bool createTranslated,
int phoneticTextFontSize, int originalTextFontSize, int translatedTextFontSize,
LyricsFontWeight fontWeight,
string fontFamilyCJK, string fontFamilyWestern,
double maxWidth, double maxHeight, TextAlignmentType type)
{
DisposeTextLayout();
if (createPhonetic && PhoneticText != "")
{
PhoneticCanvasTextLayout = new CanvasTextLayout(control, PhoneticText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = phoneticTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment(),
};
PhoneticCanvasTextLayout.SetFontFamily(PhoneticText, fontFamilyCJK, fontFamilyWestern);
}
OriginalCanvasTextLayout = new CanvasTextLayout(control, OriginalText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = originalTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
};
OriginalCanvasTextLayout.SetFontFamily(OriginalText, fontFamilyCJK, fontFamilyWestern);
if (createTranslated && TranslatedText != "")
{
TranslatedCanvasTextLayout = new CanvasTextLayout(control, TranslatedText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = translatedTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
};
TranslatedCanvasTextLayout.SetFontFamily(TranslatedText, fontFamilyCJK, fontFamilyWestern);
}
}
public void DisposeTextGeometry()
{
PhoneticCanvasGeometry?.Dispose();
PhoneticCanvasGeometry = null;
OriginalCanvasGeometry?.Dispose();
OriginalCanvasGeometry = null;
TranslatedCanvasGeometry?.Dispose();
TranslatedCanvasGeometry = null;
}
public void RecreateTextGeometry()
{
DisposeTextGeometry();
if (PhoneticCanvasTextLayout != null)
{
PhoneticCanvasGeometry = CanvasGeometry.CreateText(PhoneticCanvasTextLayout);
}
if (OriginalCanvasTextLayout != null)
{
OriginalCanvasGeometry = CanvasGeometry.CreateText(OriginalCanvasTextLayout);
}
if (TranslatedCanvasTextLayout != null)
{
TranslatedCanvasGeometry = CanvasGeometry.CreateText(TranslatedCanvasTextLayout);
}
}
}
}

View File

@@ -0,0 +1,21 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class AlbumArtAreaEffectSettings : ObservableRecipient, ICloneable
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial ImageSwitchType ImageSwitchType { get; set; } = ImageSwitchType.Slide;
public object Clone()
{
return new AlbumArtAreaEffectSettings()
{
ImageSwitchType = this.ImageSwitchType,
};
}
}
}

View File

@@ -4,35 +4,39 @@ using System;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class AlbumArtLayoutSettings : ObservableRecipient, ICloneable
public partial class AlbumArtAreaStyleSettings : ObservableRecipient, ICloneable
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TextAlignmentType SongInfoAlignmentType { get; set; } = TextAlignmentType.Left;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAutoCoverImageHeight { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverImageHeight { get; set; } = 128;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverImageRadius { get; set; } = 12; // 12 % of the cover image size
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverImageShadowAmount { get; set; } = 12;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAutoSongInfoFontSize { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SongInfoFontSize { get; set; } = 18;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ShowTitle { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ShowArtists { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ShowAlbum { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int AlbumArtSize { get; set; } = 64;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoAlbumArtSize { get; set; } = true;
public AlbumArtLayoutSettings() { }
public AlbumArtAreaStyleSettings() { }
public object Clone()
{
return new AlbumArtLayoutSettings
return new AlbumArtAreaStyleSettings
{
SongInfoAlignmentType = this.SongInfoAlignmentType,
IsAutoCoverImageHeight = this.IsAutoCoverImageHeight,
CoverImageHeight = this.CoverImageHeight,
CoverImageRadius = this.CoverImageRadius,
CoverImageShadowAmount = this.CoverImageShadowAmount,
IsAutoSongInfoFontSize = this.IsAutoSongInfoFontSize,
SongInfoFontSize = this.SongInfoFontSize,
ShowTitle = this.ShowTitle,
ShowArtists = this.ShowArtists,
ShowAlbum = this.ShowAlbum,
AlbumArtSize = this.AlbumArtSize,
AutoAlbumArtSize = this.AutoAlbumArtSize,
};
}
}

View File

@@ -12,18 +12,14 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsPureColorOverlayEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PureColorOverlayOpacity { get; set; } = 100; // 100 % = 1.0
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsCoverOverlayEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlayBlurAmount { get; set; } = 100; // 100 % of the cover image size
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlayOpacity { get; set; } = 100; // 100 % = 1.0
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlaySpeed { get; set; } = 50; // 50 % of the base rotate speed
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverAcrylicEffectAmount { get; set; } = 0;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFluidOverlayEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int FluidOverlayOpacity { get; set; } = 100;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial PaletteGeneratorType PaletteGeneratorType { get; set; } = PaletteGeneratorType.MedianCut;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsSpectrumOverlayEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SpectrumPlacement SpectrumPlacement { get; set; } = SpectrumPlacement.Bottom;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SpectrumStyle SpectrumStyle { get; set; } = SpectrumStyle.Bar;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SpectrumCount { get; set; } = 128;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsSnowFlakeOverlayEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SnowFlakeOverlayAmount { get; set; } = 10;
@@ -43,12 +39,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
IsPureColorOverlayEnabled = this.IsPureColorOverlayEnabled,
PureColorOverlayOpacity = this.PureColorOverlayOpacity,
IsCoverOverlayEnabled = this.IsCoverOverlayEnabled,
CoverOverlayBlurAmount = this.CoverOverlayBlurAmount,
CoverOverlayOpacity = this.CoverOverlayOpacity,
CoverOverlaySpeed = this.CoverOverlaySpeed,
CoverAcrylicEffectAmount = this.CoverAcrylicEffectAmount,
IsFluidOverlayEnabled = this.IsFluidOverlayEnabled,
FluidOverlayOpacity = this.FluidOverlayOpacity,
PaletteGeneratorType = this.PaletteGeneratorType,

View File

@@ -6,25 +6,10 @@ namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class LyricsEffectSettings : ObservableRecipient, ICloneable
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsBlurAmount { get; set; } = 5;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsLineFadeEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsBlurEffectEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsGlowEffectEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LineRenderingType LyricsGlowEffectScope { get; set; } = LineRenderingType.CurrentChar;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsGlowEffectAmount { get; set; } = 8;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsShadowEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LineRenderingType LyricsShadowScope { get; set; } = LineRenderingType.LineStartToCurrentChar;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsShadowAmount { get; set; } = 8;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LineRenderingType OriginalLyricsHighlightScope { get; set; } = LineRenderingType.LineStartToCurrentChar;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PhoneticLyricsHighlightAmount { get; set; } = 60; // 100% 是上界
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int OriginalLyricsHighlightAmount { get; set; } = 100; // 100% 是上界
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int TranslatedLyricsHighlightAmount { get; set; } = 60; // 100% 是上界
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsScaleEffectEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFloatAnimationEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFloatAmount { get; set; } = 1;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial EasingType LyricsScrollEasingType { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollDuration { get; set; }
@@ -33,8 +18,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollTopDelay { get; set; } = 0;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollBottomDelay { get; set; } = 0;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsVerticalEdgeOpacity { get; set; } = 0; // 0% opacity
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFanLyricsEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int FanLyricsAngle { get; set; } = 30;
@@ -56,25 +39,10 @@ namespace BetterLyrics.WinUI3.Models.Settings
{
return new LyricsEffectSettings(this.LyricsScrollTopDuration, this.LyricsScrollDuration, this.LyricsScrollBottomDuration, this.LyricsScrollEasingType)
{
LyricsBlurAmount = this.LyricsBlurAmount,
IsLyricsLineFadeEnabled = this.IsLyricsLineFadeEnabled,
IsLyricsBlurEffectEnabled = this.IsLyricsBlurEffectEnabled,
IsLyricsGlowEffectEnabled = this.IsLyricsGlowEffectEnabled,
LyricsGlowEffectScope = this.LyricsGlowEffectScope,
LyricsGlowEffectAmount = this.LyricsGlowEffectAmount,
IsLyricsShadowEnabled = this.IsLyricsShadowEnabled,
LyricsShadowScope = this.LyricsShadowScope,
LyricsShadowAmount = this.LyricsShadowAmount,
OriginalLyricsHighlightScope = this.OriginalLyricsHighlightScope,
PhoneticLyricsHighlightAmount = this.PhoneticLyricsHighlightAmount,
OriginalLyricsHighlightAmount = this.OriginalLyricsHighlightAmount,
TranslatedLyricsHighlightAmount = this.TranslatedLyricsHighlightAmount,
IsLyricsScaleEffectEnabled = this.IsLyricsScaleEffectEnabled,
IsLyricsFloatAnimationEnabled = this.IsLyricsFloatAnimationEnabled,
LyricsFloatAmount = this.LyricsFloatAmount,
LyricsScrollEasingType = this.LyricsScrollEasingType,
LyricsScrollDuration = this.LyricsScrollDuration,
@@ -83,8 +51,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
LyricsScrollTopDelay = this.LyricsScrollTopDelay,
LyricsScrollBottomDelay = this.LyricsScrollBottomDelay,
LyricsVerticalEdgeOpacity = this.LyricsVerticalEdgeOpacity,
IsFanLyricsEnabled = this.IsFanLyricsEnabled,
FanLyricsAngle = this.FanLyricsAngle,

View File

@@ -14,21 +14,27 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PhoneticLyricsFontSize { get; set; } = 12;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int OriginalLyricsFontSize { get; set; } = 24;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int TranslatedLyricsFontSize { get; set; } = 12;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TextAlignmentType LyricsAlignmentType { get; set; } = TextAlignmentType.Left;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsBgFontOpacity { get; set; } = 30; // 30% opacity
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFontStrokeWidth { get; set; } = 0;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Color LyricsCustomBgFontColor { get; set; } = Colors.White;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Color LyricsCustomFgFontColor { get; set; } = Colors.White;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Color LyricsCustomStrokeFontColor { get; set; } = Colors.White;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsFontColorType LyricsBgFontColorType { get; set; } = LyricsFontColorType.AdaptiveGrayed;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsFontColorType LyricsFgFontColorType { get; set; } = LyricsFontColorType.AdaptiveGrayed;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsFontColorType LyricsStrokeFontColorType { get; set; } = LyricsFontColorType.AdaptiveGrayed;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsFontWeight LyricsFontWeight { get; set; } = LyricsFontWeight.Bold;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double LyricsLineSpacingFactor { get; set; } = 0.5;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsTranslationSeparator { get; set; } = StringHelper.NewLine;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsCJKFontFamily { get; set; } = FontHelper.SystemFontFamilies.FirstOrDefault() ?? "";
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsWesternFontFamily { get; set; } = FontHelper.SystemFontFamilies.FirstOrDefault() ?? "";
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PlayingLineTopOffset { get; set; } = 50; // 50 %
public LyricsStyleSettings() { }
public object Clone()
@@ -40,7 +46,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
OriginalLyricsFontSize = this.OriginalLyricsFontSize,
TranslatedLyricsFontSize = this.TranslatedLyricsFontSize,
LyricsAlignmentType = this.LyricsAlignmentType,
LyricsBgFontOpacity = this.LyricsBgFontOpacity,
LyricsFontStrokeWidth = this.LyricsFontStrokeWidth,
LyricsCustomBgFontColor = this.LyricsCustomBgFontColor,
LyricsCustomFgFontColor = this.LyricsCustomFgFontColor,
@@ -50,9 +55,10 @@ namespace BetterLyrics.WinUI3.Models.Settings
LyricsStrokeFontColorType = this.LyricsStrokeFontColorType,
LyricsFontWeight = this.LyricsFontWeight,
LyricsLineSpacingFactor = this.LyricsLineSpacingFactor,
LyricsTranslationSeparator = this.LyricsTranslationSeparator,
LyricsCJKFontFamily = this.LyricsCJKFontFamily,
LyricsWesternFontFamily = this.LyricsWesternFontFamily,
PlayingLineTopOffset = this.PlayingLineTopOffset,
};
}
}

View File

@@ -13,11 +13,10 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
[GeneratedRegex(@"(\[|\<)(\d*):(\d*)\.(\d*)(\]|\>)([^\[\]\<\>]*)")]
private static partial Regex SyllableRegex();
private void ParseLrc(string raw)
private void ParseLrc(string raw, bool single)
{
var lines = raw.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
var lrcLines =
new List<(int time, string text, List<(int time, string text)> syllables)>();
var lrcLines = new List<LyricsLine>();
// 支持 [mm:ss.xx]字、<mm:ss.xx>字,毫秒两位或三位
var syllableRegex = SyllableRegex();
@@ -25,27 +24,30 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
foreach (var line in lines)
{
var matches = syllableRegex.Matches(line);
var syllables = new List<(int, string)>();
var syllables = new List<LyricsSyllable>();
int startIndex = 0;
for (int i = 0; i < matches.Count; i++)
{
var m = matches[i];
int min = int.Parse(m.Groups[2].Value);
int sec = int.Parse(m.Groups[3].Value);
int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
var match = matches[i];
int min = int.Parse(match.Groups[2].Value);
int sec = int.Parse(match.Groups[3].Value);
int ms = int.Parse(match.Groups[4].Value.PadRight(3, '0'));
int totalMs = min * 60_000 + sec * 1000 + ms;
string text = m.Groups[6].Value;
string text = match.Groups[6].Value;
syllables.Add((totalMs, text));
syllables.Add(new LyricsSyllable { StartMs = totalMs, Text = text, StartIndex = startIndex });
startIndex += text.Length;
}
if (syllables.Count > 1)
{
lrcLines.Add(
(
syllables[0].Item1,
string.Concat(syllables.Select(s => s.Item2)),
syllables
)
);
lrcLines.Add(new LyricsLine
{
StartMs = syllables[0].StartMs,
OriginalText = string.Concat(syllables.Select(s => s.Text)),
LyricsSyllables = syllables
});
}
else
{
@@ -57,74 +59,56 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
int? lineStartTime = null;
if (bracketMatches.Count > 0)
{
var m = bracketMatches[0];
int min = int.Parse(m.Groups[1].Value);
int sec = int.Parse(m.Groups[2].Value);
int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
var match = bracketMatches[0];
int min = int.Parse(match.Groups[1].Value);
int sec = int.Parse(match.Groups[2].Value);
int ms = int.Parse(match.Groups[4].Value.PadRight(3, '0'));
lineStartTime = min * 60_000 + sec * 1000 + ms;
content = bracketRegex!.Replace(line, "").Trim();
if (content == "//") content = "";
lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
lrcLines.Add(new LyricsLine { StartMs = lineStartTime.Value, OriginalText = content });
}
}
}
// 按时间分组
var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList();
int languageCount = 0;
if (grouped != null && grouped.Count > 0)
if (single)
{
// 计算最大语言数量
languageCount = grouped.Max(g => g.Count());
LyricsDataArr.Add(new LyricsData(lrcLines));
}
// 初始化每种语言的歌词列表
int langStartIndex = LyricsDataArr.Count;
for (int i = 0; i < languageCount; i++) LyricsDataArr.Add(new LyricsData());
// 遍历每个时间分组
if (grouped != null)
else
{
foreach (var group in grouped)
// 按时间分组
var grouped = lrcLines.GroupBy(l => l.StartMs).OrderBy(g => g.Key).ToList();
int languageCount = 0;
if (grouped != null && grouped.Count > 0)
{
var linesInGroup = group.ToList();
for (int langIdx = 0; langIdx < languageCount; langIdx++)
// 计算最大语言数量
languageCount = grouped.Max(g => g.Count());
}
// 初始化每种语言的歌词列表
int langStartIndex = LyricsDataArr.Count;
for (int i = 0; i < languageCount; i++) LyricsDataArr.Add(new LyricsData());
// 遍历每个时间分组
if (grouped != null)
{
foreach (var group in grouped)
{
// 只添加有对应行的语言,否则跳过
if (langIdx < linesInGroup.Count)
var linesInGroup = group.ToList();
for (int langIdx = 0; langIdx < languageCount; langIdx++)
{
var (start, text, syllables) = linesInGroup[langIdx];
var line = new LyricsLine
// 只添加有对应行的语言,否则跳过
if (langIdx < linesInGroup.Count)
{
StartMs = start,
OriginalText = text,
LyricsChars = [],
};
if (syllables != null && syllables.Count > 0)
{
int currentIndex = 0;
for (int j = 0; j < syllables.Count; j++)
{
var (charStart, charText) = syllables[j];
int startIndex = currentIndex;
line.LyricsChars.Add(
new LyricsChar
{
StartMs = charStart,
Text = charText ?? "",
StartIndex = startIndex,
}
);
currentIndex += charText?.Length ?? 0;
}
var lyricsLine = linesInGroup[langIdx];
LyricsDataArr[langStartIndex + langIdx].LyricsLines.Add(lyricsLine);
}
LyricsDataArr[langStartIndex + langIdx].LyricsLines.Add(line);
// 没有翻译行则不补原文,直接跳过
}
// 没有翻译行则不补原文,直接跳过
}
}
}
}
}
}

View File

@@ -22,7 +22,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
StartMs = lineRead.StartTime ?? 0,
EndMs = lineRead.EndTime ?? 0,
OriginalText = lineRead.Text,
LyricsChars = [],
LyricsSyllables = [],
};
var syllables = (lineRead as Lyricify.Lyrics.Models.SyllableLineInfo)?.Syllables;
@@ -36,14 +36,14 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
)
{
var syllable = syllables[syllableIndex];
var charTiming = new LyricsChar
var charTiming = new LyricsSyllable
{
StartMs = syllable.StartTime,
EndMs = syllable.EndTime,
Text = syllable.Text,
StartIndex = startIndex,
};
lineWrite.LyricsChars.Add(charTiming);
lineWrite.LyricsSyllables.Add(charTiming);
startIndex += syllable.Text.Length;
}
}

View File

@@ -33,15 +33,9 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
.Where(s => s.Name.LocalName == "span")
.ToList();
var romanTextSpans = spans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-roman")
.ToList();
var originalTextSpans = spans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == null)
.ToList();
var translationTextSpans = spans
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
.ToList();
// 处理原文span后的空白
for (int i = 0; i < originalTextSpans.Count; i++)
@@ -56,7 +50,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
// 拼接空白字符后的原文
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
var originalCharTimings = new List<LyricsChar>();
var originalCharTimings = new List<LyricsSyllable>();
int originalStartIndex = 0;
foreach (var span in originalTextSpans)
{
@@ -64,7 +58,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
string? sEnd = span.Attribute("end")?.Value;
int sStartMs = ParseTtmlTime(sBegin);
int sEndMs = ParseTtmlTime(sEnd);
originalCharTimings.Add(new LyricsChar
originalCharTimings.Add(new LyricsSyllable
{
StartMs = sStartMs,
EndMs = sEndMs,
@@ -83,7 +77,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
StartMs = pStartMs,
EndMs = pEndMs,
OriginalText = originalText,
LyricsChars = originalCharTimings,
LyricsSyllables = originalCharTimings,
});
// 解析 x-role
@@ -114,7 +108,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
.ToList();
string text = string.Concat(textSpans.Select(s => s.Value));
var charTimings = new List<LyricsChar>();
var charTimings = new List<LyricsSyllable>();
int startIndex = 0;
foreach (var span in textSpans)
{
@@ -122,7 +116,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
string? sEnd = span.Attribute("end")?.Value;
int sStartMs = ParseTtmlTime(sBegin);
int sEndMs = ParseTtmlTime(sEnd);
charTimings.Add(new LyricsChar
charTimings.Add(new LyricsSyllable
{
StartMs = sStartMs,
EndMs = sEndMs,
@@ -138,7 +132,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
StartMs = pStartMs,
EndMs = pEndMs,
OriginalText = text,
LyricsChars = charTimings,
LyricsSyllables = charTimings,
});
}
}

View File

@@ -4,7 +4,9 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Parsers;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
@@ -12,14 +14,17 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
public partial class LyricsParser
{
private static readonly ILogger<LyricsParser> _logger = Ioc.Default.GetRequiredService<ILogger<LyricsParser>>();
public List<LyricsData> LyricsDataArr { get; private set; } = [];
public void Parse(SongInfo? songInfo, LyricsSearchResult? lyricsSearchResult)
{
_logger.LogInformation("LyricsParser.Parse");
LyricsDataArr = [];
if (lyricsSearchResult?.Raw == null)
if (string.IsNullOrWhiteSpace(lyricsSearchResult?.Raw))
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder((int)(songInfo?.DurationMs ?? 0)));
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
}
else
{
@@ -27,7 +32,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
case LyricsFormat.Lrc:
case LyricsFormat.Eslrc:
ParseLrc(lyricsSearchResult.Raw);
ParseLrc(lyricsSearchResult.Raw, lyricsSearchResult.Provider.IsRemote());
break;
case LyricsFormat.Qrc:
ParseQrcKrc(QrcParser.Parse(lyricsSearchResult.Raw).Lines);
@@ -41,6 +46,11 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
default:
break;
}
if (LyricsDataArr.Count == 0)
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
}
}
LoadTranslation(lyricsSearchResult);
LoadTransliteration(lyricsSearchResult);
@@ -49,14 +59,14 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
private void LoadTranslation(LyricsSearchResult? lyricsSearchResult)
{
if (lyricsSearchResult?.Translation != null)
if (!string.IsNullOrWhiteSpace(lyricsSearchResult?.Translation))
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.QQ:
case LyricsSearchProvider.Kugou:
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Translation);
ParseLrc(lyricsSearchResult.Translation, true);
break;
default:
break;
@@ -66,12 +76,12 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
private void LoadTransliteration(LyricsSearchResult? lyricsSearchResult)
{
if (lyricsSearchResult?.Transliteration != null)
if (!string.IsNullOrWhiteSpace(lyricsSearchResult?.Transliteration))
{
switch (lyricsSearchResult.Provider)
{
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Transliteration);
ParseLrc(lyricsSearchResult.Transliteration, true);
LyricsDataArr.LastOrDefault()?.LanguageCode = PhoneticHelper.RomanCode;
break;
default:
@@ -102,7 +112,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToPinyin(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
LyricsSyllables = line.LyricsSyllables.Select(c => new LyricsSyllable
{
StartMs = c.StartMs,
EndMs = c.EndMs,
@@ -123,7 +133,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToJyutping(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
LyricsSyllables = line.LyricsSyllables.Select(c => new LyricsSyllable
{
StartMs = c.StartMs,
EndMs = c.EndMs,
@@ -147,7 +157,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToRomaji(line.OriginalText),
LyricsChars = line.LyricsChars.Select(c => new LyricsChar
LyricsSyllables = line.LyricsSyllables.Select(c => new LyricsSyllable
{
StartMs = c.StartMs,
EndMs = c.EndMs,

View File

@@ -0,0 +1,110 @@
using BetterLyrics.WinUI3.Extensions;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI;
namespace BetterLyrics.WinUI3.Renderer
{
public partial class FluidBackgroundRenderer : IDisposable
{
private PixelShaderEffect? _fluidEffect;
private float _timeAccumulator = 0f;
private Vector3 _c1, _c2, _c3, _c4;
public bool IsEnabled { get; set; } = false;
public double Opacity { get; set; } = 1.0;
public bool EnableLightWave { get; set; } = false;
public async Task LoadResourcesAsync()
{
Dispose();
try
{
var uri = new Uri("ms-appx:///Assets/FluidEffect.bin");
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(uri);
using (var stream = await file.OpenReadAsync())
{
var buffer = new Windows.Storage.Streams.Buffer((uint)stream.Size);
await stream.ReadAsync(buffer, (uint)stream.Size, Windows.Storage.Streams.InputStreamOptions.None);
byte[] bytes = buffer.ToArray();
_fluidEffect = new PixelShaderEffect(bytes);
_fluidEffect.Properties["EnableLightWave"] = EnableLightWave;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[FluidRenderer] Load Failed: {ex.Message}");
_fluidEffect = null;
}
}
public void UpdateColors(Color c1, Color c2, Color c3, Color c4)
{
_c1 = c1.ToVector3RGB();
_c2 = c2.ToVector3RGB();
_c3 = c3.ToVector3RGB();
_c4 = c4.ToVector3RGB();
}
public void Update(TimeSpan deltaTime)
{
if (_fluidEffect == null || !IsEnabled) return;
_timeAccumulator += (float)deltaTime.TotalSeconds;
_fluidEffect.Properties["iTime"] = _timeAccumulator;
_fluidEffect.Properties["color1"] = _c1;
_fluidEffect.Properties["color2"] = _c2;
_fluidEffect.Properties["color3"] = _c3;
_fluidEffect.Properties["color4"] = _c4;
_fluidEffect.Properties["EnableLightWave"] = EnableLightWave;
}
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
if (_fluidEffect == null || !IsEnabled || Opacity <= 0) return;
float pixelWidth = control.ConvertDipsToPixels((float)control.Size.Width, CanvasDpiRounding.Round);
float pixelHeight = control.ConvertDipsToPixels((float)control.Size.Height, CanvasDpiRounding.Round);
_fluidEffect.Properties["Width"] = pixelWidth;
_fluidEffect.Properties["Height"] = pixelHeight;
if (Opacity >= 1.0)
{
ds.DrawImage(_fluidEffect);
}
else
{
using (var opacityEffect = new OpacityEffect
{
Source = _fluidEffect,
Opacity = (float)Opacity
})
{
ds.DrawImage(opacityEffect);
}
}
}
public void Dispose()
{
_fluidEffect?.Dispose();
_fluidEffect = null;
}
}
}

View File

@@ -0,0 +1,49 @@
using BetterLyrics.WinUI3.Shaders;
using ComputeSharp.D2D1.WinUI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
namespace BetterLyrics.WinUI3.Renderer
{
public partial class FogRenderer : IDisposable
{
private PixelShaderEffect<FogEffect>? _fogEffect;
private float _timeAccumulator = 0f;
public bool IsEnabled { get; set; } = false;
public void LoadResources()
{
Dispose();
_fogEffect = new PixelShaderEffect<FogEffect>();
}
public void Update(double deltaTime)
{
if (_fogEffect == null || !IsEnabled) return;
_timeAccumulator += (float)deltaTime;
}
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
if (_fogEffect == null || !IsEnabled) return;
float width = control.ConvertDipsToPixels((float)control.Size.Width, CanvasDpiRounding.Round);
float height = control.ConvertDipsToPixels((float)control.Size.Height, CanvasDpiRounding.Round);
_fogEffect.ConstantBuffer = new FogEffect(
_timeAccumulator,
new float2(width, height)
);
ds.DrawImage(_fogEffect);
}
public void Dispose()
{
_fogEffect?.Dispose();
_fogEffect = null;
}
}
}

View File

@@ -0,0 +1,263 @@
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Windows.UI;
namespace BetterLyrics.WinUI3.Renderer
{
public class LyricsRenderer
{
private readonly PlayingLineRenderer _playingRenderer = new();
private readonly UnplayingLineRenderer _unplayingRenderer = new();
private Matrix4x4 _threeDimMatrix = Matrix4x4.Identity;
public void Draw(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
IList<RenderLyricsLine>? lines,
int playingLineIndex,
int mouseHoverLineIndex,
bool isMousePressing,
int startVisibleIndex,
int endVisibleIndex,
double lyricsX,
double lyricsY,
double lyricsWidth,
double lyricsHeight,
double userScrollOffset,
double lyricsOpacity,
double playingLineTopOffsetFactor,
LyricsWindowStatus windowStatus,
Color strokeColor,
Color bgColor,
Color fgColor,
Func<int, LinePlaybackState> getPlaybackState)
{
using (var opacityLayer = ds.CreateLayer((float)lyricsOpacity))
{
if (windowStatus.LyricsEffectSettings.Is3DLyricsEnabled)
{
using (var layer = new CanvasCommandList(control))
{
using (var layerDs = layer.CreateDrawingSession())
{
DrawLyrics(
control,
layerDs,
lines,
playingLineIndex,
mouseHoverLineIndex,
isMousePressing,
startVisibleIndex,
endVisibleIndex,
lyricsX,
lyricsY,
lyricsWidth,
lyricsHeight,
userScrollOffset,
playingLineTopOffsetFactor,
windowStatus,
strokeColor,
bgColor,
fgColor,
getPlaybackState);
}
ds.DrawImage(new Transform3DEffect
{
Source = layer,
TransformMatrix = _threeDimMatrix
});
}
}
else
{
DrawLyrics(
control,
ds,
lines,
playingLineIndex,
mouseHoverLineIndex,
isMousePressing,
startVisibleIndex,
endVisibleIndex,
lyricsX,
lyricsY,
lyricsWidth,
lyricsHeight,
userScrollOffset,
playingLineTopOffsetFactor,
windowStatus,
strokeColor,
bgColor,
fgColor,
getPlaybackState);
}
}
}
private void DrawLyrics(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
IList<RenderLyricsLine>? lines,
int playingLineIndex,
int mouseHoverLineIndex,
bool isMousePressing,
int startVisibleIndex,
int endVisibleIndex,
double lyricsX,
double lyricsY,
double lyricsWidth,
double lyricsHeight,
double userScrollOffset,
double playingLineTopOffsetFactor,
LyricsWindowStatus windowStatus,
Color strokeColor,
Color bgColor,
Color fgColor,
Func<int, LinePlaybackState> getPlaybackState)
{
if (lines == null) return;
var currentPlayingLine = lines.ElementAtOrDefault(playingLineIndex);
if (currentPlayingLine == null) return;
var effectSettings = windowStatus.LyricsEffectSettings;
var styleSettings = windowStatus.LyricsStyleSettings;
var rotationY = currentPlayingLine.OriginalPosition.WithX(effectSettings.FanLyricsAngle < 0 ? (float)lyricsWidth : 0);
for (int i = startVisibleIndex; i <= endVisibleIndex; i++)
{
var line = lines.ElementAtOrDefault(i);
if (line == null) continue;
if (line.OriginalCanvasTextLayout == null) continue;
if (line.OriginalCanvasTextLayout.LayoutBounds.Width <= 0) continue;
double yOffset = line.YOffsetTransition.Value + userScrollOffset + lyricsY + lyricsHeight * playingLineTopOffsetFactor;
var transform =
Matrix3x2.CreateScale((float)line.ScaleTransition.Value, line.CenterPosition) *
Matrix3x2.CreateRotation((float)line.AngleTransition.Value, rotationY) *
Matrix3x2.CreateTranslation((float)lyricsX, (float)yOffset);
ds.Transform = transform;
using (var textOnlyLayer = RenderBaseTextLayer(control, line, styleSettings.LyricsFontStrokeWidth, strokeColor, line.ColorTransition.Value))
{
if (i == playingLineIndex)
{
var state = getPlaybackState(i);
_playingRenderer.Draw(control, ds, textOnlyLayer, line, state, bgColor, fgColor, effectSettings);
}
else
{
_unplayingRenderer.Draw(ds, textOnlyLayer, line);
}
if (i == mouseHoverLineIndex)
{
byte opacity = isMousePressing ? (byte)32 : (byte)16;
double scale = isMousePressing ? 1.09 : 1.10;
ds.FillRoundedRectangle(
new Windows.Foundation.Rect(line.TopLeftPosition.ToPoint().WithX(0), line.BottomRightPosition.ToPoint().WithX(lyricsWidth)).Scale(scale),
8, 8, Color.FromArgb(opacity, 255, 255, 255));
}
}
ds.Transform = Matrix3x2.Identity;
}
}
private CanvasCommandList RenderBaseTextLayer(
ICanvasResourceCreator resourceCreator,
RenderLyricsLine line,
double strokeWidth,
Color strokeColor,
Color fillColor)
{
var commandList = new CanvasCommandList(resourceCreator);
using (var clds = commandList.CreateDrawingSession())
{
if (strokeWidth > 0)
{
DrawGeometrySafely(clds, line.PhoneticCanvasGeometry, line.PhoneticPosition, strokeColor, strokeWidth);
DrawGeometrySafely(clds, line.OriginalCanvasGeometry, line.OriginalPosition, strokeColor, strokeWidth);
DrawGeometrySafely(clds, line.TranslatedCanvasGeometry, line.TranslatedPosition, strokeColor, strokeWidth);
}
DrawTextLayoutSafely(clds, line.PhoneticCanvasTextLayout, line.PhoneticPosition, fillColor);
DrawTextLayoutSafely(clds, line.OriginalCanvasTextLayout, line.OriginalPosition, fillColor);
DrawTextLayoutSafely(clds, line.TranslatedCanvasTextLayout, line.TranslatedPosition, fillColor);
}
return commandList;
}
private void DrawGeometrySafely(CanvasDrawingSession ds, CanvasGeometry? geo, Vector2 pos, Color color, double width)
{
if (geo == null) return;
try
{
ds.DrawGeometry(geo, pos, color, (float)width);
}
catch (Exception) { }
}
private void DrawTextLayoutSafely(CanvasDrawingSession ds, CanvasTextLayout? layout, Vector2 pos, Color color)
{
if (layout == null) return;
try
{
ds.DrawTextLayout(layout, pos, color);
}
catch (Exception) { }
}
public void CalculateLyrics3DMatrix(LyricsEffectSettings lyricsEffect, double lyricsX, double lyricsY, double lyricsWidth, double canvasHeight)
{
if (!lyricsEffect.Is3DLyricsEnabled) return;
Vector3 center = new(
(float)(lyricsX + lyricsWidth / 2),
(float)(lyricsY + canvasHeight / 2),
0);
float rotationX = (float)(Math.PI * lyricsEffect.Lyrics3DXAngle / 180.0);
float rotationY = (float)(Math.PI * lyricsEffect.Lyrics3DYAngle / 180.0);
float rotationZ = (float)(Math.PI * lyricsEffect.Lyrics3DZAngle / 180.0);
Matrix4x4 rotation =
Matrix4x4.CreateRotationX(rotationX) *
Matrix4x4.CreateRotationY(rotationY) *
Matrix4x4.CreateRotationZ(rotationZ);
Matrix4x4 perspective = Matrix4x4.Identity;
perspective.M34 = 1.0f / lyricsEffect.Lyrics3DDepth;
// 组合变换:
// 1. 将中心移到原点
// 2. 旋转
// 3. 应用透视
// 4. 将中心移回原位
_threeDimMatrix =
Matrix4x4.CreateTranslation(-center) *
rotation *
perspective *
Matrix4x4.CreateTranslation(center);
}
}
}

View File

@@ -1,40 +0,0 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
namespace BetterLyrics.WinUI3.Renderer
{
public sealed partial class LyricsRenderer : UserControl
{
public LyricsRendererViewModel ViewModel { get; set; }
public LyricsRenderer()
{
InitializeComponent();
ViewModel = Ioc.Default.GetRequiredService<LyricsRendererViewModel>();
}
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)
{
ViewModel.Update(sender, args);
}
private void LyricsCanvas_Unloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
LyricsCanvas.RemoveFromVisualTree();
LyricsCanvas = null;
}
private void LyricsCanvas_CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
ViewModel.CreateResources(sender, args);
}
}
}

View File

@@ -0,0 +1,303 @@
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Linq;
using System.Numerics;
using Windows.Foundation;
using Windows.UI;
namespace BetterLyrics.WinUI3.Renderer
{
public class PlayingLineRenderer
{
public void Draw(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
ICanvasImage textOnlyLayer,
RenderLyricsLine line,
LinePlaybackState playbackState,
Color bgColor,
Color fgColor,
LyricsEffectSettings settings)
{
DrawPhonetic(ds, textOnlyLayer, line);
DrawOriginalText(control, ds, textOnlyLayer, line, playbackState, bgColor, fgColor, settings);
DrawTranslated(ds, textOnlyLayer, line);
}
private void DrawPhonetic(CanvasDrawingSession ds, ICanvasImage source, RenderLyricsLine line)
{
if (line.PhoneticCanvasTextLayout == null) return;
var opacity = line.PhoneticOpacityTransition.Value;
var blur = line.BlurAmountTransition.Value;
var bounds = line.PhoneticCanvasTextLayout.LayoutBounds;
var destRect = new Rect(
bounds.X + line.PhoneticPosition.X,
bounds.Y + line.PhoneticPosition.Y,
bounds.Width,
bounds.Height
);
ds.DrawImage(new OpacityEffect
{
Source = new GaussianBlurEffect
{
BlurAmount = (float)blur,
Source = new CropEffect
{
Source = source,
BorderMode = EffectBorderMode.Hard,
SourceRectangle = destRect,
},
BorderMode = EffectBorderMode.Soft
},
Opacity = (float)opacity,
});
}
private void DrawTranslated(CanvasDrawingSession ds, ICanvasImage source, RenderLyricsLine line)
{
if (line.TranslatedCanvasTextLayout == null) return;
var opacity = line.TranslatedOpacityTransition.Value;
var blur = line.BlurAmountTransition.Value;
var bounds = line.TranslatedCanvasTextLayout.LayoutBounds;
var destRect = new Rect(
bounds.X + line.TranslatedPosition.X,
bounds.Y + line.TranslatedPosition.Y,
bounds.Width,
bounds.Height
);
ds.DrawImage(new OpacityEffect
{
Source = new GaussianBlurEffect
{
BlurAmount = (float)blur,
Source = new CropEffect
{
Source = source,
BorderMode = EffectBorderMode.Hard,
SourceRectangle = destRect,
},
BorderMode = EffectBorderMode.Soft
},
Opacity = (float)opacity,
});
}
private void DrawOriginalText(
ICanvasResourceCreator resourceCreator,
CanvasDrawingSession ds,
ICanvasImage source,
RenderLyricsLine line,
LinePlaybackState state,
Color bgColor,
Color fgColor,
LyricsEffectSettings settings)
{
if (line.OriginalCanvasTextLayout == null) return;
var curCharIndex = state.SyllableStartIndex + state.SyllableLength * state.SyllableProgress;
float fadeWidth = (1f / Math.Max(1, line.OriginalText.Length)) * 0.5f;
var lineRegions = line.OriginalCanvasTextLayout.GetCharacterRegions(0, line.OriginalText.Length);
foreach (var subLineRegion in lineRegions)
{
DrawSubLineRegion(resourceCreator, ds, source, line, subLineRegion, curCharIndex, fadeWidth, bgColor, fgColor, state, settings);
}
}
private void DrawSubLineRegion(
ICanvasResourceCreator resourceCreator,
CanvasDrawingSession ds,
ICanvasImage source,
RenderLyricsLine line,
CanvasTextLayoutRegion subLineRegion,
double curCharIndex,
float fadeWidth,
Color bgColor,
Color fgColor,
LinePlaybackState state,
LyricsEffectSettings settings)
{
var blur = line.BlurAmountTransition.Value;
var playedOpacity = line.PlayedOriginalOpacityTransition.Value;
var unplayedOpacity = line.UnplayedOriginalOpacityTransition.Value;
var subLineLayoutBounds = subLineRegion.LayoutBounds;
Rect subLineRect = new(
subLineLayoutBounds.X + line.OriginalPosition.X,
subLineLayoutBounds.Y + line.OriginalPosition.Y,
subLineLayoutBounds.Width,
subLineLayoutBounds.Height
);
using (var gradientLayer = new CanvasCommandList(resourceCreator))
{
using (var gradientLayerDs = gradientLayer.CreateDrawingSession())
{
float progressInRegion = (float)((curCharIndex - subLineRegion.CharacterIndex) / subLineRegion.CharacterCount);
progressInRegion = Math.Clamp(progressInRegion, 0, 1 + fadeWidth);
var stop1 = fgColor.WithAlpha((byte)(255 * playedOpacity));
var stop2 = bgColor.WithAlpha((byte)(255 * unplayedOpacity));
using (var gradientBrush = new CanvasLinearGradientBrush(resourceCreator,
[
new CanvasGradientStop { Position = 0, Color = stop1 },
new CanvasGradientStop { Position = progressInRegion, Color = stop1 },
// 这里做判断是防止子行未播放时左侧出现渐变的问题
new CanvasGradientStop { Position = progressInRegion == 0 ? 0 : (progressInRegion + fadeWidth), Color = stop2 },
new CanvasGradientStop { Position = 1 + fadeWidth, Color = stop2 }
]))
{
gradientBrush.StartPoint = new Vector2((float)subLineRect.X, (float)subLineRect.Y);
gradientBrush.EndPoint = new Vector2((float)(subLineRect.X + subLineRect.Width), (float)subLineRect.Y);
gradientLayerDs.FillRectangle(subLineRect, gradientBrush);
}
}
// 这里 gradientLayer 上色的时候已经限制了 Rect 区域,不用再套一个 CropEffect
using (var textWithOpacityLayer = new AlphaMaskEffect
{
Source = source,
AlphaMask = gradientLayer
})
{
if (!settings.IsLyricsFloatAnimationEnabled && !settings.IsLyricsGlowEffectEnabled && !settings.IsLyricsScaleEffectEnabled)
{
ds.DrawImage(textWithOpacityLayer);
}
else
{
int endCharIndex = subLineRegion.CharacterIndex + subLineRegion.CharacterCount;
for (int i = subLineRegion.CharacterIndex; i < endCharIndex; i++)
{
DrawSingleCharacter(ds, line, i, curCharIndex, textWithOpacityLayer, state, settings);
}
}
}
}
}
private void DrawSingleCharacter(
CanvasDrawingSession ds,
RenderLyricsLine line,
int charIndex,
double exactProgressIndex,
ICanvasImage source,
LinePlaybackState state,
LyricsEffectSettings settings)
{
var curCharIndexInt = (int)Math.Floor(exactProgressIndex);
if (line.OriginalCanvasTextLayout == null) return;
var charRegions = line.OriginalCanvasTextLayout.GetCharacterRegions(charIndex, 1);
if (charRegions.Length == 0) return;
var charRegion = charRegions[0];
var charLayoutBounds = charRegion.LayoutBounds;
var sourceCharRect = new Rect(
charLayoutBounds.X + line.OriginalPosition.X,
charLayoutBounds.Y + line.OriginalPosition.Y,
charLayoutBounds.Width,
charLayoutBounds.Height
);
double floatOffset = 0;
double scale = 1;
double glow = 0;
bool drawGlow = false;
if (settings.IsLyricsFloatAnimationEnabled)
{
double targetFloatOffset = sourceCharRect.Height * 0.1;
// 已经浮完了的
if (charIndex < curCharIndexInt)
{
floatOffset = 0;
}
// 正在浮的
else if (charIndex == curCharIndexInt)
{
var p = exactProgressIndex - curCharIndexInt;
floatOffset = -targetFloatOffset + p * targetFloatOffset;
}
// 还没浮的
else
{
floatOffset = -targetFloatOffset;
}
// 制造句间上浮过度动画,这里用任何一个 Transition 都行,主要是获取当前行的进入视野的 Progress
floatOffset *= line.YOffsetTransition.Progress;
}
var parentSyllable = line.LyricsSyllables.FirstOrDefault(x => x.StartIndex <= charIndex && charIndex < x.StartIndex + x.Text.Length);
if (parentSyllable != null && parentSyllable.IsLongDuration && parentSyllable.StartIndex == state.SyllableStartIndex)
{
if (settings.IsLyricsScaleEffectEnabled)
{
scale += Math.Sin(state.SyllableProgress * Math.PI) * 0.15;
}
if (settings.IsLyricsGlowEffectEnabled)
{
glow = Math.Sin(state.SyllableProgress * Math.PI) * sourceCharRect.Height * 0.2;
drawGlow = true;
}
}
var destCharRect = sourceCharRect.Scale(scale).AddY(-floatOffset);
if (drawGlow)
{
var sourcePlayedCharRect = new Rect(
sourceCharRect.X,
sourceCharRect.Y,
sourceCharRect.Width,
sourceCharRect.Height
);
if (charIndex == curCharIndexInt)
{
var p = exactProgressIndex - curCharIndexInt;
sourcePlayedCharRect.Width *= p;
}
else if (charIndex > curCharIndexInt)
{
sourcePlayedCharRect.Width = 0;
}
using (var glowEffect = new GaussianBlurEffect
{
Source = new CropEffect
{
Source = source,
SourceRectangle = sourcePlayedCharRect,
BorderMode = EffectBorderMode.Hard
},
BlurAmount = (float)glow,
BorderMode = EffectBorderMode.Soft
})
{
ds.DrawImage(glowEffect, destCharRect.Extend(sourceCharRect.Height), sourceCharRect.Extend(sourceCharRect.Height));
}
}
ds.DrawImage(source, destCharRect, sourceCharRect);
}
}
}

View File

@@ -0,0 +1,25 @@
using BetterLyrics.WinUI3.Extensions;
using Microsoft.Graphics.Canvas;
using Windows.Foundation;
using Windows.UI;
namespace BetterLyrics.WinUI3.Renderer
{
public class PureColorBackgroundRenderer
{
public void Draw(
CanvasDrawingSession ds,
Rect bounds,
Color color,
double opacity,
bool isEnabled)
{
if (!isEnabled || opacity <= 0) return;
ds.FillRectangle(
bounds,
color.WithAlpha((byte)(opacity * 255))
);
}
}
}

View File

@@ -0,0 +1,53 @@
using BetterLyrics.WinUI3.Shaders;
using ComputeSharp.D2D1.WinUI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
namespace BetterLyrics.WinUI3.Renderer
{
public partial class SnowRenderer : IDisposable
{
private PixelShaderEffect<SnowEffect>? _snowEffect;
private float _timeAccumulator = 0f;
public bool IsEnabled { get; set; } = false;
public float Amount { get; set; } = 0.5f;
public float Speed { get; set; } = 1.0f;
public void LoadResources()
{
Dispose();
_snowEffect = new PixelShaderEffect<SnowEffect>();
}
public void Update(double deltaTime)
{
if (_snowEffect == null || !IsEnabled) return;
_timeAccumulator += (float)deltaTime;
}
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
if (_snowEffect == null || !IsEnabled) return;
float width = control.ConvertDipsToPixels((float)control.Size.Width, CanvasDpiRounding.Round);
float height = control.ConvertDipsToPixels((float)control.Size.Height, CanvasDpiRounding.Round);
_snowEffect.ConstantBuffer = new SnowEffect(
_timeAccumulator,
new float2(width, height),
Amount, // 0.0 ~ 1.0
Speed
);
ds.DrawImage(_snowEffect);
}
public void Dispose()
{
_snowEffect?.Dispose();
_snowEffect = null;
}
}
}

View File

@@ -0,0 +1,195 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.UI;
using System;
using System.Numerics;
using Windows.UI;
namespace BetterLyrics.WinUI3.Renderer
{
public partial class SpectrumRenderer : IDisposable
{
private CanvasGeometry? _spectrumGeometry;
public void Draw(
ICanvasResourceCreator resourceCreator,
CanvasDrawingSession ds,
float[]? spectrumData,
int barCount,
bool isEnabled,
SpectrumPlacement placement,
SpectrumStyle style,
double canvasWidth,
double canvasHeight,
Color fillColor
)
{
_spectrumGeometry?.Dispose();
_spectrumGeometry = null;
if (!isEnabled || spectrumData == null || spectrumData.Length == 0) return;
_spectrumGeometry = CreateGeometry(resourceCreator, spectrumData, barCount, placement, style, canvasWidth, canvasHeight);
if (_spectrumGeometry != null)
{
DrawGeometry(ds, _spectrumGeometry, fillColor, placement, canvasHeight);
}
}
private CanvasGeometry? CreateGeometry(
ICanvasResourceCreator creator,
float[] data,
int barCount,
SpectrumPlacement placement,
SpectrumStyle style,
double width,
double height)
{
if (barCount < 2) return null;
float maxDataVal = 0;
int checkCount = Math.Min(barCount, data.Length);
for (int i = 0; i < checkCount; i++)
{
if (data[i] > maxDataVal) maxDataVal = data[i];
}
float limitY = (float)height * 0.2f; // 高度限制为总高度的 20%
float scaleRatio = 1.0f;
if (maxDataVal > limitY)
{
scaleRatio = limitY / maxDataVal;
}
using var pathBuilder = new CanvasPathBuilder(creator);
if (style == SpectrumStyle.Bar)
{
float totalStep = (float)width / barCount;
float gap = 2.0f;
float barWidth = totalStep - gap;
if (barWidth < 1.0f) { barWidth = totalStep; gap = 0f; }
for (int i = 0; i < barCount; i++)
{
float rawVal = i < data.Length ? data[i] : 0;
float barHeight = rawVal * scaleRatio;
if (barHeight < 0.5f) continue;
float x = i * totalStep;
float topY, bottomY;
if (placement == SpectrumPlacement.Top)
{
topY = 0;
bottomY = barHeight;
}
else // Bottom
{
topY = (float)height - barHeight;
bottomY = (float)height;
}
// 绘制独立矩形
pathBuilder.BeginFigure(new Vector2(x, topY));
pathBuilder.AddLine(new Vector2(x + barWidth, topY));
pathBuilder.AddLine(new Vector2(x + barWidth, bottomY));
pathBuilder.AddLine(new Vector2(x, bottomY));
pathBuilder.EndFigure(CanvasFigureLoop.Closed);
}
}
else
{
var points = new Vector2[barCount];
float pointSpacing = (float)width / (barCount - 1);
for (int i = 0; i < barCount; i++)
{
float rawVal = i < data.Length ? data[i] : 0;
float y = rawVal * scaleRatio;
// 处理翻转
if (placement == SpectrumPlacement.Bottom)
{
y = (float)height - y;
}
points[i] = new Vector2(i * pointSpacing, y);
}
// 绘制曲线
pathBuilder.BeginFigure(points[0]);
for (int i = 0; i < barCount - 1; i++)
{
Vector2 p0 = points[Math.Max(i - 1, 0)];
Vector2 p1 = points[i];
Vector2 p2 = points[i + 1];
Vector2 p3 = points[Math.Min(i + 2, barCount - 1)];
Vector2 cp1 = p1 + (p2 - p0) / 6.0f;
Vector2 cp2 = p2 - (p3 - p1) / 6.0f;
pathBuilder.AddCubicBezier(cp1, cp2, p2);
}
// 封口
if (placement == SpectrumPlacement.Top)
{
pathBuilder.AddLine(new Vector2(points[barCount - 1].X, 0));
pathBuilder.AddLine(new Vector2(points[0].X, 0));
}
else
{
pathBuilder.AddLine(new Vector2(points[barCount - 1].X, (float)height));
pathBuilder.AddLine(new Vector2(points[0].X, (float)height));
}
pathBuilder.EndFigure(CanvasFigureLoop.Closed);
}
return CanvasGeometry.CreatePath(pathBuilder);
}
private void DrawGeometry(
CanvasDrawingSession ds,
CanvasGeometry geometry,
Color color,
SpectrumPlacement placement,
double height)
{
var stops = new CanvasGradientStop[]
{
new() { Position = 0.0f, Color = Colors.Transparent },
new() { Position = 0.7f, Color = Colors.Transparent },
new() { Position = 1.0f, Color = color }
};
using var brush = new CanvasLinearGradientBrush(ds, stops);
if (placement == SpectrumPlacement.Top)
{
brush.StartPoint = new Vector2(0, (float)height);
brush.EndPoint = new Vector2(0, 0);
}
else
{
brush.StartPoint = new Vector2(0, 0);
brush.EndPoint = new Vector2(0, (float)height);
}
ds.FillGeometry(geometry, brush);
}
public void Dispose()
{
_spectrumGeometry?.Dispose();
_spectrumGeometry = null;
}
}
}

View File

@@ -0,0 +1,94 @@
using BetterLyrics.WinUI3.Models;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Text;
using System.Numerics;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Renderer
{
public class UnplayingLineRenderer
{
public void Draw(
CanvasDrawingSession ds,
ICanvasImage textOnlyLayer,
RenderLyricsLine line)
{
var blurAmount = (float)line.BlurAmountTransition.Value;
if (line.PhoneticCanvasTextLayout != null)
{
var opacity = line.PhoneticOpacityTransition.Value;
DrawPart(ds, textOnlyLayer,
line.PhoneticCanvasTextLayout,
line.PhoneticPosition,
blurAmount,
(float)opacity);
}
if (line.OriginalCanvasTextLayout != null)
{
double opacity;
if (line.PlayedOriginalOpacityTransition.StartValue > line.UnplayedOriginalOpacityTransition.StartValue)
{
opacity = line.PlayedOriginalOpacityTransition.Value;
}
else
{
opacity = line.UnplayedOriginalOpacityTransition.Value;
}
DrawPart(ds, textOnlyLayer,
line.OriginalCanvasTextLayout,
line.OriginalPosition,
blurAmount,
(float)opacity);
}
if (line.TranslatedCanvasTextLayout != null)
{
var opacity = line.TranslatedOpacityTransition.Value;
DrawPart(ds, textOnlyLayer,
line.TranslatedCanvasTextLayout,
line.TranslatedPosition,
blurAmount,
(float)opacity);
}
}
private void DrawPart(
CanvasDrawingSession ds,
ICanvasImage source,
CanvasTextLayout layout,
Vector2 position,
float blur,
float opacity)
{
if (opacity <= 0) return;
var bounds = layout.LayoutBounds;
var destRect = new Rect(
bounds.X + position.X,
bounds.Y + position.Y,
bounds.Width,
bounds.Height
);
ds.DrawImage(new OpacityEffect
{
Source = new GaussianBlurEffect
{
BlurAmount = blur,
Source = new CropEffect
{
Source = source,
SourceRectangle = destRect,
BorderMode = EffectBorderMode.Hard,
},
BorderMode = EffectBorderMode.Soft
},
Opacity = opacity
});
}
}
}

View File

@@ -26,7 +26,7 @@ namespace BetterLyrics.WinUI3.Services.DiscordService
{
StatusDisplay = StatusDisplayType.Details,
Type = ActivityType.Listening,
Buttons = new Button[] { new() { Label = "Get this status", Url = Constants.Link.MicrosoftStoreUrl } },
Buttons = new Button[] { new() { Label = "Get this status", Url = Constants.Link.MicrosoftStore } },
Assets = new Assets
{
LargeImageKey = "banner",

View File

@@ -45,21 +45,21 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
LiveStates.LyricsWindowStatus.UpdateMonitorBounds();
WindowHook.SetIsWorkArea<LyricsWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
WindowHook.SetIsWorkArea<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
if (LiveStates.LyricsWindowStatus.IsWorkArea)
{
WindowHook.UpdateWorkArea<LyricsWindow>();
WindowHook.UpdateWorkArea<NowPlayingWindow>();
}
await Task.Delay(300);
WindowHook.SetIsShowInSwitchers<LyricsWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
WindowHook.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
WindowHook.SetIsShowInSwitchers<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
WindowHook.SetIsAlwaysOnTop<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
WindowHook.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
WindowHook.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
WindowHook.SetIsClickThrough<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
WindowHook.SetIsBorderless<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
WindowHook.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
WindowHook.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
WindowHook.SetTitleBarArea<NowPlayingWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
// 下述代码可以删除,但是为了避免给用户造成操作上的疑虑,暂时保留
if (LiveStates.LyricsWindowStatus.IsWorkArea)
@@ -67,7 +67,7 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
LiveStates.LyricsWindowStatus.WindowBounds = LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea();
}
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds);
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.WindowBounds);
LiveStates.LyricsWindowStatus.WindowX = LiveStates.LyricsWindowStatus.WindowBounds.X;
LiveStates.LyricsWindowStatus.WindowY = LiveStates.LyricsWindowStatus.WindowBounds.Y;
LiveStates.LyricsWindowStatus.WindowWidth = LiveStates.LyricsWindowStatus.WindowBounds.Width;
@@ -84,11 +84,11 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
{
case nameof(LyricsWindowStatus.IsWorkArea):
LiveStates.IsLyricsWindowStatusRefreshing = true;
WindowHook.SetIsWorkArea<LyricsWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
WindowHook.SetIsWorkArea<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
LiveStates.IsLyricsWindowStatusRefreshing = false;
if (LiveStates.LyricsWindowStatus.IsWorkArea)
{
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
}
break;
case nameof(LyricsWindowStatus.DockHeight):
@@ -98,37 +98,37 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
if (LiveStates.LyricsWindowStatus.IsWorkArea)
{
LiveStates.IsLyricsWindowStatusRefreshing = true;
WindowHook.UpdateWorkArea<LyricsWindow>();
WindowHook.UpdateWorkArea<NowPlayingWindow>();
LiveStates.IsLyricsWindowStatusRefreshing = false;
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
}
break;
case nameof(LyricsWindowStatus.IsShownInSwitchers):
WindowHook.SetIsShowInSwitchers<LyricsWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
WindowHook.SetIsShowInSwitchers<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
break;
case nameof(LyricsWindowStatus.IsAlwaysOnTop):
WindowHook.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
WindowHook.SetIsAlwaysOnTop<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
break;
case nameof(LyricsWindowStatus.IsClickThrough):
WindowHook.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
WindowHook.SetIsClickThrough<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
break;
case nameof(LyricsWindowStatus.IsBorderless):
WindowHook.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
WindowHook.SetIsBorderless<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
break;
case nameof(LyricsWindowStatus.WindowX):
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithX(LiveStates.LyricsWindowStatus.WindowX));
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithX(LiveStates.LyricsWindowStatus.WindowX));
break;
case nameof(LyricsWindowStatus.WindowY):
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithY(LiveStates.LyricsWindowStatus.WindowY));
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithY(LiveStates.LyricsWindowStatus.WindowY));
break;
case nameof(LyricsWindowStatus.WindowWidth):
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithWidth(LiveStates.LyricsWindowStatus.WindowWidth));
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithWidth(LiveStates.LyricsWindowStatus.WindowWidth));
break;
case nameof(LyricsWindowStatus.WindowHeight):
WindowHook.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithHeight(LiveStates.LyricsWindowStatus.WindowHeight));
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithHeight(LiveStates.LyricsWindowStatus.WindowHeight));
break;
case nameof(LyricsWindowStatus.TitleBarArea):
WindowHook.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
WindowHook.SetTitleBarArea<NowPlayingWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
break;
case nameof(LyricsWindowStatus.AutoShowOrHideWindow):
WindowHook.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);

View File

@@ -40,7 +40,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
_lrcLibHttpClient = new();
_lrcLibHttpClient.DefaultRequestHeaders.Add(
"User-Agent",
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.GitHubUrl})"
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.GitHub})"
);
_amllTtmlDbHttpClient = new();
_appleMusic = new AppleMusic();
@@ -144,32 +144,42 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
List<LyricsSearchResult> lyricsSearchResults = [];
// 曲目没有被映射
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == songInfo.PlayerId)?.LyricsSearchProvidersInfo ?? [])
var mediaSourceProviderInfo = _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == songInfo.PlayerId);
if (mediaSourceProviderInfo != null)
{
if (!provider.IsEnabled)
// 曲目没有被映射
foreach (var provider in mediaSourceProviderInfo.LyricsSearchProvidersInfo)
{
continue;
}
lyricsSearchResult = await SearchSingleAsync(
((SongInfo)songInfo.Clone())
.WithTitle(overridenTitle)
.WithArtist(overridenArtists)
.WithAlbum(overridenAlbum),
provider.Provider, checkCache, token);
if (lyricsSearchResult.IsFound)
{
switch (lyricsSearchType)
if (!provider.IsEnabled)
{
case LyricsSearchType.Sequential:
return lyricsSearchResult;
case LyricsSearchType.BestMatch:
lyricsSearchResults.Add((LyricsSearchResult)lyricsSearchResult.Clone());
break;
default:
break;
continue;
}
lyricsSearchResult = await SearchSingleAsync(
((SongInfo)songInfo.Clone())
.WithTitle(overridenTitle)
.WithArtist(overridenArtists)
.WithAlbum(overridenAlbum),
provider.Provider, checkCache, token);
int matchingThreshold = mediaSourceProviderInfo.MatchingThreshold;
if (provider.IsMatchingThresholdOverwritten)
{
matchingThreshold = provider.MatchingThreshold;
}
if (lyricsSearchResult.IsFound && lyricsSearchResult.MatchPercentage >= matchingThreshold)
{
switch (lyricsSearchType)
{
case LyricsSearchType.Sequential:
return lyricsSearchResult;
case LyricsSearchType.BestMatch:
lyricsSearchResults.Add((LyricsSearchResult)lyricsSearchResult.Clone());
break;
default:
break;
}
}
}
}
@@ -214,42 +224,36 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}
}
if (provider.IsLocal())
switch (provider)
{
if (provider == LyricsSearchProvider.LocalMusicFile)
{
case LyricsSearchProvider.QQ:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.QQMusic);
break;
case LyricsSearchProvider.Kugou:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.Kugou);
break;
case LyricsSearchProvider.Netease:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.Netease);
break;
case LyricsSearchProvider.LrcLib:
lyricsSearchResult = await SearchLrcLibAsync(songInfo);
break;
case LyricsSearchProvider.AmllTtmlDb:
lyricsSearchResult = await SearchAmllTtmlDbAsync(songInfo);
break;
case LyricsSearchProvider.LocalMusicFile:
lyricsSearchResult = SearchEmbedded(songInfo);
}
else
{
break;
case LyricsSearchProvider.LocalLrcFile:
case LyricsSearchProvider.LocalEslrcFile:
case LyricsSearchProvider.LocalTtmlFile:
lyricsSearchResult = await SearchFile(songInfo, provider.GetLyricsFormat());
}
}
else
{
switch (provider)
{
case LyricsSearchProvider.LrcLib:
lyricsSearchResult = await SearchLrcLibAsync(songInfo);
break;
case LyricsSearchProvider.QQ:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.QQMusic);
break;
case LyricsSearchProvider.Kugou:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.Kugou);
break;
case LyricsSearchProvider.Netease:
lyricsSearchResult = await SearchQQNeteaseKugouAsync(songInfo, Searchers.Netease);
break;
case LyricsSearchProvider.AmllTtmlDb:
lyricsSearchResult = await SearchAmllTtmlDbAsync(songInfo);
break;
case LyricsSearchProvider.AppleMusic:
lyricsSearchResult = await SearchAppleMusicAsync(songInfo);
break;
default:
break;
}
break;
case LyricsSearchProvider.AppleMusic:
lyricsSearchResult = await SearchAppleMusicAsync(songInfo);
break;
default:
break;
}
if (token.IsCancellationRequested)
@@ -262,12 +266,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
}
if (lyricsSearchResult.IsFound)
if (provider.IsRemote())
{
if (provider.IsRemote())
{
FileHelper.WriteLyricsCache(songInfo, lyricsSearchResult);
}
FileHelper.WriteLyricsCache(songInfo, lyricsSearchResult);
}
return lyricsSearchResult;
@@ -293,7 +294,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
foreach (var file in DirectoryHelper.GetAllFiles(folder.Path, $"*{format.ToFileExtension()}"))
{
int score = MetadataComparer.CalculateScore(songInfo, file);
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult { Reference = file });
if (score > maxScore)
{
bestFile = file;
@@ -324,8 +325,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
private LyricsSearchResult SearchEmbedded(SongInfo songInfo)
{
int maxScore = 0;
int bestScore = 0;
string? bestFile = null;
string? bestRaw = null;
var lyricsSearchResult = new LyricsSearchResult
{
@@ -341,18 +343,25 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
if (FileHelper.MusicExtensions.Contains(Path.GetExtension(file)))
{
var track = new Track(file);
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult
{
Title = track.Title,
Artists = track.Artist.Split(ATL.Settings.DisplayValueSeparator),
Album = track.Album,
Duration = track.Duration
});
var raw = track.GetRawLyrics();
if (score > maxScore)
if (!string.IsNullOrEmpty(raw))
{
maxScore = score;
bestFile = file;
int score = MetadataComparer.CalculateScore(songInfo, new LyricsSearchResult
{
Title = track.Title,
Artists = track.Artist.Split(ATL.Settings.DisplayValueSeparator),
Album = track.Album,
Duration = track.Duration,
Reference = file,
});
if (score > bestScore)
{
bestScore = score;
bestFile = file;
bestRaw = raw;
}
}
}
}
@@ -368,10 +377,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Album = track.Album;
lyricsSearchResult.Duration = track.Duration;
lyricsSearchResult.Raw = track.GetRawLyrics();
lyricsSearchResult.Raw = bestRaw;
lyricsSearchResult.Reference = bestFile;
lyricsSearchResult.MatchPercentage = maxScore;
lyricsSearchResult.MatchPercentage = bestScore;
}
return lyricsSearchResult;
@@ -554,7 +562,11 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
ISearchResult? result;
if (searcher == Searchers.Netease && songInfo.SongId != null)
{
result = new NeteaseSearchResult(songInfo.Title, songInfo.Artists, songInfo.Album, songInfo.Artists, (int)songInfo.DurationMs, songInfo.SongId);
result = new NeteaseSearchResult("", [], "", [], 0, songInfo.SongId);
}
else if (searcher == Searchers.QQMusic && songInfo.SongId != null)
{
result = new QQMusicSearchResult("", [], "", [], 0, songInfo.SongId, "");
}
else
{
@@ -584,7 +596,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
lyricsSearchResult.Raw = response?.Lrc?.Lyric;
lyricsSearchResult.Translation = response?.Tlyric?.Lyric;
lyricsSearchResult.Transliteration = response?.Romalrc.Lyric;
lyricsSearchResult.Transliteration = response?.Romalrc?.Lyric;
lyricsSearchResult.Reference = $"https://music.163.com/song?id={neteaseResult.Id}";
}
else if (result is KugouSearchResult kugouResult)

View File

@@ -3,12 +3,10 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
@@ -21,6 +19,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
Task PreviousAsync();
Task NextAsync();
Task ChangePosition(double seconds);
Task ChangeLyricsLine(int index);
void UpdateLyrics();
void UpdateTranslations();
@@ -34,9 +33,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
TimeSpan CurrentPosition { get; }
LyricsData? CurrentLyricsData { get; }
SoftwareBitmap? SoftwareBitmap { get; }
List<Color> LightAccentColors { get; }
List<Color> DarkAccentColors { get; }
BitmapImage? AlbumArtBitmapImage { get; }
AlbumArtThemeColors AlbumArtThemeColors { get; }
TranslationSearchProvider? TranslationSearchProvider { get; }
LyricsSearchResult? CurrentLyricsSearchResult { get; }

View File

@@ -1,7 +1,12 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.Logging;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -17,9 +22,12 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SoftwareBitmap? SoftwareBitmap { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<Color> LightAccentColors { get; set; } = Enumerable.Repeat(Colors.Black, 4).ToList();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<Color> DarkAccentColors { get; set; } = Enumerable.Repeat(Colors.Black, 4).ToList();
private Color _envColor = Colors.Transparent;
private List<Color> _lightAccentColors = Enumerable.Repeat(Colors.Black, 4).ToList();
private List<Color> _darkAccentColors = Enumerable.Repeat(Colors.Black, 4).ToList();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial BitmapImage? AlbumArtBitmapImage { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AlbumArtThemeColors AlbumArtThemeColors { get; set; } = new();
private void UpdateAlbumArt()
{
@@ -54,19 +62,143 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
decoder = await ImageHelper.MakeSquareWithThemeColor(buffer, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType);
if (token.IsCancellationRequested) return;
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
if (token.IsCancellationRequested) return;
albumArtSwBitmap.DpiX = 96;
albumArtSwBitmap.DpiY = 96;
var lightPalette = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, false);
var darkPalette = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, true);
if (token.IsCancellationRequested) return;
SoftwareBitmap = albumArtSwBitmap;
LightAccentColors = lightPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
DarkAccentColors = darkPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
var bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(ImageHelper.ToIRandomAccessStream(buffer));
if (token.IsCancellationRequested) return;
_lightAccentColors = lightPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
_darkAccentColors = darkPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
UpdateAlbumArtThemeColors();
AlbumArtBitmapImage = bitmapImage;
}
private void UpdateAlbumArtThemeColors()
{
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
var result = new AlbumArtThemeColors();
result.EnvColor = _envColor;
ElementTheme themeTypeSent;
if (status.IsAdaptToEnvironment)
{
themeTypeSent = Helper.ColorHelper.GetElementThemeFromBackgroundColor(result.EnvColor);
}
else
{
themeTypeSent = status.LyricsBackgroundSettings.LyricsBackgroundTheme;
}
bool isLight = themeTypeSent switch
{
ElementTheme.Default => Application.Current.RequestedTheme == ApplicationTheme.Light,
ElementTheme.Light => true,
ElementTheme.Dark => false,
_ => false
};
Color adaptiveGrayedFontColor;
Color grayedEnvironmentalColor;
Color? adaptiveColoredFontColor;
Color darkColor = Colors.Black;
Color lightColor = Colors.White;
if (isLight)
{
adaptiveGrayedFontColor = darkColor;
// brightness = 0.7f;
grayedEnvironmentalColor = lightColor;
result.AccentColor1 = _lightAccentColors.ElementAtOrDefault(0);
result.AccentColor2 = _lightAccentColors.ElementAtOrDefault(1);
result.AccentColor3 = _lightAccentColors.ElementAtOrDefault(2);
result.AccentColor4 = _lightAccentColors.ElementAtOrDefault(3);
}
else
{
adaptiveGrayedFontColor = lightColor;
// brightness = 0.3f;
grayedEnvironmentalColor = darkColor;
result.AccentColor1 = _darkAccentColors.ElementAtOrDefault(0);
result.AccentColor2 = _darkAccentColors.ElementAtOrDefault(1);
result.AccentColor3 = _darkAccentColors.ElementAtOrDefault(2);
result.AccentColor4 = _darkAccentColors.ElementAtOrDefault(3);
}
if (status.IsAdaptToEnvironment)
{
adaptiveColoredFontColor = Helper.ColorHelper.GetForegroundColor(result.EnvColor);
}
else
{
if (isLight)
adaptiveColoredFontColor = _darkAccentColors.ElementAtOrDefault(0);
else
adaptiveColoredFontColor = _lightAccentColors.ElementAtOrDefault(0);
}
result.ThemeType = themeTypeSent;
// 背景字色
switch (status.LyricsStyleSettings.LyricsBgFontColorType)
{
case LyricsFontColorType.AdaptiveGrayed:
result.BgFontColor = adaptiveGrayedFontColor;
break;
case LyricsFontColorType.AdaptiveColored:
result.BgFontColor = adaptiveColoredFontColor ?? adaptiveGrayedFontColor;
break;
case LyricsFontColorType.Custom:
result.BgFontColor = status.LyricsStyleSettings.LyricsCustomBgFontColor;
break;
default:
result.BgFontColor = adaptiveGrayedFontColor;
break;
}
// 前景字色
switch (status.LyricsStyleSettings.LyricsFgFontColorType)
{
case LyricsFontColorType.AdaptiveGrayed:
result.FgFontColor = adaptiveGrayedFontColor;
break;
case LyricsFontColorType.AdaptiveColored:
result.FgFontColor = adaptiveColoredFontColor ?? adaptiveGrayedFontColor;
break;
case LyricsFontColorType.Custom:
result.FgFontColor = status.LyricsStyleSettings.LyricsCustomFgFontColor;
break;
default:
result.FgFontColor = adaptiveGrayedFontColor;
break;
}
// 描边颜色
switch (status.LyricsStyleSettings.LyricsStrokeFontColorType)
{
case LyricsFontColorType.AdaptiveGrayed:
result.StrokeFontColor = grayedEnvironmentalColor.WithBrightness(0.7);
break;
case LyricsFontColorType.AdaptiveColored:
result.StrokeFontColor = result.EnvColor.WithBrightness(0.7);
break;
case LyricsFontColorType.Custom:
result.StrokeFontColor = status.LyricsStyleSettings.LyricsCustomStrokeFontColor;
break;
default:
result.StrokeFontColor = Colors.Transparent;
break;
}
AlbumArtThemeColors = result;
}
}
}

View File

@@ -7,7 +7,6 @@ using BetterLyrics.WinUI3.Parsers.LyricsParser;
using CommunityToolkit.Mvvm.ComponentModel;
using Lyricify.Lyrics.Helpers.General;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
@@ -25,7 +24,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private int _langIndex = 0;
private List<LyricsData> _lyricsDataArr = [];
public LyricsData? CurrentLyricsData => _lyricsDataArr.ElementAtOrDefault(_langIndex);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsData? CurrentLyricsData { get; private set; }
public event EventHandler<LyricsChangedEventArgs>? LyricsChanged;
@@ -35,15 +34,17 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
[ObservableProperty] public partial bool IsTranslating { get; set; } = false;
private void SetCurrentLyricsData()
{
CurrentLyricsData = _lyricsDataArr.ElementAtOrDefault(_langIndex);
}
private async Task RefreshTranslationAsync(CancellationToken token)
{
TranslationSearchProvider = null;
_lyricsDataArr.ElementAtOrDefault(0)?.ClearTranslatedText();
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
});
App.Current.Resources.DispatcherQueue.TryEnqueue(SetCurrentLyricsData);
IsTranslating = true;
@@ -53,10 +54,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
IsTranslating = false;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
});
App.Current.Resources.DispatcherQueue.TryEnqueue(SetCurrentLyricsData);
}
private async Task SetTranslatedTextAsync(CancellationToken token)
@@ -86,7 +85,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_logger.LogInformation("Found translated text in lyrics data at index {FoundIndex}", found);
_lyricsDataArr.FirstOrDefault()?.SetTranslatedText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
_lyricsDataArr.FirstOrDefault()?.SetTranslatedText(_lyricsDataArr[found], 50);
TranslationSearchProvider = CurrentLyricsSearchResult?.Provider.ToTranslationSearchProvider();
}
else if (_settingsService.AppSettings.TranslationSettings.IsLibreTranslateEnabled)
@@ -99,7 +98,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (token.IsCancellationRequested) return;
if (translated == string.Empty) return;
_lyricsDataArr.FirstOrDefault()?.SetTranslation(translated, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator);
_lyricsDataArr.FirstOrDefault()?.SetTranslation(translated);
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
}
@@ -141,7 +140,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (found >= 0)
{
_logger.LogInformation("Found phonetic text in lyrics data at index {FoundIndex}", found);
_lyricsDataArr.FirstOrDefault()?.SetPhoneticText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
_lyricsDataArr.FirstOrDefault()?.SetPhoneticText(_lyricsDataArr[found], 50);
}
}
@@ -153,10 +152,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
CurrentLyricsSearchResult = null;
_lyricsDataArr = [LyricsData.GetLoadingPlaceholder()];
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
});
App.Current.Resources.DispatcherQueue.TryEnqueue(SetCurrentLyricsData);
if (CurrentSongInfo != null)
{
@@ -179,10 +175,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
// Show original first while loading phonetic and translated
ApplyChinesePreference();
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
});
App.Current.Resources.DispatcherQueue.TryEnqueue(SetCurrentLyricsData);
UpdateTranslations();
}

View File

@@ -14,7 +14,6 @@ using BetterLyrics.WinUI3.Services.DiscordService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.ResourceService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using BetterLyrics.WinUI3.ViewModels;
@@ -26,6 +25,7 @@ using CommunityToolkit.WinUI;
using EvtSource;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
@@ -36,6 +36,7 @@ using System.Threading.Tasks;
using Vanara.Windows.Shell;
using Windows.Media.Control;
using Windows.Storage.Streams;
using Windows.UI;
using WindowsMediaController;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
@@ -46,7 +47,10 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>,
IRecipient<PropertyChangedMessage<PaletteGeneratorType>>,
IRecipient<PropertyChangedMessage<ChineseRomanization>>,
IRecipient<PropertyChangedMessage<List<string>>>
IRecipient<PropertyChangedMessage<List<string>>>,
IRecipient<PropertyChangedMessage<Color>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<LyricsFontColorType>>
{
private EventSourceReader? _sse = null;
private readonly MediaManager _mediaManager = new();
@@ -59,7 +63,6 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private readonly ILibWatcherService _libWatcherService;
private readonly ILiveStatesService _liveStatesService;
private readonly IDiscordService _discordService;
private readonly IResourceService _resourceService;
private readonly ILogger<MediaSessionsService> _logger;
private double _lxMusicPositionSeconds = 0;
@@ -81,7 +84,6 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
ILiveStatesService liveStatesService,
IDiscordService discordService,
ITranslateService libreTranslateService,
IResourceService resourceService,
ILogger<MediaSessionsService> logger)
{
_settingsService = settingsService;
@@ -91,7 +93,6 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_translateService = libreTranslateService;
_liveStatesService = liveStatesService;
_discordService = discordService;
_resourceService = resourceService;
_logger = logger;
_onMediaPropsChangedTimer = _dispatcherQueue.CreateTimer();
@@ -129,7 +130,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private void UpdatePlayOrPauseSongShortcut()
{
GlobalHotKeyHook.UpdateHotKey<LyricsWindow>(ShortcutID.PlayOrPauseSong, _settingsService.AppSettings.GeneralSettings.PlayOrPauseShortcut, (() =>
GlobalHotKeyHook.UpdateHotKey<NowPlayingWindow>(ShortcutID.PlayOrPauseSong, _settingsService.AppSettings.GeneralSettings.PlayOrPauseShortcut, (() =>
{
if (CurrentIsPlaying)
{
@@ -144,7 +145,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private void UpdatePreviousSongShortcut()
{
GlobalHotKeyHook.UpdateHotKey<LyricsWindow>(ShortcutID.PreviousSong, _settingsService.AppSettings.GeneralSettings.PreviousSongShortcut, () =>
GlobalHotKeyHook.UpdateHotKey<NowPlayingWindow>(ShortcutID.PreviousSong, _settingsService.AppSettings.GeneralSettings.PreviousSongShortcut, () =>
{
_ = PreviousAsync();
});
@@ -152,7 +153,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private void UpdateNextSongShortcut()
{
GlobalHotKeyHook.UpdateHotKey<LyricsWindow>(ShortcutID.NextSong, _settingsService.AppSettings.GeneralSettings.NextSongShortcut, () =>
GlobalHotKeyHook.UpdateHotKey<NowPlayingWindow>(ShortcutID.NextSong, _settingsService.AppSettings.GeneralSettings.NextSongShortcut, () =>
{
_ = NextAsync();
});
@@ -183,6 +184,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
case nameof(MediaSourceProviderInfo.LyricsSearchType):
UpdateLyrics();
break;
case nameof(MediaSourceProviderInfo.MatchingThreshold):
UpdateLyrics();
break;
default:
break;
}
@@ -318,14 +322,16 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
currentMediaSourceProviderInfo?.PositionOffset = 0;
}
string fixedArtist = mediaProperties?.Artist ?? "N/A";
string fixedAlbum = mediaProperties?.AlbumTitle ?? "N/A";
string? fixedArtist = mediaProperties?.Artist;
string? fixedAlbum = mediaProperties?.AlbumTitle;
string? songId = null;
if (PlayerIDHelper.IsAppleMusic(sessionId))
{
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault() ?? (mediaProperties?.Artist ?? "N/A");
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault() ?? (mediaProperties?.AlbumTitle ?? "N/A");
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault();
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault();
fixedAlbum = fixedAlbum?.Replace(" - Single", "");
fixedAlbum = fixedAlbum?.Replace(" - EP", "");
}
else if (PlayerIDHelper.IsNeteaseFamily(sessionId))
{
@@ -333,6 +339,12 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.NetEaseCloudMusicTrackID))?
.Replace(ExtendedGenreFiled.NetEaseCloudMusicTrackID, "");
}
else if (sessionId == PlayerID.QQMusic)
{
songId = mediaProperties?.Genres
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.QQMusicTrackID))?
.Replace(ExtendedGenreFiled.QQMusicTrackID, "");
}
var linkedFileName = mediaProperties?.Genres
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.FileName))?
@@ -340,18 +352,15 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
CurrentSongInfo = new SongInfo
{
Title = mediaProperties?.Title ?? "N/A",
Artists = fixedArtist.SplitByCommonSplitter(),
Album = fixedAlbum,
Title = mediaProperties?.Title ?? "",
Artists = fixedArtist?.SplitByCommonSplitter() ?? [],
Album = fixedAlbum ?? "",
DurationMs = mediaSession?.ControlSession?.GetTimelineProperties().EndTime.TotalMilliseconds ?? 0,
PlayerId = sessionId,
SongId = songId,
LinkedFileName = linkedFileName
};
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (PlayerIDHelper.IsLXMusic(sessionId))
{
StartSSE();
@@ -582,7 +591,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (picUrl != null)
{
_logger.LogInformation("LX Music Album Art URL: {url}", picUrl);
_lxMusicAlbumArtBytes = await ImageHelper.GetImageBytesFromUrlAsync(picUrl);
_lxMusicAlbumArtBytes = await ImageHelper.GetImageByteArrayFromUrlAsync(picUrl);
if (_lxMusicAlbumArtBytes != null)
{
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
@@ -644,9 +653,17 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
}
}
public async Task ChangeLyricsLine(int index)
{
if (CurrentLyricsData?.LyricsLines?.ElementAtOrDefault(index)?.StartMs is int startMs)
{
await ChangePosition(startMs / 1000.0);
}
}
partial void OnCurrentIsPlayingChanged(bool value)
{
if (WindowHook.GetWindowHandle<LyricsWindow>() is IntPtr hwnd)
if (WindowHook.GetWindowHandle<NowPlayingWindow>() is IntPtr hwnd)
{
TaskbarList.SetProgressState(hwnd, value ? TaskbarButtonProgressState.Normal : TaskbarButtonProgressState.Paused);
}
@@ -654,7 +671,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
partial void OnCurrentPositionChanged(TimeSpan value)
{
if (WindowHook.GetWindowHandle<LyricsWindow>() is IntPtr hwnd)
if (WindowHook.GetWindowHandle<NowPlayingWindow>() is IntPtr hwnd)
{
TaskbarList.SetProgressValue(hwnd, (ulong)value.TotalSeconds, (ulong)(CurrentSongInfo?.Duration ?? value.TotalSeconds));
}
@@ -715,14 +732,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
public void Receive(PropertyChangedMessage<string> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsTranslationSeparator))
{
UpdateTranslations();
}
}
else if (message.Sender is TranslationSettings)
if (message.Sender is TranslationSettings)
{
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageCode))
{
@@ -739,6 +749,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (message.PropertyName == nameof(LiveStates.LyricsWindowStatus))
{
UpdateAlbumArtThemeColors();
UpdateTranslations();
}
}
@@ -765,5 +776,62 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
}
}
}
public void Receive(PropertyChangedMessage<Color> message)
{
if (message.Sender is NowPlayingWindowViewModel)
{
if (message.PropertyName == nameof(NowPlayingWindowViewModel.BackdropAccentColor))
{
_envColor = message.NewValue;
UpdateAlbumArtThemeColors();
}
}
else if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomBgFontColor))
{
UpdateAlbumArtThemeColors();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomFgFontColor))
{
UpdateAlbumArtThemeColors();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomStrokeFontColor))
{
UpdateAlbumArtThemeColors();
}
}
}
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
if (message.Sender is LyricsBackgroundSettings)
{
if (message.PropertyName == nameof(LyricsBackgroundSettings.LyricsBackgroundTheme))
{
UpdateAlbumArtThemeColors();
}
}
}
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsBgFontColorType))
{
UpdateAlbumArtThemeColors();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFgFontColorType))
{
UpdateAlbumArtThemeColors();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsStrokeFontColorType))
{
UpdateAlbumArtThemeColors();
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More