mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 19:24:55 +08:00
Compare commits
55 Commits
v1.0.142.0
...
v1.1.159.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b60952916c | ||
|
|
ec0917b6c8 | ||
|
|
583fa106ce | ||
|
|
88488e4813 | ||
|
|
6e65310b6d | ||
|
|
22bd7c2252 | ||
|
|
5c50bd569a | ||
|
|
401c33003c | ||
|
|
664451c530 | ||
|
|
657c81add5 | ||
|
|
1dd63ab9ba | ||
|
|
0a9b9bf484 | ||
|
|
794079f20b | ||
|
|
be9a67f57d | ||
|
|
5b5d62d688 | ||
|
|
dfe428645e | ||
|
|
8e2c977a44 | ||
|
|
47806c924d | ||
|
|
bbda1cd797 | ||
|
|
2099332f02 | ||
|
|
016d9a626f | ||
|
|
cff6f202d6 | ||
|
|
f643f86567 | ||
|
|
e9b50c84ac | ||
|
|
4c590bcf6f | ||
|
|
68a1c6a465 | ||
|
|
f46364a491 | ||
|
|
75f047e389 | ||
|
|
1cb21b1373 | ||
|
|
4592be10e8 | ||
|
|
c0217150c1 | ||
|
|
0705bde0e2 | ||
|
|
3f5122c93f | ||
|
|
97f061d887 | ||
|
|
77525a62ff | ||
|
|
ad473bbd1d | ||
|
|
b1126026c2 | ||
|
|
2f84155e6e | ||
|
|
1769167811 | ||
|
|
551da8c0b0 | ||
|
|
a5b3671ce3 | ||
|
|
181a06c932 | ||
|
|
93d567e21d | ||
|
|
9da8510de6 | ||
|
|
3ed9e599be | ||
|
|
e277faea9e | ||
|
|
21dcd7de4b | ||
|
|
31540beaa0 | ||
|
|
63ffe6b661 | ||
|
|
5cb880021c | ||
|
|
b00f2b5865 | ||
|
|
90a75f1b96 | ||
|
|
735f03542f | ||
|
|
b7853ded26 | ||
|
|
88fc0adbec |
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
public class ExtendedGenreFiled
|
||||
{
|
||||
public const string NetEaseCloudMusicTrackID = "NCM-";
|
||||
public const string QQMusicTrackID = "QQ-";
|
||||
public const string FileName = "FILENAME-";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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=}"
|
||||
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=}">
|
||||
<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=}">
|
||||
<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=}">
|
||||
<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=}"
|
||||
IsExpanded="True">
|
||||
<dev:SettingsCard x:Uid="SettingsPageShowTitle" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<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=}">
|
||||
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.ShowArtists, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
<dev:SettingsCard x:Uid="SettingsPageShowAlbum" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.ShowAlbum, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -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>
|
||||
135
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/Dragger.xaml.cs
Normal file
135
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/Dragger.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
172
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/InstantTip.cs
Normal file
172
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/InstantTip.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,51 +47,6 @@
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsExpander
|
||||
x:Uid="SettingsPageAlbumArtLayer"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
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>
|
||||
|
||||
|
||||
@@ -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=}">
|
||||
<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=}">
|
||||
<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=}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsLineFadeEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 高亮 -->
|
||||
<dev:SettingsExpander x:Uid="SettingsPageLyricsHighlight" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<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=}"
|
||||
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=}"
|
||||
IsExpanded="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=OneWay}">
|
||||
<dev:SettingsCard x:Uid="SettingsPageLyricsGlowEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<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=}">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsScaleEffectEnabled, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 浮动动画 -->
|
||||
<dev:SettingsExpander
|
||||
x:Uid="SettingsPageLyricsFloatAnimation"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="True">
|
||||
<dev:SettingsCard x:Uid="SettingsPageLyricsFloatAnimation" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<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=}">
|
||||
<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>
|
||||
|
||||
@@ -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=}"
|
||||
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=}"
|
||||
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=}"
|
||||
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=}"
|
||||
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=}"
|
||||
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=}"
|
||||
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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<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=}">
|
||||
<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=}">
|
||||
<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=}">
|
||||
<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=}" Style="{StaticResource GhostButtonStyle}" />
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -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=}"
|
||||
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=}"
|
||||
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=}">
|
||||
<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>
|
||||
|
||||
@@ -149,5 +149,10 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayGrid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
ViewModel.DisplayPanelHeight = e.NewSize.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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="⠿" />
|
||||
</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="⠿" />
|
||||
</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=}"
|
||||
NavigateUri="{x:Bind constants:Link.AppleMusicCfgUrl}" />
|
||||
NavigateUri="{x:Bind constants:Link.AppleMusicCfg}" />
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
Command="{x:Bind ViewModel.SaveAppleMusicMediaUserTokenCommand}"
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
Click="OnCopyClicked"
|
||||
Opacity="0">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="Copy" />
|
||||
<ToolTip x:Uid="Copy" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
x:Uid="SystemTrayMusicGallery"
|
||||
Command="{x:Bind ViewModel.OpenMusicGalleryCommand}"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}" />
|
||||
Glyph=}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SystemTraySettings"
|
||||
Command="{x:Bind ViewModel.OpenSettingsCommand}"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum SpectrumStyle
|
||||
{
|
||||
Curve,
|
||||
Bar
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 "";
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
115
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs
Normal file
115
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public struct LinePlaybackState
|
||||
{
|
||||
public int SyllableStartIndex;
|
||||
public int SyllableLength;
|
||||
public double SyllableProgress;
|
||||
}
|
||||
}
|
||||
@@ -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 = [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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() { }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
// 没有翻译行则不补原文,直接跳过
|
||||
}
|
||||
// 没有翻译行则不补原文,直接跳过
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +60,13 @@
|
||||
<Button
|
||||
x:Name="SettingsWindowButton"
|
||||
Click="SettingsWindowButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=12,
|
||||
Glyph=}"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
<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="" />
|
||||
</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="" />
|
||||
Glyph="" />
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
@@ -119,5 +119,10 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
WindowHook.OpenOrShowWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowHook.MinimizeWindow<LyricsWindow>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user