Compare commits

...

29 Commits

Author SHA1 Message Date
Zhe Fang
bfc877f924 update playback source settings structure 2025-08-10 22:38:19 -04:00
Zhe Fang
af55446004 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-08-09 12:32:36 -04:00
Zhe Fang
faf8acf669 change settings store way 2025-08-09 12:32:34 -04:00
Zhe Fang
5b79a54117 Merge pull request #78 from kusutori/dev
为列表项目添加拖动指示图标
2025-08-09 07:50:28 -04:00
kusutoriクスとり
4ef55f6ece 为列表项目添加拖动指示图标 2025-08-09 19:27:36 +08:00
Zhe Fang
0593f9aa3f add lyrics scrolling duration settings for first line and last line 2025-08-08 22:36:47 -04:00
Zhe Fang
b09a6494ff fix 2025-08-08 16:15:10 -04:00
Zhe Fang
b043f9acd0 新增:
- 歌词原文译文分隔符现可自定义
- 音乐库新增“播放全部”按钮
- 接入 Last.fm 服务(分别为播放源配置是否开启 Last.fm 听歌记录服务)

改动:
- 分别为播放源配置歌词源、设置歌词同步阈值、歌词偏移值

修复:
- 在启用强制显示在全屏应用上时从停靠模式、桌面模式切回标准模式后窗口仍永远处于顶部的异常行为
- “歌词背景不透明度“选项对桌面模式、停靠模式无效的问题
2025-08-06 19:21:05 -04:00
Zhe Fang
7b2ff0cc8f 新增:
- 歌词原文译文分隔符现可自定义
- 音乐库新增“播放全部”按钮
- 接入 Last.fm 服务(分别为播放源配置是否开启 Last.fm 听歌记录服务)

改动:
- 分别为播放源配置歌词源、设置歌词同步阈值、歌词偏移值

修复:
- 在启用强制显示在全屏应用上时从停靠模式、桌面模式切回标准模式后窗口仍永远处于顶部的异常行为
- “歌词背景不透明度“选项对桌面模式、停靠模式无效的问题
2025-08-06 19:19:49 -04:00
Zhe Fang
7c9ab73a34 Fix #76 2025-08-04 14:38:02 -04:00
Zhe Fang
a08bf91784 fix 2025-08-04 13:36:56 -04:00
Zhe Fang
61906670fd fix 2025-08-04 11:51:09 -04:00
Zhe Fang
430b2f4d28 fix 2025-08-04 11:23:07 -04:00
Zhe Fang
eb05c1ea13 update readme 2025-08-04 10:01:37 -04:00
Zhe Fang
9bebf36e6a update readme 2025-08-04 09:53:02 -04:00
Zhe Fang
d2b0b6afb1 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-08-04 09:13:33 -04:00
Zhe Fang
9c2f4fbff9 update readme 2025-08-04 09:13:31 -04:00
Zhe Fang
1b69493afd Update release-to-telegram.yml 2025-08-03 20:38:37 -04:00
Zhe Fang
c524dc013c Update release-to-telegram.yml 2025-08-03 20:35:37 -04:00
Zhe Fang
9ca5939e57 fix 2025-08-03 20:29:58 -04:00
Zhe Fang
860abd4037 fix 2025-08-03 19:43:14 -04:00
Zhe Fang
618415016f fix 2025-08-03 17:05:40 -04:00
Zhe Fang
bfcba1425d 更新 FAQ.md 中的链接文本
在 FAQ.md 文件中,修改了 FAQ 部分的链接文本。将原有的“点此访问”替换为“Click here to visit”,并在中文、日文和韩文版本中相应更新为“点此前往”、“こちらをクリック”和“여기를 클릭하세요”。
2025-08-02 12:26:36 -04:00
Zhe Fang
0dc9ebf18e Revert "Add GitHub stars badge to README files and fix encoding issues in Chinese translations"
This reverts commit 35ca28ac7b.
2025-08-02 12:24:30 -04:00
Zhe Fang
366d396b93 revert 2025-08-02 12:22:09 -04:00
Zhe Fang
35ca28ac7b Add GitHub stars badge to README files and fix encoding issues in Chinese translations 2025-08-02 12:11:16 -04:00
Zhe Fang
1505933107 update FAQ links for consistency in language and clarity 2025-08-02 11:36:51 -04:00
Zhe Fang
958227d0f2 refactor font size settings and update lyrics renderer initialization; add FAQ documentation 2025-08-02 11:35:09 -04:00
Zhe Fang
b1978fec09 update version to 1.0.40.0 and refactor image drawing methods for better opacity handling 2025-08-02 09:15:28 -04:00
171 changed files with 10495 additions and 8649 deletions

View File

@@ -12,6 +12,7 @@ jobs:
env:
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
THREAD_ID: ${{ secrets.TELEGRAM_THREAD_ID }}
RELEASE_URL: ${{ github.event.release.html_url }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_NAME: ${{ github.event.release.name }}
@@ -23,5 +24,5 @@ jobs:
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
-d chat_id="${CHAT_ID}" \
-d text="${TEXT}" \
-d parse_mode=Markdown
-d message_thread_id="${THREAD_ID}" \
-d text="${TEXT}"

1
.gitignore vendored
View File

@@ -406,3 +406,4 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package)_TemporaryKey.pfx
/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/LastFM.cs

View File

@@ -1,149 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '15.0'">
<VisualStudioVersion>15.0</VisualStudioVersion>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x86">
<Configuration>Debug</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x86">
<Configuration>Release</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup>
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
<PathToXAMLWinRTImplementations>BetterLyrics.WinUI3\</PathToXAMLWinRTImplementations>
</PropertyGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
<PropertyGroup>
<ProjectGuid>6576cd19-ef92-4099-b37d-e2d8ebdb6bf5</ProjectGuid>
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
<DefaultLanguage>zh-CN</DefaultLanguage>
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
<Content Include="Images\LargeTile.scale-100.png" />
<Content Include="Images\LargeTile.scale-125.png" />
<Content Include="Images\LargeTile.scale-150.png" />
<Content Include="Images\LargeTile.scale-200.png" />
<Content Include="Images\LargeTile.scale-400.png" />
<Content Include="Images\SmallTile.scale-100.png" />
<Content Include="Images\SmallTile.scale-125.png" />
<Content Include="Images\SmallTile.scale-150.png" />
<Content Include="Images\SmallTile.scale-200.png" />
<Content Include="Images\SmallTile.scale-400.png" />
<Content Include="Images\SplashScreen.scale-100.png" />
<Content Include="Images\SplashScreen.scale-125.png" />
<Content Include="Images\SplashScreen.scale-150.png" />
<Content Include="Images\SplashScreen.scale-200.png" />
<Content Include="Images\LockScreenLogo.scale-200.png" />
<Content Include="Images\SplashScreen.scale-400.png" />
<Content Include="Images\Square150x150Logo.scale-100.png" />
<Content Include="Images\Square150x150Logo.scale-125.png" />
<Content Include="Images\Square150x150Logo.scale-150.png" />
<Content Include="Images\Square150x150Logo.scale-200.png" />
<Content Include="Images\Square150x150Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.scale-100.png" />
<Content Include="Images\Square44x44Logo.scale-125.png" />
<Content Include="Images\Square44x44Logo.scale-150.png" />
<Content Include="Images\Square44x44Logo.scale-200.png" />
<Content Include="Images\Square44x44Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.targetsize-16.png" />
<Content Include="Images\Square44x44Logo.targetsize-24.png" />
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Images\Square44x44Logo.targetsize-256.png" />
<Content Include="Images\Square44x44Logo.targetsize-32.png" />
<Content Include="Images\Square44x44Logo.targetsize-48.png" />
<Content Include="Images\StoreLogo.scale-100.png" />
<Content Include="Images\StoreLogo.scale-125.png" />
<Content Include="Images\StoreLogo.scale-150.png" />
<Content Include="Images\StoreLogo.scale-200.png" />
<Content Include="Images\StoreLogo.scale-400.png" />
<Content Include="Images\Wide310x150Logo.scale-100.png" />
<Content Include="Images\Wide310x150Logo.scale-125.png" />
<Content Include="Images\Wide310x150Logo.scale-150.png" />
<Content Include="Images\Wide310x150Logo.scale-200.png" />
<Content Include="Images\Wide310x150Logo.scale-400.png" />
<None Include="Package.StoreAssociation.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj">
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
</ItemGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '15.0'">
<VisualStudioVersion>15.0</VisualStudioVersion>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x86">
<Configuration>Debug</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x86">
<Configuration>Release</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup>
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
<PathToXAMLWinRTImplementations>BetterLyrics.WinUI3\</PathToXAMLWinRTImplementations>
</PropertyGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
<PropertyGroup>
<ProjectGuid>6576cd19-ef92-4099-b37d-e2d8ebdb6bf5</ProjectGuid>
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
<DefaultLanguage>zh-CN</DefaultLanguage>
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
<Content Include="Images\LargeTile.scale-100.png" />
<Content Include="Images\LargeTile.scale-125.png" />
<Content Include="Images\LargeTile.scale-150.png" />
<Content Include="Images\LargeTile.scale-200.png" />
<Content Include="Images\LargeTile.scale-400.png" />
<Content Include="Images\SmallTile.scale-100.png" />
<Content Include="Images\SmallTile.scale-125.png" />
<Content Include="Images\SmallTile.scale-150.png" />
<Content Include="Images\SmallTile.scale-200.png" />
<Content Include="Images\SmallTile.scale-400.png" />
<Content Include="Images\SplashScreen.scale-100.png" />
<Content Include="Images\SplashScreen.scale-125.png" />
<Content Include="Images\SplashScreen.scale-150.png" />
<Content Include="Images\SplashScreen.scale-200.png" />
<Content Include="Images\LockScreenLogo.scale-200.png" />
<Content Include="Images\SplashScreen.scale-400.png" />
<Content Include="Images\Square150x150Logo.scale-100.png" />
<Content Include="Images\Square150x150Logo.scale-125.png" />
<Content Include="Images\Square150x150Logo.scale-150.png" />
<Content Include="Images\Square150x150Logo.scale-200.png" />
<Content Include="Images\Square150x150Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.scale-100.png" />
<Content Include="Images\Square44x44Logo.scale-125.png" />
<Content Include="Images\Square44x44Logo.scale-150.png" />
<Content Include="Images\Square44x44Logo.scale-200.png" />
<Content Include="Images\Square44x44Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.targetsize-16.png" />
<Content Include="Images\Square44x44Logo.targetsize-24.png" />
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Images\Square44x44Logo.targetsize-256.png" />
<Content Include="Images\Square44x44Logo.targetsize-32.png" />
<Content Include="Images\Square44x44Logo.targetsize-48.png" />
<Content Include="Images\StoreLogo.scale-100.png" />
<Content Include="Images\StoreLogo.scale-125.png" />
<Content Include="Images\StoreLogo.scale-150.png" />
<Content Include="Images\StoreLogo.scale-200.png" />
<Content Include="Images\StoreLogo.scale-400.png" />
<Content Include="Images\Wide310x150Logo.scale-100.png" />
<Content Include="Images\Wide310x150Logo.scale-125.png" />
<Content Include="Images\Wide310x150Logo.scale-150.png" />
<Content Include="Images\Wide310x150Logo.scale-200.png" />
<Content Include="Images\Wide310x150Logo.scale-400.png" />
<None Include="Package.StoreAssociation.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj">
<EnableMsixTooling>true</EnableMsixTooling>
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
</ItemGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
</Project>

View File

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

View File

@@ -51,6 +51,8 @@
<converter:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
<converter:MediaSourceProviderToLogoUriConverter x:Key="MediaSourceProviderToLogoUriConverter" />
<converter:MediaSourceProviderToDisplayedNameConverter x:Key="MediaSourceProviderToDisplayedNameConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
@@ -81,7 +83,10 @@
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="TitleBarToggleButtonStyle" TargetType="ToggleButton">
<Style
x:Key="TitleBarToggleButtonStyle"
BasedOn="{StaticResource ToggleButtonRevealStyle}"
TargetType="ToggleButton">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" />
@@ -292,6 +297,17 @@
<Setter Property="Padding" Value="0" />
</Style>
<Style
x:Key="SettingsScrollViewerStyle"
BasedOn="{StaticResource DefaultScrollViewerStyle}"
TargetType="ScrollViewer">
<Setter Property="Padding" Value="36,0" />
</Style>
<Style x:Key="SettingsGridStyle" TargetType="Grid">
<Setter Property="Padding" Value="0,0,0,36" />
</Style>
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="TextFillColorPrimaryBrush" />
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="TextFillColorPrimaryBrush" />
@@ -302,4 +318,5 @@
<FontFamily x:Key="IconFontFamily">ms-appx:///Assets/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -3,7 +3,15 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
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;
@@ -64,7 +72,7 @@ namespace BetterLyrics.WinUI3
private void EnsureSingleInstance()
{
bool createdNew;
_instanceMutex = new Mutex(true, MetadataHelper.AppName, out createdNew);
_instanceMutex = new Mutex(true, Constants.App.AppName, out createdNew);
if (!createdNew)
{
@@ -102,12 +110,19 @@ namespace BetterLyrics.WinUI3
})
// Services
.AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<IPlaybackService, PlaybackService>()
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>()
.AddSingleton<ITranslateService, TranslateService>()
.AddSingleton<ILastFMService, LastFMService>()
// ViewModels
.AddSingleton<AppSettingsControlViewModel>()
.AddSingleton<LyricsBackgroundSettingsControlViewModel>()
.AddSingleton<AlbumArtLayoutSettingsControlViewModel>()
.AddSingleton<PlaybackSettingsControlViewModel>()
.AddSingleton<MediaSettingsControlViewModel>()
.AddSingleton<AllLyricsSettingsControlViewModel>()
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>()
.AddSingleton<SystemTrayViewModel>()

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -22,6 +22,14 @@
<ItemGroup>
<None Remove="Assets\Segoe Fluent Icons.ttf" />
<None Remove="Assets\Wiki82.profile.xml" />
<None Remove="Controls\AlbumArtLayoutSettingsControl.xaml" />
<None Remove="Controls\AllLyricsSettingsControl.xaml" />
<None Remove="Controls\AppSettingsControl.xaml" />
<None Remove="Controls\ExtendedSlider.xaml" />
<None Remove="Controls\LyricsBavkgroundSettingsControl.xaml" />
<None Remove="Controls\LyricsSettingsControl.xaml" />
<None Remove="Controls\MediaSettingsControl.xaml" />
<None Remove="Controls\PlaybackSettingsControl.xaml" />
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Views\MusicGalleryPage.xaml" />
<None Remove="Views\MusicGalleryWindow.xaml" />
@@ -53,8 +61,8 @@
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.8" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
@@ -65,8 +73,8 @@
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.7" />
<PackageReference Include="System.Drawing.Common" Version="9.0.8" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.8" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="TinyPinyin.Net" Version="1.0.2" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
@@ -78,6 +86,11 @@
<PackageReference Include="WinUIEx" Version="2.6.0" />
<PackageReference Include="z440.atl.core" Version="7.2.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="Hqub.Lastfm">
<HintPath>..\..\..\Last.fm\src\Hqub.Lastfm\bin\Release\netstandard2.0\Hqub.Lastfm.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Page Update="Rendering\InAppLyricsRenderer.xaml">
<Generator>MSBuild:Compile</Generator>
@@ -93,27 +106,78 @@
<TrimmerRootAssembly Include="TagLibSharp" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\AIMP.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AppleMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Chrome.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Discord.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Edge.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyBox.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyState.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\foobar2000.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\iTunes.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\KugouMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\LastFM.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Leaf.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\LXMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MediaPlayerWindows11.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MusicBee.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\NetEaseCloudMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\PotPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\QQ.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\QQMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Question.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Segoe Fluent Icons.ttf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Spotify.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Telegram.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@@ -121,6 +185,46 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ExtendedSlider.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AllLyricsSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\MediaSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\PlaybackSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AlbumArtLayoutSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\LyricsBavkgroundSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AppSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\MusicGalleryWindow.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class AmllTTmlDB
{
private const string BaseUrl = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/";
public const string QueryPrefix = $"{BaseUrl}raw-lyrics/";
public const string Index = $"{BaseUrl}metadata/raw-lyrics-index.jsonl";
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class App
{
public const string AppAuthor = "Zhe Fang";
public const string AppName = "BetterLyrics";
public const string AutoStartupTaskId = "AutoStartup";
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class LXMusic
{
public const string QuerySuffix = "/subscribe-player-status?filter=progress,duration";
}
}

View File

@@ -0,0 +1,10 @@
namespace BetterLyrics.WinUI3.Constants
{
public static class LastFM
{
public const string ApiKey = "Your api key here";
public const string SharedSecret = "Your shared secret here";
public const string UnAuthUrl = "https://www.last.fm/settings/applications";
public const string SessionKeyCredentialKey = "LastFMSessionKey";
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class Link
{
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
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";
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel;
namespace BetterLyrics.WinUI3.Constants
{
public static class PlayerID
{
public const string LXMusic = "cn.toside.music.desktop";
public const string MediaPlayerWindows11 = "Microsoft.ZuneMusic_8wekyb3d8bbwe!Microsoft.ZuneMusic";
public const string AIMP = "AIMP.exe";
public const string Foobar2000 = "foobar2000.exe";
public const string MusicBee = "MusicBee.exe";
public const string PotPlayer = "PotPlayerMini64.exe";
public const string Spotify = "Spotify.exe";
public const string AppleMusic = "AppleInc.AppleMusicWin_nzyj5cx40ttqa!App";
public const string NetEaseCloudMusic = "cloudmusic.exe";
public const string KugouMusic = "kugou";
public const string QQMusic = "QQMusic.exe";
public const string iTunes = "49586DaveAntoine.MediaControllerforiTunes_9bzempp7dntjg!App";
public const string Chrome = "Chrome";
public const string Edge = "MSEdge";
public const string BetterLyrics = "37412.BetterLyrics_rd1g0rsrrtxw8!App";
public const string BetterLyricsDebug = "37412.BetterLyrics_c8mj3v9sysxb4!App";
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public class PlayerName
{
public const string LXMusic = "LX Music";
public const string MediaPlayerWindows11 = "Media Player";
public const string AIMP = "AIMP";
public const string Foobar2000 = "foobar2000";
public const string MusicBee = "MusicBee";
public const string PotPlayer = "PotPlayer";
public const string Spotify = "Spotify";
public const string AppleMusic = "Apple Music";
public const string NetEaseCloudMusic = "网易云音乐";
public const string KugouMusic = "酷狗音乐";
public const string QQMusic = "QQ 音乐";
public const string iTunes = "iTunes";
public const string Chrome = "Google Chrome";
public const string Edge = "Microsoft Edge";
public const string BetterLyrics = "BetterLyrics";
public const string BetterLyricsDebug = "BetterLyrics (Debug)";
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class Time
{
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(300);
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class iTunes
{
public const string QueryPrefix = "https://itunes.apple.com/search?";
}
}

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.AlbumArtLayoutSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="SettingsPageAlbumArt" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageAlbumRadius" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE71A;}">
<local:ExtendedSlider
Default="12"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind ViewModel.AppSettings.AlbumArtLayoutSettings.CoverImageRadius, Mode=TwoWay}" />
</controls:SettingsCard>
<TextBlock x:Uid="SettingsPageSongInfo" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageSongInfoAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.AlbumArtLayoutSettings.SongInfoAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageSongInfoLeft" />
<ComboBoxItem x:Uid="SettingsPageSongInfoCenter" />
<ComboBoxItem x:Uid="SettingsPageSongInfoRight" />
</ComboBox>
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

View File

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

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.AllLyricsSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
mc:Ignorable="d">
<Grid>
<controls:SwitchPresenter Margin="0,72,0,0" Value="{Binding SelectedItem.Tag, ElementName=LyricsSettingsSegmentedControl}">
<controls:Case Value="Standard">
<uc:LyricsSettingsControl LyricsEffectSettings="{x:Bind ViewModel.AppSettings.StandardLyricsEffectSettings, Mode=OneWay}" LyricsStyleSettings="{x:Bind ViewModel.AppSettings.StandardLyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
<controls:Case Value="Desktop">
<uc:LyricsSettingsControl LyricsEffectSettings="{x:Bind ViewModel.AppSettings.DesktopLyricsEffectSettings, Mode=OneWay}" LyricsStyleSettings="{x:Bind ViewModel.AppSettings.DesktopLyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
<controls:Case Value="Dock">
<uc:LyricsSettingsControl LyricsEffectSettings="{x:Bind ViewModel.AppSettings.DockLyricsEffectSettings, Mode=OneWay}" LyricsStyleSettings="{x:Bind ViewModel.AppSettings.DockLyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
</controls:SwitchPresenter>
<controls:Segmented
x:Name="LyricsSettingsSegmentedControl"
Margin="36,36,36,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SelectedIndex="0">
<controls:SegmentedItem x:Uid="AllLyricsSettingsControlStandard" Tag="Standard" />
<controls:SegmentedItem x:Uid="AllLyricsSettingsControlDesktop" Tag="Desktop" />
<controls:SegmentedItem x:Uid="AllLyricsSettingsControlDock" Tag="Dock" />
</controls:Segmented>
</Grid>
</UserControl>

View File

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

View File

@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.AppSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<!-- App appearance -->
<TextBlock x:Uid="SettingsPageAppAppearance" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="SettingsPageLanguage"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF2B7;}"
IsExpanded="True">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.GeneralSettings.Language, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageSystemLanguage" />
<ComboBoxItem x:Uid="SettingsPageEN" />
<ComboBoxItem x:Uid="SettingsPageSC" />
<ComboBoxItem x:Uid="SettingsPageTC" />
<ComboBoxItem x:Uid="SettingsPageJA" />
<ComboBoxItem x:Uid="SettingsPageKO" />
</ComboBox>
<controls:SettingsExpander.Items>
<controls:SettingsCard>
<Button x:Uid="SettingsPageRestart" Command="{x:Bind ViewModel.RestartAppCommand}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<!-- App behavior -->
<TextBlock x:Uid="SettingsPageAppBehavior" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageAutoStart" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF71C;}">
<ToggleSwitch
x:Name="AutoStartupToggleSwitch"
Loaded="AutoStartupToggleSwitch_Loaded"
Unloaded="AutoStartupToggleSwitch_Unloaded" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageAutoStartWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE736;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.GeneralSettings.AutoStartWindowType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageAutoStartInAppLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDockLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDesktopLyrics" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageIgnoreFullscreenWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE967;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IgnoreFullscreenWindow, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageHideWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xED1A;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.HideWindowWhenNotPlaying, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageGlobalDrag" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C2;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IsDragEverywhereEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Desktop mode -->
<TextBlock x:Uid="SettingsPageAppDesktop" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageAutoLock" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE755;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.DesktopModeSettings.AutoLockOnDesktopMode, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLockHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock
Margin="0,0,0,2"
VerticalAlignment="Center"
Text="Ctrl + Alt + " />
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.DesktopModeSettings.LockHotKeyIndex, Mode=TwoWay}">
<ComboBoxItem Content="A" />
<ComboBoxItem Content="B" />
<ComboBoxItem Content="C" />
<ComboBoxItem Content="D" />
<ComboBoxItem Content="E" />
<ComboBoxItem Content="F" />
<ComboBoxItem Content="G" />
<ComboBoxItem Content="H" />
<ComboBoxItem Content="I" />
<ComboBoxItem Content="J" />
<ComboBoxItem Content="K" />
<ComboBoxItem Content="L" />
<ComboBoxItem Content="M" />
<ComboBoxItem Content="N" />
<ComboBoxItem Content="O" />
<ComboBoxItem Content="P" />
<ComboBoxItem Content="Q" />
<ComboBoxItem Content="R" />
<ComboBoxItem Content="S" />
<ComboBoxItem Content="T" />
<ComboBoxItem Content="U" />
<ComboBoxItem Content="V" />
<ComboBoxItem Content="W" />
<ComboBoxItem Content="X" />
<ComboBoxItem Content="Y" />
<ComboBoxItem Content="Z" />
</ComboBox>
</StackPanel>
</controls:SettingsCard>
<!-- Dock mode -->
<TextBlock x:Uid="SettingsPageAppDock" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageDockMonitor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7F4;}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ComboBox ItemsSource="{x:Bind ViewModel.MonitorDeviceNames, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.AppSettings.DockModeSettings.DockMonitorDeviceName, Mode=TwoWay}" />
<Button
Command="{x:Bind ViewModel.RefreshMonitorDeviceNamesCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE72C;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDockWindowHeight" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xED5E;}">
<local:ExtendedSlider
Default="64"
Frequency="1"
Maximum="200"
Minimum="64"
Unit="px"
Value="{x:Bind ViewModel.AppSettings.DockModeSettings.DockWindowHeight, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDockPlacement" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.DockModeSettings.DockPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageDockPlacementTop" />
<ComboBoxItem x:Uid="SettingsPageDockPlacementBottom" />
</ComboBox>
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,50 @@
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class AppSettingsControl : UserControl
{
public AppSettingsControlViewModel ViewModel => (AppSettingsControlViewModel)DataContext;
public AppSettingsControl()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<AppSettingsControlViewModel>();
}
private async void AutoStartupToggleSwitch_Loaded(object sender, RoutedEventArgs e)
{
AutoStartupToggleSwitch.IsOn = await ViewModel.DetectIsAutoStartupEnabledAsync();
AutoStartupToggleSwitch.Toggled += AutoStartupToggleSwitch_Toggled;
}
private void AutoStartupToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
ViewModel.ToggleAutoStartupAsync(AutoStartupToggleSwitch.IsOn);
}
private void AutoStartupToggleSwitch_Unloaded(object sender, RoutedEventArgs e)
{
AutoStartupToggleSwitch.Toggled -= AutoStartupToggleSwitch_Toggled;
}
}
}

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.ExtendedSlider"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="6,0,2,0"
VerticalAlignment="Center"
Text="{x:Bind Value, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text="{x:Bind Unit, Mode=OneWay}" />
<Slider
Maximum="{x:Bind Maximum, Mode=OneWay}"
Minimum="{x:Bind Minimum, Mode=OneWay}"
SnapsTo="Ticks"
StepFrequency="{x:Bind Frequency, Mode=OneWay}"
TickFrequency="{x:Bind Frequency, Mode=OneWay}"
TickPlacement="Outside"
Value="{x:Bind Value, Mode=TwoWay}" />
<Button
Margin="3,0,0,0"
VerticalAlignment="Center"
Click="ResetButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind ResetButtonVisibility, Mode=OneWay}" />
<Button
x:Name="SubtractButton"
Margin="3,0,0,0"
VerticalAlignment="Center"
Click="SubtractButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE738;}"
Style="{StaticResource GhostButtonStyle}" />
<Button
x:Name="AddButton"
Margin="3,0,0,0"
VerticalAlignment="Center"
Click="AddButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE710;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,129 @@
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class ExtendedSlider : UserControl
{
public ExtendedSlider()
{
InitializeComponent();
}
private void Subtract()
{
if (Value - Frequency < Minimum)
{
Value = Minimum;
}
else
{
Value -= Frequency;
}
}
private void Add()
{
if (Value + Frequency > Maximum)
{
Value = Maximum;
}
else
{
Value += Frequency;
}
}
private void SubtractTimer_Tick(object? sender, object e)
{
Subtract();
}
private void AddTimer_Tick(object? sender, object e)
{
Add();
}
public static readonly DependencyProperty FrequencyProperty =
DependencyProperty.Register(nameof(Frequency), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(default));
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register(nameof(Minimum), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(default));
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register(nameof(Maximum), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(default));
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(default));
public static readonly DependencyProperty DefaultProperty =
DependencyProperty.Register(nameof(Default), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(default));
public static readonly DependencyProperty ResetButtonVisibilityProperty =
DependencyProperty.Register(nameof(ResetButtonVisibility), typeof(Visibility), typeof(ExtendedSlider), new PropertyMetadata(Visibility.Visible));
public static readonly DependencyProperty UnitProperty =
DependencyProperty.Register(nameof(Unit), typeof(string), typeof(ExtendedSlider), new PropertyMetadata(""));
public double Frequency
{
get => (double)GetValue(FrequencyProperty);
set => SetValue(FrequencyProperty, value);
}
public double Minimum
{
get => (double)GetValue(MinimumProperty);
set => SetValue(MinimumProperty, value);
}
public double Maximum
{
get => (double)GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
public double Value
{
get => (double)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public double Default
{
get => (double)GetValue(DefaultProperty);
set => SetValue(DefaultProperty, value);
}
public Visibility ResetButtonVisibility
{
get => (Visibility)GetValue(ResetButtonVisibilityProperty);
set => SetValue(ResetButtonVisibilityProperty, value);
}
public string Unit
{
get => (string)GetValue(UnitProperty);
set => SetValue(UnitProperty, value);
}
private void ResetButton_Click(object sender, RoutedEventArgs e)
{
Value = Default;
}
private void SubtractButton_Click(object sender, RoutedEventArgs e)
{
Subtract();
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
Add();
}
}
}

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.LyricsBavkgroundSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageTheme" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE790;}">
<ComboBox x:Name="ThemeComboBox" SelectedIndex="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.LyricsBackgroundTheme, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageFollowSystem" />
<ComboBoxItem x:Uid="SettingsPageLight" />
<ComboBoxItem x:Uid="SettingsPageDark" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsPureColorBgOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF573;}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.PureColorOverlayOpacity, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF573;}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.CoverOverlayOpacity, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundSpeed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC49;}">
<uc:ExtendedSlider
Default="50"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.CoverOverlaySpeed, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<uc:ExtendedSlider
Default="100"
Frequency="1"
Maximum="100"
Minimum="0"
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.CoverOverlayBlurAmount, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageBackgroundAcrylicEffectAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<uc:ExtendedSlider
Default="0"
Frequency="1"
Maximum="10"
Minimum="0"
Value="{x:Bind ViewModel.AppSettings.LyricsBackgroundSettings.CoverAcrylicEffectAmount, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

View File

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

View File

@@ -0,0 +1,323 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.LyricsSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<!-- Lyrics style -->
<TextBlock x:Uid="SettingsPageLyricsStyle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind LyricsStyleSettings.LyricsAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsLeft" />
<ComboBoxItem x:Uid="SettingsPageLyricsCenter" />
<ComboBoxItem x:Uid="SettingsPageLyricsRight" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontFamily" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D2;}">
<ComboBox ItemsSource="{x:Bind SystemFontNames, Mode=OneWay}" SelectedIndex="{x:Bind LyricsStyleSettings.SelectedFontFamilyIndex, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontWeight" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8DD;}">
<ComboBox SelectedIndex="{x:Bind LyricsStyleSettings.LyricsFontWeight, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsThin" />
<ComboBoxItem x:Uid="SettingsPageLyricsExtraLight" />
<ComboBoxItem x:Uid="SettingsPageLyricsLight" />
<ComboBoxItem x:Uid="SettingsPageLyricsSemiLight" />
<ComboBoxItem x:Uid="SettingsPageLyricsNormal" />
<ComboBoxItem x:Uid="SettingsPageLyricsMedium" />
<ComboBoxItem x:Uid="SettingsPageLyricsSemiBold" />
<ComboBoxItem x:Uid="SettingsPageLyricsBold" />
<ComboBoxItem x:Uid="SettingsPageLyricsExtraBold" />
<ComboBoxItem x:Uid="SettingsPageLyricsBlack" />
<ComboBoxItem x:Uid="SettingsPageLyricsExtraBlack" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBgFontOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE799;}">
<local:ExtendedSlider
Default="30"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsStyleSettings.LyricsBgFontOpacity, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontStrokeWidth" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC12;}">
<local:ExtendedSlider
Default="0"
Frequency="1"
Maximum="5"
Minimum="0"
Value="{x:Bind LyricsStyleSettings.LyricsFontStrokeWidth, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsStrokeFontColor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D3;}">
<ComboBox SelectedIndex="{x:Bind LyricsStyleSettings.LyricsStrokeFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveColored" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveGrayed" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorCustom" />
</ComboBox>
</controls:SettingsCard>
<ColorPicker
ColorSpectrumShape="Box"
IsAlphaEnabled="True"
IsAlphaSliderVisible="True"
IsAlphaTextInputVisible="True"
IsColorChannelTextInputVisible="True"
IsColorSliderVisible="True"
IsHexInputVisible="True"
IsMoreButtonVisible="True"
Color="{x:Bind LyricsStyleSettings.LyricsCustomStrokeFontColor, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsStyleSettings.LyricsStrokeFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsStyleSettings.LyricsStrokeFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="NotEqual"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ColorPicker>
<controls:SettingsCard x:Uid="SettingsPageLyricsBgFontColor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D3;}">
<ComboBox SelectedIndex="{x:Bind LyricsStyleSettings.LyricsBgFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveColored" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveGrayed" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorCustom" />
</ComboBox>
</controls:SettingsCard>
<ColorPicker
ColorSpectrumShape="Box"
IsAlphaEnabled="True"
IsAlphaSliderVisible="True"
IsAlphaTextInputVisible="True"
IsColorChannelTextInputVisible="True"
IsColorSliderVisible="True"
IsHexInputVisible="True"
IsMoreButtonVisible="True"
Color="{x:Bind LyricsStyleSettings.LyricsCustomBgFontColor, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsStyleSettings.LyricsBgFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsStyleSettings.LyricsBgFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="NotEqual"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ColorPicker>
<controls:SettingsCard x:Uid="SettingsPageLyricsFgFontColor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D3;}">
<ComboBox SelectedIndex="{x:Bind LyricsStyleSettings.LyricsFgFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveColored" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveGrayed" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorCustom" />
</ComboBox>
</controls:SettingsCard>
<ColorPicker
ColorSpectrumShape="Box"
IsAlphaEnabled="True"
IsAlphaSliderVisible="True"
IsAlphaTextInputVisible="True"
IsColorChannelTextInputVisible="True"
IsColorSliderVisible="True"
IsHexInputVisible="True"
IsMoreButtonVisible="True"
Color="{x:Bind LyricsStyleSettings.LyricsCustomFgFontColor, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsStyleSettings.LyricsFgFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsStyleSettings.LyricsFgFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="NotEqual"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ColorPicker>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E9;}">
<local:ExtendedSlider
Frequency="2"
Maximum="96"
Minimum="12"
ResetButtonVisibility="Collapsed"
Value="{x:Bind LyricsStyleSettings.LyricsFontSize, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF579;}">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsLineSpacingFactorSlider"
Default="0.5"
Frequency="0.1"
Maximum="2"
Minimum="0"
Unit="x"
Value="{x:Bind LyricsStyleSettings.LyricsLineSpacingFactor, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsTranslationSeparator" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF57B;}">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBox AcceptsReturn="True" Text="{x:Bind LyricsStyleSettings.LyricsTranslationSeparator, Mode=TwoWay}" />
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FontSize=12, Glyph=&#xE8FB;}" Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
<!-- Effect -->
<TextBlock
x:Uid="SettingsPageLyricsEffect"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="Effect" />
<controls:SettingsCard x:Uid="SettingsPageLyricsVerticalEdgeOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF573;}">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsVerticalEdgeOpacitySlider"
Default="0"
Frequency="1"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsEffectSettings.LyricsVerticalEdgeOpacity, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsBlurAmountExtendedSlider"
Default="5"
Frequency="1"
Maximum="10"
Minimum="0"
Value="{x:Bind LyricsEffectSettings.LyricsBlurAmount, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsHighlightScope" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7E6;}">
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsHighlightScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentChar" />
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeLineStartToCurrentChar" />
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentLine" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsExpander
x:Uid="SettingsPageLyricsGlowEffect"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE9A9;}"
IsExpanded="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls: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>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageLyricsFloatAnimation" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8C5;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsFloatAnimationEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageFan" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEBC5;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsFanLyricsEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsExpander
x:Uid="SettingsPageScrollEasing"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xECE7;}"
IsExpanded="True">
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsScrollEasingType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEasingTypeLinear" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeSmoothStep" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutSine" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutQuad" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutCubic" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutQuart" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutQuint" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutExpo" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutCirc" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutBack" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutElastic" />
<ComboBoxItem x:Uid="SettingsPageEasingTypeEaseInOutBounce" />
</ComboBox>
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageScrollTopDuration">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsScrollTopDurationExtendedSlider"
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollTopDuration, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageScrollDuration">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsScrollDurationExtendedSlider"
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollDuration, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageScrollBottomDuration">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsScrollBottomDurationExtendedSlider"
Default="500"
Frequency="50"
Maximum="1000"
Minimum="50"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScrollBottomDuration, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,53 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models.Settings;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class LyricsSettingsControl : UserControl
{
public ObservableCollection<string> SystemFontNames { get; set; } = [.. FontHelper.SystemFontFamilies];
public LyricsSettingsControl()
{
InitializeComponent();
}
public static readonly DependencyProperty LyricsStyleSettingsProperty =
DependencyProperty.Register(nameof(LyricsStyleSettings), typeof(LyricsStyleSettings), typeof(LyricsSettingsControl), new PropertyMetadata(default));
public LyricsStyleSettings LyricsStyleSettings
{
get => (LyricsStyleSettings)GetValue(LyricsStyleSettingsProperty);
set => SetValue(LyricsStyleSettingsProperty, value);
}
public static readonly DependencyProperty LyricsEffectSettingsProperty =
DependencyProperty.Register(nameof(LyricsEffectSettings), typeof(LyricsEffectSettings), typeof(LyricsSettingsControl), new PropertyMetadata(default));
public LyricsEffectSettings LyricsEffectSettings
{
get => (LyricsEffectSettings)GetValue(LyricsEffectSettingsProperty);
set => SetValue(LyricsEffectSettingsProperty, value);
}
}
}

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.MediaSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="SettingsPageMusicLib"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8B7;}"
IsExpanded="True"
ItemsSource="{x:Bind ViewModel.AppSettings.LocalMediaFolders, Mode=OneWay}">
<controls:SettingsExpander.ItemTemplate>
<DataTemplate>
<controls:SettingsCard>
<controls:SettingsCard.Header>
<HyperlinkButton
Click="LocalFolderHyperlinkButton_Click"
Content="{Binding Path, Mode=OneWay}"
Tag="{Binding Path, Mode=OneWay}" />
</controls:SettingsCard.Header>
<StackPanel Orientation="Horizontal">
<HyperlinkButton
x:Uid="SettingsPageRemovePath"
Click="SettingsPageRemovePathButton_Click"
Tag="{Binding}" />
<ToggleSwitch DataContext="{Binding}" IsOn="{Binding IsEnabled, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
</DataTemplate>
</controls:SettingsExpander.ItemTemplate>
<controls:SettingsExpander.ItemsHeader>
<InfoBar
x:Uid="SettingsPageRemoveInfo"
BorderThickness="0"
CornerRadius="0"
IsClosable="False"
IsOpen="True"
Severity="Success">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.LocalMediaFolders.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.LocalMediaFolders.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</InfoBar>
</controls:SettingsExpander.ItemsHeader>
<controls:SettingsExpander.ItemsFooter>
<controls:SettingsCard x:Uid="SettingsPageAddFolder" Style="{StaticResource DefaultSettingsExpanderItemStyle}">
<Button
x:Uid="SettingsPageAddFolderButton"
Command="{x:Bind ViewModel.SelectAndAddFolderCommand}"
CommandParameter="{Binding ElementName=RootGrid}" />
</controls:SettingsCard>
</controls:SettingsExpander.ItemsFooter>
</controls:SettingsExpander>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,50 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.System;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class MediaSettingsControl : UserControl
{
public MediaSettingsControlViewModel ViewModel => (MediaSettingsControlViewModel)DataContext;
public MediaSettingsControl()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<MediaSettingsControlViewModel>();
}
private void SettingsPageRemovePathButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.RemoveFolderAsync((LocalMediaFolder)(sender as HyperlinkButton)!.Tag);
}
private async void LocalFolderHyperlinkButton_Click(object sender, RoutedEventArgs e)
{
if (sender is HyperlinkButton button && button.Tag is string uriStr)
{
if (Uri.TryCreate(uriStr, UriKind.Absolute, out var uri))
{
await Launcher.LaunchUriAsync(uri);
}
}
}
}
}

View File

@@ -0,0 +1,320 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.PlaybackSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<NavigationView
VerticalAlignment="Top"
Background="Transparent"
IsBackButtonVisible="Collapsed"
IsBackEnabled="False"
IsSettingsVisible="False"
MenuItemsSource="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo, Mode=OneWay}"
PaneDisplayMode="Top"
SelectedItem="{x:Bind ViewModel.SelectedMediaSourceProvider, Mode=TwoWay}">
<NavigationView.MenuItemTemplate>
<DataTemplate x:DataType="models:MediaSourceProviderInfo">
<NavigationViewItem>
<NavigationViewItem.Icon>
<ImageIcon Source="{Binding Provider, Converter={StaticResource MediaSourceProviderToLogoUriConverter}, Mode=OneWay}" />
</NavigationViewItem.Icon>
<NavigationViewItem.Content>
<TextBlock
MaxWidth="200"
Text="{Binding Provider, Converter={StaticResource MediaSourceProviderToDisplayedNameConverter}, Mode=OneWay}"
TextWrapping="Wrap" />
</NavigationViewItem.Content>
</NavigationViewItem>
</DataTemplate>
</NavigationView.MenuItemTemplate>
</NavigationView>
<ScrollViewer Margin="0,72,0,0" Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageMediaSourceProvidersConfig">
<ToggleSwitch IsOn="{x:Bind ViewModel.SelectedMediaSourceProvider.IsEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLastFMTrack" IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.SelectedMediaSourceProvider.IsLastFMTrackEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- 时间轴相关配置 -->
<controls:SettingsCard x:Uid="SettingsPageLyricsTimelineThreshold">
<local:ExtendedSlider
x:Uid="SettingsPageLyricsTimelineThresholdReset"
Frequency="100"
Maximum="1000"
Minimum="0"
ResetButtonVisibility="Collapsed"
Unit="ms"
Value="{x:Bind ViewModel.SelectedMediaSourceProvider.TimelineSyncThreshold, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsExpander x:Uid="MainPagePositionOffsetSlider" IsExpanded="True">
<local:ExtendedSlider
x:Uid="SettingsPagePositionOffsetReset"
Default="0"
Frequency="100"
Maximum="5000"
Minimum="-5000"
Unit="ms"
Value="{x:Bind ViewModel.SelectedMediaSourceProvider.PositionOffset, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="LyricsPagePositionOffsetHint">
<ToggleSwitch IsOn="{x:Bind ViewModel.SelectedMediaSourceProvider.ResetPositionOffsetOnSongChanged, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<!-- 专辑封面源配置 -->
<TextBlock x:Uid="SettingsPageAlbumArtSearchProvidersConfig" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<ListView
x:Name="AlbumArtSearchProvidersListView"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="AlbumArtSearchProvidersListView_DragItemsCompleted"
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
ItemsSource="{x:Bind ViewModel.SelectedMediaSourceProvider.AlbumArtSearchProvidersInfo, Mode=OneWay}"
SelectionMode="None">
<ListView.OpacityTransition>
<ScalarTransition />
</ListView.OpacityTransition>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:AlbumArtSearchProviderInfo">
<controls:SettingsCard Header="{Binding Provider, Converter={StaticResource AlbumArtSearchProviderToDisplayNameConverter}, Mode=OneWay}">
<controls:SettingsCard.HeaderIcon>
<FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x283F;" />
</controls:SettingsCard.HeaderIcon>
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- 歌词源配置 -->
<TextBlock x:Uid="SettingsPageLyricsSearchProvidersConfig" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<ListView
x:Name="LyricsSearchProvidersListView"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="LyricsSearchProvidersListView_DragItemsCompleted"
ItemsSource="{x:Bind ViewModel.SelectedMediaSourceProvider.LyricsSearchProvidersInfo, Mode=OneWay}"
SelectionMode="None">
<ListView.OpacityTransition>
<ScalarTransition />
</ListView.OpacityTransition>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:LyricsSearchProviderInfo">
<controls:SettingsCard Header="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}, Mode=OneWay}">
<controls:SettingsCard.HeaderIcon>
<FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x283F;" />
</controls:SettingsCard.HeaderIcon>
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</ScrollViewer>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
<StackPanel
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="12">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<Image MaxWidth="200" Source="/Assets/Leaf.png" />
<TextBlock
x:Uid="SettingsPagePlaybackNotFound"
HorizontalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
<Grid Grid.Column="1">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<!-- Last.fm -->
<TextBlock x:Uid="SettingsPageLastFM" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="SettingsPageLastFMManager"
HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/LastFM.png}"
IsExpanded="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="6">
<Button
x:Uid="SettingsPageLastFMAuth"
Command="{x:Bind ViewModel.LastFMAuthCommand}"
IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
<Button
x:Uid="SettingsPageLastFMUnAuth"
Command="{x:Bind ViewModel.LastFMUnAuthCommand}"
IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}" />
</StackPanel>
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLastFMUsername" IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<HyperlinkButton Content="{x:Bind ViewModel.LastFMUser.Name, Mode=OneWay}" NavigateUri="{x:Bind ViewModel.LastFMUser.Url, Mode=OneWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLastFMPlaycount" IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<TextBlock Text="{x:Bind ViewModel.LastFMUser.Playcount, Mode=OneWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLastFMRegistered" IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<TextBlock Text="{x:Bind ViewModel.LastFMUser.Registered.ToLongDateString(), Mode=OneWay}" />
</controls:SettingsCard>
<controls:SettingsCard IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<Button x:Uid="SettingsPageLastFMRefresh" Command="{x:Bind ViewModel.LastFMRefreshCommand}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<!-- Lyrics translation -->
<TextBlock x:Uid="SettingsPageTranslation" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander
x:Uid="LyricsPageTranslationEnabled"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE774;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageTargetLanguage">
<ComboBox SelectedIndex="{x:Bind ViewModel.AppSettings.TranslationSettings.SelectedTargetLanguageIndex, Mode=TwoWay}">
<ComboBoxItem Content="العربية" Tag="ar" />
<ComboBoxItem Content="Azərbaycan dili" Tag="az" />
<ComboBoxItem Content="简体中文" Tag="zh-Hans" />
<ComboBoxItem Content="繁體中文" Tag="zh-Hant" />
<ComboBoxItem Content="Čeština" Tag="cs" />
<ComboBoxItem Content="Dansk" Tag="da" />
<ComboBoxItem Content="Nederlands" Tag="nl" />
<ComboBoxItem Content="English" Tag="en" />
<ComboBoxItem Content="Esperanto" Tag="eo" />
<ComboBoxItem Content="Suomi" Tag="fi" />
<ComboBoxItem Content="Français" Tag="fr" />
<ComboBoxItem Content="Deutsch" Tag="de" />
<ComboBoxItem Content="Ελληνικά" Tag="el" />
<ComboBoxItem Content="עברית" Tag="he" />
<ComboBoxItem Content="हिन्दी" Tag="hi" />
<ComboBoxItem Content="Magyar" Tag="hu" />
<ComboBoxItem Content="Bahasa Indonesia" Tag="id" />
<ComboBoxItem Content="Gaeilge" Tag="ga" />
<ComboBoxItem Content="Italiano" Tag="it" />
<ComboBoxItem Content="日本語" Tag="ja" />
<ComboBoxItem Content="한국어" Tag="ko" />
<ComboBoxItem Content="فارسی" Tag="fa" />
<ComboBoxItem Content="Polski" Tag="pl" />
<ComboBoxItem Content="Português" Tag="pt" />
<ComboBoxItem Content="Русский" Tag="ru" />
<ComboBoxItem Content="Slovenčina" Tag="sk" />
<ComboBoxItem Content="Español" Tag="es" />
<ComboBoxItem Content="Svenska" Tag="sv" />
<ComboBoxItem Content="Türkçe" Tag="tr" />
<ComboBoxItem Content="Українська" Tag="uk" />
<ComboBoxItem Content="Tiếng Việt" Tag="vi" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageTranslationConfig">
<controls:SettingsCard.Description>
<HyperlinkButton Margin="0,6,0,0" NavigateUri="https://github.com/LibreTranslate/LibreTranslate">
<TextBlock
x:Uid="SettingsPageTranslationInfoLink"
FontSize="14"
TextWrapping="Wrap" />
</HyperlinkButton>
</controls:SettingsCard.Description>
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLibreTranslateServer" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBox
x:Name="LibreTranslateServerTextBox"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
PlaceholderText="http://localhost:5000"
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.LibreTranslateServer, Mode=TwoWay}" />
<Button
x:Uid="SettingsPageServerTestButton"
Command="{x:Bind ViewModel.LibreTranslateServerTestCommand}"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="LyricsPageTranslationOnly" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsTranslationEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.ShowTranslationOnly, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="LyricsPageLyricsProviderPrefix">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.LyricsSearchProvider, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="LyricsPageTranslationProviderPrefix">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
</controls:SettingsCard>
<!-- LX music server -->
<TextBlock x:Uid="SettingsPageLXMusicServer" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBox
IsEnabled="{x:Bind ViewModel.IsLXMusicServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
PlaceholderText="http://127.0.0.1:23330"
Text="{x:Bind ViewModel.AppSettings.GeneralSettings.LXMusicServer, Mode=TwoWay}" />
<Button
x:Uid="SettingsPageServerTestButton"
Command="{x:Bind ViewModel.LXMusicServerTestCommand}"
IsEnabled="{x:Bind ViewModel.IsLXMusicServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</StackPanel>
</controls:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,45 @@
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class PlaybackSettingsControl : UserControl
{
public PlaybackSettingsControlViewModel ViewModel => (PlaybackSettingsControlViewModel)DataContext;
public PlaybackSettingsControl()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<PlaybackSettingsControlViewModel>();
}
private void AlbumArtSearchProvidersListView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
// <20><> AlbumArtSearchProvidersInfo <20><><EFBFBD><EFBFBD> CollectionChanged <20>¼<EFBFBD>
ViewModel.SelectedMediaSourceProvider?.AlbumArtSearchProvidersInfo?.Refresh();
}
private void LyricsSearchProvidersListView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
// <20><> LyricsSearchProvidersInfo <20><><EFBFBD><EFBFBD> CollectionChanged <20>¼<EFBFBD>
ViewModel.SelectedMediaSourceProvider?.LyricsSearchProvidersInfo?.Refresh();
}
}
}

View File

@@ -15,9 +15,9 @@ namespace BetterLyrics.WinUI3.Converter
return provider switch
{
LyricsSearchProvider.LrcLib => "LrcLib",
LyricsSearchProvider.QQ => "QQ",
LyricsSearchProvider.Netease => "Netease",
LyricsSearchProvider.Kugou => "Kugou",
LyricsSearchProvider.QQ => "QQ 音乐",
LyricsSearchProvider.Netease => "网易云音乐",
LyricsSearchProvider.Kugou => "酷狗音乐",
LyricsSearchProvider.AmllTtmlDb => "amll-ttml-db",
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),

View File

@@ -0,0 +1,47 @@
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class MediaSourceProviderToDisplayedNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string provider)
{
return provider switch
{
PlayerID.Spotify => PlayerName.Spotify,
PlayerID.AppleMusic => PlayerName.AppleMusic,
PlayerID.iTunes => PlayerName.iTunes,
PlayerID.KugouMusic => PlayerName.KugouMusic,
PlayerID.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
PlayerID.QQMusic => PlayerName.QQMusic,
PlayerID.LXMusic => PlayerName.LXMusic,
PlayerID.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
PlayerID.AIMP => PlayerName.AIMP,
PlayerID.Foobar2000 => PlayerName.Foobar2000,
PlayerID.MusicBee => PlayerName.MusicBee,
PlayerID.PotPlayer => PlayerName.PotPlayer,
PlayerID.Chrome => PlayerName.Chrome,
PlayerID.Edge => PlayerName.Edge,
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
_ => provider,
};
}
return value?.ToString() ?? "";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,47 @@
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class MediaSourceProviderToLogoUriConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string provider)
{
return provider switch
{
PlayerID.Spotify => PathHelper.SpotifyLogoPath,
PlayerID.AppleMusic => PathHelper.AppleMusicLogoPath,
PlayerID.iTunes => PathHelper.iTunesLogoPath,
PlayerID.KugouMusic => PathHelper.KugouMusicLogoPath,
PlayerID.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
PlayerID.QQMusic => PathHelper.QQMusicLogoPath,
PlayerID.LXMusic => PathHelper.LXMusicLogoPath,
PlayerID.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
PlayerID.AIMP => PathHelper.AIMPLogoPath,
PlayerID.Foobar2000 => PathHelper.Foobar2000LogoPath,
PlayerID.MusicBee => PathHelper.MusicBeeLogoPath,
PlayerID.PotPlayer => PathHelper.PotPlayerLogoPath,
PlayerID.Chrome => PathHelper.ChromeLogoPath,
PlayerID.Edge => PathHelper.EdgeLogoPath,
PlayerID.BetterLyrics => PathHelper.LogoPath,
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
_ => PathHelper.UnknownPlayerLogoPath,
};
}
return PathHelper.UnknownPlayerLogoPath;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -15,9 +15,9 @@ namespace BetterLyrics.WinUI3.Converter
return provider switch
{
TranslationSearchProvider.LrcLib => "LrcLib",
TranslationSearchProvider.QQ => "QQ",
TranslationSearchProvider.Netease => "Netease",
TranslationSearchProvider.Kugou => "Kugou",
TranslationSearchProvider.QQ => "QQ 音乐",
TranslationSearchProvider.Netease => "网易云音乐",
TranslationSearchProvider.Kugou => "酷狗音乐",
TranslationSearchProvider.AmllTtmlDb => "amll-ttml-db",
TranslationSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
TranslationSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum SettingsStoreType
{
Container,
JSON
}
}

View File

@@ -8,8 +8,9 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Events
{
public class AlbumArtChangedEventArgs(SoftwareBitmap? albumArtSwBitmap, Color? albumArtLightAccentColor, Color? albumArtDarkAccentColor) : EventArgs
public class AlbumArtChangedEventArgs(byte[]? bytes, SoftwareBitmap? albumArtSwBitmap, Color? albumArtLightAccentColor, Color? albumArtDarkAccentColor) : EventArgs
{
public byte[]? Bytes { get; set; } = bytes;
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap;
public Color? AlbumArtLightAccentColor { get; set; } = albumArtLightAccentColor;
public Color? AlbumArtDarkAccentColor { get; set; } = albumArtDarkAccentColor;

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Events
{
public class LastFMIsAuthenticatedChangedEventArgs : EventArgs
{
public bool IsAuthenticated { get; set; }
public LastFMIsAuthenticatedChangedEventArgs(bool isAuthenticated)
{
IsAuthenticated = isAuthenticated;
}
}
}

View File

@@ -0,0 +1,18 @@
using Hqub.Lastfm.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Events
{
public class LastFMUserChangedEventArgs : EventArgs
{
public User? User { get; set; }
public LastFMUserChangedEventArgs(User? user)
{
User = user;
}
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Extensions
{
// https://stackoverflow.com/a/32013610/11048731
public class FullyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection() : base()
{ }
public FullyObservableCollection(List<T> list) : base(list)
{
ObserveAll();
}
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
{
ObserveAll();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action == NotifyCollectionChangedAction.Add ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
base.OnCollectionChanged(e);
}
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
{
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
}
protected override void ClearItems()
{
foreach (T item in Items)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
{
foreach (T item in Items)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
T typedSender = (T)sender;
int i = Items.IndexOf(typedSender);
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
OnItemPropertyChanged(i, e);
}
public void Refresh()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
/// <summary>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
{
/// <summary>
/// Gets the index in the collection for which the property change has occurred.
/// </summary>
/// <value>
/// Index in parent collection.
/// </value>
public int CollectionIndex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index in the collection of changed item.</param>
/// <param name="name">The name of the property that changed.</param>
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
{
CollectionIndex = index;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
{ }
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Windows.UI;
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),
};
}
}
}

View File

@@ -52,7 +52,7 @@ namespace BetterLyrics.WinUI3.Helper
return Color.FromArgb(255, fg.R, fg.G, fg.B);
}
public static Color GetInterpolatedColor(float progress, Color startColor, Color targetColor)
public static Color GetInterpolatedColor(double progress, Color startColor, Color targetColor)
{
byte Lerp(byte a, byte b) => (byte)(a + (progress * (b - a)));
return Color.FromArgb(

View File

@@ -1,5 +1,5 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
@@ -67,21 +67,8 @@ namespace BetterLyrics.WinUI3.Helper
);
}
// <20>Ӵ洢<D3B4><E6B4A2><EFBFBD><EFBFBD>ȡĿ<C8A1><C4BF><EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD>λ<EFBFBD><CEBB>
int targetWidth = _settingsService.DesktopWindowWidth;
int targetHeight = _settingsService.DesktopWindowHeight;
int targetX = _settingsService.DesktopWindowLeft;
int targetY = _settingsService.DesktopWindowTop;
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
window.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(
targetX,
targetY,
targetWidth,
targetHeight
)
);
window.AppWindow.MoveAndResize(_settingsService.AppSettings.DesktopModeSettings.WindowBounds.ToRectInt32());
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
if (!_originalTopmostStates.ContainsKey(hwnd))

View File

@@ -1,5 +1,5 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
@@ -78,7 +78,7 @@ namespace BetterLyrics.WinUI3.Helper
RegisterAppBar(hwnd, monitorDeviceName, appBarHeight, dockPlacement);
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(_settingsService.DockMonitorDeviceName);
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(_settingsService.AppSettings.DockModeSettings.DockMonitorDeviceName);
int screenWidth = monitorInfo.rcMonitor.Width;
int screenHeight = monitorInfo.rcMonitor.Bottom - monitorInfo.rcMonitor.Top;

View File

@@ -10,67 +10,67 @@ namespace BetterLyrics.WinUI3.Helper
{
public class EasingHelper
{
public static float EaseInOutSine(float t)
public static double EaseInOutSine(double t)
{
return -(MathF.Cos(MathF.PI * t) - 1f) / 2f;
return -(Math.Cos(Math.PI * t) - 1f) / 2f;
}
public static float EaseInOutQuad(float t)
public static double EaseInOutQuad(double t)
{
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
public static float EaseInOutCubic(float t)
public static double EaseInOutCubic(double t)
{
return t < 0.5f ? 4 * t * t * t : 1 - MathF.Pow(-2 * t + 2, 3) / 2;
return t < 0.5f ? 4 * t * t * t : 1 - Math.Pow(-2 * t + 2, 3) / 2;
}
public static float EaseInOutQuart(float t)
public static double EaseInOutQuart(double t)
{
return t < 0.5f ? 8 * t * t * t * t : 1 - MathF.Pow(-2 * t + 2, 4) / 2;
return t < 0.5f ? 8 * t * t * t * t : 1 - Math.Pow(-2 * t + 2, 4) / 2;
}
public static float EaseInOutQuint(float t)
public static double EaseInOutQuint(double t)
{
return t < 0.5f ? 16 * t * t * t * t * t : 1 - MathF.Pow(-2 * t + 2, 5) / 2;
return t < 0.5f ? 16 * t * t * t * t * t : 1 - Math.Pow(-2 * t + 2, 5) / 2;
}
public static float EaseInOutExpo(float t)
public static double EaseInOutExpo(double t)
{
return t == 0
? 0
: t == 1
? 1
: t < 0.5 ? MathF.Pow(2, 20 * t - 10) / 2
: (2 - MathF.Pow(2, -20 * t + 10)) / 2;
: t < 0.5 ? Math.Pow(2, 20 * t - 10) / 2
: (2 - Math.Pow(2, -20 * t + 10)) / 2;
}
public static float EaseInOutCirc(float t)
public static double EaseInOutCirc(double t)
{
return t < 0.5f
? (1 - MathF.Sqrt(1 - MathF.Pow(2 * t, 2))) / 2
: (MathF.Sqrt(1 - MathF.Pow(-2 * t + 2, 2)) + 1) / 2;
? (1 - Math.Sqrt(1 - Math.Pow(2 * t, 2))) / 2
: (Math.Sqrt(1 - Math.Pow(-2 * t + 2, 2)) + 1) / 2;
}
public static float EaseInOutBack(float t)
public static double EaseInOutBack(double t)
{
float c1 = 1.70158f;
float c2 = c1 * 1.525f;
double c1 = 1.70158f;
double c2 = c1 * 1.525f;
return t < 0.5
? (MathF.Pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
: (MathF.Pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
? (Math.Pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
: (Math.Pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
}
public static float EaseInOutElastic(float t)
public static double EaseInOutElastic(double t)
{
if (t == 0 || t == 1) return t;
float p = 0.3f;
float s = p / 4;
double p = 0.3f;
double s = p / 4;
return t < 0.5f
? -(MathF.Pow(2, 20 * t - 10) * MathF.Sin((20 * t - 11.125f) * (2 * MathF.PI) / p)) / 2
: (MathF.Pow(2, -20 * t + 10) * MathF.Sin((20 * t - 11.125f) * (2 * MathF.PI) / p)) / 2 + 1;
? -(Math.Pow(2, 20 * t - 10) * Math.Sin((20 * t - 11.125f) * (2 * Math.PI) / p)) / 2
: (Math.Pow(2, -20 * t + 10) * Math.Sin((20 * t - 11.125f) * (2 * Math.PI) / p)) / 2 + 1;
}
private static float EaseOutBounce(float t)
private static double EaseOutBounce(double t)
{
if (t < 4 / 11f)
{
@@ -90,7 +90,7 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public static float EaseInOutBounce(float t)
public static double EaseInOutBounce(double t)
{
if (t < 0.5f)
{
@@ -102,11 +102,17 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public static float SmoothStep(float t)
public static double SmoothStep(double t)
{
return t * t * (3f - 2f * t);
}
public static float Linear(float t) => t;
public static double CubicBezier(double t, double p0, double p1, double p2, double p3)
{
double u = 1 - t;
return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
}
public static double Linear(double t) => t;
}
}

View File

@@ -69,7 +69,7 @@ namespace BetterLyrics.WinUI3.Helper
public static bool IsSwitchableNormalizedMatch(string fileName, string q1, string q2)
{
var normFileName = StringHelper.Normalize(fileName.Normalize());
var normFileName = StringHelper.Normalize(fileName);
var normQ1 = StringHelper.Normalize(q1);
var normQ2 = StringHelper.Normalize(q2);

View File

@@ -1,4 +1,4 @@
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Graphics.Canvas.Text;
using System;
@@ -15,6 +15,6 @@ namespace BetterLyrics.WinUI3.Helper
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies();
public static string GetUserPreferredFontFamily() => SystemFontFamilies.ElementAtOrDefault(_settingsService.SelectedFontFamilyIndex) ?? "Segoe UI";
public static string GetUserPreferredFontFamily() => SystemFontFamilies.ElementAtOrDefault(_settingsService.AppSettings.StandardLyricsStyleSettings.SelectedFontFamilyIndex) ?? "Segoe UI";
}
}

View File

@@ -6,6 +6,7 @@ using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media.Imaging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@@ -137,7 +138,7 @@ namespace BetterLyrics.WinUI3.Helper
return buffer;
}
public static float GetAverageLuminance(CanvasBitmap bitmap)
public static double GetAverageLuminance(CanvasBitmap bitmap)
{
var pixels = bitmap.GetPixelBytes();
double sum = 0;
@@ -151,7 +152,7 @@ namespace BetterLyrics.WinUI3.Helper
double y = 0.299 * r + 0.587 * g + 0.114 * b;
sum += y / 255.0;
}
return (float)(sum / (pixels.Length / 4));
return (double)(sum / (pixels.Length / 4));
}
public static byte[] MakeSquareWithThemeColor(byte[] imageBytes)
@@ -168,37 +169,55 @@ namespace BetterLyrics.WinUI3.Helper
var themeColor = Rgba32.ParseHex(GetAccentColorsFromByte(imageBytes, 1).FirstOrDefault().ToHex());
// 新建正方形画布
using var square = new Image<Rgba32>(size, size, themeColor);
// 计算居中位置
int offsetX = (size - image.Width) / 2;
int offsetY = (size - image.Height) / 2;
// 绘制原图到正方形画布
square.Mutate(ctx => ctx.DrawImage(image, new Point(offsetX, offsetY), 1f));
// 保存为 PNG 字节流
using var ms = new MemoryStream();
square.Save(ms, new PngEncoder());
square.Save(ms, new JpegEncoder());
return ms.ToArray();
}
public static byte[] Resize(byte[] imageBytes, int size)
{
using Image image = Image.Load(imageBytes);
var factor = Math.Max(size / image.Width, size / image.Height);
if (factor <= 1)
using (Image image = Image.Load(imageBytes))
{
return imageBytes;
}
int width = image.Width * factor;
int height = image.Height * factor;
image.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
var factor = Math.Max((double)size / image.Width, (double)size / image.Height);
using var ms = new MemoryStream();
image.Save(ms, new PngEncoder());
return ms.ToArray();
int width = (int)(image.Width * factor);
int height = (int)(image.Height * factor);
if (factor > 1)
{
image.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
}
else
{
image.Mutate(x => x.Resize(width, height, KnownResamplers.NearestNeighbor));
}
using var ms = new MemoryStream();
image.Save(ms, new JpegEncoder());
return ms.ToArray();
}
}
public static byte[] GenerateNoiseBGRA(int width, int height)
{
var random = new Random();
var pixelData = new byte[width * height * 4];
for (int i = 0; i < width * height; i++)
{
byte gray = (byte)random.Next(0, 256);
pixelData[i * 4 + 0] = gray; // B
pixelData[i * 4 + 1] = gray; // G
pixelData[i * 4 + 2] = gray; // R
pixelData[i * 4 + 3] = 255; // A
}
return pixelData;
}
}
}

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Helpers.General;
using NTextCat;
@@ -100,7 +101,7 @@ namespace BetterLyrics.WinUI3.Services
public static string GetUserTargetLanguageCode()
{
return SupportedTargetLanguages[_settingsService.SelectedTargetLanguageIndex].Code;
return SupportedTargetLanguages[_settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex].Code;
}
public static int GetDefaultTargetLanguageIndex()

View File

@@ -14,9 +14,6 @@ namespace BetterLyrics.WinUI3.Helper
public static class MetadataHelper
{
public const string AppAuthor = "Zhe Fang";
public const string AppDisplayName = "Better Lyrics";
public const string AppName = "BetterLyrics";
public static string AppVersion
{
get
@@ -25,24 +22,5 @@ namespace BetterLyrics.WinUI3.Helper
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
}
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
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 static async Task<DateTime> GetBuildDate()
{
var assembly = Assembly.GetExecutingAssembly();
var filePath = assembly.Location;
if (!File.Exists(filePath))
return DateTime.MinValue;
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
// 获取文件基本属性
BasicProperties props = await file.GetBasicPropertiesAsync();
// 返回修改日期
return props.DateModified.DateTime;
}
}
}

View File

@@ -1,331 +0,0 @@
using System;
using System.Buffers;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.Storage;
using static BetterLyrics.WinUI3.Helper.NoiseOverlayHelper.BitmapFileCreator;
namespace BetterLyrics.WinUI3.Helper
{
internal static class NoiseOverlayHelper
{
const string NoiseOverlayFileName = "noise_overlay.bmp";
static readonly string NoiseOverlayFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "Assets", NoiseOverlayFileName);
/// <summary>
/// 生成 BGRA 格式的灰阶噪声像素数据
/// </summary>
public static byte[] GenerateNoiseBitmapBGRA(int width, int height)
{
var random = new Random();
var pixelData = new byte[width * height * 4];
for (int i = 0; i < width * height; i++)
{
byte gray = (byte)random.Next(0, 256);
pixelData[i * 4 + 0] = gray; // B
pixelData[i * 4 + 1] = gray; // G
pixelData[i * 4 + 2] = gray; // R
pixelData[i * 4 + 3] = 255; // A
}
return pixelData;
}
/// <summary>
/// 生成单色灰阶随机噪声
/// </summary>
/// <param name="outputPath">输出文件路径</param>
/// <param name="width">图片宽度</param>
/// <param name="height">图片高度</param>
public static BitmapFile GenerateNoiseBitmap(int width, int height)
{
const uint NumOfGrayscale = 16;
uint bitCount = NextPowerOfTwo((uint)Math.Round(Math.Sqrt(NumOfGrayscale)));
var palette = BitmapFileCreator.CreateGrayscalePalette(16);
var pixelData = GenerateRandomNoise(width, height, bitCount);
var fileHeader = BitmapFileCreator.CreateFileHeader(palette, pixelData);
var infoHeader = BitmapFileCreator.CreateInfoHeader(width, height, bitCount);
return new BitmapFile(fileHeader, infoHeader, palette, pixelData);
}
/// <summary>
/// 读取噪声图片的位头信息
/// </summary>
public static BitmapFileCreator.WINBMPINFOHEADER ReadBitmapInfoHeaders(string? FilePath)
{
var _filePath = FilePath ?? NoiseOverlayFilePath;
using var fs = new FileStream(_filePath, FileMode.Open, FileAccess.Read);
using var br = new BinaryReader(fs);
// 跳过文件头
fs.Seek(Marshal.SizeOf<BitmapFileCreator.BITMAPFILEHEADER>(), SeekOrigin.Begin);
// 读取信息头
byte[] infoHeaderBytes = br.ReadBytes(Marshal.SizeOf<BitmapFileCreator.WINBMPINFOHEADER>());
return MemoryMarshal.Read<BitmapFileCreator.WINBMPINFOHEADER>(infoHeaderBytes);
}
public static BitmapFileCreator.WINBMPINFOHEADER ReadBitmapInfoHeaders(byte[] FileBytes)
{
// 跳过文件头
var offset = Marshal.SizeOf<BitmapFileCreator.BITMAPFILEHEADER>();
// 读取信息头
var infoHeaderLength = Marshal.SizeOf<BitmapFileCreator.WINBMPINFOHEADER>();
Span<byte> infoHeaderBytes = new(FileBytes, offset, infoHeaderLength);
return MemoryMarshal.Read<BitmapFileCreator.WINBMPINFOHEADER>(infoHeaderBytes);
}
/// <summary>
/// safe 的写入 struct 到流
/// </summary>
/// <typeparam name="T">值 strcut</typeparam>
/// <param name="stream">要写入的字节流</param>
/// <param name="structure">要被写入的结构</param>
public static void WriteStruct<T>(Stream stream, in T structure) where T : struct
{
int size = Unsafe.SizeOf<T>();
byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
try
{
MemoryMarshal.Write(buffer, in structure);
stream.Write(buffer, 0, size);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
/// <summary>
/// 随机填充位图内容
/// </summary>
/// <param name="width">填充宽度</param>
/// <param name="height">填充高度</param>
/// <param name="bitCount">单个调色盘索引所占比特位数</param>
/// <returns>字节数据</returns>
private static byte[] GenerateRandomNoise(int width, int height, uint bitCount)
{
// 创建位图行字节数4K 对齐
int rowSize = ((width * (int)bitCount + 31) >> 5) << 2;
// 创建随机位图数据
Random rnd = new();
return Enumerable.Range(0, rowSize * height)
.Select(i => (byte)rnd.Next(0x00, 0xFF))
.ToArray();
}
private static uint NextPowerOfTwo(uint value)
{
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return value + 1;
}
public static class BitmapFileCreator
{
/// <summary>
/// 创建BMP文件头
/// </summary>
/// <param name="palette">调色盘数据</param>
/// <param name="pixelData">像素数据</param>
/// <returns>文件头结构</returns>
public static BITMAPFILEHEADER CreateFileHeader(byte[] palette, byte[] pixelData)
{
return new BITMAPFILEHEADER
{
bfType = 0x4D42,
bfSize = (uint)(Marshal.SizeOf<BITMAPFILEHEADER>() +
Marshal.SizeOf<WINBMPINFOHEADER>() +
palette.Length +
pixelData.Length),
bfReserved1 = 0,
bfReserved2 = 0,
bfOffBits = (uint)(Marshal.SizeOf<BITMAPFILEHEADER>() +
Marshal.SizeOf<WINBMPINFOHEADER>() +
palette.Length)
};
}
/// <summary>
/// 将指定值填充到为最接近的2的幂数
/// </summary>
/// <summary>
/// 生成灰阶调色盘
/// </summary>
/// <param name="colors">灰阶数量</param>
/// <returns>调色盘byte数组</returns>
public static byte[] CreateGrayscalePalette(int colors)
{
return Enumerable.Range(0, colors)
.SelectMany(i => Enumerable.Repeat((byte)(i * 0x10), 4))
.ToArray();
}
/// <summary>
/// 创建BMP信息头
/// </summary>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="bitCount">单个像素(调色盘索引)位数</param>
/// <returns>BMP信息头结构</returns>
public static WINBMPINFOHEADER CreateInfoHeader(int width, int height, uint bitCount)
{
return new WINBMPINFOHEADER
{
biSize = (uint)Marshal.SizeOf<WINBMPINFOHEADER>(),
biWidth = (uint)width,
biHeight = (uint)height,
biPlanes = 1,
biBitCount = (ushort)bitCount,
biCompression = 0,
biSizeImage = 0,
biXPelsPerMeter = 0,
biYPelsPerMeter = 0,
biClrUsed = 0,
biClrImportant = 0
};
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
/// <summary>
/// BMP 位图文件头
/// </summary>
public struct BITMAPFILEHEADER
{
/// <summary>
/// 文件类型标识,用于指定文件格式
/// </summary>
public ushort bfType;
/// <summary>
/// 文件大小,以字节为单位
/// </summary>
public uint bfSize;
/// <summary>
/// 保留字段,未使用
/// </summary>
public ushort bfReserved1;
/// <summary>
/// 保留字段,未使用
/// </summary>
public ushort bfReserved2;
/// <summary>
/// 像素数据的起始位置,以字节为单位,从文件头开始计算
/// </summary>
public uint bfOffBits;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
/// <summary>
/// BMP 位图信息头
/// </summary>
public struct WINBMPINFOHEADER
{
/// <summary>
/// 指定此结构体的字节大小。
/// </summary>
public uint biSize;
/// <summary>
/// 以像素为单位的位图宽度。
/// </summary>
public uint biWidth;
/// <summary>
/// 以像素为单位的位图高度。
/// </summary>
public uint biHeight;
/// <summary>
/// 目标设备的平面数通常为1。
/// </summary>
public ushort biPlanes;
/// <summary>
/// 每个像素的位数表示颜色深度如1、4、8、16、24、32等。
/// </summary>
public ushort biBitCount;
/// <summary>
/// 压缩类型0表示不压缩。
/// </summary>
public uint biCompression;
/// <summary>
/// 位图图像数据的大小以字节为单位若图像未压缩该值可设为0。
/// </summary>
public uint biSizeImage;
/// <summary>
/// 每米X轴方向的像素数水平分辨率通常设为0。
/// </summary>
public uint biXPelsPerMeter;
/// <summary>
/// 每米Y轴方向的像素数垂直分辨率通常设为0。
/// </summary>
public uint biYPelsPerMeter;
/// <summary>
/// 实际使用的颜色索引数若为0则使用位图中实际出现的颜色数。
/// </summary>
public uint biClrUsed;
/// <summary>
/// 重要颜色索引数0表示所有颜色都重要。
/// </summary>
public uint biClrImportant;
}
}
public class BitmapFile(BitmapFileCreator.BITMAPFILEHEADER fileHeader, BitmapFileCreator.WINBMPINFOHEADER infoHeader, byte[] palette, byte[] pixelData)
{
public BITMAPFILEHEADER FileHeader = fileHeader;
public WINBMPINFOHEADER InfoHeader = infoHeader;
public byte[] Palette = palette;
public byte[] PixelData = pixelData;
/// <summary>
/// 转换为byte[]
/// </summary>
/// <param name="bf"></param>
public static explicit operator byte[](BitmapFile bf)
{
var result = new byte[bf.FileHeader.bfSize];
var ms = new MemoryStream(result);
bf.WriteToStream(ms);
return result;
}
public byte[] ToByteArray() => (byte[])this;
public Task<byte[]> ToByteArrayAsync() { return Task.FromResult(ToByteArray());}
/// <summary>
/// 写入此结构到流中
/// </summary>
public void WriteToStream(Stream stream)
{
NoiseOverlayHelper.WriteStruct(stream, FileHeader);
NoiseOverlayHelper.WriteStruct(stream, InfoHeader);
stream.Write(Palette);
stream.Write(PixelData);
stream.Flush();
}
}
}
}

View File

@@ -32,6 +32,10 @@ namespace BetterLyrics.WinUI3.Helper
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
// Windows.Graphics.Imaging.SoftwareBitmap
isDisposedField = objType.GetField("_objRef_global__System_IDisposable", BindingFlags.NonPublic | BindingFlags.Instance);
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
// System.IO.FileStream
var strategyField = objType.GetField("_strategy", BindingFlags.NonPublic | BindingFlags.Instance);
if (strategyField != null)

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Security.Credentials;
namespace BetterLyrics.WinUI3.Helper
{
public static class PasswordVaultHelper
{
/// <summary>
/// 保存敏感数据到 PasswordVault
/// </summary>
/// <param name="resource">资源标识,比如 "MyApp"</param>
/// <param name="key">键名,比如 "SessionKey"</param>
/// <param name="value">要保存的值</param>
public static void Save(string resource, string key, string value)
{
var vault = new PasswordVault();
// 删除旧值(避免重复存储)
try
{
var oldCredential = vault.Retrieve(resource, key);
if (oldCredential != null)
{
vault.Remove(oldCredential);
}
}
catch
{
// 没有旧值就忽略
}
vault.Add(new PasswordCredential(resource, key, value));
}
/// <summary>
/// 从 PasswordVault 获取敏感数据
/// </summary>
/// <param name="resource">资源标识</param>
/// <param name="key">键名</param>
/// <returns>存储的值,若不存在则返回 null</returns>
public static string? Get(string resource, string key)
{
var vault = new PasswordVault();
try
{
var credential = vault.Retrieve(resource, key);
credential.RetrievePassword();
return credential.Password;
}
catch
{
return null;
}
}
/// <summary>
/// 删除指定的敏感数据
/// </summary>
public static void Delete(string resource, string key)
{
var vault = new PasswordVault();
try
{
var credential = vault.Retrieve(resource, key);
vault.Remove(credential);
}
catch
{
// 不存在就忽略
}
}
}
}

View File

@@ -11,13 +11,30 @@ namespace BetterLyrics.WinUI3.Helper
{
public class PathHelper
{
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
public static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
//public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Core14.profile.xml");
public static string SettingsDirectory => Path.Combine(LocalFolder, "settings");
public static string SettingsFilePath => Path.Combine(SettingsDirectory, "settings.json");
public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Wiki82.profile.xml");
public static string LogoPath => Path.Combine(AssetsFolder, "Logo.ico");
public static string AIMPLogoPath => Path.Combine(AssetsFolder, "AIMP.png");
public static string Foobar2000LogoPath => Path.Combine(AssetsFolder, "foobar2000.png");
public static string MusicBeeLogoPath => Path.Combine(AssetsFolder, "MusicBee.png");
public static string SpotifyLogoPath => Path.Combine(AssetsFolder, "Spotify.png");
public static string AppleMusicLogoPath => Path.Combine(AssetsFolder, "AppleMusic.png");
public static string iTunesLogoPath => Path.Combine(AssetsFolder, "iTunes.png");
public static string KugouMusicLogoPath => Path.Combine(AssetsFolder, "KugouMusic.png");
public static string NetEaseCloudMusicLogoPath => Path.Combine(AssetsFolder, "NetEaseCloudMusic.png");
public static string QQMusicLogoPath => Path.Combine(AssetsFolder, "QQMusic.png");
public static string LXMusicLogoPath => Path.Combine(AssetsFolder, "LXMusic.png");
public static string MediaPlayerWindows11LogoPath => Path.Combine(AssetsFolder, "MediaPlayerWindows11.png");
public static string PotPlayerLogoPath => Path.Combine(AssetsFolder, "PotPlayer.png");
public static string ChromeLogoPath => Path.Combine(AssetsFolder, "Chrome.png");
public static string EdgeLogoPath => Path.Combine(AssetsFolder, "Edge.png");
public static string UnknownPlayerLogoPath => Path.Combine(AssetsFolder, "Question.png");
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
@@ -43,6 +60,8 @@ namespace BetterLyrics.WinUI3.Helper
public static void EnsureDirectories()
{
Directory.CreateDirectory(SettingsDirectory);
Directory.CreateDirectory(LogDirectory);
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Graphics;
namespace BetterLyrics.WinUI3.Helper
{
public static class RectHelper
{
public static RectInt32 ToRectInt32(this Windows.Foundation.Rect rect)
{
return new RectInt32(
(int)rect.X,
(int)rect.Y,
(int)rect.Width,
(int)rect.Height
);
}
}
}

View File

@@ -38,7 +38,7 @@ namespace BetterLyrics.WinUI3.Helper
{
if (_endpointVolume != null)
{
float level = _endpointVolume.GetMasterVolumeLevelScalar();
double level = _endpointVolume.GetMasterVolumeLevelScalar();
_masterVolume = (int)(level * 100);
}

View File

@@ -10,21 +10,22 @@ namespace BetterLyrics.WinUI3.Helper
where T : struct
{
private T _currentValue;
private float _durationSeconds;
private double _durationSeconds;
private EasingType? _easingType;
private Func<T, T, float, T> _interpolator;
private Func<T, T, double, T> _interpolator;
private bool _isTransitioning;
private float _progress;
private double _progress;
private T _startValue;
private T _targetValue;
public float DurationSeconds => _durationSeconds;
public double DurationSeconds => _durationSeconds;
public bool IsTransitioning => _isTransitioning;
public T Value => _currentValue;
public T TargetValue => _targetValue;
public EasingType? EasingType => _easingType;
public ValueTransition(T initialValue, float durationSeconds, Func<T, T, float, T>? interpolator = null, EasingType? easingType = null)
public ValueTransition(T initialValue, double durationSeconds, Func<T, T, double, T>? interpolator = null, EasingType? easingType = null)
{
_currentValue = initialValue;
_startValue = initialValue;
@@ -45,12 +46,12 @@ namespace BetterLyrics.WinUI3.Helper
}
else
{
_easingType = EasingType.Linear;
_easingType = Enums.EasingType.Linear;
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
}
}
public void SetDuration(float seconds)
public void SetDuration(double seconds)
{
if (seconds <= 0)
throw new ArgumentOutOfRangeException(nameof(seconds), "Duration must be positive.");
@@ -102,7 +103,7 @@ namespace BetterLyrics.WinUI3.Helper
{
if (!_isTransitioning) return;
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
_progress += (double)(elapsedTime / TimeSpan.FromSeconds(_durationSeconds));
if (_progress >= 1f)
{
_progress = 1f;
@@ -115,51 +116,51 @@ namespace BetterLyrics.WinUI3.Helper
}
}
private Func<T, T, float, T> GetInterpolatorByEasingType(EasingType type)
private Func<T, T, double, T> GetInterpolatorByEasingType(EasingType type)
{
if (typeof(T) == typeof(float))
if (typeof(T) == typeof(double))
{
return (start, end, progress) =>
{
float s = (float)(object)start;
float e = (float)(object)end;
float t = progress;
double s = (double)(object)start;
double e = (double)(object)end;
double t = progress;
switch (type)
{
case EasingType.EaseInOutSine:
case Enums.EasingType.EaseInOutSine:
t = EasingHelper.EaseInOutSine(t);
break;
case EasingType.EaseInOutQuad:
case Enums.EasingType.EaseInOutQuad:
t = EasingHelper.EaseInOutQuad(t);
break;
case EasingType.EaseInOutCubic:
case Enums.EasingType.EaseInOutCubic:
t = EasingHelper.EaseInOutCubic(t);
break;
case EasingType.EaseInOutQuart:
case Enums.EasingType.EaseInOutQuart:
t = EasingHelper.EaseInOutQuart(t);
break;
case EasingType.EaseInOutQuint:
case Enums.EasingType.EaseInOutQuint:
t = EasingHelper.EaseInOutQuint(t);
break;
case EasingType.EaseInOutExpo:
case Enums.EasingType.EaseInOutExpo:
t = EasingHelper.EaseInOutExpo(t);
break;
case EasingType.EaseInOutCirc:
case Enums.EasingType.EaseInOutCirc:
t = EasingHelper.EaseInOutCirc(t);
break;
case EasingType.EaseInOutBack:
case Enums.EasingType.EaseInOutBack:
t = EasingHelper.EaseInOutBack(t);
break;
case EasingType.EaseInOutElastic:
case Enums.EasingType.EaseInOutElastic:
t = EasingHelper.EaseInOutElastic(t);
break;
case EasingType.EaseInOutBounce:
case Enums.EasingType.EaseInOutBounce:
t = EasingHelper.EaseInOutBounce(t);
break;
case EasingType.SmoothStep:
case Enums.EasingType.SmoothStep:
t = EasingHelper.SmoothStep(t);
break;
case EasingType.Linear:
case Enums.EasingType.Linear:
t = EasingHelper.Linear(t);
break;
default:

View File

@@ -86,6 +86,16 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public static void ExitApp()
{
LyricsWindow? lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow != null)
{
DockModeHelper.Disable(lyricsWindow);
}
Environment.Exit(0);
}
private static void TrackWindow(object window)
{
if (!_activeWindows.Contains(window))

View File

@@ -5,13 +5,11 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
public partial class AlbumArtSearchProviderInfo : ObservableObject
public partial class AlbumArtSearchProviderInfo : ObservableRecipient
{
[ObservableProperty]
public partial bool IsEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; }
[ObservableProperty]
public partial AlbumArtSearchProvider Provider { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AlbumArtSearchProvider Provider { get; set; }
public AlbumArtSearchProviderInfo() { }

View File

@@ -4,13 +4,10 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
public partial class LocalMediaFolder : ObservableObject
public partial class LocalMediaFolder : ObservableRecipient
{
[ObservableProperty]
public partial bool IsEnabled { get; set; }
[ObservableProperty]
public partial string Path { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string Path { get; set; }
public LocalMediaFolder() { }

View File

@@ -26,7 +26,7 @@ namespace BetterLyrics.WinUI3.Models
LyricsLines = lyricsLines;
}
public void SetDisplayedTextAlongWith(LyricsData translationData, int toleranceMs = 0)
public void SetDisplayedTextAlongWith(LyricsData translationData, string separator, int toleranceMs = 0)
{
foreach (var line in LyricsLines)
{
@@ -47,11 +47,11 @@ namespace BetterLyrics.WinUI3.Models
{
tmp = ChineseConverter.ConvertToSimplifiedChinese(transLine.OriginalText);
}
line.DisplayedText = $"{line.OriginalText}\n{tmp}";
line.DisplayedText = $"{line.OriginalText}{separator}{tmp}";
}
else
{
line.DisplayedText = $"{line.OriginalText}\n{transLine.OriginalText}";
line.DisplayedText = $"{line.OriginalText}{separator}{transLine.OriginalText}";
}
}
else
@@ -62,7 +62,7 @@ namespace BetterLyrics.WinUI3.Models
}
}
public void SetDisplayedTextAlongWith(string translation)
public void SetDisplayedTextAlongWith(string translation, string separator)
{
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
int i = 0;
@@ -74,7 +74,7 @@ namespace BetterLyrics.WinUI3.Models
}
else
{
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}{translationArr[i]}";
line.DisplayedText = $"{line.OriginalText}{separator}{translationArr[i]}";
}
i++;
}

View File

@@ -1,24 +1,61 @@
// 2025/6/23 by Zhe Fang
using System.Collections.Generic;
using System.Numerics;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Shapes;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Windows.Foundation;
using Windows.Graphics.Effects;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models
{
public class LyricsLine
{
private const float _animationDuration = 0.3f;
public ValueTransition<float> AngleTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> BlurAmountTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> HighlightOpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> OpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> ScaleTransition { get; set; } = new(initialValue: 0.95f, durationSeconds: _animationDuration);
private const double _animationDuration = 0.3;
public ValueTransition<double> AngleTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<double> BlurAmountTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<double> HighlightOpacityTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<double> OpacityTransition { get; set; } = new(
initialValue: 0,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<double> ScaleTransition { get; set; } = new(
initialValue: 0.75,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<double> YOffsetTransition { get; set; } = new(
initialValue: 0,
durationSeconds: 0.5,
easingType: EasingType.EaseInOutSine
);
public CanvasTextLayout? CanvasTextLayout { get; set; }
public CanvasTextLayout? CanvasTextLayout { get; private set; }
public Vector2 CenterPosition { get; set; }
public Vector2 CenterPosition { get; private set; }
public Vector2 Position { get; set; }
public List<LyricsChar> LyricsChars { get; set; } = [];
@@ -29,5 +66,467 @@ namespace BetterLyrics.WinUI3.Models
public string DisplayedText { get; set; } = "";
public string OriginalText { get; set; } = "";
public CanvasGeometry? TextGeometry { get; private set; }
/// <summary>
/// 背景文字层(底字)
/// </summary>
public CanvasCommandList? BackgroundFontEffect { get; private set; }
/// <summary>
/// 背景层
/// </summary>
public OpacityEffect? BackgroundEffect { get; private set; }
/// <summary>
/// 辉光层
/// </summary>
public GaussianBlurEffect? ForegroundBlurEffect { get; private set; }
/// <summary>
/// 高亮层
/// </summary>
public AlphaMaskEffect? ForegroundHighlightEffect { get; private set; }
/// <summary>
/// 前景文字层
/// </summary>
public CanvasCommandList? ForegroundFontEffect { get; private set; }
public CanvasCommandList? ComposedLineEffect { get; private set; }
public CanvasCommandList? CurrentCharMask { get; private set; }
public CanvasCommandList? LineStartToCurrentCharMask { get; private set; }
public CanvasCommandList? CurrentLineMask { get; private set; }
public CanvasCommandList? PlaceholderEffect { get; private set; }
public void UpdateCenterPosition(double maxWidth, TextAlignmentType type)
{
if (CanvasTextLayout == null)
{
return;
}
double centerY = Position.Y + (double)CanvasTextLayout.LayoutBounds.Height;
CenterPosition = type switch
{
TextAlignmentType.Left => new Vector2(Position.X, (float)centerY),
TextAlignmentType.Center => new Vector2((float)(Position.X + maxWidth / 2.0), (float)centerY),
TextAlignmentType.Right => new Vector2((float)(Position.X + maxWidth), (float)centerY),
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
};
}
public void RecreateTextLayout(ICanvasAnimatedControl control, CanvasTextFormat textFormat, double maxWidth, double maxHeight, TextAlignmentType type)
{
CanvasTextLayout?.Dispose();
CanvasTextLayout = null;
CanvasTextLayout = new CanvasTextLayout(control, DisplayedText, textFormat, (float)maxWidth, (float)maxHeight);
CanvasTextLayout.HorizontalAlignment = type.ToCanvasHorizontalAlignment();
}
public void DisposeTextGeometry()
{
TextGeometry?.Dispose();
TextGeometry = null;
}
public void RecreateTextGeometry()
{
DisposeTextGeometry();
if (CanvasTextLayout == null)
{
return;
}
TextGeometry = CanvasGeometry.CreateText(CanvasTextLayout);
}
public void DisposeFontEffects()
{
BackgroundFontEffect?.Dispose();
BackgroundFontEffect = null;
ForegroundFontEffect?.Dispose();
ForegroundFontEffect = null;
}
public void RecreateFontEffect(ICanvasAnimatedControl control, Color strokeColor, int strokeWidth, Color bgFontColor, Color fgFontColor)
{
DisposeFontEffects();
if (TextGeometry == null)
{
return;
}
BackgroundFontEffect = new CanvasCommandList(control);
using var bgFontEffectDs = BackgroundFontEffect.CreateDrawingSession();
ForegroundFontEffect = new CanvasCommandList(control);
using var fgFontEffectDs = ForegroundFontEffect.CreateDrawingSession();
// 大于 0 才描边,避免不必要的资源浪费
if (strokeWidth > 0)
{
bgFontEffectDs.DrawGeometry(TextGeometry, Position, strokeColor, strokeWidth); // 描边
fgFontEffectDs.DrawGeometry(TextGeometry, Position, strokeColor, strokeWidth); // 描边
}
bgFontEffectDs.FillGeometry(TextGeometry, Position, bgFontColor); // 填充
fgFontEffectDs.FillGeometry(TextGeometry, Position, fgFontColor); // 填充
}
/// <summary>
/// 背景层
/// </summary>
/// <param name="lyricsLayerOpacity">_lyricsOpacityTransition.Value</param>
public void RecreateBackgroundEffect(double lyricsLayerOpacity)
{
BackgroundEffect?.Dispose();
BackgroundEffect = null;
if (BackgroundFontEffect == null)
{
return;
}
BackgroundEffect = new OpacityEffect
{
Source = new GaussianBlurEffect
{
Source = BackgroundFontEffect,
BlurAmount = (float)BlurAmountTransition.Value,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
},
Opacity = (float)(OpacityTransition.Value * lyricsLayerOpacity),
};
}
public void UpdateBackgroundEffect(double lyricsLayerOpacity)
{
BackgroundEffect?.Opacity = (float)(OpacityTransition.Value * lyricsLayerOpacity);
GaussianBlurEffect? blurEffect = (GaussianBlurEffect?)(BackgroundEffect?.Source);
blurEffect?.BlurAmount = (float)BlurAmountTransition.Value;
}
private IGraphicsEffectSource GetAlphaMask(ICanvasAnimatedControl control, LineRenderingType lineRenderingType)
{
if (PlaceholderEffect == null)
{
RecreatePlaceholder(control);
}
var result = lineRenderingType switch
{
LineRenderingType.CurrentChar => CurrentCharMask,
LineRenderingType.LineStartToCurrentChar => LineStartToCurrentCharMask,
// Here, cuz AlphaMask only takes care of alpha channel
// so ForegroundFontEffect can be a mask for CurrentLine
// And we don't need to create a new mask for CurrentLine
LineRenderingType.CurrentLine => CurrentLineMask,
_ => PlaceholderEffect
};
return result ?? PlaceholderEffect!;
}
/// <summary>
/// 销毁并重新创建辉光效果层
/// 仅需在布局重构 (Relayout) 时调用
/// </summary>
/// <param name="lineRenderingType">_lyricsGlowEffectScope</param>
/// <param name="glowEffectAmount">_lyricsGlowEffectAmount</param>
public void RecreateForegroundBlurEffect(ICanvasAnimatedControl control, LineRenderingType lineRenderingType, double glowEffectAmount)
{
ForegroundBlurEffect?.Dispose();
ForegroundBlurEffect = null;
if (ForegroundFontEffect == null)
{
return;
}
var mask = GetAlphaMask(control, lineRenderingType);
if (mask == null)
{
return;
}
ForegroundBlurEffect = new GaussianBlurEffect
{
Source = new AlphaMaskEffect
{
Source = ForegroundFontEffect,
AlphaMask = mask,
},
BlurAmount = (float)glowEffectAmount,
Optimization = EffectOptimization.Speed,
};
}
/// <summary>
/// 仅当前行需要调用此方法(每次 Update 都调用一次)
/// </summary>
/// <param name="control"></param>
/// <param name="lineRenderingType"></param>
/// <param name="glowEffectAmount"></param>
public void UpdateForegroundBlurEffect(ICanvasAnimatedControl control, LineRenderingType lineRenderingType, double glowEffectAmount)
{
if (ForegroundBlurEffect == null)
{
return;
}
if (ForegroundFontEffect == null)
{
return;
}
var mask = GetAlphaMask(control, lineRenderingType);
if (mask == null)
{
return;
}
ForegroundBlurEffect.BlurAmount = (float)glowEffectAmount;
var alphaMaskEffect = (AlphaMaskEffect)ForegroundBlurEffect.Source;
alphaMaskEffect.Source = ForegroundFontEffect;
alphaMaskEffect.AlphaMask = mask;
}
/// <summary>
/// 销毁并重新创建高亮效果层
/// 仅需在布局重构 (Relayout) 时调用
/// </summary>
/// <param name="control"></param>
/// <param name="lineRenderingType"></param>
public void RecreateForegroundHighlightEffect(ICanvasAnimatedControl control, LineRenderingType lineRenderingType)
{
ForegroundHighlightEffect?.Dispose();
ForegroundHighlightEffect = null;
if (ForegroundFontEffect == null)
{
return;
}
var mask = GetAlphaMask(control, lineRenderingType);
if (mask == null)
{
return;
}
ForegroundHighlightEffect = new AlphaMaskEffect
{
Source = ForegroundFontEffect,
AlphaMask = mask,
};
}
/// <summary>
/// 仅当前行需要调用此方法(每次 Update 都调用一次)
/// </summary>
/// <param name="control"></param>
/// <param name="lineRenderingType"></param>
public void UpdateForegroundHighlightEffect(ICanvasAnimatedControl control, LineRenderingType lineRenderingType)
{
if (ForegroundHighlightEffect == null)
{
return;
}
if (ForegroundFontEffect == null)
{
return;
}
var mask = GetAlphaMask(control, lineRenderingType);
if (mask == null)
{
return;
}
ForegroundHighlightEffect.Source = ForegroundFontEffect;
ForegroundHighlightEffect.AlphaMask = mask;
}
/// <summary>
/// 仅当前播放行需要调用此方法(每次 Update 都调用一次)
/// </summary>
/// <param name="control"></param>
/// <param name="playingLineIndex"></param>
/// <param name="charStartIndex"></param>
/// <param name="charLength"></param>
/// <param name="charProgress"></param>
public void RecreateCurrentCharMask(ICanvasAnimatedControl control, int charStartIndex, int charLength, double charProgress)
{
CurrentCharMask?.Dispose();
CurrentCharMask = null;
CurrentCharMask = new CanvasCommandList(control);
if (CanvasTextLayout == null)
{
return;
}
using var ds = CurrentCharMask.CreateDrawingSession();
var highlightRegion = CanvasTextLayout
.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 + Position.Y,
highlightWidth,
highlightRegion.LayoutBounds.Height
);
var fadeInRect = new Rect(
highlightRect.Right - fadingWidth,
highlightRegion.LayoutBounds.Y + Position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
var fadeOutRect = new Rect(
highlightRect.Right,
highlightRegion.LayoutBounds.Y + Position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
// Brushes
using var fadeInBrush = 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);
}
/// <summary>
/// 仅当前播放行需要调用此方法(每次 Update 都调用一次)
/// </summary>
/// <param name="control"></param>
/// <param name="playingLineIndex"></param>
/// <param name="charStartIndex"></param>
/// <param name="charLength"></param>
/// <param name="charProgress"></param>
public void RecreateLineStartToCurrentCharMask(ICanvasAnimatedControl control, int charStartIndex, int charLength, double charProgress)
{
LineStartToCurrentCharMask?.Dispose();
LineStartToCurrentCharMask = null;
LineStartToCurrentCharMask = new CanvasCommandList(control);
if (CanvasTextLayout == null)
{
return;
}
using var ds = LineStartToCurrentCharMask.CreateDrawingSession();
var regions = CanvasTextLayout.GetCharacterRegions(0, charStartIndex);
var highlightRegion = CanvasTextLayout
.GetCharacterRegions(charStartIndex, charLength)
.FirstOrDefault();
if (regions.Length > 0)
{
// Draw the mask for the current line
for (int j = 0; j < regions.Length; j++)
{
var region = regions[j];
var rect = new Rect(
region.LayoutBounds.X,
region.LayoutBounds.Y + Position.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
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 + Position.Y,
highlightWidth,
highlightRegion.LayoutBounds.Height
);
var fadeInRect = new Rect(
highlightRect.Right - fadingWidth,
highlightRegion.LayoutBounds.Y + Position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
var fadeOutRect = new Rect(
highlightRect.Right,
highlightRegion.LayoutBounds.Y + Position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
// Brushes
using var fadeOutBrush = CanvasHelper.CreateHorizontalFillBrush(
control,
[(0f, 1f), (1f, 0f)],
(double)highlightRect.Right,
fadingWidth
);
ds.FillRectangle(highlightRect, Color.FromArgb(255, 128, 128, 128));
ds.FillRectangle(fadeOutRect, fadeOutBrush);
}
/// <summary>
/// 重建当前行遮罩
/// 仅需在布局重构 (Relayout) 时调用
/// </summary>
/// <param name="control"></param>
public void RecreateCurrentLineMask(ICanvasAnimatedControl control)
{
CurrentLineMask?.Dispose();
CurrentLineMask = null;
if (CanvasTextLayout == null)
{
return;
}
CurrentLineMask = new CanvasCommandList(control);
using var ds = CurrentLineMask.CreateDrawingSession();
var regions = CanvasTextLayout.GetCharacterRegions(0, 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 + Position.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
ds.FillRectangle(rect, Colors.White);
}
}
}
public void RecreatePlaceholder(ICanvasAnimatedControl control)
{
PlaceholderEffect?.Dispose();
PlaceholderEffect = null;
PlaceholderEffect = new CanvasCommandList(control);
}
}
}

View File

@@ -5,13 +5,11 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
public partial class LyricsSearchProviderInfo : ObservableObject
public partial class LyricsSearchProviderInfo : ObservableRecipient
{
[ObservableProperty]
public partial bool IsEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; }
[ObservableProperty]
public partial LyricsSearchProvider Provider { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider Provider { get; set; }
public LyricsSearchProviderInfo() { }

View File

@@ -1,24 +1,93 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace BetterLyrics.WinUI3.Models
{
public partial class MediaSourceProviderInfo : ObservableObject
public partial class MediaSourceProviderInfo : ObservableRecipient
{
[ObservableProperty]
public partial bool IsEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; }
[ObservableProperty]
public partial string Provider { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string Provider { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLastFMTrackEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int TimelineSyncThreshold { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PositionOffset { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ResetPositionOffsetOnSongChanged { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
public MediaSourceProviderInfo() { }
public MediaSourceProviderInfo(string provider, bool isEnabled)
public MediaSourceProviderInfo(string provider) : base()
{
switch (provider)
{
case Constants.PlayerID.AppleMusic:
// Apple Music 的特性
TimelineSyncThreshold = 1000;
PositionOffset = 1000;
break;
default:
// 设置 100 以防不必要的重复同步
TimelineSyncThreshold = 100;
PositionOffset = 0;
break;
}
Provider = provider;
IsEnabled = isEnabled;
IsEnabled = true;
IsLastFMTrackEnabled = false;
ResetPositionOffsetOnSongChanged = false;
LyricsSearchProvidersInfo = [.. Enum.GetValues<LyricsSearchProvider>().Select(p => new LyricsSearchProviderInfo(p, true))];
AlbumArtSearchProvidersInfo = [.. Enum.GetValues<AlbumArtSearchProvider>().Select(p => new AlbumArtSearchProviderInfo(p, true))];
}
partial void OnAlbumArtSearchProvidersInfoChanged(FullyObservableCollection<AlbumArtSearchProviderInfo> oldValue, FullyObservableCollection<AlbumArtSearchProviderInfo> newValue)
{
oldValue?.CollectionChanged -= AlbumArtSearchProvidersInfo_CollectionChanged;
oldValue?.ItemPropertyChanged -= AlbumArtSearchProvidersInfo_ItemPropertyChanged;
newValue?.CollectionChanged += AlbumArtSearchProvidersInfo_CollectionChanged;
newValue?.ItemPropertyChanged += AlbumArtSearchProvidersInfo_ItemPropertyChanged;
}
partial void OnLyricsSearchProvidersInfoChanged(FullyObservableCollection<LyricsSearchProviderInfo> oldValue, FullyObservableCollection<LyricsSearchProviderInfo> newValue)
{
oldValue?.CollectionChanged -= LyricsSearchProvidersInfo_CollectionChanged;
oldValue?.ItemPropertyChanged -= LyricsSearchProvidersInfo_ItemPropertyChanged;
newValue?.CollectionChanged += LyricsSearchProvidersInfo_CollectionChanged;
newValue?.ItemPropertyChanged += LyricsSearchProvidersInfo_ItemPropertyChanged;
}
private void AlbumArtSearchProvidersInfo_ItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(AlbumArtSearchProvidersInfo));
}
private void AlbumArtSearchProvidersInfo_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(AlbumArtSearchProvidersInfo));
}
private void LyricsSearchProvidersInfo_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(LyricsSearchProvidersInfo));
}
private void LyricsSearchProvidersInfo_ItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(LyricsSearchProvidersInfo));
}
}

View File

@@ -0,0 +1,18 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class AlbumArtLayoutSettings : ObservableRecipient
{
[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
public AlbumArtLayoutSettings() { }
}
}

View File

@@ -0,0 +1,35 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class AppSettings : ObservableRecipient
{
public string Version { get; set; } = Helper.MetadataHelper.AppVersion;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsStyleSettings StandardLyricsStyleSettings { get; set; } = new LyricsStyleSettings(32, TextAlignmentType.Left, 0);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsStyleSettings DesktopLyricsStyleSettings { get; set; } = new LyricsStyleSettings(28, TextAlignmentType.Center, 2);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsStyleSettings DockLyricsStyleSettings { get; set; } = new LyricsStyleSettings(16, TextAlignmentType.Center, 0);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsEffectSettings StandardLyricsEffectSettings { get; set; } = new LyricsEffectSettings(100, 500, 1000, EasingType.EaseInOutSine);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsEffectSettings DesktopLyricsEffectSettings { get; set; } = new LyricsEffectSettings(500, 500, 500, EasingType.EaseInOutSine);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsEffectSettings DockLyricsEffectSettings { get; set; } = new LyricsEffectSettings(500, 500, 500, EasingType.EaseInOutSine);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial StandardModeSettings StandardModeSettings { get; set; } = new StandardModeSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DesktopModeSettings DesktopModeSettings { get; set; } = new DesktopModeSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DockModeSettings DockModeSettings { get; set; } = new DockModeSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsBackgroundSettings LyricsBackgroundSettings { get; set; } = new LyricsBackgroundSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AlbumArtLayoutSettings AlbumArtLayoutSettings { get; set; } = new AlbumArtLayoutSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TranslationSettings TranslationSettings { get; set; } = new TranslationSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial GeneralSettings GeneralSettings { get; set; } = new GeneralSettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial MusicGallerySettings MusicGallerySettings { get; set; } = new MusicGallerySettings();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LocalMediaFolder> LocalMediaFolders { get; set; } = [];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; } = [];
public AppSettings() { }
}
}

View File

@@ -0,0 +1,20 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Graphics;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class DesktopModeSettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Rect WindowBounds { get; set; } = new Rect(100, 100, 400, 200);
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoLockOnDesktopMode { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LockHotKeyIndex { get; set; } = 'U' - 'A'; // Default to 'U' key
public DesktopModeSettings() { }
}
}

View File

@@ -0,0 +1,20 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class DockModeSettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DockPlacement DockPlacement { get; set; } = DockPlacement.Top;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int DockWindowHeight { get; set; } = 64;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string DockMonitorDeviceName { get; set; } = MonitorHelper.GetPrimaryMonitorDeviceName();
public DockModeSettings() { }
}
}

View File

@@ -0,0 +1,24 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class GeneralSettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AutoStartWindowType AutoStartWindowType { get; set; } = AutoStartWindowType.StandardMode;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Language Language { get; set; } = Language.FollowSystem;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IgnoreFullscreenWindow { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LXMusicServer { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool HideWindowWhenNotPlaying { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsDragEverywhereEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsDisplayType DisplayType { get; set; } = LyricsDisplayType.SplitView;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsImmersiveMode { get; set; } = false;
public GeneralSettings() { }
}
}

View File

@@ -0,0 +1,14 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class LastFMSettings : ObservableRecipient
{
public LastFMSettings() { }
}
}

View File

@@ -0,0 +1,22 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class LyricsBackgroundSettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial ElementTheme LyricsBackgroundTheme { get; set; } = ElementTheme.Dark;
[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 PureColorOverlayOpacity { 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;
public LyricsBackgroundSettings() { }
}
}

View File

@@ -0,0 +1,33 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class LyricsEffectSettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsBlurAmount { get; set; } = 5;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LineRenderingType LyricsGlowEffectScope { get; set; } = LineRenderingType.CurrentChar;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LineRenderingType LyricsHighlightScope { get; set; } = LineRenderingType.LineStartToCurrentChar;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFloatAnimationEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollDuration { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollTopDuration { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollBottomDuration { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsVerticalEdgeOpacity { get; set; } = 0; // 0% opacity
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFanLyricsEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsGlowEffectEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial EasingType LyricsScrollEasingType { get; set; }
public LyricsEffectSettings(int lyricsScrollTopDuration, int lyricsScrollDuration, int lyricsScrollBottomDuration, EasingType lyricsScrollEasingType)
{
LyricsScrollTopDuration = lyricsScrollTopDuration;
LyricsScrollDuration = lyricsScrollDuration;
LyricsScrollBottomDuration = lyricsScrollBottomDuration;
LyricsScrollEasingType = lyricsScrollEasingType;
}
}
}

View File

@@ -0,0 +1,41 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class LyricsStyleSettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFontSize { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TextAlignmentType LyricsAlignmentType { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsBgFontOpacity { get; set; } = 30; // 30% opacity
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFontStrokeWidth { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Color LyricsCustomBgFontColor { get; set; } = Colors.White;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Color LyricsCustomFgFontColor { get; set; } = Colors.White;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Color LyricsCustomStrokeFontColor { get; set; } = Colors.White;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsFontColorType LyricsBgFontColorType { get; set; } = LyricsFontColorType.AdaptiveGrayed;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsFontColorType LyricsFgFontColorType { get; set; } = LyricsFontColorType.AdaptiveGrayed;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsFontColorType LyricsStrokeFontColorType { get; set; } = LyricsFontColorType.AdaptiveGrayed;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsFontWeight LyricsFontWeight { get; set; } = LyricsFontWeight.Bold;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double LyricsLineSpacingFactor { get; set; } = 0.5;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsTranslationSeparator { get; set; } = StringHelper.NewLine;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsFontFamily { get; set; } = FontHelper.SystemFontFamilies.FirstOrDefault() ?? "";
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SelectedFontFamilyIndex { get; set; } = 0;
public LyricsStyleSettings() { }
public LyricsStyleSettings(int lyricsFontSize, TextAlignmentType lyricsAlignmentType, int lyricsFontStrokeWidth)
{
LyricsFontSize = lyricsFontSize;
LyricsAlignmentType = lyricsAlignmentType;
LyricsFontStrokeWidth = lyricsFontStrokeWidth;
}
}
}

View File

@@ -0,0 +1,17 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class MusicGallerySettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial PlaybackOrder PlaybackOrder { get; set; } = PlaybackOrder.RepeatAll;
public MusicGallerySettings() { }
}
}

View File

@@ -0,0 +1,18 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Graphics;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class StandardModeSettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Rect WindowBounds { get; set; } = new Rect(100, 100, 1000, 600);
public StandardModeSettings() { }
}
}

View File

@@ -0,0 +1,21 @@
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models.Settings
{
public partial class TranslationSettings : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLibreTranslateEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LibreTranslateServer { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsTranslationEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ShowTranslationOnly { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SelectedTargetLanguageIndex { get; set; } = LanguageHelper.GetDefaultTargetLanguageIndex();
public TranslationSettings() { }
}
}

View File

@@ -1,9 +1,8 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
namespace BetterLyrics.WinUI3.Renderer
{

View File

@@ -1,20 +1,15 @@
// 2025/6/23 by Zhe Fang
using System.Collections.Generic;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using System.Text.Json;
using System.Text.Json.Serialization;
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Serialization
{
[JsonSerializable(typeof(List<AlbumArtSearchProviderInfo>))]
[JsonSerializable(typeof(List<LyricsSearchProviderInfo>))]
[JsonSerializable(typeof(List<MediaSourceProviderInfo>))]
[JsonSerializable(typeof(List<LocalMediaFolder>))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(TranslateResponse))]
[JsonSerializable(typeof(JsonElement))]
[JsonSerializable(typeof(AppSettings))]
[JsonSourceGenerationOptions(WriteIndented = true)]
internal partial class SourceGenerationContext : JsonSerializerContext { }
}

View File

@@ -1,6 +1,7 @@
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
@@ -9,12 +10,10 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
{
public class AlbumArtSearchService : IAlbumArtSearchService
{
@@ -30,11 +29,11 @@ namespace BetterLyrics.WinUI3.Services
_iTunesHttpClinet = new();
}
public async Task<byte[]?> SearchAsync(string title, string artist, string album, byte[]? bytesFromSMTC = null)
public async Task<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC = null)
{
byte[]? result = null;
foreach (var provider in _settingsService.AlbumArtSearchProvidersInfo)
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.AlbumArtSearchProvidersInfo ?? [])
{
if (!provider.IsEnabled)
{
@@ -44,7 +43,7 @@ namespace BetterLyrics.WinUI3.Services
switch (provider.Provider)
{
case AlbumArtSearchProvider.Local:
result = SearchFile(artist, album);
result = SearchFile(artist, title);
break;
case AlbumArtSearchProvider.SMTC:
result = bytesFromSMTC;
@@ -65,15 +64,15 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
private byte[]? SearchFile(string artist, string album)
private byte[]? SearchFile(string artist, string title)
{
foreach (var folder in _settingsService.LocalMediaFolders)
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
{
if (Directory.Exists(folder.Path) && folder.IsEnabled)
{
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
{
if (FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), album, artist))
if (FileHelper.IsSwitchableNormalizedMatch(Path.GetFileNameWithoutExtension(file), title, artist))
{
Track track = new(file);
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
@@ -102,7 +101,7 @@ namespace BetterLyrics.WinUI3.Services
}
// Build the iTunes API URL
string url = $"https://itunes.apple.com/search?term=" + WebUtility.UrlEncode($"{artist} {album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
string url = $"{Constants.iTunes.QueryPrefix}term=" + WebUtility.UrlEncode($"{artist} {album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
// Make a request to the API
using HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
{
public interface IAlbumArtSearchService
{
Task<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC = null);
}
}

View File

@@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
{
public interface IAlbumArtSearchService
{
Task<byte[]?> SearchAsync(string title, string artist, string album, byte[]? bytesFromSMTC = null);
}
}

View File

@@ -1,111 +0,0 @@
// 2025/6/23 by Zhe Fang
using System.Collections.Generic;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using Microsoft.UI.Xaml;
using Windows.UI;
namespace BetterLyrics.WinUI3.Services
{
public interface ISettingsService
{
// App behavior
AutoStartWindowType AutoStartWindowType { get; set; }
int CoverImageRadius { get; set; }
int CoverOverlayBlurAmount { get; set; }
int CoverOverlayOpacity { get; set; }
bool IsDynamicCoverOverlayEnabled { get; set; }
int CoverAcrylicEffectAmount { get; set; }
bool IsFanLyricsEnabled { get; set; }
bool IsFirstRun { get; set; }
bool IsLyricsGlowEffectEnabled { get; set; }
Language Language { get; set; }
int DesktopWindowLeft { get; set; }
int DesktopWindowTop { get; set; }
int DesktopWindowWidth { get; set; }
int DesktopWindowHeight { get; set; }
int StandardWindowWidth { get; set; }
int StandardWindowHeight { get; set; }
int StandardWindowLeft { get; set; }
int StandardWindowTop { get; set; }
bool AutoLockOnDesktopMode { get; set; }
string LibreTranslateServer { get; set; }
int SelectedTargetLanguageIndex { get; set; }
bool ResetPositionOffsetOnSongChanged { get; set; }
int PositionOffset { get; set; }
// Lyrics lib
List<LocalMediaFolder> LocalMediaFolders { get; set; }
// Lyrics style and effetc
TextAlignmentType LyricsAlignmentType { get; set; }
TextAlignmentType SongInfoAlignmentType { get; set; }
int LyricsBlurAmount { get; set; }
Color LyricsCustomBgFontColor { get; set; }
Color LyricsCustomFgFontColor { get; set; }
Color LyricsCustomStrokeFontColor { get; set; }
int LyricsBgFontOpacity { get; set; }
LyricsFontColorType LyricsBgFontColorType { get; set; }
LyricsFontColorType LyricsFgFontColorType { get; set; }
LyricsFontColorType LyricsStrokeFontColorType { get; set; }
int LyricsStandardFontSize { get; set; }
int LyricsDockFontSize { get; set; }
int LyricsDesktopFontSize { get; set; }
ElementTheme LyricsBackgroundTheme { get; set; }
int LyricsFontStrokeWidth { get; set; }
LyricsFontWeight LyricsFontWeight { get; set; }
LineRenderingType LyricsGlowEffectScope { get; set; }
LineRenderingType LyricsHighlightScope { get; set; }
bool IsLyricsFloatAnimationEnabled { get; set; }
float LyricsLineSpacingFactor { get; set; }
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
List<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
List<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
EasingType LyricsScrollEasingType { get; set; }
int LyricsScrollDuration { get; set; }
int LyricsVerticalEdgeOpacity { get; set; }
bool IgnoreFullscreenWindow { get; set; }
bool IsTranslationEnabled { get; set; }
bool ShowTranslationOnly { get; set; }
LyricsDisplayType DisplayType { get; set; }
int TimelineSyncThreshold { get; set; }
int LockHotKeyIndex { get; set; }
bool IsImmersiveMode { get; set; }
string LXMusicServer { get; set; }
DockPlacement DockPlacement { get; set; }
bool HideWindowWhenNotPlaying { get; set; }
int DockWindowHeight { get; set; }
int SelectedFontFamilyIndex { get; set; }
string LyricsFontFamily { get; set; }
bool IsDragEverywhereEnabled { get; set; }
PlaybackOrder PlaybackOrder { get; set; }
bool IsLibreTranslateEnabled { get; set; }
string DockMonitorDeviceName { get; set; }
}
}

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