Compare commits

...

55 Commits

Author SHA1 Message Date
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
105 changed files with 5366 additions and 4696 deletions

View File

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

View File

@@ -70,6 +70,10 @@
<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;
@@ -116,7 +115,6 @@ namespace BetterLyrics.WinUI3
.AddSingleton<SettingsPageViewModel>()
.AddSingleton<LyricsPageViewModel>()
.AddSingleton<MusicGalleryViewModel>()
.AddSingleton<LyricsRendererViewModel>()
.AddSingleton<AboutControlViewModel>()
.BuildServiceProvider()
);

View File

@@ -26,6 +26,7 @@
<None Remove="Controls\AlbumArtLayoutSettingsControl.xaml" />
<None Remove="Controls\AppSettingsControl.xaml" />
<None Remove="Controls\DemoWindowGrid.xaml" />
<None Remove="Controls\Dragger.xaml" />
<None Remove="Controls\ExtendedSlider.xaml" />
<None Remove="Controls\FontFamilyAutoSuggestBox.xaml" />
<None Remove="Controls\LyricsSearchControl.xaml" />
@@ -57,6 +58,7 @@
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
@@ -65,7 +67,7 @@
<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.6.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" />
@@ -80,7 +82,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 +95,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" />
@@ -334,6 +336,11 @@
<ItemGroup>
<Folder Include="TemplateSelector\" />
</ItemGroup>
<ItemGroup>
<Page Update="Controls\Dragger.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

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

@@ -16,28 +16,17 @@
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="SettingsPageAlbumArt" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsExpander
x:Uid="SettingsPageAlbumArtSize"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE744;}"
IsExpanded="True">
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageAutoAdjust">
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.AutoAlbumArtSize, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard IsEnabled="{x:Bind AlbumArtLayoutSettings.AutoAlbumArtSize, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Frequency="2"
Maximum="800"
Minimum="10"
ResetButtonVisibility="Collapsed"
Unit="px"
Value="{x:Bind AlbumArtLayoutSettings.AlbumArtSize, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<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:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEA3A;}">
<local:ExtendedSlider
@@ -58,14 +47,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 +68,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

@@ -0,0 +1,25 @@
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.Dragger"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid
Background="Transparent"
PointerCanceled="Grid_PointerReleased"
PointerEntered="Grid_PointerEntered"
PointerExited="Grid_PointerExited"
PointerMoved="Grid_PointerMoved"
PointerPressed="Grid_PointerPressed"
PointerReleased="Grid_PointerReleased">
<Border x:Name="HitArea" Background="{ThemeResource SystemControlForegroundBaseMediumLowBrush}">
<Grid
x:Name="HandlePill"
Background="{ThemeResource TextFillColorSecondaryBrush}"
CornerRadius="4" />
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,135 @@
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using System;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Controls
{
public class DragDeltaEventArgs : EventArgs
{
public double HorizontalChange { get; }
public double VerticalChange { get; }
public DragDeltaEventArgs(double hChange, double vChange)
{
HorizontalChange = hChange;
VerticalChange = vChange;
}
}
public sealed partial class Dragger : UserControl
{
public event EventHandler DragStarted;
public event EventHandler<DragDeltaEventArgs> DragDelta;
public event EventHandler DragCompleted;
private bool _isDragging = false;
private Point _lastPoint;
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(Dragger),
new PropertyMetadata(Orientation.Vertical, OnOrientationChanged));
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (Dragger)d;
control.UpdateVisuals();
}
public Dragger()
{
this.InitializeComponent();
this.Loaded += (s, e) => UpdateVisuals();
}
private void UpdateVisuals()
{
if (Orientation == Orientation.Vertical)
{
this.ProtectedCursor = InputSystemCursor.Create(InputSystemCursorShape.SizeWestEast);
this.Width = 16;
this.Height = double.NaN; // Auto
if (HitArea != null && HandlePill != null)
{
HitArea.Width = 16;
HitArea.Height = double.NaN;
HandlePill.Width = 8;
HandlePill.Height = 32;
}
}
else
{
this.ProtectedCursor = InputSystemCursor.Create(InputSystemCursorShape.SizeNorthSouth);
this.Height = 16;
this.Width = double.NaN; // Auto
if (HitArea != null && HandlePill != null)
{
HitArea.Height = 16;
HitArea.Width = double.NaN;
HandlePill.Height = 8;
HandlePill.Width = 32;
}
}
}
private void Grid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
}
private void Grid_PointerExited(object sender, PointerRoutedEventArgs e)
{
}
private void Grid_PointerPressed(object sender, PointerRoutedEventArgs e)
{
var element = sender as UIElement;
if (element.CapturePointer(e.Pointer))
{
_isDragging = true;
_lastPoint = e.GetCurrentPoint(this.XamlRoot.Content).Position;
DragStarted?.Invoke(this, EventArgs.Empty);
}
}
private void Grid_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if (_isDragging)
{
var currentPoint = e.GetCurrentPoint(this.XamlRoot.Content).Position;
double dx = currentPoint.X - _lastPoint.X;
double dy = currentPoint.Y - _lastPoint.Y;
if (dx != 0 || dy != 0)
{
DragDelta?.Invoke(this, new DragDeltaEventArgs(dx, dy));
_lastPoint = currentPoint;
}
}
}
private void Grid_PointerReleased(object sender, PointerRoutedEventArgs e)
{
if (_isDragging)
{
var element = sender as UIElement;
_isDragging = false;
element.ReleasePointerCapture(e.Pointer);
DragCompleted?.Invoke(this, EventArgs.Empty);
}
}
}
}

View File

@@ -0,0 +1,172 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using System.Collections.Generic;
using System.Numerics;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Controls
{
public static class InstantTip
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.RegisterAttached("Content", typeof(object), typeof(InstantTip), new PropertyMetadata(null, OnContentChanged));
public static void SetContent(DependencyObject element, object value) => element.SetValue(ContentProperty, value);
public static object GetContent(DependencyObject element) => element.GetValue(ContentProperty);
private static Dictionary<int, Popup> _activePopups = [];
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FrameworkElement element)
{
element.PointerEntered -= Element_PointerEntered;
element.PointerExited -= Element_PointerExited;
element.Unloaded -= Element_Unloaded;
if (e.NewValue != null)
{
element.PointerEntered += Element_PointerEntered;
element.PointerExited += Element_PointerExited;
element.Unloaded += Element_Unloaded;
}
}
}
private static void Element_PointerEntered(object sender, PointerRoutedEventArgs e)
{
if (sender is FrameworkElement element)
{
int hashCode = element.GetHashCode();
if (_activePopups.ContainsKey(hashCode))
{
_activePopups.TryGetValue(hashCode, out var popup);
if (popup != null)
{
popup.IsOpen = true;
}
}
else
{
var rawContent = GetContent(element);
if (rawContent == null) return;
// 创建可视卡片容器 (Visual Card)
var visualCard = new Grid
{
Background = (Brush)Application.Current.Resources["AcrylicBackgroundFillColorDefaultBrush"],
CornerRadius = new CornerRadius(4),
Shadow = new ThemeShadow(),
Translation = new Vector3(0, 0, 32),
Padding = new Thickness(8, 4, 8, 4)
};
var popupContent = new Grid
{
IsHitTestVisible = false,
Opacity = 0,
Padding = new Thickness(16)
};
popupContent.Children.Add(visualCard);
var popup = new Popup
{
Child = popupContent,
IsHitTestVisible = false,
ShouldConstrainToRootBounds = false,
XamlRoot = element.XamlRoot,
};
object finalContent = rawContent;
if (rawContent is ToolTip toolTipWrapper)
{
finalContent = toolTipWrapper.Content;
}
if (finalContent is string text)
{
var textBlock = new TextBlock
{
Text = text,
FontSize = 12,
MaxWidth = 320,
TextWrapping = TextWrapping.Wrap,
Foreground = (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"]
};
visualCard.Children.Add(textBlock);
}
else if (finalContent != null)
{
var textBlock = new TextBlock
{
Text = finalContent.ToString(),
FontSize = 12,
MaxWidth = 320,
TextWrapping = TextWrapping.Wrap,
Foreground = (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"]
};
visualCard.Children.Add(textBlock);
}
var transform = element.TransformToVisual(null);
var point = transform.TransformPoint(new Point(0, element.ActualHeight));
popup.VerticalOffset = point.Y;
popup.HorizontalOffset = point.X - popupContent.Padding.Left;
popup.IsOpen = true;
App.Current.Resources.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
popupContent.OpacityTransition = new ScalarTransition();
popupContent.Opacity = 1;
});
_activePopups.Add(hashCode, popup);
}
}
}
private static void Element_PointerExited(object sender, PointerRoutedEventArgs e)
{
var element = sender as FrameworkElement;
if (element != null)
{
int hashCode = element.GetHashCode();
if (!_activePopups.ContainsKey(hashCode))
{
return;
}
_activePopups.TryGetValue(hashCode, out var popup);
if (popup != null)
{
if (popup.Child is Grid popupContent)
{
popupContent.Opacity = 0;
}
popup.IsOpen = false;
_activePopups.Remove(hashCode);
}
}
}
private static void Element_Unloaded(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement element)
{
element.PointerEntered -= Element_PointerEntered;
element.PointerExited -= Element_PointerExited;
element.Unloaded -= Element_Unloaded;
}
}
}
}

View File

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

@@ -18,147 +18,25 @@
<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>
<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 +102,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 +124,7 @@
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollTopDuration, Mode=TwoWay}" />
</dev:SettingsCard>
@@ -255,7 +133,7 @@
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollDuration, Mode=TwoWay}" />
</dev:SettingsCard>
@@ -264,7 +142,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,9 +23,9 @@
<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>
@@ -56,15 +56,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"
@@ -227,13 +218,6 @@
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,12 +125,40 @@
</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">
@@ -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
@@ -168,15 +216,6 @@
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="Advanced">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageAdvanced"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
</Pivot>
</StackPanel>
@@ -189,7 +228,7 @@
</TransitionCollection>
</controls:SwitchPresenter.ContentTransitions>
<!-- General -->
<!-- Window -->
<controls:Case Value="General">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
@@ -207,26 +246,6 @@
</StackPanel>
</dev:SettingsCard>
<dev:SettingsExpander
x:Uid="SettingsPageDisplayTypeSwitcher"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF246;}"
IsExpanded="True">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
<ComboBoxItem x:Uid="MainPageLyriscOnly" />
<ComboBoxItem x:Uid="MainPageSplitView" />
</ComboBox>
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageLayoutOrientation">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsLayoutOrientation, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLayoutOrientationHorizontal" />
<ComboBoxItem x:Uid="SettingsPageLayoutOrientationVertical" />
</ComboBox>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageWorkArea"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
@@ -336,37 +355,6 @@
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow, Mode=TwoWay}" />
</dev:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</controls:Case>
<!-- Album art area style -->
<controls:Case Value="AlbumArtStyle">
<uc:AlbumArtLayoutSettingsControl AlbumArtLayoutSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics style -->
<controls:Case Value="LyricsStyle">
<uc:LyricsStyleSettingsControl LyricsStyleSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics effect -->
<controls:Case Value="LyricsEffect">
<uc:LyricsEffectSettingsControl LyricsEffectSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsEffectSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics background -->
<controls:Case Value="LyricsBackground">
<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>
@@ -392,8 +380,58 @@
</ScrollViewer>
</controls:Case>
<!-- Layout -->
<controls:Case Value="Layout">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<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" />
<ComboBoxItem x:Uid="MainPageSplitView" />
</ComboBox>
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageLayoutOrientation">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsLayoutOrientation, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLayoutOrientationHorizontal" />
<ComboBoxItem x:Uid="SettingsPageLayoutOrientationVertical" />
</ComboBox>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
</StackPanel>
</Grid>
</ScrollViewer>
</controls:Case>
<!-- Album art area style -->
<controls:Case Value="AlbumArtStyle">
<uc:AlbumArtLayoutSettingsControl AlbumArtLayoutSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics style -->
<controls:Case Value="LyricsStyle">
<uc:LyricsStyleSettingsControl LyricsStyleSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics effect -->
<controls:Case Value="LyricsEffect">
<uc:LyricsEffectSettingsControl LyricsEffectSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsEffectSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics background -->
<controls:Case Value="LyricsBackground">
<uc:LyricsBackgroundSettingsControl LyricsBackgroundSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings, Mode=OneWay}" />
</controls:Case>
</controls:SwitchPresenter>
</Grid>
</Grid>
</UserControl>

View File

@@ -149,5 +149,10 @@ namespace BetterLyrics.WinUI3.Controls
}
}
}
private void DisplayGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
ViewModel.DisplayPanelHeight = e.NewSize.Height;
}
}
}

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.NowPlayingCanvas"
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,703 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
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.Linq;
using System.Numerics;
using Windows.Foundation;
using Windows.UI;
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class NowPlayingCanvas : 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?>>
{
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 LyricsLayoutManager _layoutManager = 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 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 LyricsData? _lyricsData;
private bool _isLayoutChanged = true;
private int _playingLineIndex;
private (int Start, int End) _visibleRange;
private double _canvasTargetScrollOffset;
public TimeSpan SongPosition => _songPosition;
// <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(NowPlayingCanvas), 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(NowPlayingCanvas), 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(NowPlayingCanvas), 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(NowPlayingCanvas), 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(NowPlayingCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
public NowPlayingCanvas()
{
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);
}
private static void OnLayoutPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is NowPlayingCanvas canvas)
{
if (e.Property == LyricsStartXProperty)
{
canvas._renderLyricsStartX = Convert.ToDouble(e.NewValue);
}
else if (e.Property == LyricsStartYProperty)
{
canvas._renderLyricsStartY = Convert.ToDouble(e.NewValue);
}
else if (e.Property == LyricsWidthProperty)
{
canvas._renderLyricsWidth = Convert.ToDouble(e.NewValue);
}
else if (e.Property == LyricsHeightProperty)
{
canvas._renderLyricsHeight = Convert.ToDouble(e.NewValue);
}
else if (e.Property == LyricsOpacityProperty)
{
canvas._renderLyricsOpacity = Convert.ToDouble(e.NewValue);
}
canvas._isLayoutChanged = true;
}
}
// ====
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;
var lyricsData = _lyricsData;
double songDuration = _mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0;
bool isForceWordByWord = _settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect;
double fixedSongPositionMs = _songPosition.TotalMilliseconds + (_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
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,
lyricsData: _lyricsData,
playingLineIndex: _playingLineIndex,
startVisibleIndex: _visibleRange.Start,
endVisibleIndex: _visibleRange.End,
lyricsX: _renderLyricsStartX,
lyricsY: _renderLyricsStartY,
lyricsWidth: _renderLyricsWidth,
lyricsHeight: _renderLyricsHeight,
lyricsOpacity: _renderLyricsOpacity,
windowStatus: status,
strokeColor: lyricsThemeColors.StrokeFontColor,
bgColor: lyricsThemeColors.BgFontColor,
fgColor: lyricsThemeColors.FgFontColor,
getPlaybackState: (lineIndex) =>
{
if (lyricsData == null) return new LinePlaybackState();
var line = lyricsData.LyricsLines.ElementAtOrDefault(lineIndex);
if (line == null) return new LinePlaybackState();
var nextLine = lyricsData.LyricsLines.ElementAtOrDefault(lineIndex + 1);
return _synchronizer.GetLinePlayingProgress(
fixedSongPositionMs,
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(
$"[DEBUG]\n" +
$"Lyrics start pos: ({(int)_renderLyricsStartX}, {(int)_renderLyricsStartY})\n" +
$"Lyrics size: [{(int)_renderLyricsWidth} x {(int)_renderLyricsHeight}]\n" +
$"Playing line (idx): {_playingLineIndex}\n" +
$"Visible lines range (idx): [{_visibleRange.Start}, {_visibleRange.End}]\n" +
$"Total line count: {GetMaxLyricsLineIndexBoundaries().Item2 + 1}\n" +
$"Played: {TimeSpan.FromMilliseconds(fixedSongPositionMs)} / {TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0)}\n" +
$"Y offset: {_canvasYScrollTransition.Value}",
new Vector2(10, 40), Colors.Red);
#endif
}
private void Canvas_Update(ICanvasAnimatedControl sender, CanvasAnimatedUpdateEventArgs args)
{
var lyricsBg = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings;
var lyricsEffect = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings;
var albumArtThemeColors = _mediaSessionsService.AlbumArtThemeColors;
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(_songPosition.TotalMilliseconds, _lyricsData);
bool isPlayingLineChanged = newPlayingIndex != _playingLineIndex;
_playingLineIndex = newPlayingIndex;
#endregion
#region UpdateTargetScrollOffset
if (isPlayingLineChanged || _isLayoutChanged)
{
var targetScroll = _layoutManager.CalculateTargetScrollOffset(_lyricsData, _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
_visibleRange = _layoutManager.CalculateVisibleRange(
_lyricsData?.LyricsLines,
_canvasYScrollTransition.Value, // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>λ<EFBFBD><CEBB>
_renderLyricsStartY,
_renderLyricsHeight,
sender.Size.Height
);
_animator.UpdateVisibleLines(
_lyricsData,
_visibleRange.Start,
_visibleRange.End,
_playingLineIndex,
sender.Size.Height,
_canvasTargetScrollOffset,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings,
_canvasYScrollTransition,
albumArtThemeColors.BgFontColor,
albumArtThemeColors.FgFontColor,
elapsedTime,
_isLayoutChanged,
isPlayingLineChanged
);
_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 (_layoutManager == null || _lyricsData == null || !_isLayoutChanged) return;
_layoutManager.MeasureAndArrange(
resourceCreator: Canvas,
lyricsData: _lyricsData,
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;
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()
{
_totalPlayedTime = TimeSpan.Zero;
_totalPlayedTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
{
if (_mediaSessionsService.CurrentSongInfo == null
|| _lyricsData == null
|| _lyricsData.LyricsLines.Count == 0)
{
return new Tuple<int, int>(-1, -1);
}
return new Tuple<int, int>(0, _lyricsData.LyricsLines.Count - 1);
}
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))
{
_lyricsData = message.NewValue;
_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.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.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();
}
}
}
}
}

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

@@ -57,7 +57,7 @@
Click="OnCopyClicked"
Opacity="0">
<ToolTipService.ToolTip>
<TextBlock x:Uid="Copy" />
<ToolTip x:Uid="Copy" />
</ToolTipService.ToolTip>
<Button.OpacityTransition>
<ScalarTransition />

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,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,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,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

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

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

@@ -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))
{
@@ -186,6 +186,16 @@ 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);

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,52 +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);
if (string.IsNullOrEmpty(infoRaw) && songInfo.LinkedFileName is string linkedFileName)
infoRaw = linkedFileName;
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() ?? "";
@@ -105,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

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

@@ -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)
@@ -118,6 +127,7 @@ namespace BetterLyrics.WinUI3.Hooks
{
throw new ArgumentException("Unsupported window type", nameof(T));
}
TrackWindow(window);
var castedWindow = (Window)window;
castedWindow.Restore();

View File

@@ -0,0 +1,115 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using System;
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 UpdateVisibleLines(
LyricsData? lyricsData,
int startVisibleIndex,
int endVisibleIndex,
int playingLineIndex,
double canvasHeight,
double targetYScrollOffset,
LyricsEffectSettings lyricsEffect,
ValueTransition<double> canvasYScrollTransition,
Color bgColor,
Color fgColor,
TimeSpan elapsedTime,
bool isLayoutChanged,
bool isPlayingLineChanged)
{
if (lyricsData == null) return;
var currentPlayingLine = lyricsData.LyricsLines.ElementAtOrDefault(playingLineIndex);
if (currentPlayingLine == null) return;
// 循环更新可见行
for (int i = startVisibleIndex; i <= endVisibleIndex + 1; i++)
{
var line = lyricsData.LyricsLines.ElementAtOrDefault(i);
if (line == null) continue;
if (isLayoutChanged || isPlayingLineChanged)
{
int lineCountDelta = i - playingLineIndex;
int absLineCountDelta = Math.Abs(lineCountDelta);
double distanceFromPlayingLine = Math.Abs(line.OriginalPosition.Y - currentPlayingLine.OriginalPosition.Y);
double distanceFactor = Math.Clamp(distanceFromPlayingLine / (canvasHeight / 2), 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(5 * distanceFactor);
line.ScaleTransition.SetDuration(yScrollDuration);
line.ScaleTransition.SetDelay(yScrollDelay);
line.ScaleTransition.StartTransition(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale));
line.UnplayingOpacityTransition.SetDuration(yScrollDuration);
line.UnplayingOpacityTransition.SetDelay(yScrollDelay);
line.UnplayingOpacityTransition.StartTransition(absLineCountDelta == 0 ? 0.3 : (1 - distanceFactor) * 0.3);
line.PlayingOpacityTransition.SetDuration(yScrollDuration);
line.PlayingOpacityTransition.SetDelay(yScrollDelay);
line.PlayingOpacityTransition.StartTransition(absLineCountDelta == 0 ? 1 : (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.PlayingOpacityTransition.Update(elapsedTime);
line.UnplayingOpacityTransition.Update(elapsedTime);
line.YOffsetTransition.Update(elapsedTime);
line.ColorTransition.Update(elapsedTime);
}
}
}
}

View File

@@ -0,0 +1,204 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
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 void MeasureAndArrange(
ICanvasAnimatedControl resourceCreator,
LyricsData? lyricsData,
LyricsWindowStatus status,
AppSettings appSettings,
double canvasWidth,
double canvasHeight,
double lyricsWidth,
double lyricsHeight)
{
if (lyricsData == 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;
foreach (var line in lyricsData.LyricsLines)
{
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.TopPosition = new Vector2(0, (float)currentY);
// 注音层
line.PhoneticPosition = line.TopPosition;
if (line.PhoneticCanvasTextLayout != null)
{
currentY += line.PhoneticCanvasTextLayout.LayoutBounds.Height;
// 间距
currentY += (line.PhoneticCanvasTextLayout.LayoutBounds.Height / line.PhoneticCanvasTextLayout.LineCount) * 0.1;
}
// 原文层
line.OriginalPosition = new Vector2(0, (float)currentY);
if (line.OriginalCanvasTextLayout != null)
{
currentY += line.OriginalCanvasTextLayout.LayoutBounds.Height;
}
// 翻译层
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;
}
// 底部坐标
line.BottomPosition = new Vector2(0, (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 double? CalculateTargetScrollOffset(
LyricsData? lyricsData,
int playingLineIndex)
{
var lines = lyricsData?.LyricsLines;
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.TranslatedPosition.Y
+ (currentLine.TranslatedCanvasTextLayout?.LayoutBounds.Height ?? 0)
- currentLine.PhoneticPosition.Y) / 2.0;
}
/// <summary>
/// 计算当前屏幕可见的行范围
/// 返回值: (StartVisibleIndex, EndVisibleIndex)
/// </summary>
public (int Start, int End) CalculateVisibleRange(
IList<LyricsLine>? lines,
double currentScrollOffset,
double lyricsY,
double lyricsHeight,
double canvasHeight)
{
if (lines == null || lines.Count == 0) return (-1, -1);
double offset = currentScrollOffset + lyricsY + lyricsHeight / 2;
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);
}
private int FindFirstVisibleLine(IList<LyricsLine> 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.BottomPosition.Y;
// 理论上说应该使用下面这一行来精确计算视野内的首个可见行,但是考虑到动画视觉效果,还是注释掉了
//if (value >= lyricsY) { result = mid; right = mid - 1; }
if (value >= 0) { result = mid; right = mid - 1; }
else { left = mid + 1; }
}
return result;
}
private int FindLastVisibleLine(IList<LyricsLine> 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.BottomPosition.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,9 @@
namespace BetterLyrics.WinUI3.Models
{
public struct LinePlaybackState
{
public int SyllableStartIndex;
public int SyllableLength;
public double SyllableProgress;
}
}

View File

@@ -19,7 +19,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 +47,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 +68,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 +89,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 +131,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 +158,7 @@ namespace BetterLyrics.WinUI3.Models
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
PhoneticText = "",
OriginalText = "● ● ●",
TranslatedText = "",
LyricsChars = [],
},
]);
}

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,55 @@ 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.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 double AnimationDuration { get; set; } = 0.3;
public ValueTransition<double> AngleTransition { get; set; }
public ValueTransition<double> BlurAmountTransition { get; set; }
public ValueTransition<double> UnplayingOpacityTransition { get; set; }
public ValueTransition<double> PlayingOpacityTransition { 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; }
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; } = [];
/// <summary>
/// 顶部坐标(相对于坐标原点)
/// </summary>
public Vector2 TopPosition { get; set; }
/// <summary>
/// 中心坐标(相对于坐标原点)
/// </summary>
public Vector2 CenterPosition { get; private set; }
/// <summary>
/// 底部坐标(相对于坐标原点)
/// </summary>
public Vector2 BottomPosition { get; set; }
public List<LyricsSyllable> LyricsSyllables { get; set; } = [];
public int? DurationMs => EndMs - StartMs;
public int? EndMs { get; set; }
@@ -86,6 +77,45 @@ namespace BetterLyrics.WinUI3.Models
public CanvasGeometry? TranslatedCanvasGeometry { get; private set; }
public CanvasGeometry? PhoneticCanvasGeometry { get; private set; }
public LyricsLine()
{
AngleTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
BlurAmountTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
UnplayingOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
PlayingOpacityTransition = 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)
@@ -93,13 +123,13 @@ namespace BetterLyrics.WinUI3.Models
return;
}
double centerY = OriginalPosition.Y + (OriginalCanvasTextLayout?.LayoutBounds.Height ?? 0) / 2;
double centerY = (TopPosition.Y + BottomPosition.Y) / 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),
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),
};
}

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

@@ -35,6 +35,7 @@ namespace BetterLyrics.WinUI3.Models
[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;
@@ -191,11 +192,13 @@ namespace BetterLyrics.WinUI3.Models
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

@@ -7,15 +7,15 @@ namespace BetterLyrics.WinUI3.Models.Settings
public partial class AlbumArtLayoutSettings : ObservableRecipient, ICloneable
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TextAlignmentType SongInfoAlignmentType { get; set; } = TextAlignmentType.Left;
[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() { }
@@ -31,8 +31,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
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,9 @@ 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 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 +17,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 +38,9 @@ namespace BetterLyrics.WinUI3.Models.Settings
{
return new LyricsEffectSettings(this.LyricsScrollTopDuration, this.LyricsScrollDuration, this.LyricsScrollBottomDuration, this.LyricsScrollEasingType)
{
LyricsBlurAmount = this.LyricsBlurAmount,
IsLyricsLineFadeEnabled = this.IsLyricsLineFadeEnabled,
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 +49,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
LyricsScrollTopDelay = this.LyricsScrollTopDelay,
LyricsScrollBottomDelay = this.LyricsScrollBottomDelay,
LyricsVerticalEdgeOpacity = this.LyricsVerticalEdgeOpacity,
IsFanLyricsEnabled = this.IsFanLyricsEnabled,
FanLyricsAngle = this.FanLyricsAngle,

View File

@@ -15,7 +15,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
[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;
@@ -25,7 +24,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
[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() ?? "";
@@ -40,7 +38,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,7 +47,6 @@ namespace BetterLyrics.WinUI3.Models.Settings
LyricsStrokeFontColorType = this.LyricsStrokeFontColorType,
LyricsFontWeight = this.LyricsFontWeight,
LyricsLineSpacingFactor = this.LyricsLineSpacingFactor,
LyricsTranslationSeparator = this.LyricsTranslationSeparator,
LyricsCJKFontFamily = this.LyricsCJKFontFamily,
LyricsWesternFontFamily = this.LyricsWesternFontFamily,
};

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

@@ -50,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)
{
@@ -58,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,
@@ -77,7 +77,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
StartMs = pStartMs,
EndMs = pEndMs,
OriginalText = originalText,
LyricsChars = originalCharTimings,
LyricsSyllables = originalCharTimings,
});
// 解析 x-role
@@ -108,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)
{
@@ -116,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,
@@ -132,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,237 @@
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.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,
LyricsData? lyricsData,
int playingLineIndex,
int startVisibleIndex,
int endVisibleIndex,
double lyricsX,
double lyricsY,
double lyricsWidth,
double lyricsHeight,
double lyricsOpacity,
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,
lyricsData,
playingLineIndex,
startVisibleIndex,
endVisibleIndex,
lyricsX,
lyricsY,
lyricsWidth,
lyricsHeight,
windowStatus,
strokeColor,
bgColor,
fgColor,
getPlaybackState);
}
ds.DrawImage(new Transform3DEffect
{
Source = layer,
TransformMatrix = _threeDimMatrix
});
}
}
else
{
DrawLyrics(
control,
ds,
lyricsData,
playingLineIndex,
startVisibleIndex,
endVisibleIndex,
lyricsX,
lyricsY,
lyricsWidth,
lyricsHeight,
windowStatus,
strokeColor,
bgColor,
fgColor,
getPlaybackState);
}
}
}
private void DrawLyrics(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
LyricsData? lyricsData,
int playingLineIndex,
int startVisibleIndex,
int endVisibleIndex,
double lyricsX,
double lyricsY,
double lyricsWidth,
double lyricsHeight,
LyricsWindowStatus windowStatus,
Color strokeColor,
Color bgColor,
Color fgColor,
Func<int, LinePlaybackState> getPlaybackState)
{
if (lyricsData == null) return;
var currentPlayingLine = lyricsData.LyricsLines.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 = lyricsData.LyricsLines.ElementAtOrDefault(i);
if (line == null) continue;
if (line.OriginalCanvasTextLayout == null) continue;
if (line.OriginalCanvasTextLayout.LayoutBounds.Width <= 0) continue;
double yOffset = line.YOffsetTransition.Value + lyricsY + lyricsHeight / 2;
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);
}
}
ds.Transform = Matrix3x2.Identity;
}
}
private CanvasCommandList RenderBaseTextLayer(
ICanvasResourceCreator resourceCreator,
LyricsLine 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,
LyricsLine 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, LyricsLine line)
{
if (line.PhoneticCanvasTextLayout == null) return;
var opacity = line.UnplayingOpacityTransition.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, LyricsLine line)
{
if (line.TranslatedCanvasTextLayout == null) return;
var opacity = line.UnplayingOpacityTransition.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,
LyricsLine 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,
LyricsLine line,
CanvasTextLayoutRegion subLineRegion,
double curCharIndex,
float fadeWidth,
Color bgColor,
Color fgColor,
LinePlaybackState state,
LyricsEffectSettings settings)
{
var blur = line.BlurAmountTransition.Value;
var playingOpacity = line.PlayingOpacityTransition.Value;
var unplayingOpacity = line.UnplayingOpacityTransition.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 * playingOpacity));
var stop2 = bgColor.WithAlpha((byte)(255 * unplayingOpacity));
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,
LyricsLine 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,
LyricsLine line)
{
var blurAmount = (float)line.BlurAmountTransition.Value;
if (line.PhoneticCanvasTextLayout != null)
{
var opacity = line.UnplayingOpacityTransition.Value;
DrawPart(ds, textOnlyLayer,
line.PhoneticCanvasTextLayout,
line.PhoneticPosition,
blurAmount,
(float)opacity);
}
if (line.OriginalCanvasTextLayout != null)
{
double opacity;
if (line.PlayingOpacityTransition.StartValue > line.UnplayingOpacityTransition.StartValue)
{
opacity = line.PlayingOpacityTransition.Value;
}
else
{
opacity = line.UnplayingOpacityTransition.Value;
}
DrawPart(ds, textOnlyLayer,
line.OriginalCanvasTextLayout,
line.OriginalPosition,
blurAmount,
(float)opacity);
}
if (line.TranslatedCanvasTextLayout != null)
{
var opacity = line.UnplayingOpacityTransition.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

@@ -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
{
@@ -34,9 +32,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();
@@ -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();
@@ -715,14 +724,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))
{
@@ -765,5 +767,62 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
}
}
}
public void Receive(PropertyChangedMessage<Color> message)
{
if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(LyricsWindowViewModel.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();
}
}
}
}
}

View File

@@ -130,7 +130,7 @@
<value>Picture-in-picture mode</value>
</data>
<data name="AppSettingsControlGeneral.Text" xml:space="preserve">
<value>General</value>
<value>Window</value>
</data>
<data name="ArtistsSplitHint.Text" xml:space="preserve">
<value>When typing multiple artists, please separate them with one of the following delimiters (do not mix them)</value>
@@ -144,7 +144,7 @@
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<data name="Copy.Content" xml:space="preserve">
<value>Copy</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
@@ -183,7 +183,7 @@
<data name="HostWindowMusicGalleryButtonToolTip.Content" xml:space="preserve">
<value>Music gallery</value>
</data>
<data name="HostWindowSettingsButtonToolTip.Text" xml:space="preserve">
<data name="HostWindowSettingsButtonToolTip.Content" xml:space="preserve">
<value>Settings</value>
</data>
<data name="ImportPlaylistSuccessfully" xml:space="preserve">
@@ -231,6 +231,12 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>Lyrics not found</value>
</data>
<data name="LyricsPageCachePath.Header" xml:space="preserve">
<value>Cache path</value>
</data>
<data name="LyricsPageCachePath.Value" xml:space="preserve">
<value>Cache path</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>Lyrics provider</value>
</data>
@@ -270,10 +276,13 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>Translation provider</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<data name="LyricsParseError" xml:space="preserve">
<value>Lyrics parsing failed</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>Album</value>
</data>
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>Artist</value>
</data>
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
@@ -312,7 +321,7 @@
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
<value>Target lyrics search provider</value>
</data>
<data name="LyricsSearchControlTitle.Text" xml:space="preserve">
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
<value>Title</value>
</data>
<data name="LyricsSearchPageTitle" xml:space="preserve">
@@ -598,6 +607,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>Album art area style</value>
</data>
<data name="SettingsPageAlignment.Header" xml:space="preserve">
<value>Alignment</value>
</data>
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
<value>Base URL</value>
</data>
@@ -655,12 +667,18 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageBorderlessHotKey.Header" xml:space="preserve">
<value>Borderless lyrics window shortcut</value>
</data>
<data name="SettingsPageBottomGapFactor.Header" xml:space="preserve">
<value>Bottom gap height factor</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>Including log files, network lyrics cache</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>Cache</value>
</data>
<data name="SettingsPageCenter.Content" xml:space="preserve">
<value>Center</value>
</data>
<data name="SettingsPageChinese.Header" xml:space="preserve">
<value>Chinese pronunciation</value>
</data>
@@ -850,6 +868,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageFullscreenMode.Text" xml:space="preserve">
<value>Fullscreen mode</value>
</data>
<data name="SettingsPageGeneralLayoutFactor.Text" xml:space="preserve">
<value>General layout factor</value>
</data>
<data name="SettingsPageHeight.Header" xml:space="preserve">
<value>Height</value>
</data>
@@ -859,6 +880,12 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
<value>Automatically hide/show windows</value>
</data>
<data name="SettingsPageHoldDragSort.Content" xml:space="preserve">
<value>Hold here and drag to sort</value>
</data>
<data name="SettingsPageHorizontalLayoutFactor.Text" xml:space="preserve">
<value>Horizontal layout factor</value>
</data>
<data name="SettingsPageImport.Content" xml:space="preserve">
<value>Import</value>
</data>
@@ -913,6 +940,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageLastFMUsername.Header" xml:space="preserve">
<value>Username</value>
</data>
<data name="SettingsPageLayout.Text" xml:space="preserve">
<value>Layout</value>
</data>
<data name="SettingsPageLayoutOrientation.Header" xml:space="preserve">
<value>Layout orientation</value>
</data>
@@ -922,6 +952,12 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageLayoutOrientationVertical.Content" xml:space="preserve">
<value>Vertical</value>
</data>
<data name="SettingsPageLeft.Content" xml:space="preserve">
<value>Left</value>
</data>
<data name="SettingsPageLeftGapFactor.Header" xml:space="preserve">
<value>Left gap area width factor</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>Server address</value>
</data>
@@ -970,8 +1006,8 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageLyricsBold.Content" xml:space="preserve">
<value>Bold</value>
</data>
<data name="SettingsPageLyricsCenter.Content" xml:space="preserve">
<value>Center</value>
<data name="SettingsPageLyricsColFactor.Header" xml:space="preserve">
<value>Lyrics area width factor</value>
</data>
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>Lyrics effect</value>
@@ -1021,6 +1057,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>Font weight</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Description" xml:space="preserve">
<value>Enable glow effect for syllables with long duration</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
<value>Glow effect</value>
</data>
@@ -1060,8 +1099,14 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
<value>Current line start to current char</value>
</data>
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>Right</value>
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
<value>Lyrics area height factor</value>
</data>
<data name="SettingsPageLyricsScaleEffect.Description" xml:space="preserve">
<value>Enable scaling for syllables with long duration</value>
</data>
<data name="SettingsPageLyricsScaleEffect.Header" xml:space="preserve">
<value>Scale effect</value>
</data>
<data name="SettingsPageLyricsSearchBestMatch.Content" xml:space="preserve">
<value>Best match</value>
@@ -1120,6 +1165,15 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
<value>Lyrics window status switcher shortcut</value>
</data>
<data name="SettingsPageLyricsWindowToolTip.Content" xml:space="preserve">
<value>Lyrics window</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>Adjusting this value will affect sequential search and best match search results, but will not affect search results in the manual lyrics search interface</value>
</data>
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
<value>Minimum matching threshold</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>Media library</value>
</data>
@@ -1135,6 +1189,12 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageMicaAlt.Content" xml:space="preserve">
<value>Mica Alt</value>
</data>
<data name="SettingsPageMiddleGapColFactor.Header" xml:space="preserve">
<value>Middle area gap width factor</value>
</data>
<data name="SettingsPageMiddleGapRowFactor.Header" xml:space="preserve">
<value>Middle gap area height factor</value>
</data>
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
<value>Play test music</value>
</data>
@@ -1168,6 +1228,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageOriginalText.Header" xml:space="preserve">
<value>Original text</value>
</data>
<data name="SettingsPageOverwriteMatchingThreshold.Header" xml:space="preserve">
<value>Set the minimum match threshold individually</value>
</data>
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
<value>Color picker style</value>
</data>
@@ -1249,6 +1312,12 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageRestart.Content" xml:space="preserve">
<value>Restart app to apply change</value>
</data>
<data name="SettingsPageRight.Content" xml:space="preserve">
<value>Right</value>
</data>
<data name="SettingsPageRightGapFactor.Header" xml:space="preserve">
<value>Right gap area width factor</value>
</data>
<data name="SettingsPageRomaji.Header" xml:space="preserve">
<value>Japanese annotation</value>
</data>
@@ -1268,7 +1337,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<value>Current line duration</value>
</data>
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
<value>Lyrics scrolling animation type</value>
<value>Lyric animation type</value>
</data>
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
<value>First line delay</value>
@@ -1318,6 +1387,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageShowInSwitchers.Header" xml:space="preserve">
<value>Show in system environment</value>
</data>
<data name="SettingsPageShowLayoutDragger.Header" xml:space="preserve">
<value>Show layout splitter</value>
</data>
<data name="SettingsPageShowTitle.Header" xml:space="preserve">
<value>Show title</value>
</data>
@@ -1330,18 +1402,6 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
<value>Song info</value>
</data>
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
<value>Alignment</value>
</data>
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
<value>Center</value>
</data>
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
<value>Left</value>
</data>
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>Right</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>Current song</value>
</data>
@@ -1354,12 +1414,21 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageSpectrumLayerPlacement.Header" xml:space="preserve">
<value>Spectrum placement</value>
</data>
<data name="SettingsPageSpectrumLayerStyle.Header" xml:space="preserve">
<value>Spectrum style</value>
</data>
<data name="SettingsPageSpectrumPlacementBottom.Content" xml:space="preserve">
<value>Bottom</value>
</data>
<data name="SettingsPageSpectrumPlacementTop.Content" xml:space="preserve">
<value>Top</value>
</data>
<data name="SettingsPageSpectrumStyleBar.Content" xml:space="preserve">
<value>Bar</value>
</data>
<data name="SettingsPageSpectrumStyleCurve.Content" xml:space="preserve">
<value>Curve</value>
</data>
<data name="SettingsPageSpeed.Header" xml:space="preserve">
<value>Motion rate</value>
</data>
@@ -1402,6 +1471,15 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageToggleHotKey.Header" xml:space="preserve">
<value>Switch in and cut out shortcut keys</value>
</data>
<data name="SettingsPageTopGapFactor.Header" xml:space="preserve">
<value>Top gap area height factor</value>
</data>
<data name="SettingsPageTrackSummaryColFactor.Header" xml:space="preserve">
<value>Track summary area width factor</value>
</data>
<data name="SettingsPageTrackSummaryRowFactor.Header" xml:space="preserve">
<value>Track summary area height factor</value>
</data>
<data name="SettingsPageTranslatedText.Header" xml:space="preserve">
<value>Translated text</value>
</data>
@@ -1420,6 +1498,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageVersion.Text" xml:space="preserve">
<value>Version</value>
</data>
<data name="SettingsPageVerticalLayoutFactor.Text" xml:space="preserve">
<value>Vertical layout factor</value>
</data>
<data name="SettingsPageWesternChar.Header" xml:space="preserve">
<value>Latin alphabet</value>
</data>
@@ -1483,4 +1564,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="TryRunMultipleInstance" xml:space="preserve">
<value>BetterLyrics is already running</value>
</data>
<data name="UserGuide.Content" xml:space="preserve">
<value>User Guide</value>
</data>
</root>

View File

@@ -130,7 +130,7 @@
<value>ピクチャーインピクチャーモード</value>
</data>
<data name="AppSettingsControlGeneral.Text" xml:space="preserve">
<value>一般的な</value>
<value>ウィンドウ</value>
</data>
<data name="ArtistsSplitHint.Text" xml:space="preserve">
<value>複数のアーティストを入力する場合は、次の区切り記号のいずれかで区切ってください(混在しないでください)</value>
@@ -144,7 +144,7 @@
<data name="Cancel" xml:space="preserve">
<value>キャンセル</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<data name="Copy.Content" xml:space="preserve">
<value>コピー</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
@@ -183,7 +183,7 @@
<data name="HostWindowMusicGalleryButtonToolTip.Content" xml:space="preserve">
<value>ミュージックギャラリー</value>
</data>
<data name="HostWindowSettingsButtonToolTip.Text" xml:space="preserve">
<data name="HostWindowSettingsButtonToolTip.Content" xml:space="preserve">
<value>設定</value>
</data>
<data name="ImportPlaylistSuccessfully" xml:space="preserve">
@@ -231,6 +231,12 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>歌詞が見つかりません</value>
</data>
<data name="LyricsPageCachePath.Header" xml:space="preserve">
<value>キャッシュパス</value>
</data>
<data name="LyricsPageCachePath.Value" xml:space="preserve">
<value>キャッシュパス</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>歌詞プロバイダー</value>
</data>
@@ -270,10 +276,13 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻訳プロバイダー</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<data name="LyricsParseError" xml:space="preserve">
<value>歌詞の解析に失敗しました</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>アルバム</value>
</data>
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>アーティスト</value>
</data>
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
@@ -312,7 +321,7 @@
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
<value>ターゲット歌詞検索プロバイダー</value>
</data>
<data name="LyricsSearchControlTitle.Text" xml:space="preserve">
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
<value>タイトル</value>
</data>
<data name="LyricsSearchPageTitle" xml:space="preserve">
@@ -598,6 +607,9 @@
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>アルバムエリアスタイル</value>
</data>
<data name="SettingsPageAlignment.Header" xml:space="preserve">
<value>アライメント</value>
</data>
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
<value>ベースURL</value>
</data>
@@ -655,12 +667,18 @@
<data name="SettingsPageBorderlessHotKey.Header" xml:space="preserve">
<value>ボーダーレス歌詞ウィンドウショートカット</value>
</data>
<data name="SettingsPageBottomGapFactor.Header" xml:space="preserve">
<value>ボトムギャップハイトファクター</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>ログファイル、ネットワーク歌詞キャッシュを含む</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>キャッシュ</value>
</data>
<data name="SettingsPageCenter.Content" xml:space="preserve">
<value>中心</value>
</data>
<data name="SettingsPageChinese.Header" xml:space="preserve">
<value>中国の発音</value>
</data>
@@ -850,6 +868,9 @@
<data name="SettingsPageFullscreenMode.Text" xml:space="preserve">
<value>フルスクリーンモード</value>
</data>
<data name="SettingsPageGeneralLayoutFactor.Text" xml:space="preserve">
<value>一般的なレイアウトファクター</value>
</data>
<data name="SettingsPageHeight.Header" xml:space="preserve">
<value>高さ</value>
</data>
@@ -859,6 +880,12 @@
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
<value>Windowsを自動的に非表示/表示します</value>
</data>
<data name="SettingsPageHoldDragSort.Content" xml:space="preserve">
<value>ここを長押ししてドラッグして並べ替えましょう</value>
</data>
<data name="SettingsPageHorizontalLayoutFactor.Text" xml:space="preserve">
<value>水平レイアウト係数</value>
</data>
<data name="SettingsPageImport.Content" xml:space="preserve">
<value>インポート</value>
</data>
@@ -913,6 +940,9 @@
<data name="SettingsPageLastFMUsername.Header" xml:space="preserve">
<value>ユーザー名</value>
</data>
<data name="SettingsPageLayout.Text" xml:space="preserve">
<value>レイアウト</value>
</data>
<data name="SettingsPageLayoutOrientation.Header" xml:space="preserve">
<value>レイアウトの向き</value>
</data>
@@ -922,6 +952,12 @@
<data name="SettingsPageLayoutOrientationVertical.Content" xml:space="preserve">
<value>垂直方向</value>
</data>
<data name="SettingsPageLeft.Content" xml:space="preserve">
<value>左</value>
</data>
<data name="SettingsPageLeftGapFactor.Header" xml:space="preserve">
<value>左ギャップエリア幅係数</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>サーバーアドレス</value>
</data>
@@ -970,8 +1006,8 @@
<data name="SettingsPageLyricsBold.Content" xml:space="preserve">
<value>大胆な</value>
</data>
<data name="SettingsPageLyricsCenter.Content" xml:space="preserve">
<value>中心</value>
<data name="SettingsPageLyricsColFactor.Header" xml:space="preserve">
<value>歌詞エリア幅係数</value>
</data>
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌詞効果</value>
@@ -1021,6 +1057,9 @@
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>フォント重量</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Description" xml:space="preserve">
<value>長時間の音節にグロウ効果を有効にします</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
<value>グロー効果</value>
</data>
@@ -1060,8 +1099,14 @@
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
<value>現在のラインが現在の文字から始まります</value>
</data>
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value></value>
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
<value>歌詞エリアの高さ係数</value>
</data>
<data name="SettingsPageLyricsScaleEffect.Description" xml:space="preserve">
<value>長時間の音節のスケーリングを有効にします</value>
</data>
<data name="SettingsPageLyricsScaleEffect.Header" xml:space="preserve">
<value>スケール効果</value>
</data>
<data name="SettingsPageLyricsSearchBestMatch.Content" xml:space="preserve">
<value>ベストマッチ</value>
@@ -1120,6 +1165,15 @@
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
<value>歌詞ウィンドウステータススイッチャーショートカット</value>
</data>
<data name="SettingsPageLyricsWindowToolTip.Content" xml:space="preserve">
<value>歌詞ウィンドウ</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>この値を調整すると、シーケンシャル検索と最適な検索結果に影響しますが、手動歌詞検索インターフェースの検索結果には影響しません</value>
</data>
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
<value>最小マッチングしきい値</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>メディアライブラリ</value>
</data>
@@ -1135,6 +1189,12 @@
<data name="SettingsPageMicaAlt.Content" xml:space="preserve">
<value>マイカル</value>
</data>
<data name="SettingsPageMiddleGapColFactor.Header" xml:space="preserve">
<value>ミドルエリアギャップ幅係数</value>
</data>
<data name="SettingsPageMiddleGapRowFactor.Header" xml:space="preserve">
<value>ミドルギャップエリアハイトファクター</value>
</data>
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
<value>テスト音楽を再生します</value>
</data>
@@ -1168,6 +1228,9 @@
<data name="SettingsPageOriginalText.Header" xml:space="preserve">
<value>原文</value>
</data>
<data name="SettingsPageOverwriteMatchingThreshold.Header" xml:space="preserve">
<value>最低マッチしきい値を個別に設定してください</value>
</data>
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
<value>カラーピックスタイル</value>
</data>
@@ -1249,6 +1312,12 @@
<data name="SettingsPageRestart.Content" xml:space="preserve">
<value>変更を適用するためのアプリを再起動します</value>
</data>
<data name="SettingsPageRight.Content" xml:space="preserve">
<value>右</value>
</data>
<data name="SettingsPageRightGapFactor.Header" xml:space="preserve">
<value>右ギャップエリア幅係数</value>
</data>
<data name="SettingsPageRomaji.Header" xml:space="preserve">
<value>日本の注釈</value>
</data>
@@ -1268,7 +1337,7 @@
<value>現在のライン期間</value>
</data>
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
<value>歌詞スクロールアニメーションタイプ</value>
<value>リリックアニメーションタイプ</value>
</data>
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
<value>最初の行の遅延</value>
@@ -1318,6 +1387,9 @@
<data name="SettingsPageShowInSwitchers.Header" xml:space="preserve">
<value>システム環境に表示します</value>
</data>
<data name="SettingsPageShowLayoutDragger.Header" xml:space="preserve">
<value>レイアウトスプリッターを表示</value>
</data>
<data name="SettingsPageShowTitle.Header" xml:space="preserve">
<value>タイトルを表示</value>
</data>
@@ -1330,18 +1402,6 @@
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
<value>曲情報</value>
</data>
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
<value>アライメント</value>
</data>
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
<value>中心</value>
</data>
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
<value>左</value>
</data>
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>右</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>現在の曲です</value>
</data>
@@ -1354,12 +1414,21 @@
<data name="SettingsPageSpectrumLayerPlacement.Header" xml:space="preserve">
<value>スペクトルの位置</value>
</data>
<data name="SettingsPageSpectrumLayerStyle.Header" xml:space="preserve">
<value>スペクトラムスタイル</value>
</data>
<data name="SettingsPageSpectrumPlacementBottom.Content" xml:space="preserve">
<value>底</value>
</data>
<data name="SettingsPageSpectrumPlacementTop.Content" xml:space="preserve">
<value>トップ</value>
</data>
<data name="SettingsPageSpectrumStyleBar.Content" xml:space="preserve">
<value>コラム</value>
</data>
<data name="SettingsPageSpectrumStyleCurve.Content" xml:space="preserve">
<value>曲線</value>
</data>
<data name="SettingsPageSpeed.Header" xml:space="preserve">
<value>モーション</value>
</data>
@@ -1402,6 +1471,15 @@
<data name="SettingsPageToggleHotKey.Header" xml:space="preserve">
<value>ショートカットキーを切り込んで切り取ります</value>
</data>
<data name="SettingsPageTopGapFactor.Header" xml:space="preserve">
<value>トップギャップエリアの高さ係数</value>
</data>
<data name="SettingsPageTrackSummaryColFactor.Header" xml:space="preserve">
<value>トラックサマリーエリア幅係数</value>
</data>
<data name="SettingsPageTrackSummaryRowFactor.Header" xml:space="preserve">
<value>トラックサマリーエリアの高さ係数</value>
</data>
<data name="SettingsPageTranslatedText.Header" xml:space="preserve">
<value>译文</value>
</data>
@@ -1420,6 +1498,9 @@
<data name="SettingsPageVersion.Text" xml:space="preserve">
<value>バージョン</value>
</data>
<data name="SettingsPageVerticalLayoutFactor.Text" xml:space="preserve">
<value>垂直レイアウト係数</value>
</data>
<data name="SettingsPageWesternChar.Header" xml:space="preserve">
<value>英字</value>
</data>
@@ -1483,4 +1564,7 @@
<data name="TryRunMultipleInstance" xml:space="preserve">
<value>BetterLyrics はすでに実行されています</value>
</data>
<data name="UserGuide.Content" xml:space="preserve">
<value>利用案内</value>
</data>
</root>

View File

@@ -130,7 +130,7 @@
<value>사진 인당 모드</value>
</data>
<data name="AppSettingsControlGeneral.Text" xml:space="preserve">
<value>일반적인</value>
<value>윈도우</value>
</data>
<data name="ArtistsSplitHint.Text" xml:space="preserve">
<value>여러 아티스트를 입력할 때 다음 구분 기호 중 하나로 구분하십시오 (혼합하지 마십시오)</value>
@@ -144,7 +144,7 @@
<data name="Cancel" xml:space="preserve">
<value>취소</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<data name="Copy.Content" xml:space="preserve">
<value>접수</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
@@ -183,7 +183,7 @@
<data name="HostWindowMusicGalleryButtonToolTip.Content" xml:space="preserve">
<value>음악 갤러리</value>
</data>
<data name="HostWindowSettingsButtonToolTip.Text" xml:space="preserve">
<data name="HostWindowSettingsButtonToolTip.Content" xml:space="preserve">
<value>설정</value>
</data>
<data name="ImportPlaylistSuccessfully" xml:space="preserve">
@@ -231,6 +231,12 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>가사를 찾을 수 없습니다</value>
</data>
<data name="LyricsPageCachePath.Header" xml:space="preserve">
<value>캐시</value>
</data>
<data name="LyricsPageCachePath.Value" xml:space="preserve">
<value>캐시</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>가사 제공자</value>
</data>
@@ -270,10 +276,13 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>번역 제공자</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<data name="LyricsParseError" xml:space="preserve">
<value>가사 구문 분석에 실패했습니다</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>앨범</value>
</data>
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>아티스트</value>
</data>
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
@@ -312,7 +321,7 @@
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
<value>가사 검색 공급자를 타겟팅하십시오</value>
</data>
<data name="LyricsSearchControlTitle.Text" xml:space="preserve">
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
<value>제목</value>
</data>
<data name="LyricsSearchPageTitle" xml:space="preserve">
@@ -598,6 +607,9 @@
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>앨범 영역 스타일</value>
</data>
<data name="SettingsPageAlignment.Header" xml:space="preserve">
<value>조정</value>
</data>
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
<value>기본 URL</value>
</data>
@@ -655,12 +667,18 @@
<data name="SettingsPageBorderlessHotKey.Header" xml:space="preserve">
<value>테두리 없는 가사 창 단축키</value>
</data>
<data name="SettingsPageBottomGapFactor.Header" xml:space="preserve">
<value>하단 간격 높이 계수</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>로그 파일, 네트워크 가사 캐시 포함</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>은닉처</value>
</data>
<data name="SettingsPageCenter.Content" xml:space="preserve">
<value>센터</value>
</data>
<data name="SettingsPageChinese.Header" xml:space="preserve">
<value>화음</value>
</data>
@@ -850,6 +868,9 @@
<data name="SettingsPageFullscreenMode.Text" xml:space="preserve">
<value>전체화면 모드</value>
</data>
<data name="SettingsPageGeneralLayoutFactor.Text" xml:space="preserve">
<value>일반 레이아웃 요소</value>
</data>
<data name="SettingsPageHeight.Header" xml:space="preserve">
<value>신장</value>
</data>
@@ -859,6 +880,12 @@
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
<value>Windows를 자동으로 숨기고 표시합니다</value>
</data>
<data name="SettingsPageHoldDragSort.Content" xml:space="preserve">
<value>여기를 누른 상태에서 드래그하여 정렬하세요</value>
</data>
<data name="SettingsPageHorizontalLayoutFactor.Text" xml:space="preserve">
<value>수평 레이아웃 요소</value>
</data>
<data name="SettingsPageImport.Content" xml:space="preserve">
<value>가져오기</value>
</data>
@@ -913,6 +940,9 @@
<data name="SettingsPageLastFMUsername.Header" xml:space="preserve">
<value>사용자 이름</value>
</data>
<data name="SettingsPageLayout.Text" xml:space="preserve">
<value>레이아웃</value>
</data>
<data name="SettingsPageLayoutOrientation.Header" xml:space="preserve">
<value>레이아웃 방향</value>
</data>
@@ -922,6 +952,12 @@
<data name="SettingsPageLayoutOrientationVertical.Content" xml:space="preserve">
<value>세로</value>
</data>
<data name="SettingsPageLeft.Content" xml:space="preserve">
<value>왼쪽</value>
</data>
<data name="SettingsPageLeftGapFactor.Header" xml:space="preserve">
<value>왼쪽 갭 영역 너비 계수</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>서버 주소</value>
</data>
@@ -970,8 +1006,8 @@
<data name="SettingsPageLyricsBold.Content" xml:space="preserve">
<value>용감한</value>
</data>
<data name="SettingsPageLyricsCenter.Content" xml:space="preserve">
<value>센터</value>
<data name="SettingsPageLyricsColFactor.Header" xml:space="preserve">
<value>가사 영역 너비 계수</value>
</data>
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>가사 효과</value>
@@ -1021,6 +1057,9 @@
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>글꼴 무게</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Description" xml:space="preserve">
<value>지속 시간이 긴 음절에 대해 발광 효과를 활성화합니다</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
<value>글로우 효과</value>
</data>
@@ -1060,8 +1099,14 @@
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
<value>현재 라인은 현재 숯으로 시작합니다</value>
</data>
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>오른쪽</value>
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
<value>가사 영역 높이 계수</value>
</data>
<data name="SettingsPageLyricsScaleEffect.Description" xml:space="preserve">
<value>장시간 음절 배율을 조정할 수 있습니다</value>
</data>
<data name="SettingsPageLyricsScaleEffect.Header" xml:space="preserve">
<value>스케일 효과</value>
</data>
<data name="SettingsPageLyricsSearchBestMatch.Content" xml:space="preserve">
<value>최상의 검색 결과</value>
@@ -1120,6 +1165,15 @@
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
<value>가사 창 상태 전환기 바로 가기</value>
</data>
<data name="SettingsPageLyricsWindowToolTip.Content" xml:space="preserve">
<value>가사 창</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>이 값을 조정하면 순차 검색 및 검색 결과와 가장 일치하는 검색 결과에 영향을 미치지만 수동 가사 검색 인터페이스의 검색 결과에는 영향을 미치지 않습니다</value>
</data>
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
<value>최소 일치 임계값</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>미디어 라이브러리</value>
</data>
@@ -1135,6 +1189,12 @@
<data name="SettingsPageMicaAlt.Content" xml:space="preserve">
<value>운모 대체</value>
</data>
<data name="SettingsPageMiddleGapColFactor.Header" xml:space="preserve">
<value>중간 영역 갭 폭 계수</value>
</data>
<data name="SettingsPageMiddleGapRowFactor.Header" xml:space="preserve">
<value>중간 간격 영역 높이 계수</value>
</data>
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
<value>테스트 음악을 재생하십시오</value>
</data>
@@ -1168,6 +1228,9 @@
<data name="SettingsPageOriginalText.Header" xml:space="preserve">
<value>원본</value>
</data>
<data name="SettingsPageOverwriteMatchingThreshold.Header" xml:space="preserve">
<value>최소 일치 임계값을 개별적으로 설정하십시오</value>
</data>
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
<value>색상 선택 스타일</value>
</data>
@@ -1249,6 +1312,12 @@
<data name="SettingsPageRestart.Content" xml:space="preserve">
<value>변경 사항을 적용하려면 앱을 다시 시작하십시오</value>
</data>
<data name="SettingsPageRight.Content" xml:space="preserve">
<value>오른쪽</value>
</data>
<data name="SettingsPageRightGapFactor.Header" xml:space="preserve">
<value>오른쪽 간격 영역 너비 계수</value>
</data>
<data name="SettingsPageRomaji.Header" xml:space="preserve">
<value>일본 주석</value>
</data>
@@ -1268,7 +1337,7 @@
<value>현재 라인 기간</value>
</data>
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
<value>가사 스크롤링 애니메이션 유형</value>
<value>가사 애니메이션 유형</value>
</data>
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
<value>첫 번째 줄 지연</value>
@@ -1318,6 +1387,9 @@
<data name="SettingsPageShowInSwitchers.Header" xml:space="preserve">
<value>시스템 환경에서 보여주세요</value>
</data>
<data name="SettingsPageShowLayoutDragger.Header" xml:space="preserve">
<value>레이아웃 스플리터 표시</value>
</data>
<data name="SettingsPageShowTitle.Header" xml:space="preserve">
<value>제목 표시</value>
</data>
@@ -1330,18 +1402,6 @@
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
<value>노래 정보</value>
</data>
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
<value>조정</value>
</data>
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
<value>센터</value>
</data>
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
<value>왼쪽</value>
</data>
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>오른쪽</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>현재 노래</value>
</data>
@@ -1354,12 +1414,21 @@
<data name="SettingsPageSpectrumLayerPlacement.Header" xml:space="preserve">
<value>스펙트럼 위치</value>
</data>
<data name="SettingsPageSpectrumLayerStyle.Header" xml:space="preserve">
<value>스펙트럼 스타일</value>
</data>
<data name="SettingsPageSpectrumPlacementBottom.Content" xml:space="preserve">
<value>맨 아래</value>
</data>
<data name="SettingsPageSpectrumPlacementTop.Content" xml:space="preserve">
<value>맨 위</value>
</data>
<data name="SettingsPageSpectrumStyleBar.Content" xml:space="preserve">
<value>기둥</value>
</data>
<data name="SettingsPageSpectrumStyleCurve.Content" xml:space="preserve">
<value>곡선</value>
</data>
<data name="SettingsPageSpeed.Header" xml:space="preserve">
<value>모션</value>
</data>
@@ -1402,6 +1471,15 @@
<data name="SettingsPageToggleHotKey.Header" xml:space="preserve">
<value>바로 가기 키를 자르고 잘라냅니다</value>
</data>
<data name="SettingsPageTopGapFactor.Header" xml:space="preserve">
<value>상단 간격 영역 높이 계수</value>
</data>
<data name="SettingsPageTrackSummaryColFactor.Header" xml:space="preserve">
<value>트랙 요약 영역 너비 계수</value>
</data>
<data name="SettingsPageTrackSummaryRowFactor.Header" xml:space="preserve">
<value>트랙 요약 영역 높이 계수</value>
</data>
<data name="SettingsPageTranslatedText.Header" xml:space="preserve">
<value>번역</value>
</data>
@@ -1420,6 +1498,9 @@
<data name="SettingsPageVersion.Text" xml:space="preserve">
<value>버전</value>
</data>
<data name="SettingsPageVerticalLayoutFactor.Text" xml:space="preserve">
<value>수직 레이아웃 요소</value>
</data>
<data name="SettingsPageWesternChar.Header" xml:space="preserve">
<value>라틴 알파벳</value>
</data>
@@ -1483,4 +1564,7 @@
<data name="TryRunMultipleInstance" xml:space="preserve">
<value>BetterLyrics 가 이미 실행 중입니다</value>
</data>
<data name="UserGuide.Content" xml:space="preserve">
<value>사용 안내서</value>
</data>
</root>

View File

@@ -130,7 +130,7 @@
<value>画中画模式</value>
</data>
<data name="AppSettingsControlGeneral.Text" xml:space="preserve">
<value>通用</value>
<value>窗口</value>
</data>
<data name="ArtistsSplitHint.Text" xml:space="preserve">
<value>键入多位艺术家时请以下述一种分隔符分隔(不要混用)</value>
@@ -144,7 +144,7 @@
<data name="Cancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<data name="Copy.Content" xml:space="preserve">
<value>拷贝</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
@@ -183,7 +183,7 @@
<data name="HostWindowMusicGalleryButtonToolTip.Content" xml:space="preserve">
<value>音乐库</value>
</data>
<data name="HostWindowSettingsButtonToolTip.Text" xml:space="preserve">
<data name="HostWindowSettingsButtonToolTip.Content" xml:space="preserve">
<value>设置</value>
</data>
<data name="ImportPlaylistSuccessfully" xml:space="preserve">
@@ -231,6 +231,12 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>未找到歌词</value>
</data>
<data name="LyricsPageCachePath.Header" xml:space="preserve">
<value>缓存路径</value>
</data>
<data name="LyricsPageCachePath.Value" xml:space="preserve">
<value>缓存路径</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>歌词来源</value>
</data>
@@ -270,10 +276,13 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻译来源</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<data name="LyricsParseError" xml:space="preserve">
<value>歌词解析失败</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>专辑</value>
</data>
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>艺术家</value>
</data>
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
@@ -312,7 +321,7 @@
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
<value>目标歌词搜索提供商</value>
</data>
<data name="LyricsSearchControlTitle.Text" xml:space="preserve">
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
<value>标题</value>
</data>
<data name="LyricsSearchPageTitle" xml:space="preserve">
@@ -598,6 +607,9 @@
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>专辑区域样式</value>
</data>
<data name="SettingsPageAlignment.Header" xml:space="preserve">
<value>对齐方式</value>
</data>
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
<value>基本 URL</value>
</data>
@@ -655,12 +667,18 @@
<data name="SettingsPageBorderlessHotKey.Header" xml:space="preserve">
<value>无边框歌词窗口快捷方式</value>
</data>
<data name="SettingsPageBottomGapFactor.Header" xml:space="preserve">
<value>底部间隙高度因子</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>包括日志文件,网络歌词缓存</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>缓存</value>
</data>
<data name="SettingsPageCenter.Content" xml:space="preserve">
<value>居中</value>
</data>
<data name="SettingsPageChinese.Header" xml:space="preserve">
<value>中文注音</value>
</data>
@@ -850,6 +868,9 @@
<data name="SettingsPageFullscreenMode.Text" xml:space="preserve">
<value>全屏模式</value>
</data>
<data name="SettingsPageGeneralLayoutFactor.Text" xml:space="preserve">
<value>一般布局系数</value>
</data>
<data name="SettingsPageHeight.Header" xml:space="preserve">
<value>高度</value>
</data>
@@ -859,6 +880,12 @@
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
<value>自动隐藏/显示窗口</value>
</data>
<data name="SettingsPageHoldDragSort.Content" xml:space="preserve">
<value>按住此处并拖动以排序</value>
</data>
<data name="SettingsPageHorizontalLayoutFactor.Text" xml:space="preserve">
<value>水平布局系数</value>
</data>
<data name="SettingsPageImport.Content" xml:space="preserve">
<value>导入</value>
</data>
@@ -913,6 +940,9 @@
<data name="SettingsPageLastFMUsername.Header" xml:space="preserve">
<value>用户名</value>
</data>
<data name="SettingsPageLayout.Text" xml:space="preserve">
<value>布局</value>
</data>
<data name="SettingsPageLayoutOrientation.Header" xml:space="preserve">
<value>布局方向</value>
</data>
@@ -922,6 +952,12 @@
<data name="SettingsPageLayoutOrientationVertical.Content" xml:space="preserve">
<value>垂直</value>
</data>
<data name="SettingsPageLeft.Content" xml:space="preserve">
<value>靠左</value>
</data>
<data name="SettingsPageLeftGapFactor.Header" xml:space="preserve">
<value>左间隙区域宽度因子</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>服务器地址</value>
</data>
@@ -970,8 +1006,8 @@
<data name="SettingsPageLyricsBold.Content" xml:space="preserve">
<value>粗体</value>
</data>
<data name="SettingsPageLyricsCenter.Content" xml:space="preserve">
<value>居中</value>
<data name="SettingsPageLyricsColFactor.Header" xml:space="preserve">
<value>歌词区域宽度因子</value>
</data>
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌词动效</value>
@@ -1021,6 +1057,9 @@
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>字体粗细</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Description" xml:space="preserve">
<value>为持续时间较长的音节启用发光效果</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
<value>辉光效果</value>
</data>
@@ -1060,8 +1099,14 @@
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
<value>当前歌词开始到当前字符</value>
</data>
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>靠右</value>
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
<value>歌词区域高度因子</value>
</data>
<data name="SettingsPageLyricsScaleEffect.Description" xml:space="preserve">
<value>为持续时间较长的音节启用缩放</value>
</data>
<data name="SettingsPageLyricsScaleEffect.Header" xml:space="preserve">
<value>缩放效果</value>
</data>
<data name="SettingsPageLyricsSearchBestMatch.Content" xml:space="preserve">
<value>最佳匹配</value>
@@ -1120,6 +1165,15 @@
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
<value>歌词窗口状态切换器快捷方式</value>
</data>
<data name="SettingsPageLyricsWindowToolTip.Content" xml:space="preserve">
<value>歌词窗口</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>调整此值将影响顺序搜索和最佳匹配搜索结果,但不会影响手动歌词搜索界面中的搜索结果</value>
</data>
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
<value>最小匹配阈值</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>媒体库</value>
</data>
@@ -1135,6 +1189,12 @@
<data name="SettingsPageMicaAlt.Content" xml:space="preserve">
<value>云母(替代样式)</value>
</data>
<data name="SettingsPageMiddleGapColFactor.Header" xml:space="preserve">
<value>中间区域间隙宽度系数</value>
</data>
<data name="SettingsPageMiddleGapRowFactor.Header" xml:space="preserve">
<value>中间间隙区域高度系数</value>
</data>
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
<value>播放测试音乐</value>
</data>
@@ -1168,6 +1228,9 @@
<data name="SettingsPageOriginalText.Header" xml:space="preserve">
<value>原文</value>
</data>
<data name="SettingsPageOverwriteMatchingThreshold.Header" xml:space="preserve">
<value>单独设置最小匹配阈值</value>
</data>
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
<value>取色风格</value>
</data>
@@ -1249,6 +1312,12 @@
<data name="SettingsPageRestart.Content" xml:space="preserve">
<value>重启应用以应用更改</value>
</data>
<data name="SettingsPageRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageRightGapFactor.Header" xml:space="preserve">
<value>右间隙区域宽度因子</value>
</data>
<data name="SettingsPageRomaji.Header" xml:space="preserve">
<value>日语注音</value>
</data>
@@ -1268,7 +1337,7 @@
<value>当前行持续时间</value>
</data>
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
<value>歌词滚动动画类型</value>
<value>歌词动画类型</value>
</data>
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
<value>首行延时</value>
@@ -1318,6 +1387,9 @@
<data name="SettingsPageShowInSwitchers.Header" xml:space="preserve">
<value>在系统环境中显示</value>
</data>
<data name="SettingsPageShowLayoutDragger.Header" xml:space="preserve">
<value>显示布局拆分器</value>
</data>
<data name="SettingsPageShowTitle.Header" xml:space="preserve">
<value>显示标题</value>
</data>
@@ -1330,18 +1402,6 @@
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
<value>歌曲信息</value>
</data>
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
<value>对齐方式</value>
</data>
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
<value>居中</value>
</data>
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
<value>靠左</value>
</data>
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>当前歌曲</value>
</data>
@@ -1354,12 +1414,21 @@
<data name="SettingsPageSpectrumLayerPlacement.Header" xml:space="preserve">
<value>频谱位置</value>
</data>
<data name="SettingsPageSpectrumLayerStyle.Header" xml:space="preserve">
<value>频谱风格</value>
</data>
<data name="SettingsPageSpectrumPlacementBottom.Content" xml:space="preserve">
<value>底部</value>
</data>
<data name="SettingsPageSpectrumPlacementTop.Content" xml:space="preserve">
<value>顶部</value>
</data>
<data name="SettingsPageSpectrumStyleBar.Content" xml:space="preserve">
<value>柱形</value>
</data>
<data name="SettingsPageSpectrumStyleCurve.Content" xml:space="preserve">
<value>曲线</value>
</data>
<data name="SettingsPageSpeed.Header" xml:space="preserve">
<value>运动速率</value>
</data>
@@ -1402,6 +1471,15 @@
<data name="SettingsPageToggleHotKey.Header" xml:space="preserve">
<value>切入与切出快捷键</value>
</data>
<data name="SettingsPageTopGapFactor.Header" xml:space="preserve">
<value>顶部间隙区域高度因子</value>
</data>
<data name="SettingsPageTrackSummaryColFactor.Header" xml:space="preserve">
<value>曲目摘要区域宽度因子</value>
</data>
<data name="SettingsPageTrackSummaryRowFactor.Header" xml:space="preserve">
<value>曲目摘要区域高度系数</value>
</data>
<data name="SettingsPageTranslatedText.Header" xml:space="preserve">
<value>译文</value>
</data>
@@ -1420,6 +1498,9 @@
<data name="SettingsPageVersion.Text" xml:space="preserve">
<value>版本号</value>
</data>
<data name="SettingsPageVerticalLayoutFactor.Text" xml:space="preserve">
<value>垂直布局系数</value>
</data>
<data name="SettingsPageWesternChar.Header" xml:space="preserve">
<value>拉丁字母</value>
</data>
@@ -1483,4 +1564,7 @@
<data name="TryRunMultipleInstance" xml:space="preserve">
<value>BetterLyrics 已经在运行</value>
</data>
<data name="UserGuide.Content" xml:space="preserve">
<value>使用指南</value>
</data>
</root>

View File

@@ -130,7 +130,7 @@
<value>畫中畫模式</value>
</data>
<data name="AppSettingsControlGeneral.Text" xml:space="preserve">
<value>一般</value>
<value>视窗</value>
</data>
<data name="ArtistsSplitHint.Text" xml:space="preserve">
<value>鍵入多位藝術家時請以下述一種分隔符號分隔(不要混用)</value>
@@ -144,7 +144,7 @@
<data name="Cancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="Copy.Text" xml:space="preserve">
<data name="Copy.Content" xml:space="preserve">
<value>複製</value>
</data>
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
@@ -183,7 +183,7 @@
<data name="HostWindowMusicGalleryButtonToolTip.Content" xml:space="preserve">
<value>音樂庫</value>
</data>
<data name="HostWindowSettingsButtonToolTip.Text" xml:space="preserve">
<data name="HostWindowSettingsButtonToolTip.Content" xml:space="preserve">
<value>設定</value>
</data>
<data name="ImportPlaylistSuccessfully" xml:space="preserve">
@@ -231,6 +231,12 @@
<data name="LyricsNotFound" xml:space="preserve">
<value>找不到歌詞</value>
</data>
<data name="LyricsPageCachePath.Header" xml:space="preserve">
<value>檔案路徑緩存</value>
</data>
<data name="LyricsPageCachePath.Value" xml:space="preserve">
<value>檔案路徑緩存</value>
</data>
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>歌詞來源</value>
</data>
@@ -270,10 +276,13 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻譯來源</value>
</data>
<data name="LyricsSearchControlAlbum.Text" xml:space="preserve">
<data name="LyricsParseError" xml:space="preserve">
<value>歌詞解析失敗</value>
</data>
<data name="LyricsSearchControlAlbum.Header" xml:space="preserve">
<value>專輯</value>
</data>
<data name="LyricsSearchControlArtist.Text" xml:space="preserve">
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
<value>藝術家</value>
</data>
<data name="LyricsSearchControlAutoGenerated.Tag" xml:space="preserve">
@@ -312,7 +321,7 @@
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
<value>目標歌詞搜尋提供者</value>
</data>
<data name="LyricsSearchControlTitle.Text" xml:space="preserve">
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
<value>標題</value>
</data>
<data name="LyricsSearchPageTitle" xml:space="preserve">
@@ -598,6 +607,9 @@
<data name="SettingsPageAlbumStyle.Text" xml:space="preserve">
<value>專輯區域樣式</value>
</data>
<data name="SettingsPageAlignment.Header" xml:space="preserve">
<value>對齊方式</value>
</data>
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
<value>基本網址</value>
</data>
@@ -655,12 +667,18 @@
<data name="SettingsPageBorderlessHotKey.Header" xml:space="preserve">
<value>無邊框歌詞視窗捷徑</value>
</data>
<data name="SettingsPageBottomGapFactor.Header" xml:space="preserve">
<value>底部間隙高度因子</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>包括日誌文件,網絡歌詞緩存</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>快取</value>
</data>
<data name="SettingsPageCenter.Content" xml:space="preserve">
<value>居中</value>
</data>
<data name="SettingsPageChinese.Header" xml:space="preserve">
<value>中文注音</value>
</data>
@@ -850,6 +868,9 @@
<data name="SettingsPageFullscreenMode.Text" xml:space="preserve">
<value>全螢幕模式</value>
</data>
<data name="SettingsPageGeneralLayoutFactor.Text" xml:space="preserve">
<value>一般布局系數</value>
</data>
<data name="SettingsPageHeight.Header" xml:space="preserve">
<value>高度</value>
</data>
@@ -859,6 +880,12 @@
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
<value>自動隱藏/顯示窗口</value>
</data>
<data name="SettingsPageHoldDragSort.Content" xml:space="preserve">
<value>按住這裡並拖曳以進行排序</value>
</data>
<data name="SettingsPageHorizontalLayoutFactor.Text" xml:space="preserve">
<value>水平布局系數</value>
</data>
<data name="SettingsPageImport.Content" xml:space="preserve">
<value>匯入</value>
</data>
@@ -913,6 +940,9 @@
<data name="SettingsPageLastFMUsername.Header" xml:space="preserve">
<value>使用者名稱</value>
</data>
<data name="SettingsPageLayout.Text" xml:space="preserve">
<value>佈局</value>
</data>
<data name="SettingsPageLayoutOrientation.Header" xml:space="preserve">
<value>版面配置方向</value>
</data>
@@ -922,6 +952,12 @@
<data name="SettingsPageLayoutOrientationVertical.Content" xml:space="preserve">
<value>縱向</value>
</data>
<data name="SettingsPageLeft.Content" xml:space="preserve">
<value>靠左</value>
</data>
<data name="SettingsPageLeftGapFactor.Header" xml:space="preserve">
<value>左間隙區域寬度因子</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>服務器地址</value>
</data>
@@ -970,8 +1006,8 @@
<data name="SettingsPageLyricsBold.Content" xml:space="preserve">
<value>粗體</value>
</data>
<data name="SettingsPageLyricsCenter.Content" xml:space="preserve">
<value>居中</value>
<data name="SettingsPageLyricsColFactor.Header" xml:space="preserve">
<value>歌詞區域寬度因子</value>
</data>
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌詞動效</value>
@@ -1021,6 +1057,9 @@
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>字體粗細</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Description" xml:space="preserve">
<value>為持續時間較長的音節啟用發光效果</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
<value>輝光效果</value>
</data>
@@ -1060,8 +1099,14 @@
<data name="SettingsPageLyricsRendingScopeLineStartToCurrentChar.Content" xml:space="preserve">
<value>當前歌詞開始到當前字符</value>
</data>
<data name="SettingsPageLyricsRight.Content" xml:space="preserve">
<value>靠右</value>
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
<value>歌詞區域高度因子</value>
</data>
<data name="SettingsPageLyricsScaleEffect.Description" xml:space="preserve">
<value>啟用長期音節的縮放功能</value>
</data>
<data name="SettingsPageLyricsScaleEffect.Header" xml:space="preserve">
<value>縮放效果</value>
</data>
<data name="SettingsPageLyricsSearchBestMatch.Content" xml:space="preserve">
<value>最佳匹配</value>
@@ -1120,6 +1165,15 @@
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
<value>歌詞視窗狀態切換器捷徑</value>
</data>
<data name="SettingsPageLyricsWindowToolTip.Content" xml:space="preserve">
<value>歌詞視窗</value>
</data>
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
<value>調整此值將影響順序搜索和最佳匹配搜索結果,但不會影響手動歌詞搜索界面中的搜索結果</value>
</data>
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
<value>最低匹配閾值</value>
</data>
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
<value>媒體庫</value>
</data>
@@ -1135,6 +1189,12 @@
<data name="SettingsPageMicaAlt.Content" xml:space="preserve">
<value>雲母(替代樣式)</value>
</data>
<data name="SettingsPageMiddleGapColFactor.Header" xml:space="preserve">
<value>中間區域間隙寬度因子</value>
</data>
<data name="SettingsPageMiddleGapRowFactor.Header" xml:space="preserve">
<value>中間間隙區域高度因子</value>
</data>
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
<value>播放測試音樂</value>
</data>
@@ -1168,6 +1228,9 @@
<data name="SettingsPageOriginalText.Header" xml:space="preserve">
<value>原文</value>
</data>
<data name="SettingsPageOverwriteMatchingThreshold.Header" xml:space="preserve">
<value>單獨設置最低匹配閾值</value>
</data>
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
<value>取色風格</value>
</data>
@@ -1249,6 +1312,12 @@
<data name="SettingsPageRestart.Content" xml:space="preserve">
<value>重啟應用程式以應用更改</value>
</data>
<data name="SettingsPageRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageRightGapFactor.Header" xml:space="preserve">
<value>右間隙區域寬度因子</value>
</data>
<data name="SettingsPageRomaji.Header" xml:space="preserve">
<value>日語注音</value>
</data>
@@ -1268,7 +1337,7 @@
<value>當前行持續時間</value>
</data>
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
<value>歌詞滾動動畫類型</value>
<value>歌詞動畫類型</value>
</data>
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
<value>首行延時</value>
@@ -1307,7 +1376,7 @@
<value>顯示專輯</value>
</data>
<data name="SettingsPageShowArtists.Header" xml:space="preserve">
<value>界面設計</value>
<value>顯示藝術家</value>
</data>
<data name="SettingsPageShowHideHotKey.Header" xml:space="preserve">
<value>歌詞窗口顯示和隱藏快捷鍵</value>
@@ -1318,6 +1387,9 @@
<data name="SettingsPageShowInSwitchers.Header" xml:space="preserve">
<value>在系統環境中顯示</value>
</data>
<data name="SettingsPageShowLayoutDragger.Header" xml:space="preserve">
<value>顯示佈局拆分器</value>
</data>
<data name="SettingsPageShowTitle.Header" xml:space="preserve">
<value>顯示標題</value>
</data>
@@ -1330,18 +1402,6 @@
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
<value>歌曲資訊</value>
</data>
<data name="SettingsPageSongInfoAlignment.Header" xml:space="preserve">
<value>對齊方式</value>
</data>
<data name="SettingsPageSongInfoCenter.Content" xml:space="preserve">
<value>居中</value>
</data>
<data name="SettingsPageSongInfoLeft.Content" xml:space="preserve">
<value>靠左</value>
</data>
<data name="SettingsPageSongInfoRight.Content" xml:space="preserve">
<value>靠右</value>
</data>
<data name="SettingsPageSongStatus.Header" xml:space="preserve">
<value>當前歌曲</value>
</data>
@@ -1354,12 +1414,21 @@
<data name="SettingsPageSpectrumLayerPlacement.Header" xml:space="preserve">
<value>頻譜位置</value>
</data>
<data name="SettingsPageSpectrumLayerStyle.Header" xml:space="preserve">
<value>頻譜風格</value>
</data>
<data name="SettingsPageSpectrumPlacementBottom.Content" xml:space="preserve">
<value>底部</value>
</data>
<data name="SettingsPageSpectrumPlacementTop.Content" xml:space="preserve">
<value>頂部</value>
</data>
<data name="SettingsPageSpectrumStyleBar.Content" xml:space="preserve">
<value>柱形</value>
</data>
<data name="SettingsPageSpectrumStyleCurve.Content" xml:space="preserve">
<value>曲線</value>
</data>
<data name="SettingsPageSpeed.Header" xml:space="preserve">
<value>動作速率</value>
</data>
@@ -1402,6 +1471,15 @@
<data name="SettingsPageToggleHotKey.Header" xml:space="preserve">
<value>切入與切出快捷鍵</value>
</data>
<data name="SettingsPageTopGapFactor.Header" xml:space="preserve">
<value>頂部間隙區域高度因子</value>
</data>
<data name="SettingsPageTrackSummaryColFactor.Header" xml:space="preserve">
<value>曲目摘要區域寬度因子</value>
</data>
<data name="SettingsPageTrackSummaryRowFactor.Header" xml:space="preserve">
<value>曲目摘要區域高度因子</value>
</data>
<data name="SettingsPageTranslatedText.Header" xml:space="preserve">
<value>譯文</value>
</data>
@@ -1420,6 +1498,9 @@
<data name="SettingsPageVersion.Text" xml:space="preserve">
<value>版本號</value>
</data>
<data name="SettingsPageVerticalLayoutFactor.Text" xml:space="preserve">
<value>垂直布局系數</value>
</data>
<data name="SettingsPageWesternChar.Header" xml:space="preserve">
<value>拉丁字母</value>
</data>
@@ -1483,4 +1564,7 @@
<data name="TryRunMultipleInstance" xml:space="preserve">
<value>BetterLyrics 已經在運作</value>
</data>
<data name="UserGuide.Content" xml:space="preserve">
<value>使用手冊</value>
</data>
</root>

View File

@@ -36,7 +36,7 @@ namespace BetterLyrics.WinUI3.ViewModels
[RelayCommand]
private async Task LaunchProjectGitHubPageAsync()
{
await Windows.System.Launcher.LaunchUriAsync(new Uri(Constants.Link.GitHubUrl));
await Windows.System.Launcher.LaunchUriAsync(new Uri(Constants.Link.GitHub));
}
[RelayCommand]

View File

@@ -1,6 +1,5 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.LiveStatesService;
@@ -8,47 +7,18 @@ using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Dispatching;
using System;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsPageViewModel : BaseViewModel,
IRecipient<PropertyChangedMessage<TimeSpan>>
public partial class LyricsPageViewModel : BaseViewModel
{
public IMediaSessionsService MediaSessionsService { get; private set; }
private readonly ILiveStatesService _liveStatesService;
private readonly ThrottleHelper _timelineThrottle = new(TimeSpan.FromSeconds(1));
public LyricsRendererViewModel.LyricsRendererViewModel LyricsRendererViewModel { get; private set; }
public LyricsPageViewModel(IMediaSessionsService mediaSessionsService, ILiveStatesService liveStatesService, LyricsRendererViewModel.LyricsRendererViewModel lyricsRendererViewModel)
{
_liveStatesService = liveStatesService;
MediaSessionsService = mediaSessionsService;
LyricsRendererViewModel = lyricsRendererViewModel;
LiveStates = _liveStatesService.LiveStates;
Volume = SystemVolumeHook.MasterVolume;
SystemVolumeHook.VolumeNotification += SystemVolumeHelper_VolumeNotification;
}
private void SystemVolumeHelper_VolumeNotification(object? sender, int e)
{
Volume = e;
}
[ObservableProperty]
public partial LiveStates LiveStates { get; set; }
[ObservableProperty]
public partial double TimelinePositionSeconds { get; set; }
[ObservableProperty]
public partial int Volume { get; set; }
@@ -67,6 +37,26 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
public partial double TimelineSliderThumbSeconds { get; set; } = 0;
public LyricsPageViewModel(IMediaSessionsService mediaSessionsService, ILiveStatesService liveStatesService)
{
_liveStatesService = liveStatesService;
MediaSessionsService = mediaSessionsService;
LiveStates = _liveStatesService.LiveStates;
Volume = SystemVolumeHook.MasterVolume;
SystemVolumeHook.VolumeNotification += SystemVolumeHelper_VolumeNotification;
}
private void SystemVolumeHelper_VolumeNotification(object? sender, int e)
{
Volume = e;
}
partial void OnTimelineSliderThumbSecondsChanged(double value)
{
TimelineSliderThumbLyricsLine = MediaSessionsService.CurrentLyricsData?.GetLyricsLine(value);
}
[RelayCommand]
private static void OpenSettingsWindow()
@@ -98,27 +88,5 @@ namespace BetterLyrics.WinUI3.ViewModels
await MediaSessionsService.NextAsync();
}
partial void OnTimelineSliderThumbSecondsChanged(double value)
{
TimelineSliderThumbLyricsLine = MediaSessionsService.CurrentLyricsData?.GetLyricsLine(value);
}
public void Receive(PropertyChangedMessage<TimeSpan> message)
{
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.TotalTime))
{
if (_timelineThrottle.CanTrigger())
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
TimelinePositionSeconds = message.NewValue.TotalSeconds;
});
}
}
}
}
}
}

View File

@@ -1,25 +0,0 @@
using Microsoft.Extensions.Logging;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
public void CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
_logger.LogInformation("Creating resources... Reason: {Reason}", args.Reason);
switch (args.Reason)
{
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.FirstTime:
_isDeviceChanged = true;
break;
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.NewDevice:
_isDeviceChanged = true;
break;
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.DpiChanged:
break;
default:
break;
}
}
}
}

View File

@@ -1,443 +0,0 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using CommunityToolkit.WinUI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using System;
using System.Linq;
using System.Numerics;
using Windows.Foundation;
using Windows.Graphics.Effects;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
// Blurred lyrics layer
using var blurredLyrics = new CanvasCommandList(control);
using (var blurredLyricsDs = blurredLyrics.CreateDrawingSession())
{
DrawBlurredLyrics(control, blurredLyricsDs);
}
using var combined = new CanvasCommandList(control);
using var combinedDs = combined.CreateDrawingSession();
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsPureColorOverlayEnabled)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment)
{
FillBackground(combinedDs, _immersiveBgColorTransition.Value, 0f,
_immersiveBgOpacityTransition.Value * _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PureColorOverlayOpacity / 100f);
}
else
{
FillBackground(combinedDs, _albumArtAccentColor1Transition.Value, 0f,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PureColorOverlayOpacity / 100.0);
}
}
DrawAlbumArtBackground(control, combinedDs);
DrawFluidBackground(control, combinedDs);
DrawSpectrum(control, combinedDs);
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.Is3DLyricsEnabled)
{
combinedDs.DrawImage(new Transform3DEffect
{
Source = blurredLyrics,
TransformMatrix = _lyrics3DMatrix
});
}
else
{
combinedDs.DrawImage(blurredLyrics);
}
ds.DrawImage(combined);
DrawAlbumArt(control, ds);
DrawSongInfo(ds);
DrawSnowEffect(ds);
DrawFogEffect(ds);
//DrawRaindropEffect(ds, combined);
if (_isDebugOverlayEnabled)
{
_drawFrameCount++;
var currentPlayingLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
if (currentPlayingLine != null)
{
GetLinePlayingProgress(
_playingLineIndex,
out int charStartIndex,
out int charLength,
out double charProgress
);
ds.DrawText(
$"[DEBUG]\n" +
$"Canvas size: {_canvasWidth}x{_canvasHeight}\n" +
$"FPS (Draw): {_displayedDrawFrameCount}\n" +
$"Playing line: {_playingLineIndex}\n" +
$"Syllable start idx: {charStartIndex}\n" +
$"Syllable len: {charLength}\n" +
$"Syllable prog: {charProgress}\n" +
$"Visible lines: [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
$"Total line count: {GetMaxLyricsLineIndexBoundaries().Item2 + 1}\n" +
$"Cur time: {TotalTime + TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0)}\n" +
$"Song duration: {TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0)}\n" +
$"Y offset: {_canvasYScrollTransition.Value}",
new Vector2(10, 40),
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White,
_debugTextFormat
);
}
if (_drawFrameStopwatch?.Elapsed.TotalSeconds >= 1.0)
{
_displayedDrawFrameCount = _drawFrameCount;
_drawFrameStopwatch?.Restart();
_drawFrameCount = 0;
}
}
}
public void DrawSpectrum(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
if (_spectrumGeometry != null)
{
var gradientStops = new CanvasGradientStop[]
{
new() { Position = 0.0f, Color = Colors.Transparent },
new() { Position = 0.7f, Color = Colors.Transparent },
new() { Position = 1.0f, Color = _adaptiveColoredFontColor ?? _albumArtAccentColor1Transition.Value }
};
using var gradientBrush = new CanvasLinearGradientBrush(ds, gradientStops);
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.SpectrumPlacement)
{
case SpectrumPlacement.Top:
gradientBrush.StartPoint = new Vector2(0, (float)_canvasHeight);
gradientBrush.EndPoint = new Vector2(0, 0);
break;
case SpectrumPlacement.Bottom:
gradientBrush.StartPoint = new Vector2(0, 0);
gradientBrush.EndPoint = new Vector2(0, (float)_canvasHeight);
break;
default:
break;
}
// 使用渐变画刷填充
ds.FillGeometry(_spectrumGeometry, gradientBrush);
// 纯色
//ds.FillGeometry(geometry, _adaptiveColoredFontColor ?? _albumArtAccentColor1Transition.Value);
// 绘制轮廓线
//var lineColor = Colors.SkyBlue;
//float strokeWidth = 2f;
//ds.DrawGeometry(geometry, _albumArtAccentColor4Transition.Value, strokeWidth);
}
}
private void DrawFluidBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
if (_fluidEffect != null && _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsFluidOverlayEnabled)
{
ds.DrawImage(new OpacityEffect
{
Source = _fluidEffect,
Opacity = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.FluidOverlayOpacity / 100f
});
}
}
private void DrawBackgroundImgae(OpacityEffect effect, CanvasDrawingSession ds, CanvasBitmap canvasBitmap)
{
double imageWidth = (double)canvasBitmap.Size.Width;
double imageHeight = (double)canvasBitmap.Size.Height;
double targetSize = Math.Sqrt(Math.Pow(_canvasWidth, 2) + Math.Pow(_canvasHeight, 2));
double scaleFactor = targetSize / Math.Min(imageWidth, imageHeight);
double x = _canvasWidth / 2 - imageWidth * scaleFactor / 2;
double y = _canvasHeight / 2 - imageHeight * scaleFactor / 2;
ds.DrawImage(effect, new Vector2((float)x, (float)y));
}
private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsCoverOverlayEnabled)
{
ds.Transform = Matrix3x2.CreateRotation((float)_rotateAngle, control.Size.ToVector2() * 0.5f);
if (_isAlbumArtBgEffectChanged && _albumArtBgEffect != null)
{
ds.DrawImage(_albumArtBgEffect);
}
else if (!_isAlbumArtBgEffectChanged && _albumArtBgRenderTarget != null)
{
double targetSize = Math.Sqrt(Math.Pow(_canvasWidth, 2) + Math.Pow(_canvasHeight, 2));
float offsetX = (float)(_canvasWidth - targetSize) / 2;
float offsetY = (float)(_canvasHeight - targetSize) / 2;
ds.DrawImage(_albumArtBgRenderTarget, new Vector2(offsetX, offsetY));
}
ds.Transform = Matrix3x2.Identity;
}
}
private void DrawAlbumArt(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
// 专辑图封面正在变动,需实时绘制
if (_isAlbumArtEffectChanged && _albumArtEffect != null)
{
ds.DrawImage(new OpacityEffect
{
Source = _albumArtEffect,
Opacity = (float)_albumArtOpacityTransition.Value
}, new Vector2((float)_albumArtXTransition.Value, (float)_albumArtYTransition.Value));
}
// 专辑图封面不再变动,使用已保存的绘制
else if (!_isAlbumArtEffectChanged && _albumArtRenderTarget != null)
{
// 这里给一个相反的偏移以恢复位置
ds.DrawImage(new OpacityEffect
{
Source = _albumArtRenderTarget,
Opacity = (float)_albumArtOpacityTransition.Value
}, new Vector2((float)_albumArtXTransition.Value, (float)_albumArtYTransition.Value) -
control.Size.ToVector2() / 2 + new Vector2((float)_albumArtSize, (float)_albumArtSize) / 2);
}
}
private void DrawSongInfo(CanvasDrawingSession ds)
{
if (_maxSongInfoWidth <= 0)
{
return;
}
DrawSingleSongInfo(ds, _lastTitleTextLayout, _lastArtistTextLayout, _lastAlbumTextLayout, 1 - _songInfoOpacityTransition.Value);
DrawSingleSongInfo(ds, _titleTextLayout, _artistTextLayout, _albumTextLayout, _songInfoOpacityTransition.Value);
}
private void DrawSingleSongInfo(CanvasDrawingSession ds, CanvasTextLayout? titleLayout, CanvasTextLayout? artistLayout, CanvasTextLayout? albumLayout, double opacity)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowTitle && titleLayout != null)
{
double y = _titleYTransition.Value;
ds.DrawTextLayout(
titleLayout,
new Vector2((float)_titleXTransition.Value, (float)y),
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 255 * opacity)));
y += titleLayout.LayoutBounds.Height;
if (_liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowArtists && artistLayout != null)
{
ds.DrawTextLayout(
artistLayout,
new Vector2((float)_titleXTransition.Value, (float)y),
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 128 * opacity)));
y += artistLayout.LayoutBounds.Height;
}
if (_liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowAlbum && albumLayout != null)
{
ds.DrawTextLayout(
albumLayout,
new Vector2((float)_titleXTransition.Value, (float)y),
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 64 * opacity)));
}
}
}
private void DrawBlurredLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
var currentPlayingLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
if (currentPlayingLine == null)
{
return;
}
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i);
if (line == null) continue;
var textLayout = line.OriginalCanvasTextLayout;
if (textLayout == null) continue;
double layoutWidth = (double)textLayout.LayoutBounds.Width;
double layoutHeight = (double)textLayout.LayoutBounds.Height;
if (layoutWidth <= 0 || layoutHeight <= 0) continue;
double yOffset = line.YOffsetTransition.Value + _canvasHeight / 2 + _lyricsYTransition.Value;
//// 组合变换:缩放 -> 旋转 -> 平移
ds.Transform =
Matrix3x2.CreateScale((float)line.ScaleTransition.Value, line.CenterPosition)
* Matrix3x2.CreateRotation((float)line.AngleTransition.Value,
currentPlayingLine.OriginalPosition.WithX(_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.FanLyricsAngle < 0 ? (float)_maxLyricsWidth : 0))
* Matrix3x2.CreateTranslation((float)_lyricsX, (float)yOffset);
using var combined = new CanvasCommandList(control);
using var combinedDs = combined.CreateDrawingSession();
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层(背景歌词层次)
using var backgroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontStrokeWidth, _bgFontColor);
using var backgroundEffect = CanvasHelper.CreateBackgroundEffect(line, backgroundFontEffect, _lyricsOpacityTransition.Value);
combinedDs.DrawImage(backgroundEffect);
if (line.HighlightOpacityTransition.Value != 0)
{
GetLinePlayingProgress(i, out int charStartIndex, out int charLength, out double charProgress);
using var charMask = CanvasHelper.CreateCharMask(control, line, charStartIndex, charLength, charProgress);
using var lineStartToCharMask = CanvasHelper.CreateLineStartToCharMask(control, line, charStartIndex, charLength, charProgress,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.IsLyricsLineFadeEnabled);
using var lineMask = CanvasHelper.CreateLineMask(control, line);
using var foregroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontStrokeWidth, _fgFontColor);
using var effectLayer = new CanvasCommandList(control);
using var effectLayerDs = effectLayer.CreateDrawingSession();
if (line.PhoneticText != "" && _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.PhoneticLyricsHighlightAmount != 0)
{
using var phoneticHighlightMask = CanvasHelper.CreatePhoneticHighlightMask(control, line);
using var foregroundPhoneticHighlightEffect = CanvasHelper.CreateForegroundHighlightEffect(foregroundFontEffect, phoneticHighlightMask,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.PhoneticLyricsHighlightAmount / 100.0);
effectLayerDs.DrawImage(foregroundPhoneticHighlightEffect);
}
if (line.TranslatedText != "" && _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.TranslatedLyricsHighlightAmount != 0)
{
using var translatedHighlightMask = CanvasHelper.CreateTranslatedHighlightMask(control, line);
using var foregroundTranslatedHighlightEffect = CanvasHelper.CreateForegroundHighlightEffect(foregroundFontEffect, translatedHighlightMask,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.TranslatedLyricsHighlightAmount / 100.0);
effectLayerDs.DrawImage(foregroundTranslatedHighlightEffect);
}
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.IsLyricsShadowEnabled)
{
var shadowEffectMask = CanvasHelper.GetAlphaMask(control, charMask, lineStartToCharMask, lineMask,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.LyricsShadowScope);
using var foregroundShadowEffect = CanvasHelper.CreateForegroundShadowEffect(foregroundFontEffect, shadowEffectMask,
_albumArtAccentColor1Transition.Value, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.LyricsShadowAmount);
effectLayerDs.DrawImage(foregroundShadowEffect);
}
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.IsLyricsGlowEffectEnabled)
{
var blurEffectMask = CanvasHelper.GetAlphaMask(control, charMask, lineStartToCharMask, lineMask,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.LyricsGlowEffectScope);
using var foregroundBlurEffect = CanvasHelper.CreateForegroundBlurEffect(foregroundFontEffect, blurEffectMask,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.LyricsGlowEffectAmount);
effectLayerDs.DrawImage(foregroundBlurEffect);
}
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.OriginalLyricsHighlightAmount != 0)
{
var highlightEffectMask = CanvasHelper.GetAlphaMask(control, charMask, lineStartToCharMask, lineMask,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.OriginalLyricsHighlightScope);
using var foregroundHighlightEffect = CanvasHelper.CreateForegroundHighlightEffect(foregroundFontEffect, highlightEffectMask,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.OriginalLyricsHighlightAmount / 100.0);
effectLayerDs.DrawImage(foregroundHighlightEffect);
}
combinedDs.DrawImage(new OpacityEffect
{
Source = effectLayer,
Opacity = (float)(line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value),
});
if (i == _playingLineIndex)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.IsLyricsFloatAnimationEnabled)
{
ds.DrawImage(new DisplacementMapEffect
{
Source = combined,
Displacement = lineStartToCharMask,
XChannelSelect = EffectChannelSelect.Red,
YChannelSelect = EffectChannelSelect.Alpha,
Amount = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings.LyricsFloatAmount,
});
}
else
{
ds.DrawImage(combined);
}
}
else
{
ds.DrawImage(combined);
}
}
else
{
ds.DrawImage(combined);
}
// Reset scale
ds.Transform = Matrix3x2.Identity;
}
}
private void DrawSnowEffect(CanvasDrawingSession ds)
{
if (_snowEffect != null && _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsSnowFlakeOverlayEnabled)
{
ds.DrawImage(_snowEffect);
}
}
private void DrawFogEffect(CanvasDrawingSession ds)
{
if (_fogEffect != null && _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsFogOverlayEnabled)
{
ds.DrawImage(_fogEffect);
}
}
private void DrawRaindropEffect(CanvasDrawingSession ds, IGraphicsEffectSource source)
{
if (_raindropEffect != null)
{
_raindropEffect.Sources[0] = source;
ds.DrawImage(_raindropEffect);
}
}
private void FillBackground(CanvasDrawingSession ds, Color color, double radius, double opacity)
{
ds.FillRoundedRectangle(
new Rect(0, 0, _canvasWidth, _canvasHeight),
(float)radius,
(float)radius,
color.WithAlpha((byte)(opacity * 255))
);
}
}
}

View File

@@ -1,308 +0,0 @@
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Shaders;
using ComputeSharp.D2D1.WinUI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using System;
using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Streams;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
private OpacityEffect? _albumArtBgEffect;
private CanvasCommandList? _albumArtEffect;
private PixelShaderEffect? _fluidEffect;
private PixelShaderEffect<SnowEffect>? _snowEffect;
private PixelShaderEffect<FogEffect>? _fogEffect;
private PixelShaderEffect<RaindropEffect>? _raindropEffect;
private OpacityEffect CreateBgImageEffect(CanvasBitmap canvasBitmap, double opacity)
{
double imageWidth = (double)canvasBitmap.Size.Width;
double imageHeight = (double)canvasBitmap.Size.Height;
double targetSize = Math.Sqrt(Math.Pow(_canvasWidth, 2) + Math.Pow(_canvasHeight, 2));
double scaleFactor = targetSize / Math.Min(imageWidth, imageHeight);
// Original source: https://zhuanlan.zhihu.com/p/37178216
double gain = _lyricsBgBrightnessTransition.Value;
double whiteX = 1 - 0.5f * gain;
double whiteY = 0.5f + 0.5f * gain;
double blackX = 0.5f - 0.5f * gain;
double blackY = 0 + 0.5f * gain;
return new OpacityEffect
{
Source = new BrightnessEffect
{
Source = new ScaleEffect
{
Scale = new Vector2((float)scaleFactor),
Source = canvasBitmap,
},
WhitePoint = new Vector2((float)whiteX, (float)whiteY),
BlackPoint = new Vector2((float)blackX, (float)blackY),
},
Opacity = (float)opacity,
};
}
private OpacityEffect? CreateFgImageEffect(ICanvasAnimatedControl control, CanvasBitmap canvasBitmap, double opacity)
{
if (opacity == 0) return null;
double imageWidth = (double)canvasBitmap.Size.Width;
double imageHeight = (double)canvasBitmap.Size.Height;
double scaleFactor = _albumArtSize / Math.Min(imageWidth, imageHeight);
if (scaleFactor < 0.01f) return null;
double cornerRadius = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.CoverImageRadius / 100f * _albumArtSize / 2;
var cornerRadiusMask = new CanvasCommandList(control);
using var cornerRadiusMaskDs = cornerRadiusMask.CreateDrawingSession();
cornerRadiusMaskDs.FillRoundedRectangle(
new Rect(0, 0, imageWidth * scaleFactor, imageHeight * scaleFactor),
(float)cornerRadius, (float)cornerRadius, Colors.White
);
return new OpacityEffect
{
Source = new AlphaMaskEffect
{
Source = new ScaleEffect
{
Scale = new Vector2((float)scaleFactor),
Source = canvasBitmap,
},
AlphaMask = cornerRadiusMask,
},
Opacity = (float)opacity,
};
}
private void DisposeAlbumArtBgEffect()
{
_albumArtBgEffect?.Dispose();
_albumArtBgEffect = null;
}
/// <summary>
/// 更新专辑封面背景效果
/// <para>应该在以下任意条件满足时调用此函数:</para>
/// <para><seealso cref="_isCanvasWidthChanged"/> == true</para>
/// <para><seealso cref="_isCanvasHeightChanged"/> == true</para>
/// <para><seealso cref="_albumArtChanged"/> == true</para>
/// <para><seealso cref="_isAlbumArtBgBlurAmountChanged"/> == true</para>
/// <para><seealso cref="_isCoverAcrylicEffectAmountChanged"/> == true</para>
/// <para><seealso cref="_isAlbumArtBgOpacityChanged"/> == true</para>
/// <para><seealso cref="_albumArtBgTransition"/> 正在变化</para>
/// <para><seealso cref="_albumArtAccentColor1Transition"/> 正在变化</para>
/// 如果上述条件均不满足,需调用 <seealso cref="UpdateAlbumArtBgRenderTarget"/> 来更新渲染缓存
/// </summary>
/// <param name="control"></param>
private void UpdateAlbumArtBgEffect(ICanvasAnimatedControl control)
{
DisposeAlbumArtBgEffect();
using var overlappedCovers = new CanvasCommandList(control);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
if (_lastAlbumArtCanvasBitmap != null && !_lastAlbumArtCanvasBitmap.IsDisposed())
{
using var lastBgImageEffect = CreateBgImageEffect(_lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
DrawBackgroundImgae(lastBgImageEffect, overlappedCoversDs, _lastAlbumArtCanvasBitmap);
}
if (_albumArtCanvasBitmap != null && !_albumArtCanvasBitmap.IsDisposed())
{
using var bgImageEffect = CreateBgImageEffect(_albumArtCanvasBitmap, _albumArtBgTransition.Value);
DrawBackgroundImgae(bgImageEffect, overlappedCoversDs, _albumArtCanvasBitmap);
}
using var blurredCover = new GaussianBlurEffect
{
BlurAmount = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.CoverOverlayBlurAmount,
Source = overlappedCovers,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
};
var combined = new CanvasCommandList(control);
using var combinedDs = combined.CreateDrawingSession();
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.CoverAcrylicEffectAmount > 0 && _coverAcrylicNoiseCanvasBitmap != null)
{
// 应用亚克力噪点效果
combinedDs.DrawImage(new BlendEffect
{
Mode = BlendEffectMode.SoftLight,
Background = blurredCover,
Foreground = new OpacityEffect
{
Source = _coverAcrylicNoiseCanvasBitmap,
Opacity = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.CoverAcrylicEffectAmount / 100f,
},
});
}
else
{
combinedDs.DrawImage(blurredCover);
}
_albumArtBgEffect = new OpacityEffect
{
Opacity = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.CoverOverlayOpacity / 100f,
Source = combined,
};
}
private void DisposeAlbumArtBgRenderTarget()
{
_albumArtBgRenderTarget?.Dispose();
_albumArtBgRenderTarget = null;
}
private void UpdateAlbumArtBgRenderTarget(ICanvasAnimatedControl control)
{
DisposeAlbumArtBgRenderTarget();
double targetSize = Math.Sqrt(Math.Pow(_canvasWidth, 2) + Math.Pow(_canvasHeight, 2));
_albumArtBgRenderTarget = new CanvasRenderTarget(control, (float)targetSize, (float)targetSize);
using var ds = _albumArtBgRenderTarget.CreateDrawingSession();
float offsetX = -(float)(_canvasWidth - targetSize) / 2;
float offsetY = -(float)(_canvasHeight - targetSize) / 2;
ds.Clear(Colors.Transparent);
ds.DrawImage(_albumArtBgEffect, new Vector2(offsetX, offsetY));
}
private void DisposeAlbumArtEffect()
{
_albumArtEffect?.Dispose();
_albumArtEffect = null;
}
/// <summary>
/// 更新专辑封面效果
/// <para>应该在以下任意条件满足时调用此函数:</para>
/// <para><seealso cref="_isCanvasWidthChanged"/> == true</para>
/// <para><seealso cref="_isCanvasHeightChanged"/> == true</para>
/// <para><seealso cref="_albumArtChanged"/> == true</para>
/// <para><seealso cref="_isAlbumArtShadowAmountChanged"/> == true</para>
/// <para><seealso cref="_albumArtBgTransition"/> 正在变化</para>
/// <para><seealso cref="_albumArtAccentColor1Transition"/> 正在变化</para>
/// 如果上述条件均不满足,需调用 <seealso cref="UpdateAlbumArtRenderTarget"/> 来更新渲染缓存
/// </summary>
/// <param name="control"></param>
private void UpdateAlbumArtEffect(ICanvasAnimatedControl control)
{
DisposeAlbumArtEffect();
using var overlappedCovers = new CanvasCommandList(control);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
if (_lastAlbumArtCanvasBitmap != null)
{
using var lastFgImageEffect = CreateFgImageEffect(control, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
if (lastFgImageEffect != null)
{
overlappedCoversDs.DrawImage(lastFgImageEffect);
}
}
if (_albumArtCanvasBitmap != null)
{
using var fgImageEffect = CreateFgImageEffect(control, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
if (fgImageEffect != null)
{
overlappedCoversDs.DrawImage(fgImageEffect);
}
}
_albumArtEffect = new CanvasCommandList(control);
using var combinedDs = _albumArtEffect.CreateDrawingSession();
combinedDs.DrawImage(new ShadowEffect
{
Source = overlappedCovers,
ShadowColor = _albumArtAccentColor1Transition.Value,
BlurAmount = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.CoverImageShadowAmount,
Optimization = EffectOptimization.Speed,
});
combinedDs.DrawImage(overlappedCovers);
}
private void DisposeAlbumArtRenderTarget()
{
_albumArtRenderTarget?.Dispose();
_albumArtRenderTarget = null;
}
private void UpdateAlbumArtRenderTarget(ICanvasAnimatedControl control)
{
DisposeAlbumArtRenderTarget();
_albumArtRenderTarget = new CanvasRenderTarget(control, (float)_canvasWidth, (float)_canvasHeight);
using var ds = _albumArtRenderTarget.CreateDrawingSession();
ds.Clear(Colors.Transparent);
// 给一个偏移,是为了避免绘制时从原点开始,这样会造成阴影被裁切
ds.DrawImage(_albumArtEffect, control.Size.ToVector2() / 2 - new Vector2((float)_albumArtSize, (float)_albumArtSize) / 2);
}
private void DisposeFluidEffect()
{
_fluidEffect?.Dispose();
_fluidEffect = null;
}
private async void RecreateFluidEffect(ICanvasAnimatedControl control)
{
DisposeFluidEffect();
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/FluidEffect.bin"));
IBuffer buffer = await FileIO.ReadBufferAsync(file);
var bytes = buffer.ToArray();
_fluidEffect = new PixelShaderEffect(bytes);
_fluidEffect.Properties["Width"] = (float)control.ConvertDipsToPixels((float)control.Size.Width, CanvasDpiRounding.Round);
_fluidEffect.Properties["Height"] = (float)control.ConvertDipsToPixels((float)control.Size.Height, CanvasDpiRounding.Round);
_fluidEffect.Properties["color1"] = _albumArtAccentColor1Transition.Value.ToVector3RGB();
_fluidEffect.Properties["color2"] = _albumArtAccentColor2Transition.Value.ToVector3RGB();
_fluidEffect.Properties["color3"] = _albumArtAccentColor3Transition.Value.ToVector3RGB();
_fluidEffect.Properties["color4"] = _albumArtAccentColor4Transition.Value.ToVector3RGB();
_fluidEffect.Properties["EnableLightWave"] = false;
}
private void DisposeSnowEffect()
{
_snowEffect?.Dispose();
_snowEffect = null;
}
private void RecreateSnowEffect()
{
DisposeSnowEffect();
_snowEffect = new();
}
private void DisposeFogEffect()
{
_fogEffect?.Dispose();
_fogEffect = null;
}
private void RecreateFogEffect()
{
DisposeFogEffect();
_fogEffect = new();
}
}
}

View File

@@ -1,537 +0,0 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
: IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<string>>,
IRecipient<PropertyChangedMessage<double>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<Color>>,
IRecipient<PropertyChangedMessage<LyricsDisplayType>>,
IRecipient<PropertyChangedMessage<LyricsLayoutOrientation>>,
IRecipient<PropertyChangedMessage<LyricsFontColorType>>,
IRecipient<PropertyChangedMessage<TextAlignmentType>>,
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
IRecipient<PropertyChangedMessage<LineRenderingType>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<EasingType>>,
IRecipient<PropertyChangedMessage<TimeSpan>>,
IRecipient<PropertyChangedMessage<AlbumArtLayoutSettings>>,
IRecipient<PropertyChangedMessage<LyricsBackgroundSettings>>,
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>,
IRecipient<PropertyChangedMessage<SongInfo?>>,
IRecipient<PropertyChangedMessage<SoftwareBitmap?>>,
IRecipient<PropertyChangedMessage<List<Color>>>
{
private void OnSongInfoChanged()
{
_lastSongTitle = _songTitle;
_songTitle = _mediaSessionsService.CurrentSongInfo?.Title;
_isSongTitleChanged = true;
_lastSongArtists = _songArtists;
_songArtists = _mediaSessionsService.CurrentSongInfo?.DisplayArtists;
_isSongArtistChanged = true;
_lastSongAlbum = _songAlbum;
_songAlbum = _mediaSessionsService.CurrentSongInfo?.Album;
_isSongAlbumChanged = true;
_songDurationMs = (int)(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? TimeSpan.FromMinutes(99).TotalMilliseconds);
_songInfoOpacityTransition.Reset(0f);
_songInfoOpacityTransition.StartTransition(1f);
TotalTime = TimeSpan.Zero;
// 处理 Last.fm 追踪
_totalPlayingTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
private void OnSoftwareBitmapChanged()
{
_lastAlbumArtCanvasBitmap?.Dispose();
_lastAlbumArtCanvasBitmap = null;
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
_albumArtSwBitmap = _mediaSessionsService.SoftwareBitmap;
_albumArtChanged = true;
}
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is AboutControlViewModel)
{
if (message.PropertyName == nameof(AboutControlViewModel.IsDebugOverlayEnabled))
{
_isDebugOverlayEnabled = message.NewValue;
_isDebugOverlayEnabledChanged = true;
}
}
else if (message.Sender is LyricsEffectSettings)
{
if (message.PropertyName == nameof(LyricsEffectSettings.IsLyricsGlowEffectEnabled))
{
}
else if (message.PropertyName == nameof(LyricsEffectSettings.IsFanLyricsEnabled))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.IsLyricsFloatAnimationEnabled))
{
}
else if (message.PropertyName == nameof(LyricsEffectSettings.Is3DLyricsEnabled))
{
_isLyrics3DMatrixChanged = true;
}
}
else if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.IsDynamicLyricsFontSize))
{
_isLayoutChanged = true;
}
}
else if (message.Sender is AlbumArtLayoutSettings)
{
if (message.PropertyName == nameof(AlbumArtLayoutSettings.ShowTitle))
{
_isSongTitleVisibilityChanged = true;
}
else if (message.PropertyName == nameof(AlbumArtLayoutSettings.ShowArtists))
{
_isSongArtistVisibilityChanged = true;
}
else if (message.PropertyName == nameof(AlbumArtLayoutSettings.ShowAlbum))
{
_isSongAlbumVisibilityChanged = true;
}
else if (message.PropertyName == nameof(AlbumArtLayoutSettings.AutoAlbumArtSize))
{
_isAlbumArtSizeChanged = true;
}
else if (message.PropertyName == nameof(AlbumArtLayoutSettings.IsAutoSongInfoFontSize))
{
UpdateSongInfoFontSize();
}
}
else if (message.Sender is LyricsBackgroundSettings)
{
if (message.PropertyName == nameof(LyricsBackgroundSettings.IsSpectrumOverlayEnabled))
{
_isSpectrumOverlayEnabledChanged = true;
}
else if (message.PropertyName == nameof(LyricsBackgroundSettings.IsFluidOverlayEnabled))
{
_isFluidOverlayEnabledChanged = true;
}
else if (message.PropertyName == nameof(LyricsBackgroundSettings.IsSnowFlakeOverlayEnabled))
{
_isSnowOverlayEnabledChanged = true;
}
else if (message.PropertyName == nameof(LyricsBackgroundSettings.IsFogOverlayEnabled))
{
_isFogOverlayEnabledChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<Color> message)
{
if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(LyricsWindowViewModel.BackdropAccentColor))
{
_immersiveBgColorTransition.StartTransition(message.NewValue);
_environmentalColor = message.NewValue;
UpdateColorConfig();
}
}
else if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomBgFontColor))
{
UpdateColorConfig();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomFgFontColor))
{
UpdateColorConfig();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomStrokeFontColor))
{
UpdateColorConfig();
}
}
}
public void Receive(PropertyChangedMessage<double> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsLineSpacingFactor))
{
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is AlbumArtLayoutSettings)
{
if (message.PropertyName == nameof(AlbumArtLayoutSettings.CoverImageRadius))
{
_isAlbumArtCornerRadiusChanged = true;
}
else if (message.PropertyName == nameof(AlbumArtLayoutSettings.CoverImageShadowAmount))
{
_isAlbumArtShadowAmountChanged = true;
}
else if (message.PropertyName == nameof(AlbumArtLayoutSettings.SongInfoFontSize))
{
UpdateSongInfoFontSize();
}
else if (message.PropertyName == nameof(AlbumArtLayoutSettings.AlbumArtSize))
{
_isAlbumArtSizeChanged = true;
}
}
else if (message.Sender is LyricsBackgroundSettings)
{
if (message.PropertyName == nameof(LyricsBackgroundSettings.CoverOverlayOpacity))
{
_isAlbumArtBgOpacityChanged = true;
}
else if (message.PropertyName == nameof(LyricsBackgroundSettings.CoverOverlayBlurAmount))
{
_isAlbumArtBgBlurAmountChanged = true;
}
else if (message.PropertyName == nameof(LyricsBackgroundSettings.CoverAcrylicEffectAmount))
{
_isCoverAcrylicEffectAmountChanged = true;
}
else if (message.PropertyName == nameof(LyricsBackgroundSettings.CoverOverlaySpeed))
{
}
}
else if (message.Sender is LyricsEffectSettings)
{
if (message.PropertyName == nameof(LyricsEffectSettings.LyricsVerticalEdgeOpacity))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsBlurAmount))
{
_isLayoutChanged = true;
}
else 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;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.Lyrics3DXAngle))
{
_isLyrics3DMatrixChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.Lyrics3DYAngle))
{
_isLyrics3DMatrixChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.Lyrics3DZAngle))
{
_isLyrics3DMatrixChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.Lyrics3DDepth))
{
_isLyrics3DMatrixChanged = true;
}
}
else 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.LyricsBgFontOpacity))
{
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<LineRenderingType> message)
{
if (message.Sender is LyricsEffectSettings)
{
if (message.PropertyName == nameof(LyricsEffectSettings.LyricsGlowEffectScope))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsEffectSettings.OriginalLyricsHighlightScope))
{
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<TextAlignmentType> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsAlignmentType))
{
_isLayoutChanged = true;
}
}
else if (message.Sender is AlbumArtLayoutSettings)
{
if (message.PropertyName == nameof(AlbumArtLayoutSettings.SongInfoAlignmentType))
{
_isSongInfoAlignmentTypeChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
{
if (message.Sender is LyricsWindowStatus)
{
if (message.PropertyName == nameof(LyricsWindowStatus.LyricsDisplayType))
{
_isDisplayTypeChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<LyricsLayoutOrientation> message)
{
if (message.Sender is LyricsWindowStatus)
{
if (message.PropertyName == nameof(LyricsWindowStatus.LyricsLayoutOrientation))
{
_isLyricsLayoutOrientationChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsBgFontColorType))
{
UpdateColorConfig();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFgFontColorType))
{
UpdateColorConfig();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsStrokeFontColorType))
{
UpdateColorConfig();
}
}
}
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFontWeight))
{
_isLyricsFontWeightChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
if (message.Sender is LyricsBackgroundSettings)
{
if (message.PropertyName == nameof(LyricsBackgroundSettings.LyricsBackgroundTheme))
{
UpdateColorConfig();
}
}
}
public void Receive(PropertyChangedMessage<EasingType> message)
{
if (message.Sender is LyricsEffectSettings)
{
if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollEasingType))
{
_canvasYScrollTransition.SetEasingType(message.NewValue);
}
}
}
public void Receive(PropertyChangedMessage<string> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCJKFontFamily))
{
_isLyricsFontFamilyChanged = true;
}
}
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsWesternFontFamily))
{
_isLyricsFontFamilyChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<LyricsWindowStatus> message)
{
if (message.Sender is LiveStates)
{
if (message.PropertyName == nameof(LiveStates.LyricsWindowStatus))
{
UpdateColorConfig();
UpdateSongInfoFontSize();
_isLayoutChanged = true;
_isLyricsWindowsStatusChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<AlbumArtLayoutSettings> message)
{
if (message.Sender is LyricsWindowStatus)
{
if (message.PropertyName == nameof(LyricsWindowStatus.AlbumArtLayoutSettings))
{
_isAlbumArtCornerRadiusChanged = true;
_isAlbumArtShadowAmountChanged = true;
UpdateSongInfoFontSize();
}
}
}
public void Receive(PropertyChangedMessage<LyricsBackgroundSettings> message)
{
if (message.Sender is LyricsWindowStatus)
{
if (message.PropertyName == nameof(LyricsWindowStatus.LyricsBackgroundSettings))
{
_isAlbumArtBgOpacityChanged = true;
_isAlbumArtBgBlurAmountChanged = true;
_isCoverAcrylicEffectAmountChanged = true;
UpdateColorConfig();
}
}
}
public void Receive(PropertyChangedMessage<SongInfo?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
{
OnSongInfoChanged();
}
}
}
public void Receive(PropertyChangedMessage<TimeSpan> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
{
var diff = Math.Abs(TotalTime.TotalMilliseconds - _mediaSessionsService.CurrentPosition.TotalMilliseconds);
var timelineSyncThreshold = _mediaSessionsService.CurrentMediaSourceProviderInfo?.TimelineSyncThreshold ?? 0;
if (diff >= timelineSyncThreshold)
{
TotalTime = _mediaSessionsService.CurrentPosition;
if (TotalTime.TotalSeconds <= 1)
{
_totalPlayingTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
}
// 大跨度,刷新布局,避免歌词不显示
if (diff >= timelineSyncThreshold + 5000)
{
_isLayoutChanged = true;
}
}
}
}
public void Receive(PropertyChangedMessage<SoftwareBitmap?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.SoftwareBitmap))
{
OnSoftwareBitmapChanged();
}
}
}
public void Receive(PropertyChangedMessage<List<Color>> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.LightAccentColors))
{
UpdateColorConfig();
}
else if (message.PropertyName == nameof(IMediaSessionsService.DarkAccentColors))
{
UpdateColorConfig();
}
}
}
}
}

View File

@@ -1,107 +0,0 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
private readonly ValueTransition<double> _canvasYScrollTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutQuad
);
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<Color> _albumArtAccentColor1Transition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
private readonly ValueTransition<Color> _albumArtAccentColor2Transition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
private readonly ValueTransition<Color> _albumArtAccentColor3Transition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
private readonly ValueTransition<Color> _albumArtAccentColor4Transition = 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<double> _titleXTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutQuad
);
private readonly ValueTransition<double> _titleYTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutQuad
);
private readonly ValueTransition<double> _lyricsYTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutQuad
);
private readonly ValueTransition<double> _lyricsOpacityTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
);
private readonly ValueTransition<double> _albumArtBgTransition = new(
initialValue: 0f,
durationSeconds: 1f
);
private readonly ValueTransition<double> _albumArtOpacityTransition = new(
initialValue: 0f,
durationSeconds: 1f
);
private readonly ValueTransition<double> _albumArtXTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutQuad
);
private readonly ValueTransition<double> _albumArtYTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutQuad
);
private readonly ValueTransition<double> _songInfoOpacityTransition = new(
initialValue: 0f,
durationSeconds: 1f
);
private readonly ValueTransition<double> _lyricsBgBrightnessTransition = new(
initialValue: 0f,
durationSeconds: 1f
);
}
}

View File

@@ -1,343 +0,0 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using System;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel : BaseViewModel
{
[ObservableProperty]
public partial AppSettings AppSettings { get; set; }
private bool _isLastFMTracked = false;
/// <summary>
/// This includes all the time the song has been played, even if the user seeks back.
/// </summary>
private TimeSpan _totalPlayingTime = TimeSpan.Zero;
/// <summary>
/// Refresh elapsed time for every frame by render.
/// </summary>
private TimeSpan _elapsedTime = TimeSpan.Zero;
/// <summary>
/// Get or set the current time position of the song.
/// </summary>
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
private int _songDurationMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
private Stopwatch? _drawFrameStopwatch;
private int _drawFrameCount = 0;
private int _displayedDrawFrameCount = 0;
private SoftwareBitmap? _lastAlbumArtSwBitmap = null;
private SoftwareBitmap? _albumArtSwBitmap = null;
private CanvasBitmap? _lastAlbumArtCanvasBitmap = null;
private CanvasBitmap? _albumArtCanvasBitmap = null;
private CanvasRenderTarget? _albumArtBgRenderTarget;
private CanvasRenderTarget? _albumArtRenderTarget;
private CanvasBitmap? _coverAcrylicNoiseCanvasBitmap = null;
private double _albumArtSize = 0f;
private int _songInfoHeight = 0;
private string? _lastSongTitle;
private string? _songTitle;
private string? _lastSongArtists;
private string? _songArtists;
private string? _lastSongAlbum;
private string? _songAlbum;
private double _canvasWidth = 0f;
private double _canvasHeight = 0f;
private readonly double _defaultScale = 0.75f;
private readonly double _highlightedScale = 1.0f;
private readonly double _coverRotateBaseSpeed = 0.003f;
private double _rotateAngle = 0f;
private double _canvasTargetYScrollOffset = 0;
private double _lyricsX = 0f;
private double _maxLyricsWidth = 0f;
private double _maxSongInfoWidth = 0f;
private readonly ISettingsService _settingsService;
private readonly IMediaSessionsService _mediaSessionsService;
private readonly ILastFMService _lastFMService;
private readonly ILiveStatesService _liveStatesService;
private readonly ILogger _logger;
private double _leftMargin = 36f;
private double _middleMargin = 36f;
private double _rightMargin = 36f;
private double _topMargin = 36f;
private double _bottomMargin = 36f;
private Color _adaptiveGrayedFontColor = Colors.Transparent;
private Color? _adaptiveColoredFontColor = null;
private Color _environmentalColor = Colors.Transparent;
private Color _grayedEnvironmentalColor = Colors.Transparent;
private Color _lightColor = Colors.White;
private Color _darkColor = Colors.Black;
private Color _bgFontColor;
private Color _fgFontColor;
private Color _strokeFontColor;
private int _playingLineIndex = -1;
private int _startVisibleLineIndex = -1;
private int _endVisibleLineIndex = -1;
private LyricsData? _currentLyricsData;
private bool _isDebugOverlayEnabled = false;
private int _phoneticLyricsFontSize = 18;
private int _originalLyricsFontSize = 36;
private int _translatedLyricsFontSize = 18;
private LyricsFontWeight _originalLyricsFontWeight = LyricsFontWeight.Bold;
private CanvasTextFormat _titleTextFormat = new()
{
FontSize = 18,
FontWeight = FontWeights.Bold,
HorizontalAlignment = CanvasHorizontalAlignment.Left,
};
private CanvasTextFormat _artistTextFormat = new()
{
FontSize = 16,
FontWeight = FontWeights.Bold,
HorizontalAlignment = CanvasHorizontalAlignment.Left,
};
private CanvasTextFormat _albumTextFormat = new()
{
FontSize = 16,
FontWeight = FontWeights.Bold,
HorizontalAlignment = CanvasHorizontalAlignment.Left,
};
private CanvasTextFormat _debugTextFormat = new()
{
FontSize = 12,
FontWeight = FontWeights.ExtraBlack,
};
private CanvasTextLayout? _lastTitleTextLayout = null;
private CanvasTextLayout? _titleTextLayout = null;
private CanvasTextLayout? _lastArtistTextLayout = null;
private CanvasTextLayout? _artistTextLayout = null;
private CanvasTextLayout? _lastAlbumTextLayout = null;
private CanvasTextLayout? _albumTextLayout = null;
private CanvasGeometry? _spectrumGeometry = null;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ElementTheme ThemeTypeSent { get; set; }
private SpectrumAnalyzer? _spectrumAnalyzer;
private Matrix4x4 _lyrics3DMatrix = Matrix4x4.Identity;
public LyricsRendererViewModel(
ISettingsService settingsService,
IMediaSessionsService mediaSessionsService,
ILastFMService lastFMService,
ILiveStatesService liveStatesService)
{
_settingsService = settingsService;
_mediaSessionsService = mediaSessionsService;
_liveStatesService = liveStatesService;
_lastFMService = lastFMService;
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
AppSettings = _settingsService.AppSettings;
UpdateSongInfoFontSize();
_mediaSessionsService.LyricsChanged += MediaSessionsService_LyricsChanged;
UpdateColorConfig();
_spectrumAnalyzer = new SpectrumAnalyzer();
}
private void MediaSessionsService_LyricsChanged(object? sender, LyricsChangedEventArgs e)
{
_currentLyricsData = e.LyricsData;
_isLayoutChanged = true;
}
private int GetCurrentPlayingLineIndex()
{
var totalMs = TotalTime.TotalMilliseconds + _mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0;
if (totalMs < _currentLyricsData?.LyricsLines.FirstOrDefault()?.StartMs) return 0;
for (int i = 0; i < _currentLyricsData?.LyricsLines.Count; i++)
{
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i);
if (line == null) continue;
var nextLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i + 1);
if (nextLine != null && line.StartMs <= totalMs && totalMs < nextLine.StartMs)
{
return i;
}
else if (nextLine == null && line.StartMs <= totalMs)
{
return i;
}
}
return GetMaxLyricsLineIndexBoundaries().Item2;
}
private void GetLinePlayingProgress(int lineIndex, out int charStartIndex, out int charLength, out double charProgress)
{
charStartIndex = 0;
charLength = 0;
charProgress = 0f;
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(lineIndex);
if (line == null) return;
var nextLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(lineIndex + 1);
int lineEndMs;
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
else if (nextLine != null) lineEndMs = nextLine.StartMs;
else lineEndMs = _songDurationMs;
double now = (double)TotalTime.TotalMilliseconds + (double)(_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
// 1. 还没到本句
if (now < line.StartMs)
{
return;
}
// 2. 已经超过本句
if (now > lineEndMs)
{
charProgress = 1f;
charStartIndex = line.OriginalText.Length - 1;
charLength = 1;
return;
}
// 3. 有逐字时间轴
if (line.LyricsChars != null && line.LyricsChars.Count > 1)
{
int charTimingsCount = line.LyricsChars.Count;
for (int i = 0; i < charTimingsCount; i++)
{
var timing = line.LyricsChars[i];
var nextTiming = line.LyricsChars.ElementAtOrDefault(i + 1);
int timingEndMs;
if (timing.EndMs != null) timingEndMs = timing.EndMs.Value;
else if (nextTiming != null) timingEndMs = nextTiming.StartMs;
else timingEndMs = lineEndMs;
charStartIndex = timing.StartIndex;
charLength = timing.Text.Length;
// 当前时间在某个字的高亮区间
if (now >= timing.StartMs && now <= timingEndMs)
{
if (timingEndMs != timing.StartMs)
{
charProgress = (now - timing.StartMs) / (timingEndMs - timing.StartMs);
}
else
{
charProgress = 0f;
}
return;
}
else if (now > timingEndMs && (nextTiming == null || now < nextTiming?.StartMs))
{
charProgress = 1f;
return;
}
}
}
else
{
int textLength = line.OriginalText.Length;
if (textLength == 0) return;
if (_settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect)
{
// 没有逐字时间轴,均匀分配每个字的高亮时间
double lineProgress = (now - line.StartMs) / (lineEndMs - line.StartMs);
lineProgress = Math.Clamp(lineProgress, 0f, 1f);
// 计算当前高亮到第几个字
double charFloatIndex = lineProgress * textLength;
int charIndex = (int)charFloatIndex;
charStartIndex = Math.Clamp(charIndex, 0, textLength - 1);
charLength = 1;
// 当前字的进度0~1
charProgress = charFloatIndex - charIndex;
}
else
{
charStartIndex = textLength;
charProgress = 1f;
}
}
}
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
{
if (_mediaSessionsService.CurrentSongInfo == null
|| _currentLyricsData == null
|| _currentLyricsData.LyricsLines.Count == 0)
{
return new Tuple<int, int>(-1, -1);
}
return new Tuple<int, int>(0, _currentLyricsData.LyricsLines.Count - 1);
}
}
}

View File

@@ -3,12 +3,12 @@ using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.ResourceService;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Linq;
using System.Numerics;
namespace BetterLyrics.WinUI3.ViewModels
{
@@ -16,7 +16,6 @@ namespace BetterLyrics.WinUI3.ViewModels
{
private readonly ISettingsService _settingsService;
private readonly ILiveStatesService _liveStatesService;
private readonly IResourceService _resourceService;
[ObservableProperty]
public partial LiveStates LiveStates { get; set; }
@@ -30,11 +29,19 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
public partial ObservableCollection<string> MonitorDeviceNames { get; set; }
public LyricsWindowSettingsControlViewModel(ISettingsService settingsService, ILiveStatesService liveStatesService, IResourceService resourceService)
[ObservableProperty]
public partial bool IsConfigPanelOpened { get; set; } = false;
[ObservableProperty]
public partial Vector3 ConfigPanelTranslation { get; set; } = new();
[ObservableProperty]
public partial double DisplayPanelHeight { get; set; } = 0;
public LyricsWindowSettingsControlViewModel(ISettingsService settingsService, ILiveStatesService liveStatesService)
{
_settingsService = settingsService;
_liveStatesService = liveStatesService;
_resourceService = resourceService;
AppSettings = _settingsService.AppSettings;
LiveStates = _liveStatesService.LiveStates;
@@ -77,5 +84,31 @@ namespace BetterLyrics.WinUI3.ViewModels
{
AppSettings.WindowBoundsRecords.Add(LyricsWindowStatusExtensions.NarrowMode());
}
[RelayCommand]
private void OpenConfigPanel()
{
IsConfigPanelOpened = true;
ConfigPanelTranslation = new();
}
[RelayCommand]
private void CloseConfigPanel()
{
IsConfigPanelOpened = false;
ConfigPanelTranslation = new(0, (float)DisplayPanelHeight, 0);
}
partial void OnDisplayPanelHeightChanged(double value)
{
if (IsConfigPanelOpened)
{
OpenConfigPanel();
}
else
{
CloseConfigPanel();
}
}
}
}

View File

@@ -9,7 +9,6 @@ using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
@@ -29,7 +28,7 @@ namespace BetterLyrics.WinUI3
: BaseWindowViewModel,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<List<string>>>,
IRecipient<PropertyChangedMessage<ElementTheme>>
IRecipient<PropertyChangedMessage<AlbumArtThemeColors>>
{
private readonly ISettingsService _settingsService;
private readonly ILiveStatesService _liveStatesService;
@@ -59,9 +58,7 @@ namespace BetterLyrics.WinUI3
[ObservableProperty] public partial ElementTheme ThemeType { get; set; } = ElementTheme.Default;
[ObservableProperty] public partial double TitleBarFontSize { get; set; } = 12;
[ObservableProperty] public partial Visibility CloseButtonVisibility { get; set; } = Visibility.Visible;
[ObservableProperty] public partial double TitleBarFontSize { get; set; } = 14;
public void InitShortcuts()
{
@@ -209,13 +206,13 @@ namespace BetterLyrics.WinUI3
}
}
public void Receive(PropertyChangedMessage<ElementTheme> message)
public void Receive(PropertyChangedMessage<AlbumArtThemeColors> message)
{
if (message.Sender is LyricsRendererViewModel)
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.ThemeTypeSent))
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtThemeColors))
{
ThemeType = message.NewValue;
ThemeType = message.NewValue.ThemeType;
}
}
}

View File

@@ -17,6 +17,8 @@
xmlns:renderer="using:BetterLyrics.WinUI3.Renderer"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
Loaded="Page_Loaded"
Unloaded="Page_Unloaded"
mc:Ignorable="d">
<Grid
@@ -24,8 +26,174 @@
RightTapped="RootGrid_RightTapped"
SizeChanged="RootGrid_SizeChanged">
<!-- Lyrics area -->
<renderer:LyricsRenderer />
<!-- Win2D drawing area -->
<uc:NowPlayingCanvas x:Name="NowPlayingCanvas" />
<Grid x:Name="TrackSummaryGridContainer" Loaded="TrackSummaryGridContainer_Loaded">
<Grid.RowDefinitions>
<RowDefinition x:Name="TrackSummaryRowDef" Height="0" />
<RowDefinition x:Name="MiddleGapRowDef" Height="0" />
<RowDefinition x:Name="LyricsRowDef" Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftGapDef" Width="0" />
<ColumnDefinition x:Name="TrackSummaryColDef" Width="*" />
<ColumnDefinition x:Name="MiddleGapColDef" Width="0" />
<ColumnDefinition x:Name="LyricsColDef" Width="*" />
<ColumnDefinition x:Name="RightGapDef" Width="0" />
</Grid.ColumnDefinitions>
<!-- Album art and song info area -->
<Grid
x:Name="TrackSummaryGrid"
Grid.Row="0"
Grid.Column="1"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition x:Name="TrackSummaryGridRow0" Height="0" />
<RowDefinition x:Name="TrackSummaryGridRow1" Height="*" />
<RowDefinition x:Name="TrackSummaryGridRow2" Height="0" />
<RowDefinition x:Name="TrackSummaryGridRow3" Height="Auto" />
<RowDefinition x:Name="TrackSummaryGridRow4" Height="0" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="TrackSummaryGridCol0" Width="Auto" />
<ColumnDefinition x:Name="TrackSummaryGridCol1" Width="0" />
<ColumnDefinition x:Name="TrackSummaryGridCol2" Width="*" />
</Grid.ColumnDefinitions>
<Grid.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</Grid.OpacityTransition>
<!-- Album art -->
<Grid
x:Name="AlbumArtGrid"
Margin="-32"
Padding="32"
HorizontalAlignment="{Binding ElementName=SongInfoStackPanel, Path=HorizontalAlignment, Mode=OneWay}">
<Grid.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</Grid.OpacityTransition>
<Grid
x:Name="ShadowCastGrid"
CornerRadius="{x:Bind AlbumArtCornerRadius, Mode=OneWay}"
SizeChanged="ShadowCastGrid_SizeChanged">
<Image x:Name="LastAlbumArtImage" Stretch="Uniform">
<Image.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</Image.OpacityTransition>
</Image>
<Image x:Name="AlbumArtImage" Stretch="Uniform">
<Image.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</Image.OpacityTransition>
</Image>
</Grid>
<Border
x:Name="ShadowRect"
CornerRadius="{x:Bind AlbumArtCornerRadius, Mode=OneWay}"
Loaded="ShadowRect_Loaded"
Translation="0,0,0">
<Border.Shadow>
<ThemeShadow x:Name="Shadow" />
</Border.Shadow>
</Border>
</Grid>
<!-- Song info -->
<StackPanel x:Name="SongInfoStackPanel" HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=OneWay, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}}">
<StackPanel.OpacityTransition>
<ScalarTransition Duration="{x:Bind const:Time.AnimationDuration}" />
</StackPanel.OpacityTransition>
<dev:OpacityMaskView HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=OneWay, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}}">
<dev:OpacityMaskView.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="0.02" Color="#FFFFFFFF" />
<GradientStop Offset="0.98" Color="#FFFFFFFF" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</dev:OpacityMaskView.OpacityMask>
<dev:AutoScrollView
x:Name="TitleAutoScrollHoverEffectView"
IsPlaying="False"
PointerCanceled="TitleAutoScrollHoverEffectView_PointerCanceled"
PointerEntered="TitleAutoScrollHoverEffectView_PointerEntered"
PointerExited="TitleAutoScrollHoverEffectView_PointerExited"
ScrollingPixelsPreSecond="20">
<TextBlock
x:Name="TitleTextBlock"
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
TextTrimming="None"
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</dev:AutoScrollView>
</dev:OpacityMaskView>
<dev:OpacityMaskView HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=OneWay, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}}">
<dev:OpacityMaskView.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="0.02" Color="#FFFFFFFF" />
<GradientStop Offset="0.98" Color="#FFFFFFFF" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</dev:OpacityMaskView.OpacityMask>
<dev:AutoScrollView
x:Name="ArtistsAutoScrollHoverEffectView"
IsPlaying="False"
PointerCanceled="ArtistsAutoScrollHoverEffectView_PointerCanceled"
PointerEntered="ArtistsAutoScrollHoverEffectView_PointerEntered"
PointerExited="ArtistsAutoScrollHoverEffectView_PointerExited"
ScrollingPixelsPreSecond="20">
<TextBlock
x:Name="ArtistsTextBlock"
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
Opacity="0.5"
TextTrimming="None"
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowArtists, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</dev:AutoScrollView>
</dev:OpacityMaskView>
<dev:OpacityMaskView HorizontalAlignment="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=OneWay, Converter={StaticResource TextAlignmentTypeToHorizontalAlignmentConverter}}">
<dev:OpacityMaskView.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="Transparent" />
<GradientStop Offset="0.02" Color="#FFFFFFFF" />
<GradientStop Offset="0.98" Color="#FFFFFFFF" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</dev:OpacityMaskView.OpacityMask>
<dev:AutoScrollView
x:Name="AlbumAutoScrollHoverEffectView"
IsPlaying="False"
PointerCanceled="AlbumAutoScrollHoverEffectView_PointerCanceled"
PointerEntered="AlbumAutoScrollHoverEffectView_PointerEntered"
PointerExited="AlbumAutoScrollHoverEffectView_PointerExited"
ScrollingPixelsPreSecond="20">
<TextBlock
x:Name="AlbumTextBlock"
FontWeight="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontWeight, Converter={StaticResource LyricsFontWeightToFontWeightConverter}, Mode=OneWay}"
Opacity="0.5"
TextTrimming="None"
Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.ShowAlbum, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</dev:AutoScrollView>
</dev:OpacityMaskView>
</StackPanel>
</Grid>
<Grid x:Name="LyricsPlaceholder" SizeChanged="LyricsPlaceholder_SizeChanged">
<ScrollViewer
x:Name="LyricsScrollViewer"
PointerWheelChanged="LyricsScrollViewer_PointerWheelChanged"
Visibility="Collapsed">
<Grid />
</ScrollViewer>
</Grid>
</Grid>
<!-- Bottom command area -->
<Grid
@@ -308,8 +476,7 @@
Maximum="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DurationMs, Mode=OneWay, Converter={StaticResource MillisecondsToSecondsConverter}}"
Minimum="0"
Style="{StaticResource GhostSliderStyle}"
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}"
Value="{x:Bind ViewModel.TimelinePositionSeconds, Mode=OneWay}" />
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}" />
<Grid
x:Name="TimelineSliderLyricsLineInfo"
Margin="0,-48,0,0"

View File

@@ -1,24 +1,54 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Controls;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Numerics;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Views
{
public sealed partial class LyricsPage : Page
public sealed partial class LyricsPage : Page,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<string>>,
IRecipient<PropertyChangedMessage<SongInfo?>>,
IRecipient<PropertyChangedMessage<BitmapImage?>>,
IRecipient<PropertyChangedMessage<LyricsLayoutOrientation>>,
IRecipient<PropertyChangedMessage<LyricsDisplayType>>,
IRecipient<PropertyChangedMessage<AlbumArtThemeColors>>,
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>
{
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
private readonly LyricsRendererViewModel _lyricsRendererViewModel = Ioc.Default.GetRequiredService<LyricsRendererViewModel>();
private readonly DispatcherQueueTimer _timer = App.Current.Resources.DispatcherQueue.CreateTimer();
public CornerRadius AlbumArtCornerRadius
{
get { return (CornerRadius)GetValue(AlbumArtCornerRadiusProperty); }
set { SetValue(AlbumArtCornerRadiusProperty, value); }
}
public static readonly DependencyProperty AlbumArtCornerRadiusProperty =
DependencyProperty.Register(nameof(AlbumArtCornerRadius), typeof(double), typeof(NowPlayingCanvas), new PropertyMetadata(new CornerRadius(0)));
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
@@ -27,6 +57,394 @@ namespace BetterLyrics.WinUI3.Views
this.InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<LyricsPageViewModel>();
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<int>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<bool>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<string>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<SongInfo?>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<BitmapImage?>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsLayoutOrientation>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsDisplayType>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<AlbumArtThemeColors>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsWindowStatus>>(this);
}
private void CompositionTarget_Rendering(object? sender, object e)
{
var currentTime = NowPlayingCanvas.SongPosition.TotalSeconds;
TimelineSlider.Value = currentTime;
}
// ==== SongInfo
private void RenderTextBlock(TextBlock? sender, string? text, double fontSize)
{
if (sender == null || string.IsNullOrEmpty(text) || fontSize == 0) return;
var lyricsStyleSettings = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings;
sender.Inlines.Clear();
foreach (var ch in text)
{
var fontFamilyName = LanguageHelper.IsCJK(ch) ? lyricsStyleSettings.LyricsCJKFontFamily : lyricsStyleSettings.LyricsWesternFontFamily;
sender.Inlines.Add(new Run { Text = $"{ch}", FontFamily = new FontFamily(fontFamilyName) });
}
sender.FontSize = (int)fontSize;
sender.Foreground = new SolidColorBrush(_mediaSessionsService.AlbumArtThemeColors.BgFontColor);
}
private void RenderSongInfo()
{
var lyricsLayoutMetrics = LyricsLayoutHelper.CalculateLayout(RootGrid.ActualWidth, RootGrid.ActualHeight);
var albumArtLayoutSettings = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings;
var titleFontSize = albumArtLayoutSettings.IsAutoSongInfoFontSize ? lyricsLayoutMetrics.SongTitleSize : albumArtLayoutSettings.SongInfoFontSize;
var artistsFontSize = albumArtLayoutSettings.IsAutoSongInfoFontSize ? lyricsLayoutMetrics.ArtistNameSize : albumArtLayoutSettings.SongInfoFontSize * 0.8;
var albumFontSize = albumArtLayoutSettings.IsAutoSongInfoFontSize ? lyricsLayoutMetrics.AlbumNameSize : albumArtLayoutSettings.SongInfoFontSize * 0.8;
RenderTextBlock(TitleTextBlock, _mediaSessionsService.CurrentSongInfo?.Title, titleFontSize);
RenderTextBlock(ArtistsTextBlock, _mediaSessionsService.CurrentSongInfo?.DisplayArtists, artistsFontSize);
RenderTextBlock(AlbumTextBlock, _mediaSessionsService.CurrentSongInfo?.Album, albumFontSize);
}
private void UpdateSongInfoOpacity()
{
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsDisplayType)
{
case LyricsDisplayType.AlbumArtOnly:
SongInfoStackPanel.Opacity = 1;
break;
case LyricsDisplayType.LyricsOnly:
SongInfoStackPanel.Opacity = 0;
break;
case LyricsDisplayType.SplitView:
SongInfoStackPanel.Opacity = 1;
break;
default:
break;
}
}
// ==== AlbumArt
private void UpdateAlbumArtCornerRadius()
{
var factor = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.CoverImageRadius / 100.0;
AlbumArtCornerRadius = new((AlbumArtImage.ActualHeight / 2) * factor);
}
private void UpdateAlbumArtShadow()
{
var amount = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.CoverImageShadowAmount;
ShadowRect.Translation = new(0, 0, amount);
}
private void UpdateAlbumArtOpacity()
{
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsDisplayType)
{
case LyricsDisplayType.AlbumArtOnly:
AlbumArtGrid.Opacity = 1;
break;
case LyricsDisplayType.LyricsOnly:
AlbumArtGrid.Opacity = 0;
break;
case LyricsDisplayType.SplitView:
AlbumArtGrid.Opacity = 1;
break;
default:
break;
}
}
// ====
private void UpdateTrackSummaryGridSpan()
{
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
switch (status.LyricsDisplayType)
{
case LyricsDisplayType.AlbumArtOnly:
Grid.SetRowSpan(TrackSummaryGrid, 3);
Grid.SetColumnSpan(TrackSummaryGrid, 3);
break;
case LyricsDisplayType.LyricsOnly:
break;
case LyricsDisplayType.SplitView:
switch (status.LyricsLayoutOrientation)
{
case LyricsLayoutOrientation.Horizontal:
Grid.SetRowSpan(TrackSummaryGrid, 3);
Grid.SetColumnSpan(TrackSummaryGrid, 1);
break;
case LyricsLayoutOrientation.Vertical:
Grid.SetRowSpan(TrackSummaryGrid, 1);
Grid.SetColumnSpan(TrackSummaryGrid, 3);
break;
default:
break;
}
break;
default:
break;
}
}
// ====
private void UpdateSongInfoStackPanelSpan()
{
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
switch (status.LyricsLayoutOrientation)
{
case LyricsLayoutOrientation.Horizontal:
Grid.SetRow(SongInfoStackPanel, 3);
Grid.SetRowSpan(SongInfoStackPanel, 1);
Grid.SetColumn(SongInfoStackPanel, 0);
Grid.SetColumnSpan(SongInfoStackPanel, 3);
break;
case LyricsLayoutOrientation.Vertical:
Grid.SetRow(SongInfoStackPanel, 1);
Grid.SetRowSpan(SongInfoStackPanel, 3);
Grid.SetColumn(SongInfoStackPanel, 2);
Grid.SetColumnSpan(SongInfoStackPanel, 1);
break;
default:
break;
}
}
private void UpdateLyricsPlaceholderSpan()
{
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
switch (status.LyricsDisplayType)
{
case LyricsDisplayType.AlbumArtOnly:
break;
case LyricsDisplayType.LyricsOnly:
Grid.SetRow(LyricsPlaceholder, 0);
Grid.SetRowSpan(LyricsPlaceholder, 3);
Grid.SetColumn(LyricsPlaceholder, 1);
Grid.SetColumnSpan(LyricsPlaceholder, 3);
break;
case LyricsDisplayType.SplitView:
switch (status.LyricsLayoutOrientation)
{
case LyricsLayoutOrientation.Horizontal:
Grid.SetRow(LyricsPlaceholder, 0);
Grid.SetRowSpan(LyricsPlaceholder, 3);
Grid.SetColumn(LyricsPlaceholder, 3);
Grid.SetColumnSpan(LyricsPlaceholder, 1);
break;
case LyricsLayoutOrientation.Vertical:
Grid.SetRow(LyricsPlaceholder, 0);
Grid.SetRowSpan(LyricsPlaceholder, 3);
Grid.SetColumn(LyricsPlaceholder, 1);
Grid.SetColumnSpan(LyricsPlaceholder, 3);
break;
default:
break;
}
break;
default:
break;
}
}
// ====
private void UpdateAlbumArtGridSpan()
{
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
switch (status.LyricsLayoutOrientation)
{
case LyricsLayoutOrientation.Horizontal:
Grid.SetRow(AlbumArtGrid, 1);
Grid.SetRowSpan(AlbumArtGrid, 1);
Grid.SetColumn(AlbumArtGrid, 0);
Grid.SetColumnSpan(AlbumArtGrid, 3);
break;
case LyricsLayoutOrientation.Vertical:
Grid.SetRow(AlbumArtGrid, 1);
Grid.SetRowSpan(AlbumArtGrid, 3);
Grid.SetColumn(AlbumArtGrid, 0);
Grid.SetColumnSpan(AlbumArtGrid, 1);
break;
default:
break;
}
}
// ==== Lyrics
private void UpdateLyricsOpacity()
{
switch (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsDisplayType)
{
case LyricsDisplayType.AlbumArtOnly:
NowPlayingCanvas.LyricsOpacity = 0;
break;
case LyricsDisplayType.LyricsOnly:
case LyricsDisplayType.SplitView:
NowPlayingCanvas.LyricsOpacity = 1;
break;
default:
break;
}
}
private void UpdateLyricsLayout()
{
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
switch (status.LyricsDisplayType)
{
case LyricsDisplayType.AlbumArtOnly:
break;
case LyricsDisplayType.LyricsOnly:
NowPlayingCanvas.LyricsStartX = LeftGapDef.ActualWidth;
NowPlayingCanvas.LyricsStartY = 0;
NowPlayingCanvas.LyricsWidth = TrackSummaryColDef.ActualWidth + MiddleGapColDef.ActualWidth + LyricsColDef.ActualWidth;
NowPlayingCanvas.LyricsHeight = TrackSummaryRowDef.ActualHeight + MiddleGapRowDef.ActualHeight + LyricsRowDef.ActualHeight;
break;
case LyricsDisplayType.SplitView:
switch (status.LyricsLayoutOrientation)
{
case LyricsLayoutOrientation.Horizontal:
NowPlayingCanvas.LyricsStartX = LeftGapDef.ActualWidth + TrackSummaryColDef.ActualWidth + MiddleGapColDef.ActualWidth;
NowPlayingCanvas.LyricsStartY = 0;
NowPlayingCanvas.LyricsWidth = LyricsColDef.ActualWidth;
NowPlayingCanvas.LyricsHeight = TrackSummaryRowDef.ActualHeight + MiddleGapRowDef.ActualHeight + LyricsRowDef.ActualHeight;
break;
case LyricsLayoutOrientation.Vertical:
NowPlayingCanvas.LyricsStartX = LeftGapDef.ActualWidth;
NowPlayingCanvas.LyricsStartY = 0;
NowPlayingCanvas.LyricsWidth = TrackSummaryColDef.ActualWidth + MiddleGapColDef.ActualWidth + LyricsColDef.ActualWidth;
NowPlayingCanvas.LyricsHeight = TrackSummaryRowDef.ActualHeight + MiddleGapRowDef.ActualHeight + LyricsRowDef.ActualHeight;
break;
default:
break;
}
break;
default:
break;
}
}
// ====
private void UpdateGap()
{
var lyricsLayoutMetrics = LyricsLayoutHelper.CalculateLayout(RootGrid.ActualWidth, RootGrid.ActualHeight);
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
double height = RootGrid.ActualHeight;
double width = RootGrid.ActualWidth;
double middleGapCol = 0;
double gapBetweenAlbumArtAndSongInfo = 0;
double trackSummaryRowHeight = 0;
double xMargin = 0;
double yMargin = 0;
if (height < 400)
{
middleGapCol = Math.Max(16, height * 0.1);
}
else
{
middleGapCol = Math.Max(16, width * 0.15);
}
if (height < 100)
{
gapBetweenAlbumArtAndSongInfo = 0;
}
else
{
gapBetweenAlbumArtAndSongInfo = lyricsLayoutMetrics.SongTitleSize / 2;
}
switch (status.LyricsLayoutOrientation)
{
case LyricsLayoutOrientation.Horizontal:
xMargin = Math.Clamp(Math.Min(width, height) * 0.25, 16, 128);
yMargin = Math.Max(32, Math.Min(width, height) * 0.15);
break;
case LyricsLayoutOrientation.Vertical:
xMargin = Math.Max(16, Math.Min(width, height) * 0.05);
yMargin = Math.Max(16, Math.Min(width, height) * 0.05);
trackSummaryRowHeight = Math.Max(64, height * 0.25);
break;
default:
break;
}
MiddleGapColDef.Width = new(middleGapCol);
TrackSummaryGridRow0.Height = new(yMargin);
TrackSummaryGridRow4.Height = new(yMargin);
LeftGapDef.Width = RightGapDef.Width = new(xMargin);
TrackSummaryRowDef.Height = new(trackSummaryRowHeight);
TrackSummaryGridCol1.Width = TrackSummaryGridRow2.Height = new(gapBetweenAlbumArtAndSongInfo);
}
private void OnLayoutChanged()
{
_timer.Debounce(() =>
{
UpdateGap();
UpdateSongInfoOpacity();
UpdateLyricsOpacity();
UpdateAlbumArtOpacity();
UpdateAlbumArtShadow();
UpdateTrackSummaryGridSpan();
UpdateAlbumArtGridSpan();
UpdateSongInfoStackPanelSpan();
UpdateLyricsPlaceholderSpan();
UpdateAlbumArtCornerRadius();
UpdateLyricsLayout();
}, Constants.Time.DebounceTimeout);
}
// ====
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
var width = e.NewSize.Width;
var height = e.NewSize.Height;
RenderSongInfo();
OnLayoutChanged();
if (width < 500 || height < 100)
{
if (BottomCommandGrid.Children.Count != 0)
{
BottomCommandGrid.Children.Remove(BottomCommandContent);
BottomCommandFlyoutContainer.Children.Add(BottomCommandContent);
}
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 0, 0);
}
else
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
BottomCommandFlyout.Hide();
BottomCommandFlyoutContainer.Children.Remove(BottomCommandContent);
BottomCommandGrid.Children.Add(BottomCommandContent);
}
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 12, 0);
}
}
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
@@ -57,29 +475,6 @@ namespace BetterLyrics.WinUI3.Views
PlaybackSettingsFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width < 500 || e.NewSize.Height < 100)
{
if (BottomCommandGrid.Children.Count != 0)
{
BottomCommandGrid.Children.Remove(BottomCommandContent);
BottomCommandFlyoutContainer.Children.Add(BottomCommandContent);
}
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 0, 0);
}
else
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
BottomCommandFlyout.Hide();
BottomCommandFlyoutContainer.Children.Remove(BottomCommandContent);
BottomCommandGrid.Children.Add(BottomCommandContent);
}
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 12, 0);
}
}
private void VolumeButton_Click(object sender, RoutedEventArgs e)
{
VolumeFlyout.ShowAt(BottomLeftCommandStackPanel);
@@ -183,21 +578,214 @@ namespace BetterLyrics.WinUI3.Views
SystemVolumeHook.MasterVolume = ViewModel.Volume;
}
//private void ScrollGrid_PointerWheelChanged(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
//{
// if (e.Pointer.PointerDeviceType != PointerDeviceType.Mouse)
// return;
private void ShadowRect_Loaded(object sender, RoutedEventArgs e)
{
Shadow.Receivers.Add(ShadowCastGrid);
}
// var position = e.GetCurrentPoint(ScrollGrid);
// var delta = position.Properties.MouseWheelDelta;
// double notches = delta / 120.0;
private void TitleAutoScrollHoverEffectView_PointerCanceled(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
TitleAutoScrollHoverEffectView.IsPlaying = false;
}
// _lyricsRendererViewModel.ScrollDeltaTime = System.TimeSpan.FromSeconds(notches);
// _lyricsRendererViewModel.IsScrolling = true;
private void TitleAutoScrollHoverEffectView_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
TitleAutoScrollHoverEffectView.IsPlaying = true;
}
// Debug.WriteLine(notches);
private void TitleAutoScrollHoverEffectView_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
TitleAutoScrollHoverEffectView.IsPlaying = false;
}
// e.Handled = true;
//}
private void ArtistsAutoScrollHoverEffectView_PointerCanceled(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
ArtistsAutoScrollHoverEffectView.IsPlaying = false;
}
private void ArtistsAutoScrollHoverEffectView_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
ArtistsAutoScrollHoverEffectView.IsPlaying = true;
}
private void ArtistsAutoScrollHoverEffectView_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
ArtistsAutoScrollHoverEffectView.IsPlaying = false;
}
private void AlbumAutoScrollHoverEffectView_PointerCanceled(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
AlbumAutoScrollHoverEffectView.IsPlaying = false;
}
private void AlbumAutoScrollHoverEffectView_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
AlbumAutoScrollHoverEffectView.IsPlaying = true;
}
private void AlbumAutoScrollHoverEffectView_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
AlbumAutoScrollHoverEffectView.IsPlaying = false;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void Page_Unloaded(object sender, RoutedEventArgs e)
{
CompositionTarget.Rendering -= CompositionTarget_Rendering;
}
private void LyricsPlaceholder_SizeChanged(object sender, SizeChangedEventArgs e)
{
OnLayoutChanged();
}
private void TrackSummaryGridContainer_Loaded(object sender, RoutedEventArgs e)
{
OnLayoutChanged();
}
private void ShadowCastGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateAlbumArtCornerRadius();
}
// ====
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is AlbumArtLayoutSettings)
{
if (message.PropertyName == nameof(AlbumArtLayoutSettings.SongInfoFontSize))
{
RenderSongInfo();
}
else if (message.PropertyName == nameof(AlbumArtLayoutSettings.CoverImageRadius))
{
UpdateAlbumArtCornerRadius();
}
else if (message.PropertyName == nameof(AlbumArtLayoutSettings.CoverImageShadowAmount))
{
UpdateAlbumArtShadow();
}
}
}
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is AlbumArtLayoutSettings)
{
if (message.PropertyName == nameof(AlbumArtLayoutSettings.IsAutoSongInfoFontSize))
{
RenderSongInfo();
}
}
}
public void Receive(PropertyChangedMessage<string> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCJKFontFamily))
{
RenderSongInfo();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsWesternFontFamily))
{
RenderSongInfo();
}
}
}
public async void Receive(PropertyChangedMessage<SongInfo?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
{
SongInfoStackPanel.Opacity = 0;
await Task.Delay(Constants.Time.AnimationDuration);
RenderSongInfo();
SongInfoStackPanel.Opacity = 1;
UpdateSongInfoOpacity();
}
}
}
public async void Receive(PropertyChangedMessage<BitmapImage?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapImage))
{
LastAlbumArtImage.Source = AlbumArtImage.Source;
LastAlbumArtImage.Opacity = 1;
await Task.Delay(Constants.Time.AnimationDuration);
AlbumArtImage.Opacity = 0;
await Task.Delay(Constants.Time.AnimationDuration);
AlbumArtImage.Source = message.NewValue;
LastAlbumArtImage.Opacity = 0;
AlbumArtImage.Opacity = 1;
UpdateAlbumArtCornerRadius();
}
}
}
public void Receive(PropertyChangedMessage<LyricsLayoutOrientation> message)
{
if (message.Sender is LyricsWindowStatus)
{
if (message.PropertyName == nameof(LyricsWindowStatus.LyricsLayoutOrientation))
{
OnLayoutChanged();
}
}
}
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
{
if (message.Sender is LyricsWindowStatus)
{
if (message.PropertyName == nameof(LyricsWindowStatus.LyricsDisplayType))
{
OnLayoutChanged();
}
}
}
public void Receive(PropertyChangedMessage<AlbumArtThemeColors> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtThemeColors))
{
RenderSongInfo();
}
}
}
public void Receive(PropertyChangedMessage<LyricsWindowStatus> message)
{
if (message.Sender is LiveStates)
{
if (message.PropertyName == nameof(LiveStates.LyricsWindowStatus))
{
OnLayoutChanged();
}
}
}
private void LyricsScrollViewer_PointerWheelChanged(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
var pointerPoint = e.GetCurrentPoint(LyricsScrollViewer);
int mouseWheelDelta = pointerPoint.Properties.MouseWheelDelta;
NowPlayingCanvas.LyricsStartY += mouseWheelDelta;
}
}
}

View File

@@ -60,12 +60,13 @@
<Button
x:Name="SettingsWindowButton"
Click="SettingsWindowButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE713;}"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE713;" />
<ToolTipService.ToolTip>
<TextBlock x:Uid="HostWindowSettingsButtonToolTip" />
<ToolTip x:Uid="HostWindowSettingsButtonToolTip" />
</ToolTipService.ToolTip>
</Button>
@@ -92,16 +93,26 @@
<ScalarTransition />
</StackPanel.OpacityTransition>
<!-- Window Minimize -->
<Button
x:Name="MinimizeButton"
Click="MinimizeButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE738;" />
</Button>
<!-- Window Close -->
<Button
x:Name="CloseButton"
Click="CloseButton_Click"
Style="{StaticResource TitleBarButtonStyle}"
Visibility="{x:Bind ViewModel.CloseButtonVisibility, Mode=OneWay}">
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE653;" />
Glyph="&#xE711;" />
</Button>
</StackPanel>

View File

@@ -119,5 +119,10 @@ namespace BetterLyrics.WinUI3.Views
{
WindowHook.OpenOrShowWindow<SettingsWindow>();
}
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
{
WindowHook.MinimizeWindow<LyricsWindow>();
}
}
}

View File

@@ -564,13 +564,11 @@
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip>
<ToolTip.Content>
<Grid>
<TextBlock x:Name="PlaybackRepeatAllHint" x:Uid="MusicGalleryPageQueueLoop" />
<TextBlock x:Name="PlaybackRepeatOneHint" x:Uid="MusicGalleryPageSingleLoop" />
<TextBlock x:Name="PlaybackShuffleHint" x:Uid="MusicGalleryPageQueueRandom" />
</Grid>
</ToolTip.Content>
<Grid>
<TextBlock x:Name="PlaybackRepeatAllHint" x:Uid="MusicGalleryPageQueueLoop" />
<TextBlock x:Name="PlaybackRepeatOneHint" x:Uid="MusicGalleryPageSingleLoop" />
<TextBlock x:Name="PlaybackShuffleHint" x:Uid="MusicGalleryPageQueueRandom" />
</Grid>
</ToolTip>
</ToolTipService.ToolTip>
<Button.Content>

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