mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
Compare commits
172 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c9ab73a34 | ||
|
|
a08bf91784 | ||
|
|
61906670fd | ||
|
|
430b2f4d28 | ||
|
|
eb05c1ea13 | ||
|
|
9bebf36e6a | ||
|
|
d2b0b6afb1 | ||
|
|
9c2f4fbff9 | ||
|
|
1b69493afd | ||
|
|
c524dc013c | ||
|
|
9ca5939e57 | ||
|
|
860abd4037 | ||
|
|
618415016f | ||
|
|
bfcba1425d | ||
|
|
0dc9ebf18e | ||
|
|
366d396b93 | ||
|
|
35ca28ac7b | ||
|
|
1505933107 | ||
|
|
958227d0f2 | ||
|
|
b1978fec09 | ||
|
|
010040b376 | ||
|
|
35272d8324 | ||
|
|
29a714fe87 | ||
|
|
78edc2b3ce | ||
|
|
932f9d3a4f | ||
|
|
a56a7af08c | ||
|
|
5427c1992f | ||
|
|
64d69f44c3 | ||
|
|
d75fe4b27a | ||
|
|
eac9b3e28a | ||
|
|
d6975ba2d4 | ||
|
|
aae8e1322a | ||
|
|
88aac0eaf0 | ||
|
|
c463d9bc5c | ||
|
|
f305b469f2 | ||
|
|
ea276f3e2e | ||
|
|
15601e6ee4 | ||
|
|
44b14cab17 | ||
|
|
0a97c56c77 | ||
|
|
ac5dc5991f | ||
|
|
d22dc673e8 | ||
|
|
9eb2b83796 | ||
|
|
1cf6226a06 | ||
|
|
bce330a9e0 | ||
|
|
687e286c2e | ||
|
|
fc05035053 | ||
|
|
f45f5ead01 | ||
|
|
0eeea77896 | ||
|
|
78ca7d8435 | ||
|
|
98d2ac404d | ||
|
|
b4b1ffd58e | ||
|
|
2522bd00ab | ||
|
|
0601039fcf | ||
|
|
857b95e525 | ||
|
|
4be855f11a | ||
|
|
9e1018770d | ||
|
|
1235a09d19 | ||
|
|
1647c3a2f1 | ||
|
|
588838acaa | ||
|
|
25c772434c | ||
|
|
2c597a3b37 | ||
|
|
80a44977a6 | ||
|
|
0389fa6a56 | ||
|
|
30ca476d8d | ||
|
|
6439dee5ef | ||
|
|
f6e5a24fe4 | ||
|
|
77aad546bc | ||
|
|
7fdbe664ba | ||
|
|
df76074ce9 | ||
|
|
31939630a3 | ||
|
|
50500626f8 | ||
|
|
fb1f0c8fc7 | ||
|
|
9036b3be5f | ||
|
|
51f840c0ef | ||
|
|
b3a98cdaa2 | ||
|
|
7799a8dd94 | ||
|
|
e84d1faf71 | ||
|
|
66f0fd0f8f | ||
|
|
4f0dbe4836 | ||
|
|
d7a53e360a | ||
|
|
cb76341666 | ||
|
|
537584e557 | ||
|
|
e06a50d8e8 | ||
|
|
01776ed9a8 | ||
|
|
1b7f53c055 | ||
|
|
3233d35a05 | ||
|
|
ee52dafa0b | ||
|
|
03ddf42152 | ||
|
|
bb4ee6bae7 | ||
|
|
cb5a9f8042 | ||
|
|
1d5d3bfa72 | ||
|
|
06379ebaba | ||
|
|
45eb2a1506 | ||
|
|
0e96c35d2d | ||
|
|
152ecc7ff0 | ||
|
|
8b38aa1e33 | ||
|
|
79e6995384 | ||
|
|
1d6e19a718 | ||
|
|
23cb4b11a9 | ||
|
|
11461a615b | ||
|
|
e8f0359fb2 | ||
|
|
eba81e8be3 | ||
|
|
2e0d437b08 | ||
|
|
cbb0b87392 | ||
|
|
3f63043150 | ||
|
|
77f2474562 | ||
|
|
8aa5c392df | ||
|
|
a453f4862d | ||
|
|
0e6702f3c3 | ||
|
|
412cdbdaf4 | ||
|
|
02dbbf983c | ||
|
|
8cf9830644 | ||
|
|
b04dfedf7e | ||
|
|
3d4e3209e6 | ||
|
|
e92130af85 | ||
|
|
de3d6f1695 | ||
|
|
da377838e8 | ||
|
|
67cf6e47c8 | ||
|
|
abf4c3498f | ||
|
|
ff8c85b2d0 | ||
|
|
8cbdb32931 | ||
|
|
757f9f4156 | ||
|
|
c632f4b01a | ||
|
|
a843d0a0e3 | ||
|
|
905df92b05 | ||
|
|
7445299537 | ||
|
|
ba4958f837 | ||
|
|
8ca5245bf5 | ||
|
|
89aa97552a | ||
|
|
aa692e2735 | ||
|
|
c7ee26f284 | ||
|
|
b103e6efd1 | ||
|
|
16cd12e22b | ||
|
|
34bdbc89bc | ||
|
|
b649e9761d | ||
|
|
a9807f4f09 | ||
|
|
def287715d | ||
|
|
966f926112 | ||
|
|
4568293b51 | ||
|
|
10115ab0a8 | ||
|
|
ecefaedcb9 | ||
|
|
b9aae0866d | ||
|
|
afbfcc921e | ||
|
|
8f997ca3d9 | ||
|
|
042d557eb1 | ||
|
|
153679228d | ||
|
|
5a4fe54ac2 | ||
|
|
14e8d88cd1 | ||
|
|
79e726db33 | ||
|
|
d4f1949833 | ||
|
|
6135f996bf | ||
|
|
5b97621af4 | ||
|
|
0f084d04f8 | ||
|
|
bcb114a171 | ||
|
|
7bbe2a3045 | ||
|
|
fab4e8fe22 | ||
|
|
bc4a06577e | ||
|
|
22a790fff0 | ||
|
|
5e3d0cb78f | ||
|
|
59ee2a6fc3 | ||
|
|
fd5e752b43 | ||
|
|
df31d03da7 | ||
|
|
a7ebba4a62 | ||
|
|
6ca1a84a54 | ||
|
|
bab5a827f6 | ||
|
|
3b1e0389aa | ||
|
|
ae05a55d31 | ||
|
|
4b6d417db3 | ||
|
|
86118bac02 | ||
|
|
7bfbec4b01 | ||
|
|
a5d6dd1305 | ||
|
|
68f690e1a7 |
15
.github/FUNDING.yml
vendored
Normal file
15
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [jayfunc]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: founchoo
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
18
.github/workflows/issues-translator.yml
vendored
Normal file
18
.github/workflows/issues-translator.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: 'issue-translator'
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: usthe/issues-translate-action@v2.7
|
||||
with:
|
||||
IS_MODIFY_TITLE: false
|
||||
# not require, default false, . Decide whether to modify the issue title
|
||||
# if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot.
|
||||
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
|
||||
# not require. Customize the translation robot prefix message.
|
||||
69
.github/workflows/pages.yml
vendored
69
.github/workflows/pages.yml
vendored
@@ -1,69 +0,0 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
|
||||
name: Deploy Jekyll site to Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["dev", "stable"]
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: "3.3"
|
||||
bundler-cache: true
|
||||
cache-version: 0
|
||||
working-directory: "${{ github.workspace }}/docs"
|
||||
- name: Setup Pages
|
||||
id: pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Build with Jekyll
|
||||
# Outputs to the './_site' directory by default
|
||||
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
||||
env:
|
||||
JEKYLL_ENV: production
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/_site/
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
28
.github/workflows/release-to-telegram.yml
vendored
Normal file
28
.github/workflows/release-to-telegram.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Notify Telegram on GitHub Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send release info to Telegram
|
||||
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 }}
|
||||
RELEASE_BODY: ${{ github.event.release.body }}
|
||||
run: |
|
||||
TEXT="🚀 *New Release:* ${RELEASE_TAG} - ${RELEASE_NAME}%0A"
|
||||
TEXT+="📝 *Description:*%0A${RELEASE_BODY}%0A"
|
||||
TEXT+="🔗 [View on GitHub](${RELEASE_URL})"
|
||||
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
|
||||
-d chat_id="${CHAT_ID}" \
|
||||
-d message_thread_id="${THREAD_ID}" \
|
||||
-d text="${TEXT}"
|
||||
22
.github/workflows/releases-to-discord.yml
vendored
Normal file
22
.github/workflows/releases-to-discord.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Notify Discord on GitHub Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
github-releases-to-discord:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: GitHub Releases to Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1
|
||||
with:
|
||||
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
||||
color: "2105893"
|
||||
username: "GitHub"
|
||||
avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png"
|
||||
content: "||@everyone||"
|
||||
footer_title: "Changelog"
|
||||
reduce_headings: true
|
||||
@@ -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)' < '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.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '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>
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.11.0" />
|
||||
Version="1.0.42.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsExpander/SettingsExpander.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
@@ -47,7 +48,9 @@
|
||||
<converter:IntToCornerRadius x:Key="IntToCornerRadius" />
|
||||
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
|
||||
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<converter:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
|
||||
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
|
||||
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
@@ -78,11 +81,14 @@
|
||||
<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" />
|
||||
<Setter Property="Padding" Value="16,0" />
|
||||
<Setter Property="Padding" Value="16,9,16,11" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
|
||||
@@ -92,11 +98,212 @@
|
||||
<Setter Property="Padding" Value="8" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="CardGridStyle" TargetType="Grid">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GhostSliderStyle" TargetType="Slider">
|
||||
<Setter Property="Background" Value="{ThemeResource ControlStrokeColorOnAccentDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="ManipulationMode" Value="None" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-7,0,-7,0" />
|
||||
<Setter Property="IsFocusEngagementEnabled" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Slider">
|
||||
<Grid Margin="{TemplateBinding Padding}">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="0,1,1,0" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="HeaderContentPresenter"
|
||||
Grid.Row="0"
|
||||
Margin="{ThemeResource SliderTopHeaderMargin}"
|
||||
x:DeferLoadStrategy="Lazy"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
|
||||
Foreground="{ThemeResource SliderHeaderForeground}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
x:Name="SliderContainer"
|
||||
Grid.Row="1"
|
||||
Background="{ThemeResource SliderContainerBackground}"
|
||||
Control.IsTemplateFocusTarget="True">
|
||||
<Grid x:Name="HorizontalTemplate" MinHeight="{ThemeResource SliderHorizontalHeight}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{ThemeResource SliderPreContentMargin}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle
|
||||
x:Name="HorizontalTrackRect"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="2"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="HorizontalDecreaseRect"
|
||||
Grid.Row="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="TopTickBar"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,0,4"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="HorizontalInlineTickBar"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="2"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="BottomTickBar"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,4,0,0"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="HorizontalThumb"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="2"
|
||||
Height="2"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-14,-6,-14,-6"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="VerticalTemplate"
|
||||
MinWidth="{ThemeResource SliderVerticalWidth}"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{ThemeResource SliderPreContentMargin}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
x:Name="VerticalTrackRect"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="VerticalDecreaseRect"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="LeftTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="VerticalInlineTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="RightTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="VerticalThumb"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Width="24"
|
||||
Height="8"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-6,-14,-6,-14"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ListViewStretchedItemContainerStyle" TargetType="ListViewItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
|
||||
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
|
||||
<!-- Dimensions -->
|
||||
|
||||
<!-- Fonts -->
|
||||
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
|
||||
<FontFamily x:Key="IconFontFamily">ms-appx:///Assets/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
</Application>
|
||||
|
||||
@@ -10,13 +10,17 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
using Serilog;
|
||||
using ShadowViewer.Controls;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
@@ -33,6 +37,8 @@ namespace BetterLyrics.WinUI3
|
||||
public NotificationPanel? LyricsWindowNotificationPanel { get; set; }
|
||||
public NotificationPanel? SettingsWindowNotificationPanel { get; set; }
|
||||
|
||||
private static Mutex? _instanceMutex;
|
||||
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
@@ -41,6 +47,8 @@ namespace BetterLyrics.WinUI3
|
||||
DispatcherQueueTimer = DispatcherQueue.CreateTimer();
|
||||
ResourceLoader = new ResourceLoader();
|
||||
|
||||
EnsureSingleInstance();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
PathHelper.EnsureDirectories();
|
||||
ConfigureServices();
|
||||
@@ -53,13 +61,28 @@ namespace BetterLyrics.WinUI3
|
||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||
}
|
||||
|
||||
private void EnsureSingleInstance()
|
||||
{
|
||||
bool createdNew;
|
||||
_instanceMutex = new Mutex(true, MetadataHelper.AppName, out createdNew);
|
||||
|
||||
if (!createdNew)
|
||||
{
|
||||
User32.MessageBox(HWND.NULL, ResourceLoader!.GetString("TryRunMultipleInstance"), null, User32.MB_FLAGS.MB_APPLMODAL);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindow>();
|
||||
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow == null) return;
|
||||
WindowHelper.OpenWindow<LyricsWindow>();
|
||||
|
||||
lyricsWindow.AutoSelectLyricsMode();
|
||||
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow != null)
|
||||
{
|
||||
lyricsWindow.ViewModel.InitLockHotKey();
|
||||
lyricsWindow.AutoSelectLyricsMode();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureServices()
|
||||
@@ -90,6 +113,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<SystemTrayViewModel>()
|
||||
.AddSingleton<SettingsPageViewModel>()
|
||||
.AddSingleton<LyricsPageViewModel>()
|
||||
.AddSingleton<MusicGalleryViewModel>()
|
||||
.AddSingleton<LyricsRendererViewModel>()
|
||||
.BuildServiceProvider()
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/EmptyBox.png
Normal file
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/EmptyBox.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/EmptyState.png
Normal file
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/EmptyState.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Telegram.png
Normal file
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Telegram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 192 KiB |
328503
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Wiki82.profile.xml
Normal file
328503
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Wiki82.profile.xml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,118 +1,170 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="ViewModels\Lyrics\**" />
|
||||
<Content Remove="ViewModels\Lyrics\**" />
|
||||
<EmbeddedResource Remove="ViewModels\Lyrics\**" />
|
||||
<None Remove="ViewModels\Lyrics\**" />
|
||||
<Page Remove="ViewModels\Lyrics\**" />
|
||||
<PRIResource Remove="ViewModels\Lyrics\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Core14.profile.xml" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Views\SettingsWindow.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Logo.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="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.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
<PackageReference Include="NTextCat" Version="0.3.65" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<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.10" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
|
||||
<PackageReference Include="WinUIEx" Version="2.6.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\InAppLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\DesktopLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!--Disable Trimming for Specific Packages-->
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\SettingsWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\SystemTray.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Logo.ico</ApplicationIcon>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ShouldCreateLogs>True</ShouldCreateLogs>
|
||||
<AdvancedSettingsExpanded>True</AdvancedSettingsExpanded>
|
||||
<UpdateAssemblyVersion>False</UpdateAssemblyVersion>
|
||||
<UpdateAssemblyFileVersion>False</UpdateAssemblyFileVersion>
|
||||
<UpdateAssemblyInfoVersion>False</UpdateAssemblyInfoVersion>
|
||||
<UpdatePackageVersion>True</UpdatePackageVersion>
|
||||
<AssemblyInfoVersionType>SettingsVersion</AssemblyInfoVersionType>
|
||||
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
|
||||
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
|
||||
<Version>2025.6.0</Version>
|
||||
<AssemblyVersion>2025.6.18.0110</AssemblyVersion>
|
||||
<FileVersion>2025.6.18.0110</FileVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="ViewModels\Lyrics\**" />
|
||||
<Content Remove="ViewModels\Lyrics\**" />
|
||||
<EmbeddedResource Remove="ViewModels\Lyrics\**" />
|
||||
<None Remove="ViewModels\Lyrics\**" />
|
||||
<Page Remove="ViewModels\Lyrics\**" />
|
||||
<PRIResource Remove="ViewModels\Lyrics\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Segoe Fluent Icons.ttf" />
|
||||
<None Remove="Assets\Wiki82.profile.xml" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Views\MusicGalleryPage.xaml" />
|
||||
<None Remove="Views\MusicGalleryWindow.xaml" />
|
||||
<None Remove="Views\SettingsWindow.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Logo.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="3v.EvtSource" Version="2.0.0" />
|
||||
<PackageReference Include="ColorThief.ImageSharp" Version="1.0.0" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250703-build.2173" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.Shimmer" Version="0.1.250703-build.2173" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.MetadataControl" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<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.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" />
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
<PackageReference Include="NTextCat" Version="0.3.65" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.3-dev-02320" />
|
||||
<PackageReference Include="Serilog.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="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="TinyPinyin.Net" Version="1.0.2" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.CoreAudio" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.DwmApi" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
|
||||
<PackageReference Include="WinUIEx" Version="2.6.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\InAppLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\DesktopLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!--Disable Trimming for Specific Packages-->
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\Discord.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\Logo.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Logo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\QQ.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Segoe Fluent Icons.ttf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Telegram.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Wiki82.profile.xml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\MusicGalleryWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\MusicGalleryPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\SettingsWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\SystemTray.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Logo.ico</ApplicationIcon>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ShouldCreateLogs>True</ShouldCreateLogs>
|
||||
<AdvancedSettingsExpanded>True</AdvancedSettingsExpanded>
|
||||
<UpdateAssemblyVersion>False</UpdateAssemblyVersion>
|
||||
<UpdateAssemblyFileVersion>False</UpdateAssemblyFileVersion>
|
||||
<UpdateAssemblyInfoVersion>False</UpdateAssemblyInfoVersion>
|
||||
<UpdatePackageVersion>True</UpdatePackageVersion>
|
||||
<AssemblyInfoVersionType>SettingsVersion</AssemblyInfoVersionType>
|
||||
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
|
||||
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
|
||||
<Version>2025.6.0</Version>
|
||||
<AssemblyVersion>2025.6.18.0110</AssemblyVersion>
|
||||
<FileVersion>2025.6.18.0110</FileVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
x:Name="TrayIcon"
|
||||
x:FieldModifier="public"
|
||||
ContextMenuMode="SecondWindow"
|
||||
DoubleClickCommand="{x:Bind ViewModel.OpenLyricsWindowCommand}"
|
||||
IconSource="ms-appx:///Assets/Logo.ico"
|
||||
LeftClickCommand="{x:Bind ViewModel.OpenLyricsWindowCommand}"
|
||||
NoLeftClickDelay="True"
|
||||
ToolTipText="{x:Bind ViewModel.ToolTipText, Mode=OneWay}">
|
||||
<tb:TaskbarIcon.ContextFlyout>
|
||||
@@ -22,7 +24,10 @@
|
||||
AreOpenCloseAnimationsEnabled="True"
|
||||
LightDismissOverlayMode="On"
|
||||
ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||
<MenuFlyoutItem x:Uid="SystemTrayMusicGallery" Command="{x:Bind ViewModel.OpenMusicGalleryCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTraySettings" Command="{x:Bind ViewModel.OpenSettingsCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTrayResetWindowPosition" Command="{x:Bind ViewModel.ResetWindowPositionCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTrayRestart" Command="{x:Bind ViewModel.RestartAppCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTrayExit" Command="{x:Bind ViewModel.ExitAppCommand}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SystemTrayUnlock"
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
|
||||
public partial class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
internal partial class CornerRadiusToDoubleConverter : IValueConverter
|
||||
public partial class CornerRadiusToDoubleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
internal partial class EnumToIntConverter : IValueConverter
|
||||
public partial class EnumToIntConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -14,19 +14,19 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString("LyricsSearchProviderLrcLib"),
|
||||
LyricsSearchProvider.QQ => App.ResourceLoader!.GetString("LyricsSearchProviderQQ"),
|
||||
LyricsSearchProvider.Netease => App.ResourceLoader!.GetString("LyricsSearchProviderNetease"),
|
||||
LyricsSearchProvider.Kugou => App.ResourceLoader!.GetString("LyricsSearchProviderKugou"),
|
||||
LyricsSearchProvider.AmllTtmlDb => App.ResourceLoader!.GetString("LyricsSearchProviderAmllTtmlDb"),
|
||||
LyricsSearchProvider.LrcLib => "LrcLib",
|
||||
LyricsSearchProvider.QQ => "QQ",
|
||||
LyricsSearchProvider.Netease => "Netease",
|
||||
LyricsSearchProvider.Kugou => "Kugou",
|
||||
LyricsSearchProvider.AmllTtmlDb => "amll-ttml-db",
|
||||
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
|
||||
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
|
||||
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),
|
||||
LyricsSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString("LyricsSearchProviderTtmlFile"),
|
||||
_ => "",
|
||||
_ => "N/A",
|
||||
};
|
||||
}
|
||||
return "";
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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 partial class SecondsToFormattedTimeConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is double seconds)
|
||||
{
|
||||
return TimeSpan.FromSeconds(seconds).ToString(@"mm\:ss");
|
||||
}
|
||||
else if (value is int secondsInt)
|
||||
{
|
||||
return TimeSpan.FromSeconds(secondsInt).ToString(@"mm\:ss");
|
||||
}
|
||||
return value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public partial class TranslationSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is TranslationSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
TranslationSearchProvider.LrcLib => "LrcLib",
|
||||
TranslationSearchProvider.QQ => "QQ",
|
||||
TranslationSearchProvider.Netease => "Netease",
|
||||
TranslationSearchProvider.Kugou => "Kugou",
|
||||
TranslationSearchProvider.AmllTtmlDb => "amll-ttml-db",
|
||||
TranslationSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
|
||||
TranslationSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),
|
||||
TranslationSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderEslrcFile"),
|
||||
TranslationSearchProvider.LocalTtmlFile => App.ResourceLoader!.GetString("LyricsSearchProviderTtmlFile"),
|
||||
TranslationSearchProvider.LibreTranslate => "LibreTranslate",
|
||||
_ => "N/A",
|
||||
};
|
||||
}
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum CommonSongProperty
|
||||
{
|
||||
Title,
|
||||
Album,
|
||||
Artist
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum DockPlacement
|
||||
{
|
||||
Top,
|
||||
Bottom
|
||||
}
|
||||
|
||||
public static class DockPlacementExtensions
|
||||
{
|
||||
public static WindowPixelSampleMode ToWindowPixelSampleMode(this DockPlacement placement)
|
||||
{
|
||||
return placement switch
|
||||
{
|
||||
DockPlacement.Top => WindowPixelSampleMode.BelowWindow,
|
||||
DockPlacement.Bottom => WindowPixelSampleMode.AboveWindow,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(placement), placement, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,5 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
AlbumArtOnly,
|
||||
LyricsOnly,
|
||||
SplitView,
|
||||
PlaceholderOnly,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 LyricsLayoutOrientation
|
||||
{
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
}
|
||||
@@ -61,5 +61,22 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
return !provider.IsLocal();
|
||||
}
|
||||
|
||||
public static TranslationSearchProvider? ToTranslationSearchProvider(this LyricsSearchProvider? provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => TranslationSearchProvider.LrcLib,
|
||||
LyricsSearchProvider.QQ => TranslationSearchProvider.QQ,
|
||||
LyricsSearchProvider.Kugou => TranslationSearchProvider.Kugou,
|
||||
LyricsSearchProvider.Netease => TranslationSearchProvider.Netease,
|
||||
LyricsSearchProvider.AmllTtmlDb => TranslationSearchProvider.AmllTtmlDb,
|
||||
LyricsSearchProvider.LocalMusicFile => TranslationSearchProvider.LocalMusicFile,
|
||||
LyricsSearchProvider.LocalLrcFile => TranslationSearchProvider.LocalLrcFile,
|
||||
LyricsSearchProvider.LocalEslrcFile => TranslationSearchProvider.LocalEslrcFile,
|
||||
LyricsSearchProvider.LocalTtmlFile => TranslationSearchProvider.LocalTtmlFile,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum PlaybackOrder
|
||||
{
|
||||
RepeatAll,
|
||||
RepeatOne,
|
||||
Shuffle,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum TranslationSearchProvider
|
||||
{
|
||||
QQ,
|
||||
Kugou,
|
||||
Netease,
|
||||
LrcLib,
|
||||
AmllTtmlDb,
|
||||
LocalMusicFile,
|
||||
LocalLrcFile,
|
||||
LocalEslrcFile,
|
||||
LocalTtmlFile,
|
||||
LibreTranslate,
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
public enum WindowPixelSampleMode
|
||||
{
|
||||
BelowWindow,
|
||||
AboveWindow,
|
||||
WindowArea,
|
||||
WindowEdge,
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class AlbumArtChangedEventArgs : EventArgs
|
||||
public class AlbumArtChangedEventArgs(SoftwareBitmap? albumArtSwBitmap, Color? albumArtLightAccentColor, Color? albumArtDarkAccentColor) : EventArgs
|
||||
{
|
||||
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = null;
|
||||
public Color? AlbumArtAccentColor { get; set; } = null;
|
||||
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap;
|
||||
public Color? AlbumArtLightAccentColor { get; set; } = albumArtLightAccentColor;
|
||||
public Color? AlbumArtDarkAccentColor { get; set; } = albumArtDarkAccentColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
|
||||
public class TimelineChangedEventArgs(TimeSpan position, TimeSpan end) : EventArgs()
|
||||
{
|
||||
public TimeSpan Position { get; set; } = position;
|
||||
public TimeSpan End { get; set; } = end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.UI.Windowing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class AppWindowHelper
|
||||
{
|
||||
public static void SetIcons(this AppWindow appWindow)
|
||||
{
|
||||
appWindow.SetIcon(PathHelper.LogoPath);
|
||||
appWindow.SetTaskbarIcon(PathHelper.LogoPath);
|
||||
appWindow.SetTitleBarIcon(PathHelper.LogoPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class CollectionHelper
|
||||
{
|
||||
public static ObservableCollection<GroupInfoList> GetGroupedBy<T>(
|
||||
this IEnumerable<T> items,
|
||||
Func<T, object> groupKeySelector,
|
||||
Func<object, object>? orderSelector = null)
|
||||
{
|
||||
var query = from item in items
|
||||
group item by groupKeySelector(item) into g
|
||||
orderby g.Key
|
||||
select new GroupInfoList(g.Cast<object>(), orderSelector) { Key = g.Key };
|
||||
|
||||
return new ObservableCollection<GroupInfoList>(query);
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
|
||||
{
|
||||
if (collection == null) return;
|
||||
if (items == null) return;
|
||||
foreach (var item in items)
|
||||
{
|
||||
collection.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InsertRange<T>(this IList<T> list, int index, IEnumerable<T> items)
|
||||
{
|
||||
if (list == null) return;
|
||||
if (items == null) return;
|
||||
if (index < 0 || index > list.Count) return;
|
||||
foreach (var item in items)
|
||||
{
|
||||
list.Insert(index++, item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -106,18 +106,25 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, brightness);
|
||||
}
|
||||
|
||||
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, WindowPixelSampleMode mode)
|
||||
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, string monitorDeviceName, WindowPixelSampleMode mode)
|
||||
{
|
||||
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return System.Drawing.Color.Transparent;
|
||||
|
||||
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
|
||||
int screenWidth = monitorInfo.rcMonitor.Width;
|
||||
switch (mode)
|
||||
{
|
||||
case WindowPixelSampleMode.BelowWindow:
|
||||
{
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowPixelSampleMode.AboveWindow:
|
||||
{
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Top - 1;
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowPixelSampleMode.WindowArea:
|
||||
{
|
||||
|
||||
@@ -5,6 +5,8 @@ using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Vanara.PInvoke;
|
||||
using WinRT.Interop;
|
||||
@@ -73,7 +75,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
|
||||
window.AppWindow.MoveAndResize(
|
||||
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
|
||||
new Windows.Graphics.RectInt32(
|
||||
targetX,
|
||||
targetY,
|
||||
targetWidth,
|
||||
targetHeight
|
||||
)
|
||||
);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
|
||||
@@ -90,6 +97,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
int exStyle = User32.GetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE);
|
||||
|
||||
if (enable)
|
||||
{
|
||||
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
@@ -14,21 +18,29 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class DockModeHelper
|
||||
{
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
private static readonly HashSet<IntPtr> _registered = [];
|
||||
private static readonly Dictionary<IntPtr, RECT> _originalPositions = [];
|
||||
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyle = [];
|
||||
|
||||
public static void Disable(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
if (!_registered.Contains(hwnd)) return;
|
||||
|
||||
window.SetIsShownInSwitchers(true);
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
window.SetIsAlwaysOnTop(false);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
UnregisterAppBar(hwnd);
|
||||
|
||||
window.SetWindowStyle(_originalWindowStyle[hwnd]);
|
||||
_originalWindowStyle.Remove(hwnd);
|
||||
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
if (_originalPositions.TryGetValue(hwnd, out var rect))
|
||||
{
|
||||
User32.SetWindowPos(
|
||||
@@ -42,14 +54,11 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
);
|
||||
_originalPositions.Remove(hwnd);
|
||||
}
|
||||
|
||||
UnregisterAppBar(hwnd);
|
||||
}
|
||||
|
||||
public static void Enable(Window window, int appBarHeight)
|
||||
public static void Enable(Window window, string monitorDeviceName, int appBarHeight, DockPlacement dockPlacement)
|
||||
{
|
||||
window.SetIsShownInSwitchers(false);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
window.SetIsAlwaysOnTop(true);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
@@ -58,7 +67,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
_originalWindowStyle[hwnd] = window.GetWindowStyle();
|
||||
}
|
||||
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
|
||||
|
||||
if (!_originalPositions.ContainsKey(hwnd))
|
||||
{
|
||||
@@ -68,40 +76,55 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
RegisterAppBar(hwnd, appBarHeight);
|
||||
RegisterAppBar(hwnd, monitorDeviceName, appBarHeight, dockPlacement);
|
||||
|
||||
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(_settingsService.DockMonitorDeviceName);
|
||||
|
||||
int screenWidth = monitorInfo.rcMonitor.Width;
|
||||
int screenHeight = monitorInfo.rcMonitor.Bottom - monitorInfo.rcMonitor.Top;
|
||||
int y = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - appBarHeight;
|
||||
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
monitorInfo.rcMonitor.Left,
|
||||
y,
|
||||
screenWidth,
|
||||
appBarHeight,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
User32.SetWindowPosFlags.SWP_HIDEWINDOW
|
||||
);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
window.ToggleWindowStyle(true, WindowStyle.Popup);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private static void RegisterAppBar(IntPtr hwnd, int height)
|
||||
private static void RegisterAppBar(IntPtr hwnd, string monitorDeviceName, int height, DockPlacement dockPlacement)
|
||||
{
|
||||
if (_registered.Contains(hwnd)) return;
|
||||
|
||||
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
|
||||
|
||||
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
|
||||
|
||||
int top = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - height;
|
||||
int bottom = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top + height : monitorInfo.rcMonitor.Bottom;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = Shell32.ABE.ABE_TOP,
|
||||
uEdge = uEdge,
|
||||
rc = new RECT
|
||||
{
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = height,
|
||||
Left = monitorInfo.rcMonitor.Left,
|
||||
Top = top,
|
||||
Right = monitorInfo.rcMonitor.Right,
|
||||
Bottom = bottom,
|
||||
},
|
||||
};
|
||||
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_NEW, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
|
||||
_registered.Add(hwnd);
|
||||
@@ -119,40 +142,59 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
};
|
||||
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
|
||||
|
||||
_registered.Remove(hwnd);
|
||||
}
|
||||
|
||||
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight)
|
||||
public static void UpdateAppBarHeight(IntPtr hwnd, string monitorDeviceName, int newHeight, DockPlacement dockPlacement)
|
||||
{
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
App.DispatcherQueueTimer?.Debounce(() =>
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = Shell32.ABE.ABE_TOP,
|
||||
rc = new RECT
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
|
||||
|
||||
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
|
||||
|
||||
int screenWidth = monitorInfo.rcMonitor.Width;
|
||||
int top = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - newHeight;
|
||||
int bottom = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top + newHeight : monitorInfo.rcMonitor.Bottom;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = newHeight,
|
||||
},
|
||||
};
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = uEdge,
|
||||
rc = new RECT
|
||||
{
|
||||
Left = monitorInfo.rcMonitor.Left,
|
||||
Top = top,
|
||||
Right = monitorInfo.rcMonitor.Right,
|
||||
Bottom = bottom,
|
||||
},
|
||||
};
|
||||
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
|
||||
// 同步窗口实际高度
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
newHeight,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
// 同步窗口实际高度和位置
|
||||
int y = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - newHeight;
|
||||
int repeatCount = 2;
|
||||
while (repeatCount > 0)
|
||||
{
|
||||
repeatCount--;
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
monitorInfo.rcMonitor.Left,
|
||||
y,
|
||||
screenWidth,
|
||||
newHeight,
|
||||
newHeight == 0 ? User32.SetWindowPosFlags.SWP_HIDEWINDOW : User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
}
|
||||
}, TimeSpan.FromMilliseconds(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
|
||||
public static float CubicBezier(float t, float p0, float p1, float p2, float p3)
|
||||
{
|
||||
float u = 1 - t;
|
||||
return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
|
||||
}
|
||||
|
||||
public static float Linear(float t) => t;
|
||||
}
|
||||
}
|
||||
|
||||
20
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FontHelper.cs
Normal file
20
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FontHelper.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class FontHelper
|
||||
{
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies();
|
||||
|
||||
public static string GetUserPreferredFontFamily() => SystemFontFamilies.ElementAtOrDefault(_settingsService.SelectedFontFamilyIndex) ?? "Segoe UI";
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
@@ -16,6 +17,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
|
||||
private HWND _currentForeground = HWND.NULL;
|
||||
private readonly IntPtr _selfHwnd;
|
||||
private readonly ThrottleHelper _winEventProcThrottle = new(TimeSpan.FromSeconds(1));
|
||||
|
||||
public delegate void WindowChangedHandler(HWND hwnd);
|
||||
private readonly WindowChangedHandler _onWindowChanged;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class GlobalHotKeyHelper
|
||||
{
|
||||
private static Dictionary<int, Action> _hotKeyActions = [];
|
||||
private static int _nextId = 0;
|
||||
|
||||
public static void RegisterHotKey(Window window, User32.HotKeyModifiers modifiers, uint key, Action action)
|
||||
{
|
||||
HWND hwnd = WindowNative.GetWindowHandle(window);
|
||||
int id = _nextId++;
|
||||
User32.RegisterHotKey(hwnd, id, modifiers, key);
|
||||
_hotKeyActions[id] = action;
|
||||
}
|
||||
|
||||
public static void UnregisterAllHotKeys(Window window)
|
||||
{
|
||||
HWND hwnd = WindowNative.GetWindowHandle(window);
|
||||
foreach (var id in _hotKeyActions.Keys.ToList())
|
||||
{
|
||||
User32.UnregisterHotKey(hwnd, id);
|
||||
_hotKeyActions.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryInvokeAction(int id)
|
||||
{
|
||||
if (_hotKeyActions.TryGetValue(id, out var action))
|
||||
{
|
||||
action?.Invoke();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,14 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -7,10 +16,6 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
@@ -19,125 +24,83 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ImageHelper
|
||||
{
|
||||
private const int _accentColorCount = 1;
|
||||
|
||||
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(string text, int width, int height)
|
||||
public static RandomAccessStreamReference ByteArrayToRandomAccessStreamReference(byte[] bytes)
|
||||
{
|
||||
var device = CanvasDevice.GetSharedDevice();
|
||||
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
using var writer = new DataWriter(stream);
|
||||
writer.WriteBytes(bytes);
|
||||
writer.StoreAsync().GetAwaiter().GetResult();
|
||||
writer.FlushAsync().GetAwaiter().GetResult();
|
||||
writer.DetachStream();
|
||||
return RandomAccessStreamReference.CreateFromStream(stream);
|
||||
}
|
||||
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(int width, int height)
|
||||
{
|
||||
using var device = CanvasDevice.GetSharedDevice();
|
||||
using var renderTarget = new CanvasRenderTarget(device, width, height, 96);
|
||||
|
||||
// 随机生成渐变色
|
||||
Windows.UI.Color RandomColor()
|
||||
{
|
||||
var rand = new Random(Guid.NewGuid().GetHashCode());
|
||||
double h = rand.NextDouble() * 360;
|
||||
double s = 0.35 + rand.NextDouble() * 0.3; // 0.35~0.65,适中饱和度
|
||||
double l = 0.5 + rand.NextDouble() * 0.3; // 0.5~0.8,明亮
|
||||
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, l);
|
||||
}
|
||||
|
||||
Windows.UI.Color color1 = RandomColor();
|
||||
Windows.UI.Color color2 = RandomColor();
|
||||
|
||||
// 居中绘制文字
|
||||
using (var ds = renderTarget.CreateDrawingSession())
|
||||
{
|
||||
// 背景色
|
||||
ds.Clear(Colors.LightGray);
|
||||
|
||||
// 文字格式
|
||||
var format = new CanvasTextFormat
|
||||
// 绘制线性渐变背景
|
||||
using var gradientBrush = new Microsoft.Graphics.Canvas.Brushes.CanvasLinearGradientBrush(ds, color1, color2)
|
||||
{
|
||||
FontSize = Math.Min(width, height) / 6f,
|
||||
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Center,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Center,
|
||||
WordWrapping = CanvasWordWrapping.Wrap,
|
||||
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
|
||||
Options = CanvasDrawTextOptions.Default,
|
||||
StartPoint = new Vector2(0, 0),
|
||||
EndPoint = new Vector2(width, height)
|
||||
};
|
||||
|
||||
// 设定边距
|
||||
float margin = Math.Min(width, height) / 12f;
|
||||
float availableWidth = width - 2 * margin;
|
||||
float availableHeight = height - 2 * margin;
|
||||
|
||||
// 计算合适的字体大小以适应内容区域
|
||||
float fontSize = format.FontSize;
|
||||
float minFontSize = 8f;
|
||||
float maxFontSize = format.FontSize;
|
||||
CanvasTextLayout layout;
|
||||
do
|
||||
{
|
||||
format.FontSize = fontSize;
|
||||
layout = new CanvasTextLayout(
|
||||
ds,
|
||||
text,
|
||||
format,
|
||||
availableWidth,
|
||||
availableHeight
|
||||
);
|
||||
if (
|
||||
layout.LayoutBounds.Width <= availableWidth
|
||||
&& layout.LayoutBounds.Height <= availableHeight
|
||||
)
|
||||
break;
|
||||
fontSize -= 1f;
|
||||
} while (fontSize >= minFontSize);
|
||||
|
||||
// 居中绘制文字(在内容区域内居中)
|
||||
var bounds = layout.LayoutBounds;
|
||||
var x = margin + (availableWidth - (float)bounds.Width) / 2f - (float)bounds.X;
|
||||
var y = margin + (availableHeight - (float)bounds.Height) / 2f - (float)bounds.Y;
|
||||
ds.DrawTextLayout(layout, new Vector2(x, y), Colors.DarkGray);
|
||||
ds.FillRectangle(0, 0, width, height, gradientBrush);
|
||||
}
|
||||
|
||||
// 保存为 PNG 并转为 byte[]
|
||||
using (var stream = new InMemoryRandomAccessStream())
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
|
||||
var buffer = new byte[stream.Size];
|
||||
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
|
||||
{
|
||||
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
|
||||
var buffer = new byte[stream.Size];
|
||||
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
|
||||
{
|
||||
await reader.LoadAsync((uint)stream.Size);
|
||||
reader.ReadBytes(buffer);
|
||||
}
|
||||
return buffer;
|
||||
await reader.LoadAsync((uint)stream.Size);
|
||||
reader.ReadBytes(buffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static List<Color> GetAccentColorsFromByte(byte[] bytes)
|
||||
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes, int count, bool? isDark = null)
|
||||
{
|
||||
// 使用 ImageSharp 读取图片
|
||||
using var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(bytes);
|
||||
|
||||
// 简单聚类法:统计所有像素出现频率,取出现最多的前 AccentColorCount 个颜色
|
||||
var colorCount = new Dictionary<SixLabors.ImageSharp.PixelFormats.Rgba32, int>();
|
||||
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < image.Width; x++)
|
||||
{
|
||||
var color = image[x, y];
|
||||
// 可选:忽略透明像素
|
||||
if (color.A < 32) continue;
|
||||
if (colorCount.ContainsKey(color))
|
||||
colorCount[color]++;
|
||||
else
|
||||
colorCount[color] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 按出现次数排序,取前 AccentColorCount 个
|
||||
var topColors = colorCount
|
||||
.OrderByDescending(kv => kv.Value)
|
||||
.Take(_accentColorCount)
|
||||
.Select(kv => kv.Key)
|
||||
using var image = Image.Load<Rgba32>(bytes);
|
||||
var colorThief = new ColorThief.ImageSharp.ColorThief();
|
||||
var mainColor = colorThief.GetColor(image, 10, false);
|
||||
var palette = colorThief.GetPalette(image, 255, 10, false);
|
||||
var topColors = palette
|
||||
.OrderByDescending(x => x.Population)
|
||||
.Where(x => x.IsDark == (isDark ?? mainColor.IsDark))
|
||||
.Select(x => Windows.UI.Color.FromArgb(x.Color.A, x.Color.R, x.Color.G, x.Color.B))
|
||||
.Take(count)
|
||||
.ToList();
|
||||
|
||||
// 转换为 Windows.UI.Color
|
||||
return topColors
|
||||
.Select(c => Windows.UI.Color.FromArgb(c.A, c.R, c.G, c.B))
|
||||
.ToList();
|
||||
return topColors;
|
||||
}
|
||||
|
||||
|
||||
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
//{
|
||||
// var stream = new InMemoryRandomAccessStream();
|
||||
@@ -167,9 +130,11 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
|
||||
{
|
||||
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
|
||||
using var memoryStream = new MemoryStream();
|
||||
await stream.AsStreamForRead().CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
using var reader = new DataReader(stream);
|
||||
await reader.LoadAsync((uint)stream.Size);
|
||||
byte[] buffer = new byte[stream.Size];
|
||||
reader.ReadBytes(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static float GetAverageLuminance(CanvasBitmap bitmap)
|
||||
@@ -188,5 +153,49 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return (float)(sum / (pixels.Length / 4));
|
||||
}
|
||||
|
||||
public static byte[] MakeSquareWithThemeColor(byte[] imageBytes)
|
||||
{
|
||||
using var image = Image.Load<Rgba32>(imageBytes);
|
||||
|
||||
if (image.Width == image.Height)
|
||||
{
|
||||
// 已经是正方形,直接返回
|
||||
return imageBytes;
|
||||
}
|
||||
|
||||
int size = Math.Max(image.Width, image.Height);
|
||||
|
||||
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());
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] Resize(byte[] imageBytes, int size)
|
||||
{
|
||||
using Image image = Image.Load(imageBytes);
|
||||
var factor = Math.Max((float)size / image.Width, (float)size / image.Height);
|
||||
|
||||
int width = (int)(image.Width * factor);
|
||||
int height = (int)(image.Height * factor);
|
||||
image.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
image.Save(ms, new PngEncoder());
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using TinyPinyin;
|
||||
using Windows.Globalization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
@@ -55,63 +57,72 @@ namespace BetterLyrics.WinUI3.Services
|
||||
_identifier = _factory.Load(PathHelper.LanguageProfilePath);
|
||||
}
|
||||
|
||||
private static string? ThreeLetterToTwoLetter(string? threeLetterCode)
|
||||
{
|
||||
if (threeLetterCode == null) return null;
|
||||
|
||||
foreach (var ci in CultureInfo.GetCultures(CultureTypes.AllCultures))
|
||||
{
|
||||
if (string.Equals(ci.ThreeLetterISOLanguageName, threeLetterCode, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ci.TwoLetterISOLanguageName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? DetectLanguageCode(string? text)
|
||||
{
|
||||
if (text == null) return null;
|
||||
|
||||
string? code = ThreeLetterToTwoLetter(_identifier.Identify(text).FirstOrDefault()?.Item1.Iso639_2T);
|
||||
if (code != null && code == "zh")
|
||||
var guessList = _identifier.Identify(text);
|
||||
string? code = guessList?.FirstOrDefault()?.Item1.Iso639_2T;
|
||||
code = code switch
|
||||
{
|
||||
if (ChineseConverter.ConvertToTraditionalChinese(text) == text)
|
||||
{
|
||||
return "zh-Hant";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "zh-Hans";
|
||||
}
|
||||
}
|
||||
"simple" => "en",
|
||||
"zh_classical" => "zh-Hant",
|
||||
"zh_yue" => "zh-Hant",
|
||||
"zh" => "zh-Hans",
|
||||
_ => code
|
||||
};
|
||||
return code;
|
||||
}
|
||||
|
||||
public static bool IsCJK(string text)
|
||||
{
|
||||
return DetectLanguageCode(text) switch
|
||||
return DetectLanguageCode(text)?.Substring(0, 2) switch
|
||||
{
|
||||
"zh" or "ja" or "ko" => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static string DetectCountryCode(string? text)
|
||||
public static string ConvertToCountryCode(string? languageCode)
|
||||
{
|
||||
if (text == null) return "en";
|
||||
var code = DetectLanguageCode(text);
|
||||
if (code == null) return "en";
|
||||
// 处理中文简体和繁体
|
||||
if (code == "zh-Hans") return "cn";
|
||||
if (code == "zh-Hant") return "cn";
|
||||
// 其他语言直接返回两字母代码
|
||||
return code;
|
||||
if (languageCode == null) return "us";
|
||||
|
||||
return languageCode switch
|
||||
{
|
||||
"zh" => "cn",
|
||||
"zh-Hans" => "cn",
|
||||
"zh-Hant" => "tw",
|
||||
"ja" => "jp",
|
||||
"ko" => "kr",
|
||||
_ => "us"
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetUserTargetLanguageCode()
|
||||
{
|
||||
return SupportedTargetLanguages[_settingsService.SelectedTargetLanguageIndex].Code;
|
||||
}
|
||||
|
||||
public static int GetDefaultTargetLanguageIndex()
|
||||
{
|
||||
int found = SupportedTargetLanguages.FindIndex(x => ApplicationLanguages.Languages.FirstOrDefault()?.Contains(x.Code) == true);
|
||||
if (found == -1) found = 7; // 默认使用英语
|
||||
return found;
|
||||
}
|
||||
|
||||
public static string GetOrderChar(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text)) return "#";
|
||||
char c = text.ElementAtOrDefault(0);
|
||||
if (char.IsLetter(c) && c < 128)
|
||||
return char.ToUpper(c).ToString();
|
||||
|
||||
if (PinyinHelper.IsChinese(c))
|
||||
{
|
||||
return PinyinHelper.GetPinyinInitials($"{c}");
|
||||
}
|
||||
|
||||
return "#";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Nito.AsyncEx;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -9,23 +10,34 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LatestOnlyTaskRunner
|
||||
{
|
||||
private CancellationTokenSource? _cts;
|
||||
private readonly AsyncLock _mutex = new();
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
public async Task RunAsync(Func<CancellationToken, Task> func)
|
||||
public async Task RunAsync(Func<CancellationToken, Task> action)
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
CancellationTokenSource oldCts;
|
||||
|
||||
// 使用 AsyncLock 保证线程安全
|
||||
using (await _mutex.LockAsync())
|
||||
{
|
||||
// 取消旧的
|
||||
oldCts = _cts;
|
||||
_cts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
oldCts?.Cancel();
|
||||
oldCts?.Dispose();
|
||||
|
||||
CancellationToken token = _cts.Token;
|
||||
|
||||
try
|
||||
{
|
||||
await func(token);
|
||||
await action(token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 可以选择忽略取消异常
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LauncherHelper
|
||||
{
|
||||
public static async Task SelectAndShowFile(string filePath)
|
||||
{
|
||||
var file = await StorageFile.GetFileFromPathAsync(filePath);
|
||||
var folder = await file.GetParentAsync();
|
||||
|
||||
var folderOptions = new FolderLauncherOptions();
|
||||
folderOptions.ItemsToSelect.Add(file);
|
||||
|
||||
await Launcher.LaunchFolderAsync(folder, folderOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
break;
|
||||
}
|
||||
}
|
||||
PostProcessLyricsLines(durationMs.Value);
|
||||
_lyricsDataArr.Add(new LyricsData()); // 为机翻预留
|
||||
return _lyricsDataArr;
|
||||
}
|
||||
|
||||
@@ -109,49 +109,57 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
// 按时间分组
|
||||
var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList();
|
||||
int languageCount = grouped.Max(g => g.Count());
|
||||
int languageCount = 0;
|
||||
if (grouped != null && grouped.Count > 0)
|
||||
{
|
||||
// 计算最大语言数量
|
||||
languageCount = grouped.Max(g => g.Count());
|
||||
}
|
||||
|
||||
// 初始化每种语言的歌词列表
|
||||
_lyricsDataArr.Clear();
|
||||
for (int i = 0; i < languageCount; i++)
|
||||
_lyricsDataArr.Add(new LyricsData());
|
||||
for (int i = 0; i < languageCount; i++) _lyricsDataArr.Add(new LyricsData());
|
||||
|
||||
// 遍历每个时间分组
|
||||
foreach (var group in grouped)
|
||||
if (grouped != null)
|
||||
{
|
||||
var linesInGroup = group.ToList();
|
||||
for (int langIdx = 0; langIdx < languageCount; langIdx++)
|
||||
foreach (var group in grouped)
|
||||
{
|
||||
// 如果该语言有翻译,取对应行,否则用原文(第一行)
|
||||
var (start, text, syllables) =
|
||||
langIdx < linesInGroup.Count ? linesInGroup[langIdx] : linesInGroup[0];
|
||||
var line = new LyricsLine
|
||||
var linesInGroup = group.ToList();
|
||||
for (int langIdx = 0; langIdx < languageCount; langIdx++)
|
||||
{
|
||||
StartMs = start,
|
||||
EndMs = 0, // 稍后统一修正
|
||||
OriginalText = text,
|
||||
CharTimings = [],
|
||||
};
|
||||
if (syllables != null && syllables.Count > 0)
|
||||
{
|
||||
int currentIndex = 0;
|
||||
for (int j = 0; j < syllables.Count; j++)
|
||||
// 只添加有对应行的语言,否则跳过
|
||||
if (langIdx < linesInGroup.Count)
|
||||
{
|
||||
var (charStart, charText) = syllables[j];
|
||||
int startIndex = currentIndex;
|
||||
line.CharTimings.Add(
|
||||
new CharTiming
|
||||
var (start, text, syllables) = linesInGroup[langIdx];
|
||||
var line = new LyricsLine
|
||||
{
|
||||
StartMs = start,
|
||||
OriginalText = text,
|
||||
LyricsChars = [],
|
||||
};
|
||||
if (syllables != null && syllables.Count > 0)
|
||||
{
|
||||
int currentIndex = 0;
|
||||
for (int j = 0; j < syllables.Count; j++)
|
||||
{
|
||||
StartMs = charStart,
|
||||
EndMs = 0, // Fixed later
|
||||
Text = charText ?? "",
|
||||
StartIndex = startIndex,
|
||||
var (charStart, charText) = syllables[j];
|
||||
int startIndex = currentIndex;
|
||||
line.LyricsChars.Add(
|
||||
new LyricsChar
|
||||
{
|
||||
StartMs = charStart,
|
||||
Text = charText ?? "",
|
||||
StartIndex = startIndex,
|
||||
}
|
||||
);
|
||||
currentIndex += charText?.Length ?? 0;
|
||||
}
|
||||
);
|
||||
currentIndex += charText?.Length ?? 0;
|
||||
}
|
||||
_lyricsDataArr[langIdx].LyricsLines.Add(line);
|
||||
}
|
||||
// 没有翻译行则不补原文,直接跳过
|
||||
}
|
||||
_lyricsDataArr[langIdx].LyricsLines.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +170,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
List<LyricsLine> originalLines = [];
|
||||
List<LyricsLine> translationLines = [];
|
||||
var xdoc = XDocument.Parse(raw);
|
||||
var xdoc = XDocument.Parse(raw, LoadOptions.PreserveWhitespace);
|
||||
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
|
||||
if (body == null) return;
|
||||
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
|
||||
@@ -170,7 +178,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
// 句级时间
|
||||
string? pBegin = p.Attribute("begin")?.Value;
|
||||
string? pEnd = p.Attribute("end")?.Value;
|
||||
int pStartMs = ParseTtmlTime(pBegin);
|
||||
int pEndMs = ParseTtmlTime(pEnd);
|
||||
|
||||
// 只获取一级span,且排除ttm:role="x-bg"的span
|
||||
var spans = p.Elements()
|
||||
@@ -186,27 +196,31 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
|
||||
.ToList();
|
||||
|
||||
// 原文(非 CJK 语言添加空格)
|
||||
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
if (!LanguageHelper.IsCJK(originalText))
|
||||
// 处理原文span后的空白
|
||||
for (int i = 0; i < originalTextSpans.Count; i++)
|
||||
{
|
||||
foreach (var span in originalTextSpans)
|
||||
var span = originalTextSpans[i];
|
||||
var nextNode = span.NodesAfterSelf().FirstOrDefault();
|
||||
if (nextNode is XText textNode)
|
||||
{
|
||||
span.Value += " ";
|
||||
span.Value += textNode.Value;
|
||||
}
|
||||
originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
}
|
||||
// 拼接空白字符后的原文
|
||||
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
|
||||
var originalCharTimings = new List<CharTiming>();
|
||||
var originalCharTimings = new List<LyricsChar>();
|
||||
int originalStartIndex = 0;
|
||||
foreach (var span in originalTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
string? sEnd = span.Attribute("end")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
originalCharTimings.Add(new CharTiming
|
||||
int sEndMs = ParseTtmlTime(sEnd);
|
||||
originalCharTimings.Add(new LyricsChar
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = 0,
|
||||
EndMs = sEndMs,
|
||||
StartIndex = originalStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
@@ -218,23 +232,25 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
originalLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = 0,
|
||||
EndMs = pEndMs,
|
||||
OriginalText = originalText,
|
||||
CharTimings = originalCharTimings,
|
||||
LyricsChars = originalCharTimings,
|
||||
});
|
||||
|
||||
// 翻译
|
||||
string translationText = string.Concat(translationTextSpans.Select(s => s.Value));
|
||||
var translationCharTimings = new List<CharTiming>();
|
||||
var translationCharTimings = new List<LyricsChar>();
|
||||
int translationStartIndex = 0;
|
||||
foreach (var span in translationTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
string? sEnd = span.Attribute("end")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
translationCharTimings.Add(new CharTiming
|
||||
int sEndMs = ParseTtmlTime(sEnd);
|
||||
translationCharTimings.Add(new LyricsChar
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = 0,
|
||||
EndMs = sEndMs,
|
||||
StartIndex = translationStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
@@ -245,9 +261,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
translationLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = 0,
|
||||
EndMs = pEndMs,
|
||||
OriginalText = translationText,
|
||||
CharTimings = translationCharTimings,
|
||||
LyricsChars = translationCharTimings,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -336,9 +352,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
var lineWrite = new LyricsLine
|
||||
{
|
||||
StartMs = lineRead.StartTime ?? 0,
|
||||
EndMs = 0,
|
||||
EndMs = lineRead.EndTime ?? 0,
|
||||
OriginalText = lineRead.Text,
|
||||
CharTimings = [],
|
||||
LyricsChars = [],
|
||||
};
|
||||
|
||||
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
|
||||
@@ -352,22 +368,14 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
)
|
||||
{
|
||||
var syllable = syllables[syllableIndex];
|
||||
var charTiming = new CharTiming
|
||||
var charTiming = new LyricsChar
|
||||
{
|
||||
StartMs = syllable.StartTime,
|
||||
EndMs = 0,
|
||||
EndMs = syllable.EndTime,
|
||||
Text = syllable.Text,
|
||||
StartIndex = startIndex,
|
||||
};
|
||||
if (syllableIndex + 1 < syllables.Count)
|
||||
{
|
||||
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
charTiming.EndMs = lineWrite.EndMs;
|
||||
}
|
||||
lineWrite.CharTimings.Add(charTiming);
|
||||
lineWrite.LyricsChars.Add(charTiming);
|
||||
startIndex += syllable.Text.Length;
|
||||
}
|
||||
}
|
||||
@@ -378,57 +386,5 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
_lyricsDataArr.Add(new LyricsData(lyricsLines));
|
||||
}
|
||||
|
||||
private void PostProcessLyricsLines(int durationMs)
|
||||
{
|
||||
for (int langIdx = 0; langIdx < _lyricsDataArr.Count; langIdx++)
|
||||
{
|
||||
var lines = _lyricsDataArr[langIdx].LyricsLines;
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
if (i + 1 < lines.Count)
|
||||
{
|
||||
lines[i].EndMs = lines[i + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
lines[i].EndMs = durationMs;
|
||||
}
|
||||
|
||||
// 修正 CharTimings 的 EndMs
|
||||
var timings = lines[i].CharTimings;
|
||||
if (timings.Count > 0)
|
||||
{
|
||||
for (int j = 0; j < timings.Count; j++)
|
||||
{
|
||||
if (j + 1 < timings.Count)
|
||||
{
|
||||
timings[j].EndMs = timings[j + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
timings[j].EndMs = lines[i].EndMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lines.Count > 0)
|
||||
{
|
||||
if (lines[0].StartMs > 0)
|
||||
{
|
||||
lines.Insert(
|
||||
0,
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = lines[0].StartMs,
|
||||
OriginalText = "● ● ●",
|
||||
CharTimings = [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class MonitorHelper
|
||||
{
|
||||
public static IEnumerable<string> GetAllMonitorDeviceNames()
|
||||
{
|
||||
var deviceNames = new List<string>();
|
||||
User32.EnumDisplayMonitors(IntPtr.Zero, null, (hMonitor, hdcMonitor, lprcMonitor, dwData) =>
|
||||
{
|
||||
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
|
||||
if (User32.GetMonitorInfo(hMonitor, ref monitorInfoEx))
|
||||
{
|
||||
deviceNames.Add(monitorInfoEx.szDevice);
|
||||
}
|
||||
return true; // 继续枚举
|
||||
}, IntPtr.Zero);
|
||||
return deviceNames;
|
||||
}
|
||||
|
||||
public static User32.MONITORINFOEX GetMonitorInfoExFromDeviceName(string deviceName)
|
||||
{
|
||||
User32.MONITORINFOEX? result = null;
|
||||
User32.EnumDisplayMonitors(IntPtr.Zero, null, (hMonitor, hdcMonitor, lprcMonitor, dwData) =>
|
||||
{
|
||||
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
|
||||
if (User32.GetMonitorInfo(hMonitor, ref monitorInfoEx))
|
||||
{
|
||||
if (string.Equals(monitorInfoEx.szDevice, deviceName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result = monitorInfoEx;
|
||||
return false; // 找到后停止枚举
|
||||
}
|
||||
}
|
||||
return true; // 继续枚举
|
||||
}, IntPtr.Zero);
|
||||
return result ?? GetPrimaryMonitorInfoEx();
|
||||
}
|
||||
|
||||
public static User32.MONITORINFOEX GetPrimaryMonitorInfoEx()
|
||||
{
|
||||
// (0,0) 总是在主屏
|
||||
var ptZero = new POINT(0, 0);
|
||||
HMONITOR hMonitor = User32.MonitorFromPoint(ptZero, User32.MonitorFlags.MONITOR_DEFAULTTOPRIMARY);
|
||||
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
|
||||
User32.GetMonitorInfo(hMonitor, ref monitorInfoEx);
|
||||
return monitorInfoEx;
|
||||
}
|
||||
|
||||
public static string GetPrimaryMonitorDeviceName()
|
||||
{
|
||||
var primaryMonitorInfo = GetPrimaryMonitorInfoEx();
|
||||
return primaryMonitorInfo.szDevice;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/NetHelper.cs
Normal file
26
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/NetHelper.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class NetHelper
|
||||
{
|
||||
public static async Task<bool> CheckConnectivity(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new System.Net.Http.HttpClient();
|
||||
// Try to reach a reliable endpoint
|
||||
var res = await client.GetAsync(url);
|
||||
return res.IsSuccessStatusCode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false; // If any exception occurs, assume no connectivity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class ObjectHelper
|
||||
{
|
||||
// Credit/Copyright to https://gist.github.com/tcartwright/dab50ebaff7c59f05013de0fb349cabd
|
||||
public static bool IsDisposed(this IDisposable obj)
|
||||
{
|
||||
/*
|
||||
TIM C: This hacky code is because MSFT does not provide a standard way to interrogate if an object is disposed or not.
|
||||
I wrote this based upon streams, but it should work for many other types of MSFT objects (maybe).
|
||||
*/
|
||||
if (obj == null) { return true; }
|
||||
|
||||
var objType = obj.GetType();
|
||||
//var foo = new System.IO.BufferedStream();
|
||||
|
||||
// the _disposed pattern should catch a lot of msft objects.... hopefully
|
||||
var isDisposedField = objType.GetField("_disposed", BindingFlags.NonPublic | BindingFlags.Instance) ??
|
||||
objType.GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (isDisposedField != null) { return Convert.ToBoolean(isDisposedField.GetValue(obj)); }
|
||||
|
||||
isDisposedField = objType.GetField("_isOpen", 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)
|
||||
{
|
||||
var strategy = strategyField.GetValue(obj);
|
||||
var isClosedField = strategy.GetType().GetProperty("IsClosed", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (isClosedField != null) { return Convert.ToBoolean(isClosedField.GetValue(strategy)); }
|
||||
}
|
||||
|
||||
// other streams that use this pattern to determine if they are disposed
|
||||
if (obj is Stream stream) { return !stream.CanRead && !stream.CanWrite; }
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
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 LanguageProfilePath => Path.Combine(AssetsFolder, "Core14.profile.xml");
|
||||
public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Wiki82.profile.xml");
|
||||
public static string LogoPath => Path.Combine(AssetsFolder, "Logo.ico");
|
||||
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
@@ -33,6 +35,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static string TranslationCacheDirectory => Path.Combine(CacheFolder, "translations");
|
||||
|
||||
public static string QQTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "qq");
|
||||
public static string NeteaseTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "netease");
|
||||
|
||||
public static string AlbumArtCacheDirectory => Path.Combine(CacheFolder, "album-art");
|
||||
|
||||
@@ -49,6 +52,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(QQTranslationCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseTranslationCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System;
|
||||
using Vanara.Extensions;
|
||||
using Vanara.PInvoke;
|
||||
using static Vanara.PInvoke.CoreAudio;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class SystemVolumeHelper
|
||||
{
|
||||
private readonly static IMMDeviceEnumerator _deviceEnumerator = new();
|
||||
private static IAudioEndpointVolume? _endpointVolume = null;
|
||||
private static VolumeCallbackImpl? _callbackImpl;
|
||||
private static int _masterVolume = 0;
|
||||
private static DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
public static event Action<int>? VolumeChanged;
|
||||
|
||||
static SystemVolumeHelper()
|
||||
{
|
||||
var device = _deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
|
||||
if (device != null)
|
||||
{
|
||||
device.Activate(typeof(IAudioEndpointVolume).GUID, 0, null, out var obj);
|
||||
if (obj is IAudioEndpointVolume endpointVolume)
|
||||
{
|
||||
_endpointVolume = endpointVolume;
|
||||
_callbackImpl = new VolumeCallbackImpl();
|
||||
_endpointVolume.RegisterControlChangeNotify(_callbackImpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前系统主音量(0~100)。
|
||||
/// </summary>
|
||||
public static int GetMasterVolume()
|
||||
{
|
||||
if (_endpointVolume != null)
|
||||
{
|
||||
float level = _endpointVolume.GetMasterVolumeLevelScalar();
|
||||
_masterVolume = (int)(level * 100);
|
||||
}
|
||||
|
||||
return _masterVolume;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前系统主音量(0~100)。
|
||||
/// </summary>
|
||||
public static void SetMasterVolume(int volume)
|
||||
{
|
||||
if (_masterVolume == volume) return;
|
||||
|
||||
_masterVolume = volume;
|
||||
_endpointVolume?.SetMasterVolumeLevelScalar(_masterVolume / 100f, Guid.Empty);
|
||||
}
|
||||
|
||||
// 内部回调实现
|
||||
private class VolumeCallbackImpl : IAudioEndpointVolumeCallback
|
||||
{
|
||||
HRESULT IAudioEndpointVolumeCallback.OnNotify(nint pNotify)
|
||||
{
|
||||
var data = pNotify.ToStructure<AUDIO_VOLUME_NOTIFICATION_DATA>();
|
||||
_masterVolume = (int)(data.fMasterVolume * 100);
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
VolumeChanged?.Invoke(_masterVolume);
|
||||
});
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ThrottleHelper
|
||||
{
|
||||
private DateTime _lastTriggerTime = DateTime.MinValue;
|
||||
private readonly TimeSpan _interval;
|
||||
|
||||
public ThrottleHelper(TimeSpan interval)
|
||||
{
|
||||
_interval = interval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可以触发(距离上次触发已超过设定间隔),如果可以则更新时间戳并返回 true,否则返回 false。
|
||||
/// </summary>
|
||||
public bool CanTrigger()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
if ((now - _lastTriggerTime) >= _interval)
|
||||
{
|
||||
_lastTriggerTime = now;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置触发时间
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_lastTriggerTime = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
if (!_isTransitioning) return;
|
||||
|
||||
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
|
||||
_progress += (float)(elapsedTime / TimeSpan.FromSeconds(_durationSeconds));
|
||||
if (_progress >= 1f)
|
||||
{
|
||||
_progress = 1f;
|
||||
|
||||
@@ -26,17 +26,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExitAllWindows()
|
||||
{
|
||||
while (_activeWindows.Count > 0)
|
||||
{
|
||||
var window = _activeWindows[0];
|
||||
((Window)window).Close();
|
||||
_activeWindows.Remove(window);
|
||||
}
|
||||
App.Current.Exit();
|
||||
}
|
||||
|
||||
public static T? GetWindowByWindowType<T>()
|
||||
{
|
||||
foreach (var window in _activeWindows)
|
||||
@@ -48,33 +37,33 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return default;
|
||||
}
|
||||
public static void OpenOrShowWindow<T>()
|
||||
public static void OpenWindow<T>()
|
||||
{
|
||||
var window = _activeWindows.Find(w => w is T);
|
||||
if (window != null)
|
||||
if (window == null)
|
||||
{
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Restore();
|
||||
}
|
||||
else
|
||||
{
|
||||
object newWindow;
|
||||
if (typeof(T) == typeof(LyricsWindow))
|
||||
{
|
||||
newWindow = new LyricsWindow();
|
||||
((LyricsWindow)newWindow).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
window = new LyricsWindow();
|
||||
((LyricsWindow)window).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
}
|
||||
else if (typeof(T) == typeof(SettingsWindow))
|
||||
{
|
||||
newWindow = new SettingsWindow();
|
||||
window = new SettingsWindow();
|
||||
}
|
||||
else if (typeof(T) == typeof(MusicGalleryWindow))
|
||||
{
|
||||
window = new MusicGalleryWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Unsupported window type", nameof(T));
|
||||
}
|
||||
((Window)newWindow).Activate();
|
||||
TrackWindow(newWindow);
|
||||
TrackWindow(window);
|
||||
}
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Restore();
|
||||
castedWindow.Activate();
|
||||
}
|
||||
|
||||
public static void RestartApp(string args = "")
|
||||
@@ -97,10 +86,32 @@ 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))
|
||||
{
|
||||
_activeWindows.Add(window);
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Closed += WindowHelper_Closed;
|
||||
}
|
||||
}
|
||||
|
||||
private static void WindowHelper_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
if (_activeWindows.Contains(sender))
|
||||
{
|
||||
_activeWindows.Remove(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class GroupInfoList : List<object>
|
||||
{
|
||||
public required object Key { get; set; }
|
||||
|
||||
public GroupInfoList(IEnumerable<object> items, Func<object, object>? orderSelector = null)
|
||||
: base(orderSelector != null
|
||||
? items.OrderBy(orderSelector)
|
||||
: items)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Key}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class LocalLyricsFolder : ObservableObject
|
||||
public partial class LocalMediaFolder : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
@@ -12,9 +12,9 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty]
|
||||
public partial string Path { get; set; }
|
||||
|
||||
public LocalLyricsFolder() { }
|
||||
public LocalMediaFolder() { }
|
||||
|
||||
public LocalLyricsFolder(string path, bool isEnabled)
|
||||
public LocalMediaFolder(string path, bool isEnabled)
|
||||
{
|
||||
Path = path;
|
||||
IsEnabled = isEnabled;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class CharTiming
|
||||
public class LyricsChar
|
||||
{
|
||||
public int EndMs { get; set; }
|
||||
public int? EndMs { get; set; }
|
||||
public int StartIndex { get; set; }
|
||||
public int StartMs { get; set; }
|
||||
public string Text { get; set; } = string.Empty;
|
||||
@@ -1,10 +1,12 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using StringHelper = BetterLyrics.WinUI3.Helper.StringHelper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
@@ -24,20 +26,39 @@ namespace BetterLyrics.WinUI3.Models
|
||||
LyricsLines = lyricsLines;
|
||||
}
|
||||
|
||||
public void SetDisplayedTextAlongWith(LyricsData translationData)
|
||||
public void SetDisplayedTextAlongWith(LyricsData translationData, int toleranceMs = 0)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
if (i >= translationData.LyricsLines.Count)
|
||||
// 在翻译歌词中查找与当前行开始时间最接近且在容忍范围内的行
|
||||
var transLine = translationData.LyricsLines
|
||||
.FirstOrDefault(t => Math.Abs(t.StartMs - line.StartMs) <= toleranceMs);
|
||||
|
||||
if (transLine != null)
|
||||
{
|
||||
line.DisplayedText = line.OriginalText; // No translation available, keep original text
|
||||
if (translationData.LanguageCode?.StartsWith("zh") == true)
|
||||
{
|
||||
string tmp = "";
|
||||
if (LanguageHelper.GetUserTargetLanguageCode() == "zh-Hant")
|
||||
{
|
||||
tmp = ChineseConverter.ConvertToTraditionalChinese(transLine.OriginalText);
|
||||
}
|
||||
else if (LanguageHelper.GetUserTargetLanguageCode() == "zh-Hans")
|
||||
{
|
||||
tmp = ChineseConverter.ConvertToSimplifiedChinese(transLine.OriginalText);
|
||||
}
|
||||
line.DisplayedText = $"{line.OriginalText}\n{tmp}";
|
||||
}
|
||||
else
|
||||
{
|
||||
line.DisplayedText = $"{line.OriginalText}\n{transLine.OriginalText}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationData.LyricsLines[i].OriginalText})";
|
||||
// 没有匹配的翻译,翻译部分留空
|
||||
line.DisplayedText = $"{line.OriginalText}";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +74,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
}
|
||||
else
|
||||
{
|
||||
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationArr[i]})";
|
||||
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}{translationArr[i]}";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
@@ -67,6 +88,30 @@ namespace BetterLyrics.WinUI3.Models
|
||||
}
|
||||
}
|
||||
|
||||
public LyricsData CreateLyricsDataFrom(string translation)
|
||||
{
|
||||
var result = new LyricsData(LyricsLines.Select(line => new LyricsLine
|
||||
{
|
||||
StartMs = line.StartMs,
|
||||
EndMs = line.EndMs,
|
||||
}).ToList());
|
||||
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
|
||||
int i = 0;
|
||||
foreach (var line in result.LyricsLines)
|
||||
{
|
||||
if (i >= translationArr.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
line.OriginalText = translationArr[i];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static LyricsData GetNotfoundPlaceholder(int durationMs)
|
||||
{
|
||||
return new LyricsData([new LyricsLine
|
||||
@@ -74,7 +119,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
StartMs = 0,
|
||||
EndMs = durationMs,
|
||||
OriginalText = App.ResourceLoader!.GetString("LyricsNotFound"),
|
||||
CharTimings = [],
|
||||
LyricsChars = [],
|
||||
}]);
|
||||
}
|
||||
|
||||
@@ -87,7 +132,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
|
||||
OriginalText = "● ● ●",
|
||||
DisplayedText = "● ● ●",
|
||||
CharTimings = [],
|
||||
LyricsChars = [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,131 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
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);
|
||||
public ValueTransition<float> AngleTransition { get; set; } = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: _animationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
public ValueTransition<float> BlurAmountTransition { get; set; } = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: _animationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
public ValueTransition<float> HighlightOpacityTransition { get; set; } = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: _animationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
public ValueTransition<float> OpacityTransition { get; set; } = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: _animationDuration,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
public ValueTransition<float> ScaleTransition { get; set; } = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: _animationDuration,
|
||||
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<CharTiming> CharTimings { get; set; } = [];
|
||||
public List<LyricsChar> LyricsChars { get; set; } = [];
|
||||
|
||||
public int DurationMs => EndMs - StartMs;
|
||||
public int EndMs { get; set; }
|
||||
public int? DurationMs => EndMs - StartMs;
|
||||
public int? EndMs { get; set; }
|
||||
public int StartMs { get; set; }
|
||||
|
||||
public string DisplayedText { get; set; } = "";
|
||||
public string OriginalText { get; set; } = "";
|
||||
|
||||
public CanvasGeometry? TextGeometry { get; private set; }
|
||||
|
||||
public CanvasCommandList? BackgroundFontEffect { get; private set; }
|
||||
public CanvasCommandList? ForegroundFontEffect { get; private set; }
|
||||
|
||||
public void UpdateCenterPosition(float maxWidth, TextAlignmentType type)
|
||||
{
|
||||
if (CanvasTextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
float centerY = Position.Y + (float)CanvasTextLayout.LayoutBounds.Height;
|
||||
CenterPosition = type switch
|
||||
{
|
||||
TextAlignmentType.Left => new Vector2(Position.X, centerY),
|
||||
TextAlignmentType.Center => new Vector2(Position.X + maxWidth / 2, centerY),
|
||||
TextAlignmentType.Right => new Vector2(Position.X + maxWidth, centerY),
|
||||
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateTextLayout(ICanvasAnimatedControl control, CanvasTextFormat textFormat, float maxWidth, float maxHeight, TextAlignmentType type)
|
||||
{
|
||||
CanvasTextLayout?.Dispose();
|
||||
CanvasTextLayout = null;
|
||||
CanvasTextLayout = new CanvasTextLayout(control, DisplayedText, textFormat, maxWidth, maxHeight);
|
||||
CanvasTextLayout.HorizontalAlignment = type.ToCanvasHorizontalAlignment();
|
||||
}
|
||||
|
||||
public void DisposeTextGeometry()
|
||||
{
|
||||
TextGeometry?.Dispose();
|
||||
TextGeometry = null;
|
||||
}
|
||||
|
||||
public void UpdateTextGeometry()
|
||||
{
|
||||
DisposeTextGeometry();
|
||||
if (CanvasTextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
TextGeometry = CanvasGeometry.CreateText(CanvasTextLayout);
|
||||
}
|
||||
|
||||
public void DisposeFontEffects()
|
||||
{
|
||||
BackgroundFontEffect?.Dispose();
|
||||
BackgroundFontEffect = null;
|
||||
ForegroundFontEffect?.Dispose();
|
||||
ForegroundFontEffect = null;
|
||||
}
|
||||
|
||||
public void UpdateFontEffect(ICanvasAnimatedControl control, bool drawStroke, Color strokeColor, int strokeWidth, Color fontColor)
|
||||
{
|
||||
DisposeFontEffects();
|
||||
if (TextGeometry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BackgroundFontEffect = new CanvasCommandList(control);
|
||||
using var bgFontEffectDs = BackgroundFontEffect.CreateDrawingSession();
|
||||
ForegroundFontEffect = new CanvasCommandList(control);
|
||||
using var fgFontEffectDs = ForegroundFontEffect.CreateDrawingSession();
|
||||
if (drawStroke)
|
||||
{
|
||||
bgFontEffectDs.DrawGeometry(TextGeometry, Position, strokeColor, strokeWidth); // 描边
|
||||
fgFontEffectDs.DrawGeometry(TextGeometry, Position, strokeColor, strokeWidth); // 描边
|
||||
}
|
||||
bgFontEffectDs.FillGeometry(TextGeometry, Position, fontColor); // 填充
|
||||
fgFontEffectDs.FillGeometry(TextGeometry, Position, fontColor); // 填充
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using ATL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class PlayQueueItem
|
||||
{
|
||||
public Track Track { get; set; }
|
||||
|
||||
public PlayQueueItem(Track track)
|
||||
{
|
||||
Track = track;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,9 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty]
|
||||
public partial string Artist { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int? Duration { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double? DurationMs { get; set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class SongsTabInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Icon { get; set; }
|
||||
|
||||
public bool IsClosable { get; set; }
|
||||
|
||||
public CommonSongProperty FilterProperty { get; set; }
|
||||
|
||||
public string FilterValue { get; set; }
|
||||
|
||||
public SongsTabInfo()
|
||||
{
|
||||
Name = string.Empty;
|
||||
Icon = string.Empty;
|
||||
IsClosable = true;
|
||||
FilterProperty = CommonSongProperty.Title;
|
||||
FilterValue = string.Empty;
|
||||
}
|
||||
|
||||
public SongsTabInfo(string name, string icon, bool isClosable, CommonSongProperty filterProperty, string filterValue)
|
||||
{
|
||||
Name = name;
|
||||
Icon = icon;
|
||||
IsClosable = isClosable;
|
||||
FilterProperty = filterProperty;
|
||||
FilterValue = filterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class TrimmedTrack
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Artist { get; set; }
|
||||
public string Album { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public string Genre { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
public int Duration { get; set; }
|
||||
public byte[]? AlbumArt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace BetterLyrics.WinUI3.Serialization
|
||||
[JsonSerializable(typeof(List<AlbumArtSearchProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<LyricsSearchProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<MediaSourceProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<LocalLyricsFolder>))]
|
||||
[JsonSerializable(typeof(List<LocalMediaFolder>))]
|
||||
[JsonSerializable(typeof(List<string>))]
|
||||
[JsonSerializable(typeof(TranslateResponse))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
|
||||
@@ -7,8 +7,10 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
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;
|
||||
|
||||
@@ -48,7 +50,11 @@ namespace BetterLyrics.WinUI3.Services
|
||||
result = bytesFromSMTC;
|
||||
break;
|
||||
case AlbumArtSearchProvider.iTunes:
|
||||
result = await SearchiTunesAsync(artist, album);
|
||||
foreach (string countryCode in new List<string>() { "us", "cn", "jp", "kr" })
|
||||
{
|
||||
result = await SearchiTunesAsync(artist, album, title, countryCode);
|
||||
if (result != null) break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -61,7 +67,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private byte[]? SearchFile(string artist, string album)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
foreach (var folder in _settingsService.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
@@ -82,7 +88,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<byte[]?> SearchiTunesAsync(string artist, string album)
|
||||
private async Task<byte[]?> SearchiTunesAsync(string artist, string album, string title, string countryCode)
|
||||
{
|
||||
// Source: https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce
|
||||
try
|
||||
@@ -96,11 +102,10 @@ namespace BetterLyrics.WinUI3.Services
|
||||
}
|
||||
|
||||
// Build the iTunes API URL
|
||||
string url = $"https://itunes.apple.com/search?term=" + artist + "+" + album + "&country=" + LanguageHelper.DetectCountryCode(album + artist) + "&entity=album";
|
||||
url.Replace(" ", "-");
|
||||
// Make a request to the API
|
||||
string url = $"https://itunes.apple.com/search?term=" + WebUtility.UrlEncode($"{artist} {album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
|
||||
|
||||
HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
|
||||
// Make a request to the API
|
||||
using HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
|
||||
public void UpdateWatchers(List<LocalLyricsFolder> folders);
|
||||
public void UpdateWatchers(List<LocalMediaFolder> folders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface ILyricsSearchService
|
||||
{
|
||||
Task<string?> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
Task<(string?, LyricsSearchProvider?)> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
@@ -9,9 +10,18 @@ namespace BetterLyrics.WinUI3.Services
|
||||
public interface IPlaybackService
|
||||
{
|
||||
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
|
||||
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
|
||||
event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
|
||||
Task PlayAsync();
|
||||
Task PauseAsync();
|
||||
Task PreviousAsync();
|
||||
Task NextAsync();
|
||||
Task ChangePosition(double seconds);
|
||||
|
||||
bool IsPlaying { get; }
|
||||
SongInfo? SongInfo { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
using System.Collections.Generic;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
@@ -21,6 +19,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
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; }
|
||||
@@ -39,9 +38,11 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
string LibreTranslateServer { get; set; }
|
||||
int SelectedTargetLanguageIndex { get; set; }
|
||||
bool ResetPositionOffsetOnSongChanged { get; set; }
|
||||
int PositionOffset { get; set; }
|
||||
// Lyrics lib
|
||||
|
||||
List<LocalLyricsFolder> LocalLyricsFolders { get; set; }
|
||||
List<LocalMediaFolder> LocalMediaFolders { get; set; }
|
||||
|
||||
// Lyrics style and effetc
|
||||
|
||||
@@ -54,11 +55,15 @@ namespace BetterLyrics.WinUI3.Services
|
||||
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 LyricsFontSize { get; set; }
|
||||
int LyricsStandardFontSize { get; set; }
|
||||
int LyricsDockFontSize { get; set; }
|
||||
int LyricsDesktopFontSize { get; set; }
|
||||
|
||||
ElementTheme LyricsBackgroundTheme { get; set; }
|
||||
|
||||
@@ -69,6 +74,8 @@ namespace BetterLyrics.WinUI3.Services
|
||||
LineRenderingType LyricsGlowEffectScope { get; set; }
|
||||
LineRenderingType LyricsHighlightScope { get; set; }
|
||||
|
||||
bool IsLyricsFloatAnimationEnabled { get; set; }
|
||||
|
||||
float LyricsLineSpacingFactor { get; set; }
|
||||
|
||||
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
@@ -83,9 +90,22 @@ namespace BetterLyrics.WinUI3.Services
|
||||
bool IgnoreFullscreenWindow { get; set; }
|
||||
|
||||
bool IsTranslationEnabled { get; set; }
|
||||
bool ShowTranslationOnly { get; set; }
|
||||
|
||||
LyricsDisplayType PreferredDisplayType { 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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,18 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class LibWatcherService : IDisposable, ILibWatcherService
|
||||
public class LibWatcherService : BaseViewModel, IDisposable, ILibWatcherService
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
private readonly Dictionary<string, FileSystemWatcher> _watchers = [];
|
||||
|
||||
public LibWatcherService(ISettingsService settingsService)
|
||||
public LibWatcherService(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
UpdateWatchers(_settingsService.LocalLyricsFolders);
|
||||
UpdateWatchers(_settingsService.LocalMediaFolders);
|
||||
}
|
||||
|
||||
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
@@ -32,7 +31,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
_watchers.Clear();
|
||||
}
|
||||
|
||||
public void UpdateWatchers(List<LocalLyricsFolder> folders)
|
||||
public void UpdateWatchers(List<LocalMediaFolder> folders)
|
||||
{
|
||||
// 移除不再监听的
|
||||
foreach (var key in _watchers.Keys.ToList())
|
||||
@@ -69,16 +68,13 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private void OnChanged(string folder, FileSystemEventArgs e)
|
||||
{
|
||||
App.DispatcherQueue!.TryEnqueue(
|
||||
Microsoft.UI.Dispatching.DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
MusicLibraryFilesChanged?.Invoke(
|
||||
this,
|
||||
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
|
||||
);
|
||||
}
|
||||
);
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
MusicLibraryFilesChanged?.Invoke(
|
||||
this,
|
||||
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Providers.Web.Kugou;
|
||||
using Lyricify.Lyrics.Searchers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NTextCat.Commons;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -85,86 +86,90 @@ namespace BetterLyrics.WinUI3.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
public async Task<(string?, LyricsSearchProvider?)> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
|
||||
|
||||
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
|
||||
try
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string? cachedLyrics;
|
||||
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
|
||||
|
||||
// Check cache first
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
return cachedLyrics;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
string? searchedLyrics = null;
|
||||
string? cachedLyrics;
|
||||
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
|
||||
|
||||
if (provider.Provider.IsLocal())
|
||||
{
|
||||
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
|
||||
// Check cache first
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
searchedLyrics = SearchEmbedded(title, artist);
|
||||
cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
{
|
||||
return (cachedLyrics, provider.Provider);
|
||||
}
|
||||
}
|
||||
|
||||
string? searchedLyrics = null;
|
||||
|
||||
if (provider.Provider.IsLocal())
|
||||
{
|
||||
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
|
||||
{
|
||||
searchedLyrics = SearchEmbedded(title, artist);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchedLyrics = await SearchFile(title, artist, lyricsFormat);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
searchedLyrics = await SearchFile(title, artist, lyricsFormat);
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (provider.Provider)
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchedLyrics))
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
}
|
||||
|
||||
return (searchedLyrics, provider.Provider);
|
||||
}
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchedLyrics))
|
||||
{
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
}
|
||||
|
||||
return searchedLyrics;
|
||||
}
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
return null;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private async Task<string?> SearchFile(string title, string artist, LyricsFormat format)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
foreach (var folder in _settingsService.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
@@ -186,7 +191,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private string? SearchEmbedded(string title, string artist)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
foreach (var folder in _settingsService.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
@@ -266,7 +271,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
|
||||
try
|
||||
{
|
||||
var response = await _amllTtmlDbHttpClient.GetAsync(url);
|
||||
using var response = await _amllTtmlDbHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
@@ -287,7 +292,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
$"&album_name={Uri.EscapeDataString(album)}" +
|
||||
$"&durationMs={Uri.EscapeDataString(duration.ToString())}";
|
||||
|
||||
var response = await _lrcLibHttpClient.GetAsync(url);
|
||||
using var response = await _lrcLibHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
@@ -327,11 +332,33 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.QQMusicApi.GetLyricsAsync(qqResult.Id);
|
||||
var original = response?.Lyrics;
|
||||
var translated = response?.Trans;
|
||||
if (!string.IsNullOrEmpty(translated))
|
||||
{
|
||||
FileHelper.WriteLyricsCache(
|
||||
title,
|
||||
artist,
|
||||
translated,
|
||||
LyricsFormat.Lrc,
|
||||
PathHelper.QQTranslationCacheDirectory
|
||||
);
|
||||
}
|
||||
return original;
|
||||
}
|
||||
else if (result is NeteaseSearchResult neteaseResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
|
||||
var translated = response?.Tlyric?.Lyric;
|
||||
if (!string.IsNullOrEmpty(translated))
|
||||
{
|
||||
FileHelper.WriteLyricsCache(
|
||||
title,
|
||||
artist,
|
||||
translated,
|
||||
LyricsFormat.Lrc,
|
||||
PathHelper.NeteaseTranslationCacheDirectory
|
||||
);
|
||||
}
|
||||
return response?.Lrc.Lyric;
|
||||
}
|
||||
else if (result is KugouSearchResult kugouResult)
|
||||
|
||||
@@ -7,19 +7,27 @@ using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using EvtSource;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Media.Control;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI.Shell;
|
||||
using WindowsMediaController;
|
||||
using static WindowsMediaController.MediaManager;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
@@ -29,17 +37,27 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
private readonly IAlbumArtSearchService _albumArtSearchService;
|
||||
private readonly ILogger<PlaybackService> _logger;
|
||||
|
||||
private readonly string _lxMusicId = "cn.toside.music.desktop";
|
||||
private double _lxMusicPositionSeconds = 0;
|
||||
private double _lxMusicDurationSeconds = 0;
|
||||
|
||||
private bool _cachedIsPlaying = false;
|
||||
|
||||
private EventSourceReader? _sse = null;
|
||||
|
||||
private readonly MediaManager _mediaManager = new();
|
||||
private readonly LatestOnlyTaskRunner _AlbumArtRefreshRunner = new();
|
||||
private readonly LatestOnlyTaskRunner _OnAnyMediaPropertyChangedRunner = new();
|
||||
|
||||
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
|
||||
private readonly LatestOnlyTaskRunner _onAnyMediaPropertyChangedRunner = new();
|
||||
|
||||
private SongInfo? _cachedSongInfo;
|
||||
private List<MediaSourceProviderInfo> _mediaSourceProvidersInfo;
|
||||
private byte[]? _SMTCAlbumArtBytes = null;
|
||||
private AlbumArtChangedEventArgs _albumArtChangedEventArgs = new AlbumArtChangedEventArgs();
|
||||
private int _targetAlbumArtSize = 400;
|
||||
|
||||
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
public event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
|
||||
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
|
||||
public event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
@@ -53,6 +71,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
InitMediaManager();
|
||||
}
|
||||
|
||||
public bool IsPlaying => _cachedIsPlaying;
|
||||
public SongInfo? SongInfo => _cachedSongInfo;
|
||||
|
||||
private bool IsMediaSourceEnabled(string id)
|
||||
{
|
||||
return _mediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id)?.IsEnabled ?? true;
|
||||
@@ -60,8 +81,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private void InitMediaManager()
|
||||
{
|
||||
_mediaManager.Start();
|
||||
|
||||
_mediaManager.OnAnySessionOpened += MediaManager_OnAnySessionOpened;
|
||||
_mediaManager.OnAnySessionClosed += MediaManager_OnAnySessionClosed;
|
||||
_mediaManager.OnFocusedSessionChanged += MediaManager_OnFocusedSessionChanged;
|
||||
@@ -69,110 +88,123 @@ namespace BetterLyrics.WinUI3.Services
|
||||
_mediaManager.OnAnyPlaybackStateChanged += MediaManager_OnAnyPlaybackStateChanged;
|
||||
_mediaManager.OnAnyTimelinePropertyChanged += MediaManager_OnAnyTimelinePropertyChanged;
|
||||
|
||||
MediaManager_OnFocusedSessionChanged(_mediaManager.GetFocusedSession());
|
||||
_mediaManager.Start();
|
||||
Task.Run(() =>
|
||||
{
|
||||
MediaManager_OnFocusedSessionChanged(null);
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession mediaSession)
|
||||
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
|
||||
{
|
||||
if (mediaSession == null || !IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId))
|
||||
{
|
||||
SendNullMessages();
|
||||
}
|
||||
else
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var props = await mediaSession.ControlSession.TryGetMediaPropertiesAsync();
|
||||
MediaManager_OnAnyMediaPropertyChanged(mediaSession, props);
|
||||
MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "TryGetMediaPropertiesAsync failed");
|
||||
SendNullMessages();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
|
||||
SendFocusedMessagesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties)
|
||||
{
|
||||
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
PositionChanged?.Invoke(this, new PositionChangedEventArgs(timelineProperties.Position));
|
||||
}
|
||||
);
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id) || mediaSession != focusedSession) return;
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(timelineProperties.Position, timelineProperties.EndTime));
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
|
||||
{
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(playbackInfo.PlaybackStatus switch
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
|
||||
_ => false,
|
||||
}));
|
||||
}
|
||||
);
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id) || mediaSession != focusedSession) return;
|
||||
|
||||
_cachedIsPlaying = playbackInfo.PlaybackStatus switch
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
|
||||
{
|
||||
_ = _OnAnyMediaPropertyChangedRunner.RunAsync(async token =>
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
string id = mediaSession.Id;
|
||||
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (!IsMediaSourceEnabled(id) || mediaSession != focusedSession) return;
|
||||
|
||||
_cachedSongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProperties.Title,
|
||||
Artist = mediaProperties.Artist,
|
||||
Album = mediaProperties.AlbumTitle,
|
||||
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = id,
|
||||
};
|
||||
|
||||
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
|
||||
|
||||
_onAnyMediaPropertyChangedRunner.RunAsync(async token =>
|
||||
{
|
||||
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
|
||||
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
string id = mediaSession.ControlSession.SourceAppUserModelId;
|
||||
if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_cachedSongInfo = new SongInfo
|
||||
if (id == _lxMusicId)
|
||||
{
|
||||
Title = mediaProperties.Title,
|
||||
Artist = mediaProperties.Artist,
|
||||
Album = mediaProperties.AlbumTitle,
|
||||
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = id,
|
||||
};
|
||||
StartSSE();
|
||||
}
|
||||
else
|
||||
{
|
||||
StopSSE();
|
||||
}
|
||||
|
||||
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
_SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
else
|
||||
{
|
||||
_SMTCAlbumArtBytes = null;
|
||||
}
|
||||
|
||||
_ = _AlbumArtRefreshRunner.RunAsync(async tokne =>
|
||||
await _albumArtRefreshRunner.RunAsync(async tokne =>
|
||||
{
|
||||
await UpdateAlbumArtRelated(tokne);
|
||||
});
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
});
|
||||
}
|
||||
});
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
if (_mediaManager.CurrentMediaSessions.Count == 0)
|
||||
{
|
||||
SendNullMessages();
|
||||
@@ -181,12 +213,19 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private void MediaManager_OnAnySessionOpened(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
SendFocusedMessagesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void RecordMediaSourceProviderInfo(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
var id = mediaSession?.ControlSession?.SourceAppUserModelId;
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
var id = mediaSession?.Id;
|
||||
if (string.IsNullOrEmpty(id)) return;
|
||||
|
||||
var found = _mediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == id);
|
||||
@@ -194,8 +233,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
_mediaSourceProvidersInfo.Add(new MediaSourceProviderInfo(id, true));
|
||||
_settingsService.MediaSourceProvidersInfo = _mediaSourceProvidersInfo;
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
MediaSourceProvidersInfoChanged?.Invoke(this, new MediaSourceProvidersInfoEventArgs(_mediaSourceProvidersInfo));
|
||||
});
|
||||
@@ -204,16 +242,27 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private void SendNullMessages()
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
_cachedSongInfo = null;
|
||||
_cachedIsPlaying = false;
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(false));
|
||||
PositionChanged?.Invoke(this, new PositionChangedEventArgs(TimeSpan.Zero));
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(TimeSpan.Zero, TimeSpan.Zero));
|
||||
});
|
||||
}
|
||||
|
||||
private async Task SendFocusedMessagesAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession == null || focusedSession.ControlSession == null) return;
|
||||
|
||||
var mediaProps = await focusedSession.ControlSession.TryGetMediaPropertiesAsync();
|
||||
MediaManager_OnAnyMediaPropertyChanged(focusedSession, mediaProps);
|
||||
MediaManager_OnAnyPlaybackStateChanged(focusedSession, focusedSession.ControlSession.GetPlaybackInfo());
|
||||
MediaManager_OnAnyTimelinePropertyChanged(focusedSession, focusedSession.ControlSession.GetTimelineProperties());
|
||||
}
|
||||
|
||||
private async Task UpdateAlbumArtRelated(CancellationToken token)
|
||||
{
|
||||
if (_cachedSongInfo == null)
|
||||
@@ -232,10 +281,13 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
if (bytes == null)
|
||||
{
|
||||
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync($"{_cachedSongInfo!.Artist} - {_cachedSongInfo.Title}", 400, 400);
|
||||
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(_targetAlbumArtSize, _targetAlbumArtSize);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
|
||||
bytes = ImageHelper.Resize(bytes, _targetAlbumArtSize);
|
||||
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
token.ThrowIfCancellationRequested();
|
||||
@@ -243,18 +295,125 @@ namespace BetterLyrics.WinUI3.Services
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_albumArtChangedEventArgs.AlbumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_albumArtChangedEventArgs.AlbumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
|
||||
var albumArtLightAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, false).FirstOrDefault();
|
||||
var albumArtDarkAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, true).FirstOrDefault();
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
AlbumArtChangedChanged?.Invoke(this, _albumArtChangedEventArgs);
|
||||
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
|
||||
});
|
||||
}
|
||||
|
||||
private void StartSSE()
|
||||
{
|
||||
try
|
||||
{
|
||||
_sse = new EventSourceReader(new Uri($"{_settingsService.LXMusicServer}/subscribe-player-status?filter=progress,duration")).Start();
|
||||
_sse.MessageReceived += Sse_MessageReceived;
|
||||
_sse.Disconnected += Sse_Disconnected;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.LogError("Failed to start SSE connection for LX Music.");
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("FailToStartLXMusicServer"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
|
||||
});
|
||||
StopSSE();
|
||||
}
|
||||
}
|
||||
|
||||
private void StopSSE()
|
||||
{
|
||||
if (_sse != null)
|
||||
{
|
||||
_sse.MessageReceived -= Sse_MessageReceived;
|
||||
_sse.Disconnected -= Sse_Disconnected;
|
||||
_sse.Dispose();
|
||||
_sse = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Sse_Disconnected(object sender, DisconnectEventArgs e)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(e.ReconnectDelay);
|
||||
if (_sse != null && !_sse.IsDisposed) _sse.Start();
|
||||
});
|
||||
}
|
||||
|
||||
private void Sse_MessageReceived(object sender, EventSourceMessageEventArgs e)
|
||||
{
|
||||
if (_cachedSongInfo?.SourceAppUserModelId == _lxMusicId)
|
||||
{
|
||||
var data = JsonSerializer.Deserialize(e.Message, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
if (data.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
if (e.Event == "progress")
|
||||
{
|
||||
_lxMusicPositionSeconds = data.GetDouble();
|
||||
}
|
||||
else if (e.Event == "duration")
|
||||
{
|
||||
_lxMusicDurationSeconds = data.GetDouble();
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(TimeSpan.FromSeconds(_lxMusicPositionSeconds), TimeSpan.FromSeconds(_lxMusicDurationSeconds)));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PlayAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession != null)
|
||||
{
|
||||
await focusedSession.ControlSession?.TryPlayAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PauseAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession != null)
|
||||
{
|
||||
await focusedSession.ControlSession?.TryPauseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PreviousAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession != null)
|
||||
{
|
||||
await focusedSession.ControlSession?.TrySkipPreviousAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task NextAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession != null)
|
||||
{
|
||||
await focusedSession.ControlSession?.TrySkipNextAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ChangePosition(double seconds)
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession != null)
|
||||
{
|
||||
await focusedSession.ControlSession?.TryChangePlaybackPositionAsync(TimeSpan.FromSeconds(seconds).Ticks);
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
@@ -263,12 +422,12 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
_mediaSourceProvidersInfo = [.. message.NewValue];
|
||||
_settingsService.MediaSourceProvidersInfo = _mediaSourceProvidersInfo;
|
||||
MediaManager_OnFocusedSessionChanged(_mediaManager.GetFocusedSession());
|
||||
MediaManager_OnFocusedSessionChanged(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>> message)
|
||||
public async void Receive(PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
@@ -276,7 +435,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
// Album art search providers info changed, re-fetch album art
|
||||
_logger.LogInformation("Album art search providers info changed, refreshing album art.");
|
||||
_ = _AlbumArtRefreshRunner.RunAsync(async tokne =>
|
||||
await _albumArtRefreshRunner.RunAsync(async tokne =>
|
||||
{
|
||||
await UpdateAlbumArtRelated(tokne);
|
||||
});
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace BetterLyrics.WinUI3.Services
|
||||
private const string CoverOverlayOpacityKey = "CoverOverlayOpacity";
|
||||
private const string IsCoverOverlayEnabledKey = "IsCoverOverlayEnabled";
|
||||
|
||||
private const string CoverAcrylicEffectAmountKey = "CoverAcrylicEffectAmount";
|
||||
|
||||
private const string DesktopWindowLeftKey = "DesktopWindowLeft";
|
||||
private const string DesktopWindowTopKey = "DesktopWindowTop";
|
||||
private const string DesktopWindowWidthKey = "DesktopWindowWidth";
|
||||
@@ -41,6 +43,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
private const string StandardWindowHeightKey = "StandardWindowHeight";
|
||||
|
||||
private const string AutoLockOnDesktopModeKey = "AutoLockOnDesktopMode";
|
||||
private const string IsImmersiveModeKey = "IsImmersiveMode";
|
||||
|
||||
private const string IsDynamicCoverOverlayEnabledKey = "IsDynamicCoverOverlayEnabled";
|
||||
private const string IsFanLyricsEnabledKey = "IsFanLyricsEnabled";
|
||||
@@ -59,7 +62,11 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private const string LyricsFontStrokeWidthKey = "LyricsFontStrokeWidth";
|
||||
|
||||
private const string LyricsFontSizeKey = "LyricsFontSize";
|
||||
// Lyrics font size
|
||||
private const string LyricsStandardFontSizeKey = "LyricsStandardFontSize";
|
||||
private const string LyricsDockFontSizeKey = "LyricsDockFontSize";
|
||||
private const string LyricsDesktopFontSizeKey = "LyricsDesktopFontSize";
|
||||
|
||||
private const string LyricsFontWeightKey = "LyricsFontWeightKey";
|
||||
private const string LyricsGlowEffectScopeKey = "LyricsGlowEffectScope";
|
||||
private const string LyricsHighlightSopeKey = "LyricsHighlightSope";
|
||||
@@ -70,10 +77,16 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private const string MediaSourceProvidersInfoKey = "MediaSourceProvidersInfo";
|
||||
|
||||
// Translation
|
||||
private const string IsTranslationEnabledKey = "IsTranslationEnabled";
|
||||
private const string ShowTranslationOnlyKey = "ShowTranslationOnly";
|
||||
private const string IsLibreTranslateEnabledKey = "IsLibreTranslateEnabled";
|
||||
private const string LibreTranslateServerKey = "LibreTranslateServer";
|
||||
private const string SelectedTargetLanguageIndexKey = "SelectedTargetLanguageIndex";
|
||||
|
||||
// LX Music
|
||||
private const string LXMusicServerKey = "LXMusicServer";
|
||||
|
||||
private const string LyricsBackgroundThemeKey = "LyricsBackgroundTheme";
|
||||
private const string IgnoreFullscreenWindowKey = "IgnoreFullscreenWindow";
|
||||
private const string PreferredDisplayTypeKey = "PreferredDisplayTypeKey";
|
||||
@@ -83,6 +96,25 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
public const string TimelineSyncThresholdKey = "TimelineSyncThreshold";
|
||||
|
||||
private const string IsLyricsFloatAnimationEnabledKey = "IsLyricsFloatAnimationEnabled";
|
||||
|
||||
private const string ResetPositionOffsetOnSongChangedKey = "ResetPositionOffsetOnSongChanged";
|
||||
private const string PlaybackOrderKey = "PlaybackOrder";
|
||||
|
||||
private const string PositionOffsetKey = "PositionOffset";
|
||||
|
||||
private const string LockHotKeyIndexKey = "LockHotKeyIndex";
|
||||
private const string DockPlacementKey = "DockPlacement";
|
||||
private const string LyricsBgFontOpacityKey = "LyricsBgFontOpacity";
|
||||
private const string HideWindowWhenNotPlayingKey = "HideWindowWhenNotPlaying";
|
||||
private const string DockWindowHeightKey = "DockWindowHeight";
|
||||
|
||||
private const string SelectedFontFamilyIndexKey = "SelectedFontFamilyIndex";
|
||||
private const string LyricsFontFamilyKey = "LyricsFontFamily";
|
||||
private const string IsDragEverywhereEnabledKey = "IsDragEverywhereEnabled";
|
||||
|
||||
private const string DockMonitorDeviceNameKey = "DockMonitorDeviceName";
|
||||
|
||||
private readonly ApplicationDataContainer _localSettings;
|
||||
|
||||
public SettingsService()
|
||||
@@ -152,6 +184,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(StandardWindowWidthKey, 1600);
|
||||
|
||||
SetDefault(AutoLockOnDesktopModeKey, false);
|
||||
SetDefault(IsImmersiveModeKey, false);
|
||||
// App behavior
|
||||
SetDefault(AutoStartWindowTypeKey, (int)AutoStartWindowType.StandardMode);
|
||||
// Album art
|
||||
@@ -160,13 +193,14 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(CoverOverlayOpacityKey, 100); // 100 % = 1.0
|
||||
SetDefault(CoverOverlayBlurAmountKey, 100);
|
||||
SetDefault(CoverImageRadiusKey, 12); // 12 %
|
||||
SetDefault(CoverAcrylicEffectAmountKey, 0);
|
||||
// Lyrics
|
||||
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Center);
|
||||
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Left);
|
||||
SetDefault(SongInfoAlignmentTypeKey, (int)TextAlignmentType.Left);
|
||||
SetDefault(LyricsFontWeightKey, (int)LyricsFontWeight.Bold);
|
||||
SetDefault(LyricsBlurAmountKey, 5);
|
||||
|
||||
SetDefault(LyricsBackgroundThemeKey, (int)ElementTheme.Default);
|
||||
SetDefault(LyricsBackgroundThemeKey, (int)ElementTheme.Dark);
|
||||
|
||||
SetDefault(LyricsBgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
|
||||
SetDefault(LyricsFgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
|
||||
@@ -176,7 +210,10 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(LyricsCustomFgFontColorKey, Colors.White.ToInt());
|
||||
SetDefault(LyricsCustomStrokeFontColorKey, Colors.White.ToInt());
|
||||
|
||||
SetDefault(LyricsFontSizeKey, 28);
|
||||
SetDefault(LyricsStandardFontSizeKey, 32);
|
||||
SetDefault(LyricsDockFontSizeKey, 16);
|
||||
SetDefault(LyricsDesktopFontSizeKey, 28);
|
||||
|
||||
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
|
||||
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
|
||||
SetDefault(IsLyricsGlowEffectEnabledKey, true);
|
||||
@@ -185,16 +222,88 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(IsFanLyricsEnabledKey, false);
|
||||
|
||||
SetDefault(LibreTranslateServerKey, "");
|
||||
SetDefault(IsTranslationEnabledKey, false);
|
||||
SetDefault(SelectedTargetLanguageIndexKey, 6);
|
||||
SetDefault(IsLibreTranslateEnabledKey, false);
|
||||
SetDefault(IsTranslationEnabledKey, true);
|
||||
SetDefault(ShowTranslationOnlyKey, false);
|
||||
SetDefault(SelectedTargetLanguageIndexKey, LanguageHelper.GetDefaultTargetLanguageIndex());
|
||||
|
||||
SetDefault(LXMusicServerKey, "");
|
||||
|
||||
SetDefault(LyricsFontStrokeWidthKey, 3);
|
||||
SetDefault(IgnoreFullscreenWindowKey, false);
|
||||
SetDefault(PreferredDisplayTypeKey, (int)LyricsDisplayType.SplitView);
|
||||
|
||||
SetDefault(LyricsScrollEasingTypeKey, (int)EasingType.EaseInOutQuad);
|
||||
SetDefault(LyricsScrollEasingTypeKey, (int)EasingType.EaseInOutSine);
|
||||
SetDefault(LyricsScrollDurationKey, 500); // 500ms
|
||||
SetDefault(TimelineSyncThresholdKey, 0); // 0ms
|
||||
|
||||
SetDefault(IsLyricsFloatAnimationEnabledKey, true);
|
||||
|
||||
SetDefault(ResetPositionOffsetOnSongChangedKey, false);
|
||||
SetDefault(PositionOffsetKey, 0);
|
||||
SetDefault(LockHotKeyIndexKey, 'U' - 'A');
|
||||
SetDefault(DockPlacementKey, (int)DockPlacement.Top);
|
||||
SetDefault(LyricsBgFontOpacityKey, 30); // 30%
|
||||
SetDefault(HideWindowWhenNotPlayingKey, false);
|
||||
SetDefault(DockWindowHeightKey, 64); // 64px
|
||||
SetDefault(SelectedFontFamilyIndexKey, 0);
|
||||
SetDefault(LyricsFontFamilyKey, FontHelper.SystemFontFamilies.ElementAtOrDefault(0));
|
||||
SetDefault(IsDragEverywhereEnabledKey, false);
|
||||
SetDefault(DockMonitorDeviceNameKey, MonitorHelper.GetPrimaryMonitorDeviceName());
|
||||
}
|
||||
|
||||
public bool IsDragEverywhereEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsDragEverywhereEnabledKey);
|
||||
set => SetValue(IsDragEverywhereEnabledKey, value);
|
||||
}
|
||||
|
||||
public string LyricsFontFamily
|
||||
{
|
||||
get => GetValue<string>(LyricsFontFamilyKey)!;
|
||||
set => SetValue(LyricsFontFamilyKey, value);
|
||||
}
|
||||
|
||||
public int SelectedFontFamilyIndex
|
||||
{
|
||||
get => GetValue<int>(SelectedFontFamilyIndexKey);
|
||||
set => SetValue(SelectedFontFamilyIndexKey, value);
|
||||
}
|
||||
|
||||
public bool HideWindowWhenNotPlaying
|
||||
{
|
||||
get => GetValue<bool>(HideWindowWhenNotPlayingKey);
|
||||
set => SetValue(HideWindowWhenNotPlayingKey, value);
|
||||
}
|
||||
|
||||
public int DockWindowHeight
|
||||
{
|
||||
get => GetValue<int>(DockWindowHeightKey);
|
||||
set => SetValue(DockWindowHeightKey, value);
|
||||
}
|
||||
|
||||
public int LyricsBgFontOpacity
|
||||
{
|
||||
get => GetValue<int>(LyricsBgFontOpacityKey);
|
||||
set => SetValue(LyricsBgFontOpacityKey, value);
|
||||
}
|
||||
|
||||
public bool ShowTranslationOnly
|
||||
{
|
||||
get => GetValue<bool>(ShowTranslationOnlyKey);
|
||||
set => SetValue(ShowTranslationOnlyKey, value);
|
||||
}
|
||||
|
||||
public DockPlacement DockPlacement
|
||||
{
|
||||
get => (DockPlacement)GetValue<int>(DockPlacementKey);
|
||||
set => SetValue(DockPlacementKey, (int)value);
|
||||
}
|
||||
|
||||
public int LockHotKeyIndex
|
||||
{
|
||||
get => GetValue<int>(LockHotKeyIndexKey);
|
||||
set => SetValue(LockHotKeyIndexKey, value);
|
||||
}
|
||||
|
||||
public EasingType LyricsScrollEasingType
|
||||
@@ -209,7 +318,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(LyricsScrollDurationKey, value);
|
||||
}
|
||||
|
||||
public LyricsDisplayType PreferredDisplayType
|
||||
public LyricsDisplayType DisplayType
|
||||
{
|
||||
get => (LyricsDisplayType)GetValue<int>(PreferredDisplayTypeKey);
|
||||
set => SetValue(PreferredDisplayTypeKey, (int)value);
|
||||
@@ -305,6 +414,12 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(IsDynamicCoverOverlayEnabledKey, value);
|
||||
}
|
||||
|
||||
public int CoverAcrylicEffectAmount
|
||||
{
|
||||
get => GetValue<int>(CoverAcrylicEffectAmountKey);
|
||||
set => SetValue(CoverAcrylicEffectAmountKey, value);
|
||||
}
|
||||
|
||||
public bool IsFanLyricsEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsFanLyricsEnabledKey);
|
||||
@@ -329,19 +444,19 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(LanguageKey, (int)value);
|
||||
}
|
||||
|
||||
public List<LocalLyricsFolder> LocalLyricsFolders
|
||||
public List<LocalMediaFolder> LocalMediaFolders
|
||||
{
|
||||
get =>
|
||||
System.Text.Json.JsonSerializer.Deserialize(
|
||||
GetValue<string>(LocalLyricsFoldersKey) ?? "[]",
|
||||
SourceGenerationContext.Default.ListLocalLyricsFolder
|
||||
SourceGenerationContext.Default.ListLocalMediaFolder
|
||||
)!;
|
||||
set =>
|
||||
SetValue(
|
||||
LocalLyricsFoldersKey,
|
||||
System.Text.Json.JsonSerializer.Serialize(
|
||||
value,
|
||||
SourceGenerationContext.Default.ListLocalLyricsFolder
|
||||
SourceGenerationContext.Default.ListLocalMediaFolder
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -406,10 +521,22 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(LyricsFontStrokeWidthKey, value);
|
||||
}
|
||||
|
||||
public int LyricsFontSize
|
||||
public int LyricsStandardFontSize
|
||||
{
|
||||
get => GetValue<int>(LyricsFontSizeKey);
|
||||
set => SetValue(LyricsFontSizeKey, value);
|
||||
get => GetValue<int>(LyricsStandardFontSizeKey);
|
||||
set => SetValue(LyricsStandardFontSizeKey, value);
|
||||
}
|
||||
|
||||
public int LyricsDockFontSize
|
||||
{
|
||||
get => GetValue<int>(LyricsDockFontSizeKey);
|
||||
set => SetValue(LyricsDockFontSizeKey, value);
|
||||
}
|
||||
|
||||
public int LyricsDesktopFontSize
|
||||
{
|
||||
get => GetValue<int>(LyricsDesktopFontSizeKey);
|
||||
set => SetValue(LyricsDesktopFontSizeKey, value);
|
||||
}
|
||||
|
||||
public LyricsFontWeight LyricsFontWeight
|
||||
@@ -505,12 +632,24 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(IsTranslationEnabledKey, value);
|
||||
}
|
||||
|
||||
public bool IsLibreTranslateEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsLibreTranslateEnabledKey);
|
||||
set => SetValue(IsLibreTranslateEnabledKey, value);
|
||||
}
|
||||
|
||||
public int SelectedTargetLanguageIndex
|
||||
{
|
||||
get => GetValue<int>(SelectedTargetLanguageIndexKey);
|
||||
set => SetValue(SelectedTargetLanguageIndexKey, value);
|
||||
}
|
||||
|
||||
public string LXMusicServer
|
||||
{
|
||||
get => GetValue<string>(LXMusicServerKey)!;
|
||||
set => SetValue(LXMusicServerKey, value);
|
||||
}
|
||||
|
||||
public bool IgnoreFullscreenWindow
|
||||
{
|
||||
get => GetValue<bool>(IgnoreFullscreenWindowKey);
|
||||
@@ -523,6 +662,42 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(TimelineSyncThresholdKey, value);
|
||||
}
|
||||
|
||||
public bool IsLyricsFloatAnimationEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsLyricsFloatAnimationEnabledKey);
|
||||
set => SetValue(IsLyricsFloatAnimationEnabledKey, value);
|
||||
}
|
||||
|
||||
public bool ResetPositionOffsetOnSongChanged
|
||||
{
|
||||
get => GetValue<bool>(ResetPositionOffsetOnSongChangedKey);
|
||||
set => SetValue(ResetPositionOffsetOnSongChangedKey, value);
|
||||
}
|
||||
|
||||
public PlaybackOrder PlaybackOrder
|
||||
{
|
||||
get => (PlaybackOrder)GetValue<int>(PlaybackOrderKey);
|
||||
set => SetValue(PlaybackOrderKey, (int)value);
|
||||
}
|
||||
|
||||
public int PositionOffset
|
||||
{
|
||||
get => GetValue<int>(PositionOffsetKey);
|
||||
set => SetValue(PositionOffsetKey, value);
|
||||
}
|
||||
|
||||
public bool IsImmersiveMode
|
||||
{
|
||||
get => GetValue<bool>(IsImmersiveModeKey);
|
||||
set => SetValue(IsImmersiveModeKey, value);
|
||||
}
|
||||
|
||||
public string DockMonitorDeviceName
|
||||
{
|
||||
get => GetValue<string>(DockMonitorDeviceNameKey)!;
|
||||
set => SetValue(DockMonitorDeviceNameKey, value);
|
||||
}
|
||||
|
||||
private T? GetValue<T>(string key)
|
||||
{
|
||||
if (_localSettings.Values.TryGetValue(key, out object? value))
|
||||
|
||||
@@ -3,6 +3,7 @@ using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -18,7 +19,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public TranslateService(ISettingsService settingsService) :base(settingsService)
|
||||
public TranslateService(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
@@ -27,7 +28,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
throw new ArgumentException("Text and target language must be provided.");
|
||||
throw new Exception(text + " is empty or null.");
|
||||
}
|
||||
|
||||
string? originalLangCode = LanguageHelper.DetectLanguageCode(text);
|
||||
@@ -46,15 +47,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer))
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(
|
||||
App.ResourceLoader!.GetString("TranslateServerNotSet"),
|
||||
Microsoft.UI.Xaml.Controls.InfoBarSeverity.Warning
|
||||
);
|
||||
});
|
||||
|
||||
throw new InvalidOperationException("LibreTranslate server URL is not configured.");
|
||||
throw new Exception("LibreTranslate server URL is not set in settings.");
|
||||
}
|
||||
|
||||
var url = $"{_settingsService.LibreTranslateServer}/translate";
|
||||
@@ -76,18 +69,18 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
public int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr)
|
||||
{
|
||||
string targetLangCode = LanguageHelper.GetUserTargetLanguageCode();
|
||||
string targetLangCode = LanguageHelper.GetUserTargetLanguageCode().Substring(0, 2);
|
||||
if (lyricsDataArr.Count > 1)
|
||||
{
|
||||
for (int i = 1; i < lyricsDataArr.Count; i++)
|
||||
for (int i = 1; i < lyricsDataArr.Count; i++)
|
||||
{
|
||||
if (lyricsDataArr[i].LanguageCode == targetLangCode)
|
||||
if (lyricsDataArr[i].LanguageCode?.Substring(0, 2) == targetLangCode)
|
||||
{
|
||||
return i; // Translation lyrics data found
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1; // No translation lyrics data found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,8 +264,14 @@
|
||||
<data name="SettingsPageLyricsLineSpacingFactorUnit.Text" xml:space="preserve">
|
||||
<value>x line height</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>Font size</value>
|
||||
<data name="SettingsPageLyricsStandardFontSize.Header" xml:space="preserve">
|
||||
<value>Font size (Standard mode)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDockFontSize.Header" xml:space="preserve">
|
||||
<value>Font size (Dock mode)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDesktopFontSize.Header" xml:space="preserve">
|
||||
<value>Font size (Desktop mode)</value>
|
||||
</data>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>Lyrics only</value>
|
||||
@@ -288,9 +294,6 @@
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>Glow effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>Glow effect scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Configure lyrics source</value>
|
||||
</data>
|
||||
@@ -304,7 +307,8 @@
|
||||
<value>Welcome to BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>Hover the mouse over the top or bottom area of the app to display more function options</value>
|
||||
<value>Click on the top-left button to enable immersive mode.
|
||||
If you encounter any problems, please go to the Settings page, About tab, and view the FAQ or contact the author for feedback</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>No music playing now</value>
|
||||
@@ -420,9 +424,15 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Dock mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDock.Text" xml:space="preserve">
|
||||
<value>Dock mode</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Desktop mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDesktop.Text" xml:space="preserve">
|
||||
<value>Desktop mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>Font weight</value>
|
||||
</data>
|
||||
@@ -477,9 +487,6 @@
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>Local music files</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LRCLIB</value>
|
||||
</data>
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
@@ -495,18 +502,6 @@
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>This folder contains added folders, please delete these folders to add the folder</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQ" xml:space="preserve">
|
||||
<value>QQ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderNetease" xml:space="preserve">
|
||||
<value>Netease</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugou" xml:space="preserve">
|
||||
<value>Kugou</value>
|
||||
</data>
|
||||
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
|
||||
<value>Show debug overlay</value>
|
||||
</data>
|
||||
@@ -528,8 +523,8 @@
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>Lock</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>To unlock after locking, go to the system tray to unlock</value>
|
||||
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
|
||||
<value>To unlock after locking, go to the system tray to unlock or press</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>Fan lyrics</value>
|
||||
@@ -589,7 +584,7 @@
|
||||
<value>Lyrics timeline offset (ms)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>Configure translation services</value>
|
||||
<value>LibreTranslate translation service</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>Server address</value>
|
||||
@@ -606,10 +601,10 @@
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>Visit https://github.com/LibreTranslate/LibreTranslate for installation instructions and more information (this software is not affiliated with this translation service in any way)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestSuccessInfo" xml:space="preserve">
|
||||
<value>Server test successful</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestFailedInfo" xml:space="preserve">
|
||||
<value>Server test failed</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
@@ -649,7 +644,7 @@
|
||||
<value>Translate server is not set, please configure it in settings first</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>Will automatically reset to 0 when switching songs</value>
|
||||
<value>Reset to 0 when switching songs</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>The translation in the lyrics will be read first. If there is no match, the machine translation will be requested from the LibreTranslate server</value>
|
||||
@@ -735,4 +730,193 @@
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>Join now</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFloatAnimation.Header" xml:space="preserve">
|
||||
<value>Floating animation</value>
|
||||
</data>
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>Scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageLockHotKey.Header" xml:space="preserve">
|
||||
<value>Unlock and lock shortcut keys</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
|
||||
<value>LX Music Server</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacement.Header" xml:space="preserve">
|
||||
<value>Dock mode placement</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementTop.Content" xml:space="preserve">
|
||||
<value>Top</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
|
||||
<value>Bottom</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
|
||||
<value>Enable translation</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
|
||||
<value>Show translation only</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontOpacity.Header" xml:space="preserve">
|
||||
<value>Font opacity (Non-current playback area)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFAQ.Header" xml:space="preserve">
|
||||
<value>Frequently asked questions</value>
|
||||
</data>
|
||||
<data name="FailToStartLXMusicServer" xml:space="preserve">
|
||||
<value>Unable to connect to LX Music server, please go to Settings - Advanced options to check if the link is entered correctly</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>Auto-hide window</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>Automatically hide the window when no songs are playing in dock mode or desktop mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockWindowHeight.Header" xml:space="preserve">
|
||||
<value>Window height</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
|
||||
<value>Lyrics font family</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Header" xml:space="preserve">
|
||||
<value>Global drag</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
|
||||
<value>Extend the title bar to the entire page so that the window can be dragged in any non-interactive area</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>Fluid acrylic effect roughness</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageTitle" xml:space="preserve">
|
||||
<value>Music gallery - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>Requesting translation from LibreTranslate failed, please check the settings or the native LibreTranslate configuration</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
|
||||
<value>File info</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
|
||||
<value>Title</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
|
||||
<value>Artist</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
|
||||
<value>Album</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
|
||||
<value>Year</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
|
||||
<value>Duration</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
|
||||
<value>Bitrate</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
|
||||
<value>Sample rate</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
|
||||
<value>Bit depth</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
|
||||
<value>Format</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
|
||||
<value>Encoder</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
|
||||
<value>Path</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Search songs</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>Sort type</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
|
||||
<value>Title</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
|
||||
<value>Album</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
|
||||
<value>Artist</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>No songs were found in the media library</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||
<value>Play queue</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Content" xml:space="preserve">
|
||||
<value>Clear play queue</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageScrollToPlayingItem.Content" xml:space="preserve">
|
||||
<value>Scroll to playing item</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueLoop.Content" xml:space="preserve">
|
||||
<value>List loop</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSingleLoop.Content" xml:space="preserve">
|
||||
<value>Single loop</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueRandom.Content" xml:space="preserve">
|
||||
<value>Random</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageRemoveFromPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Remove from play queue</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueueEmpty.Text" xml:space="preserve">
|
||||
<value>Play queue is empty</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSelectAll.Content" xml:space="preserve">
|
||||
<value>Select all</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToPlayingQueue.Content" xml:space="preserve">
|
||||
<value>Add to play queue</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToNext.Text" xml:space="preserve">
|
||||
<value>Next items</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToEnd.Text" xml:space="preserve">
|
||||
<value>End of the list</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>Add to playlist</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||
<value>Create a playlist</value>
|
||||
</data>
|
||||
<data name="SystemTrayMusicGallery.Text" xml:space="preserve">
|
||||
<value>Open music gallery</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics is already running</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>All songs</value>
|
||||
</data>
|
||||
<data name="SettingsPageTelegram.Header" xml:space="preserve">
|
||||
<value>Telegram</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
|
||||
<value>Lyrics provider</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
|
||||
<value>Translation provider</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockMonitor.Header" xml:space="preserve">
|
||||
<value>Target monitor</value>
|
||||
</data>
|
||||
<data name="SystemTrayRestart.Text" xml:space="preserve">
|
||||
<value>Resart</value>
|
||||
</data>
|
||||
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
|
||||
<value>Reset window position</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
|
||||
<value>This setting will not affect the dock mode and the dock mode will always remain centered.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -250,7 +250,7 @@
|
||||
<value>この値を調整すると、アルバム画像のバックグラウンドブラー強度も増加します。</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>現在の値:</value>
|
||||
<value>現在の値: </value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>ぼかしが有効になっている場合のGPU使用量が大幅に高くなります(> 0)</value>
|
||||
@@ -264,8 +264,14 @@
|
||||
<data name="SettingsPageLyricsLineSpacingFactorUnit.Text" xml:space="preserve">
|
||||
<value>x線の高さ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>フォントサイズ</value>
|
||||
<data name="SettingsPageLyricsStandardFontSize.Header" xml:space="preserve">
|
||||
<value>フォントサイズ(標準モード)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDockFontSize.Header" xml:space="preserve">
|
||||
<value>フォントサイズ(ドックモード)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDesktopFontSize.Header" xml:space="preserve">
|
||||
<value>フォントサイズ(デスクトップモード)</value>
|
||||
</data>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>歌詞のみ</value>
|
||||
@@ -288,9 +294,6 @@
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>グロー効果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>グローエフェクトスコープ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>歌詞ソースを構成します</value>
|
||||
</data>
|
||||
@@ -304,7 +307,8 @@
|
||||
<value>BetterLyrics へようこそ</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>マウスをアプリの上または下部の領域にホバリングして、より多くの機能オプションを表示します</value>
|
||||
<value>左上ボタンをクリックして、没入型モードを有効にします。
|
||||
問題が発生した場合は、[設定]ページ、[タブについて]にアクセスして、FAQを表示するか、フィードバックについて著者に連絡してください</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>今は音楽が再生されていません</value>
|
||||
@@ -420,9 +424,15 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ドックモード</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDock.Text" xml:space="preserve">
|
||||
<value>ドックモード</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>デスクトップモード</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDesktop.Text" xml:space="preserve">
|
||||
<value>デスクトップモード</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>フォント重量</value>
|
||||
</data>
|
||||
@@ -477,9 +487,6 @@
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>ローカル音楽ファイル</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LRCLIB</value>
|
||||
</data>
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
@@ -495,18 +502,6 @@
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>このフォルダーには追加されたフォルダーが含まれています。これらのフォルダを削除してフォルダーを追加してください</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQ" xml:space="preserve">
|
||||
<value>QQ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderNetease" xml:space="preserve">
|
||||
<value>Netease</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugou" xml:space="preserve">
|
||||
<value>Kugou</value>
|
||||
</data>
|
||||
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
|
||||
<value>デバッグオーバーレイを表示します</value>
|
||||
</data>
|
||||
@@ -528,8 +523,8 @@
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>ロック</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>ロック後にロックを解除するには、システムトレイに移動してロックを解除します</value>
|
||||
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
|
||||
<value>ロック後にロックを解除するには、システムトレイに移動してロックを解除または押します</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>ファンの歌詞</value>
|
||||
@@ -589,7 +584,7 @@
|
||||
<value>歌詞タイムラインオフセット(ミリ秒)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>翻訳サービスを構成します</value>
|
||||
<value>リブレットランレート翻訳サービス</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>サーバーアドレス</value>
|
||||
@@ -606,10 +601,10 @@
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>https://github.com/LibreTranslate/LibreTranslate にアクセスしてください。</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestSuccessInfo" xml:space="preserve">
|
||||
<value>サーバーテストが成功しました</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestFailedInfo" xml:space="preserve">
|
||||
<value>サーバーテストに失敗しました</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
@@ -649,7 +644,7 @@
|
||||
<value>翻訳サーバーは設定されていません。最初に設定で構成してください</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>曲を切り替えると、0 に自動的にリセットされます</value>
|
||||
<value>曲を切り替えるときに0にリセットします</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>歌詞の翻訳は最初に読まれます。一致していない場合、機械の翻訳はLibretranslate Serverから要求されます</value>
|
||||
@@ -735,4 +730,193 @@
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>今すぐ参加してください</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFloatAnimation.Header" xml:space="preserve">
|
||||
<value>フローティングアニメーション</value>
|
||||
</data>
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>範囲</value>
|
||||
</data>
|
||||
<data name="SettingsPageLockHotKey.Header" xml:space="preserve">
|
||||
<value>ショートカットキーのロックを解除およびロックします</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
|
||||
<value>LX Music Server</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacement.Header" xml:space="preserve">
|
||||
<value>ドックモード配置</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementTop.Content" xml:space="preserve">
|
||||
<value>トップ</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
|
||||
<value>底</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
|
||||
<value>翻訳を有効にします</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
|
||||
<value>翻訳のみを表示します</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontOpacity.Header" xml:space="preserve">
|
||||
<value>フォントの不透明度(非電流再生エリア)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFAQ.Header" xml:space="preserve">
|
||||
<value>よくある質問</value>
|
||||
</data>
|
||||
<data name="FailToStartLXMusicServer" xml:space="preserve">
|
||||
<value>LX Music Serverに接続できない場合は、設定に移動してください。リンクが正しく入力されているかどうかを確認するための高度なオプションに移動してください</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>自動ハイドウィンドウ</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>ドックモードやデスクトップモードで再生されている曲がないときにウィンドウを自動的に非表示にする</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockWindowHeight.Header" xml:space="preserve">
|
||||
<value>窓の高さ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
|
||||
<value>歌詞フォントファミリー</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Header" xml:space="preserve">
|
||||
<value>グローバルドラッグ</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
|
||||
<value>タイトルバーをページ全体に拡張して、ウィンドウを非対話領域でドラッグできるようにします</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>仿亚克力效果粗糙度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageTitle" xml:space="preserve">
|
||||
<value>音楽ギャラリー - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>リブレットランスレートからの翻訳のリクエストが失敗しました。設定またはネイティブリブレットランレート構成を確認してください</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
|
||||
<value>ファイル情報</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
|
||||
<value>タイトル</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
|
||||
<value>アーティスト</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
|
||||
<value>アルバム</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
|
||||
<value>年</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
|
||||
<value>間隔</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
|
||||
<value>ビットレート</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
|
||||
<value>サンプルレート</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
|
||||
<value>ビットの深さ</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
|
||||
<value>形式</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
|
||||
<value>エンコーダー</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
|
||||
<value>パス</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>曲を検索します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>並べ替えタイプ</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
|
||||
<value>タイトル</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
|
||||
<value>アルバム</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
|
||||
<value>アーティスト</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>メディアライブラリには歌が見つかりませんでした</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||
<value>キューを再生します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Content" xml:space="preserve">
|
||||
<value>クリアプレイキュー</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageScrollToPlayingItem.Content" xml:space="preserve">
|
||||
<value>アイテムを再生するための位置</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueLoop.Content" xml:space="preserve">
|
||||
<value>ループをリストします</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSingleLoop.Content" xml:space="preserve">
|
||||
<value>シングルループ</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueRandom.Content" xml:space="preserve">
|
||||
<value>ランダム</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageRemoveFromPlayingQueue.Text" xml:space="preserve">
|
||||
<value>プレイリストから取り外します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueueEmpty.Text" xml:space="preserve">
|
||||
<value>キューを再生するのは空です</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSelectAll.Content" xml:space="preserve">
|
||||
<value>すべてを選択します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToPlayingQueue.Content" xml:space="preserve">
|
||||
<value>キューを再生するために追加します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToNext.Text" xml:space="preserve">
|
||||
<value>次のアイテム</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToEnd.Text" xml:space="preserve">
|
||||
<value>リストの終わり</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>プレイリストに追加します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||
<value>プレイリストを作成します</value>
|
||||
</data>
|
||||
<data name="SystemTrayMusicGallery.Text" xml:space="preserve">
|
||||
<value>オープンミュージックギャラリー</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics はすでに実行されています!</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>すべての曲</value>
|
||||
</data>
|
||||
<data name="SettingsPageTelegram.Header" xml:space="preserve">
|
||||
<value>Telegram</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
|
||||
<value>歌詞プロバイダー</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
|
||||
<value>翻訳プロバイダー</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockMonitor.Header" xml:space="preserve">
|
||||
<value>ターゲットモニター</value>
|
||||
</data>
|
||||
<data name="SystemTrayRestart.Text" xml:space="preserve">
|
||||
<value>再アート</value>
|
||||
</data>
|
||||
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
|
||||
<value>ウィンドウの位置をリセットします</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
|
||||
<value>この設定はドックモードには影響しません。ドックモードは常に中心のままです。</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -250,7 +250,7 @@
|
||||
<value>이 값을 조정하면 앨범 이미지의 배경 흐림 강도가 증가합니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>현재 가치 :</value>
|
||||
<value>현재 가치 : </value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>Blur가 활성화 될 때 상당히 높은 GPU 사용량 (> 0)</value>
|
||||
@@ -264,8 +264,14 @@
|
||||
<data name="SettingsPageLyricsLineSpacingFactorUnit.Text" xml:space="preserve">
|
||||
<value>X 라인 높이</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>글꼴 크기</value>
|
||||
<data name="SettingsPageLyricsStandardFontSize.Header" xml:space="preserve">
|
||||
<value>글꼴 크기 (표준 모드)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDockFontSize.Header" xml:space="preserve">
|
||||
<value>글꼴 크기 (도크 모드)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDesktopFontSize.Header" xml:space="preserve">
|
||||
<value>글꼴 크기 (데스크탑 모드)</value>
|
||||
</data>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>가사 만</value>
|
||||
@@ -288,9 +294,6 @@
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>글로우 효과</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>글로우 효과 범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>가사 소스를 구성하십시오</value>
|
||||
</data>
|
||||
@@ -304,7 +307,8 @@
|
||||
<value>Betterlyrics에 오신 것을 환영합니다</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>더 많은 기능 옵션을 표시하려면 앱의 상단 또는 하단 영역 위로 마우스를 가져옵니다.</value>
|
||||
<value>몰입 형 모드를 활성화하려면 왼쪽 상단 버튼을 클릭하십시오.
|
||||
문제가 발생하면 설정 페이지, 탭 정보로 이동하여 FAQ를보고 저자에게 연락하여 피드백을 받으십시오.</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>지금 음악이 재생되지 않습니다</value>
|
||||
@@ -420,9 +424,15 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>도크 모드</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDock.Text" xml:space="preserve">
|
||||
<value>도크 모드</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>데스크탑 모드</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDesktop.Text" xml:space="preserve">
|
||||
<value>데스크탑 모드</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>글꼴 무게</value>
|
||||
</data>
|
||||
@@ -477,9 +487,6 @@
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>로컬 음악 파일</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LRCLIB</value>
|
||||
</data>
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
@@ -495,18 +502,6 @@
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>이 폴더에는 추가 된 폴더가 포함되어 있습니다. 폴더를 추가하려면이 폴더를 삭제하십시오.</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQ" xml:space="preserve">
|
||||
<value>QQ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderNetease" xml:space="preserve">
|
||||
<value>Netease</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugou" xml:space="preserve">
|
||||
<value>Kugou</value>
|
||||
</data>
|
||||
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
|
||||
<value>디버그 오버레이를 표시하십시오</value>
|
||||
</data>
|
||||
@@ -528,8 +523,8 @@
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>잠금</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>잠금 잠금을 해제하려면 시스템 트레이로 이동하여 잠금을 해제하십시오.</value>
|
||||
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
|
||||
<value>잠금 잠금을 해제하려면 시스템 트레이로 이동하여 잠금을 해제하거나 누릅니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>팬 가사</value>
|
||||
@@ -589,7 +584,7 @@
|
||||
<value>가사 타임 라인 오프셋 (밀리초)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>번역 서비스를 구성하십시오</value>
|
||||
<value>libretranslate 번역 서비스</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>서버 주소</value>
|
||||
@@ -606,10 +601,10 @@
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>설치 지침 및 자세한 정보는 https://github.com/LibreTranslate/LibreTranslate 를 방문하십시오 (이 소프트웨어는이 번역 서비스와 제휴하지 않습니다).</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestSuccessInfo" xml:space="preserve">
|
||||
<value>서버 테스트 성공</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestFailedInfo" xml:space="preserve">
|
||||
<value>서버 테스트가 실패했습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
@@ -649,7 +644,7 @@
|
||||
<value>번역 서버가 설정되지 않았습니다. 먼저 설정으로 구성하십시오.</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>노래를 전환 할 때 자동으로 0 으로 재설정됩니다</value>
|
||||
<value>노래를 전환 할 때 0 으로 재설정하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>가사의 번역은 먼저 읽습니다. 일치하지 않으면 LibreTranslate 서버에서 기계 번역이 요청됩니다.</value>
|
||||
@@ -735,4 +730,193 @@
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>지금 가입하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFloatAnimation.Header" xml:space="preserve">
|
||||
<value>떠 다니는 애니메이션</value>
|
||||
</data>
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageLockHotKey.Header" xml:space="preserve">
|
||||
<value>바로 가기 키를 잠금 해제하고 잠그십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
|
||||
<value>LX 음악 서버</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacement.Header" xml:space="preserve">
|
||||
<value>도크 모드 배치</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementTop.Content" xml:space="preserve">
|
||||
<value>맨 위</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
|
||||
<value>맨 아래</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
|
||||
<value>번역 활성화</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
|
||||
<value>번역 만 표시하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontOpacity.Header" xml:space="preserve">
|
||||
<value>글꼴 불투명 (비 전류 재생 영역)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFAQ.Header" xml:space="preserve">
|
||||
<value>자주 묻는 질문</value>
|
||||
</data>
|
||||
<data name="FailToStartLXMusicServer" xml:space="preserve">
|
||||
<value>LX Music Server에 연결할 수 없습니다. 설정으로 이동하십시오 - 고급 옵션이 링크가 올바르게 입력되었는지 확인하십시오.</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>자동 가죽 창</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>도크 모드 또는 데스크탑 모드에서 재생되지 않을 때는 창을 자동으로 숨기고 있습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockWindowHeight.Header" xml:space="preserve">
|
||||
<value>창 높이</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
|
||||
<value>가사 글꼴 가족</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Header" xml:space="preserve">
|
||||
<value>글로벌 드래그</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
|
||||
<value>비 중과 영역에서 창을 드래그 할 수 있도록 제목 표시 줄을 전체 페이지로 확장하십시오.</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>仿亚克力效果粗糙度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageTitle" xml:space="preserve">
|
||||
<value>음악 갤러리 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>LibreTranslate에서 번역 요청 실패, 설정 또는 기본 LibreTranslate 구성을 확인하십시오.</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
|
||||
<value>파일 정보</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
|
||||
<value>제목</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
|
||||
<value>아티스트</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
|
||||
<value>앨범</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
|
||||
<value>년도</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
|
||||
<value>지속</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
|
||||
<value>비트 레이트</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
|
||||
<value>샘플 속도</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
|
||||
<value>비트 깊이</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
|
||||
<value>체재</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
|
||||
<value>인코더</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
|
||||
<value>길</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>노래 검색</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>정렬 유형</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
|
||||
<value>제목</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
|
||||
<value>앨범</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
|
||||
<value>아티스트</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>미디어 라이브러리에는 노래가 없습니다</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||
<value>대기열을 재생하십시오</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Content" xml:space="preserve">
|
||||
<value>플레이 대기열을 클리어합니다</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageScrollToPlayingItem.Content" xml:space="preserve">
|
||||
<value>아이템을 재생하기위한 포지셔닝</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueLoop.Content" xml:space="preserve">
|
||||
<value>목록 루프</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSingleLoop.Content" xml:space="preserve">
|
||||
<value>단일 루프</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueRandom.Content" xml:space="preserve">
|
||||
<value>무작위의</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageRemoveFromPlayingQueue.Text" xml:space="preserve">
|
||||
<value>재생 목록에서 제거하십시오</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueueEmpty.Text" xml:space="preserve">
|
||||
<value>플레이 대기열이 비어 있습니다</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSelectAll.Content" xml:space="preserve">
|
||||
<value>모두를 선택하십시오</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToPlayingQueue.Content" xml:space="preserve">
|
||||
<value>재생 큐에 추가하십시오</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToNext.Text" xml:space="preserve">
|
||||
<value>다음 항목</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToEnd.Text" xml:space="preserve">
|
||||
<value>목록의 끝</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>재생 목록에 추가하십시오</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||
<value>재생 목록을 만듭니다</value>
|
||||
</data>
|
||||
<data name="SystemTrayMusicGallery.Text" xml:space="preserve">
|
||||
<value>오픈 음악 갤러리</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics 가 이미 실행 중입니다</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>모든 노래</value>
|
||||
</data>
|
||||
<data name="SettingsPageTelegram.Header" xml:space="preserve">
|
||||
<value>Telegram</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
|
||||
<value>가사 제공자</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
|
||||
<value>번역 제공자</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockMonitor.Header" xml:space="preserve">
|
||||
<value>대상 모니터</value>
|
||||
</data>
|
||||
<data name="SystemTrayRestart.Text" xml:space="preserve">
|
||||
<value>resart</value>
|
||||
</data>
|
||||
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
|
||||
<value>창 위치를 재설정합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
|
||||
<value>이 설정은 도크 모드에 영향을 미치지 않으며 도크 모드는 항상 중앙에 유지됩니다.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -250,7 +250,7 @@
|
||||
<value>调整该数值将同步提高专辑图片背景模糊强度</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>当前值:</value>
|
||||
<value>当前值: </value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>启用模糊(> 0)时将显著提升 GPU 占用率</value>
|
||||
@@ -264,8 +264,14 @@
|
||||
<data name="SettingsPageLyricsLineSpacingFactorUnit.Text" xml:space="preserve">
|
||||
<value> 倍行高</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>字体大小</value>
|
||||
<data name="SettingsPageLyricsStandardFontSize.Header" xml:space="preserve">
|
||||
<value>字体尺寸(标准模式)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDockFontSize.Header" xml:space="preserve">
|
||||
<value>字体大小(停靠模式)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDesktopFontSize.Header" xml:space="preserve">
|
||||
<value>字体尺寸(桌面模式)</value>
|
||||
</data>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>仅显示歌词</value>
|
||||
@@ -288,9 +294,6 @@
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>辉光效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>辉光效果作用范围</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置歌词源</value>
|
||||
</data>
|
||||
@@ -304,7 +307,8 @@
|
||||
<value>欢迎使用 BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>将鼠标悬停在应用程序的顶部或底部区域以显示更多功能选项</value>
|
||||
<value>单击左上按钮以启用沉浸式模式。
|
||||
如果遇到任何问题,请转到“设置”页面,关于标签,查看常见问题解答或联系作者以获取反馈</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>当前没有正在播放的音乐</value>
|
||||
@@ -420,9 +424,15 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDock.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDesktop.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>字体粗细</value>
|
||||
</data>
|
||||
@@ -477,9 +487,6 @@
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>本地音乐文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LRCLIB</value>
|
||||
</data>
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
@@ -495,18 +502,6 @@
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>该文件夹包含已添加文件夹,请删除这些文件夹以添加该文件夹</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQ" xml:space="preserve">
|
||||
<value>QQ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderNetease" xml:space="preserve">
|
||||
<value>Netease</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugou" xml:space="preserve">
|
||||
<value>Kugou</value>
|
||||
</data>
|
||||
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
|
||||
<value>显示调试覆盖层</value>
|
||||
</data>
|
||||
@@ -528,8 +523,8 @@
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>锁定</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>锁定后解锁,请转到系统托盘解锁</value>
|
||||
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
|
||||
<value>锁定后解锁,请转到系统托盘解锁或按下</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>扇形歌词</value>
|
||||
@@ -589,7 +584,7 @@
|
||||
<value>歌词时间轴偏移(毫秒)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>配置翻译服务</value>
|
||||
<value>LibreTranslate 翻译服务</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>服务器地址</value>
|
||||
@@ -606,10 +601,10 @@
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>访问 https://github.com/LibreTranslate/LibreTranslate 获取安装教程及更多信息(本软件与该翻译服务无任何联系)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestSuccessInfo" xml:space="preserve">
|
||||
<value>服务器测试成功</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestFailedInfo" xml:space="preserve">
|
||||
<value>服务器测试失败</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
@@ -646,10 +641,10 @@
|
||||
<value>设置</value>
|
||||
</data>
|
||||
<data name="TranslateServerNotSet" xml:space="preserve">
|
||||
<value>未设置翻译服务器,请先在设置中进行配置</value>
|
||||
<value>未设置Translate服务器,请先在设置中进行配置</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>切换歌曲时将自动重置为 0</value>
|
||||
<value>切换歌曲时重置为 0</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>将优先读取歌词内翻译,若无匹配则向 LibreTranslate 服务器请求机器翻译</value>
|
||||
@@ -735,4 +730,193 @@
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>立即加入</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFloatAnimation.Header" xml:space="preserve">
|
||||
<value>浮动动画</value>
|
||||
</data>
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>范围</value>
|
||||
</data>
|
||||
<data name="SettingsPageLockHotKey.Header" xml:space="preserve">
|
||||
<value>解锁和锁定快捷键</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
|
||||
<value>LX 音乐服务器</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacement.Header" xml:space="preserve">
|
||||
<value>停靠模式位置</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementTop.Content" xml:space="preserve">
|
||||
<value>顶部</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
|
||||
<value>底部</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
|
||||
<value>启用翻译</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
|
||||
<value>仅显示翻译</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontOpacity.Header" xml:space="preserve">
|
||||
<value>字体不透明度(非当前播放区域)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFAQ.Header" xml:space="preserve">
|
||||
<value>常见问题与解答</value>
|
||||
</data>
|
||||
<data name="FailToStartLXMusicServer" xml:space="preserve">
|
||||
<value>无法连接到 LX 音乐服务器,请转到设置 - 高级选项以检查是否正确输入链接</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>自动隐藏窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>停靠模式或桌面模式下,无歌曲正在播放时,自动隐藏窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockWindowHeight.Header" xml:space="preserve">
|
||||
<value>窗口高度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
|
||||
<value>歌词字体</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Header" xml:space="preserve">
|
||||
<value>全局拖拽</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
|
||||
<value>将标题栏扩展至整个页面使得在任意非交互区域均可拖拽窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>仿亚克力效果粗糙度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageTitle" xml:space="preserve">
|
||||
<value>音乐库 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>向 LibreTranslate 请求翻译失败,请检查设置或本机 LibreTranslate 配置</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
|
||||
<value>文件信息</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
|
||||
<value>标题</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
|
||||
<value>艺术家</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
|
||||
<value>专辑</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
|
||||
<value>年</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
|
||||
<value>期间</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
|
||||
<value>比特率</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
|
||||
<value>样本率</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
|
||||
<value>位深度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
|
||||
<value>格式</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
|
||||
<value>编码器</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
|
||||
<value>路径</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>搜索歌曲</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>排序类型</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
|
||||
<value>标题</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
|
||||
<value>专辑</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
|
||||
<value>艺术家</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>未在媒体库内找到任何歌曲</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||
<value>播放队列</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Content" xml:space="preserve">
|
||||
<value>清除播放队列</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageScrollToPlayingItem.Content" xml:space="preserve">
|
||||
<value>定位到播放项</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueLoop.Content" xml:space="preserve">
|
||||
<value>列表循环</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSingleLoop.Content" xml:space="preserve">
|
||||
<value>单曲循环</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueRandom.Content" xml:space="preserve">
|
||||
<value>随机</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageRemoveFromPlayingQueue.Text" xml:space="preserve">
|
||||
<value>从播放列表移除</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueueEmpty.Text" xml:space="preserve">
|
||||
<value>播放队列是空的</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSelectAll.Content" xml:space="preserve">
|
||||
<value>选择全部</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToPlayingQueue.Content" xml:space="preserve">
|
||||
<value>添加到播放队列</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToNext.Text" xml:space="preserve">
|
||||
<value>下一个项目</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToEnd.Text" xml:space="preserve">
|
||||
<value>列表的结尾</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>添加到歌单</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||
<value>创建歌单</value>
|
||||
</data>
|
||||
<data name="SystemTrayMusicGallery.Text" xml:space="preserve">
|
||||
<value>打开音乐库</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics 已经在运行</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>所有歌曲</value>
|
||||
</data>
|
||||
<data name="SettingsPageTelegram.Header" xml:space="preserve">
|
||||
<value>Telegram</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
|
||||
<value>歌词来源</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
|
||||
<value>翻译来源</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockMonitor.Header" xml:space="preserve">
|
||||
<value>目标显示器</value>
|
||||
</data>
|
||||
<data name="SystemTrayRestart.Text" xml:space="preserve">
|
||||
<value>重新启动</value>
|
||||
</data>
|
||||
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
|
||||
<value>重置窗口位置</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
|
||||
<value>此设置不会影响停靠模式,停靠模式将始终保持居中。</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>本地媒體庫</value>
|
||||
<value>本地音樂資料庫</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>新增存放音樂或歌詞的資料夾</value>
|
||||
@@ -127,7 +127,7 @@
|
||||
<value>在檔案總管中開啟</value>
|
||||
</data>
|
||||
<data name="SettingsPageRemovePath.Content" xml:space="preserve">
|
||||
<value>從應用程式中移除</value>
|
||||
<value>從應用程序中移除</value>
|
||||
</data>
|
||||
<data name="SettingsPageRemoveInfo.Title" xml:space="preserve">
|
||||
<value>您可以安全刪除以下項目</value>
|
||||
@@ -250,7 +250,7 @@
|
||||
<value>調整此數值將同步提升專輯圖片背景模糊強度</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>目前值:</value>
|
||||
<value>目前值: </value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>啟用模糊(> 0)時將顯著提升 GPU 佔用率</value>
|
||||
@@ -264,8 +264,14 @@
|
||||
<data name="SettingsPageLyricsLineSpacingFactorUnit.Text" xml:space="preserve">
|
||||
<value> 倍行高</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>字體大小</value>
|
||||
<data name="SettingsPageLyricsStandardFontSize.Header" xml:space="preserve">
|
||||
<value>字體尺寸(標準模式)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDockFontSize.Header" xml:space="preserve">
|
||||
<value>字體大小(停靠模式)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsDesktopFontSize.Header" xml:space="preserve">
|
||||
<value>字體尺寸(桌面模式)</value>
|
||||
</data>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>僅顯示歌詞</value>
|
||||
@@ -288,9 +294,6 @@
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>輝光效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>輝光效果作用範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置歌詞源</value>
|
||||
</data>
|
||||
@@ -304,7 +307,8 @@
|
||||
<value>歡迎使用 BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>將鼠標懸停在應用程序的頂部或底部區域以顯示更多功能選項</value>
|
||||
<value>單擊左上按鈕以啟用沉浸式模式。
|
||||
如果遇到任何問題,請轉到“設置”頁面,關於標籤,查看常見問題解答或聯繫作者以獲取反饋</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>目前沒有正在播放的音樂</value>
|
||||
@@ -337,7 +341,7 @@
|
||||
<value>適應歌詞背景(灰色)</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>专辑区域样式</value>
|
||||
<value>專輯區域樣式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
<value>圓角半徑</value>
|
||||
@@ -420,9 +424,15 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDock.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDesktop.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>字體粗細</value>
|
||||
</data>
|
||||
@@ -477,9 +487,6 @@
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>本地音樂文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LRCLIB</value>
|
||||
</data>
|
||||
<data name="SettingsPageJA.Content" xml:space="preserve">
|
||||
<value>日本語</value>
|
||||
</data>
|
||||
@@ -495,18 +502,6 @@
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>該文件夾包含已添加文件夾,請刪除這些文件夾以添加該文件夾</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQ" xml:space="preserve">
|
||||
<value>QQ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderNetease" xml:space="preserve">
|
||||
<value>Netease</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugou" xml:space="preserve">
|
||||
<value>Kugou</value>
|
||||
</data>
|
||||
<data name="SettingsPageDebugOverlay.Header" xml:space="preserve">
|
||||
<value>顯示調試覆蓋層</value>
|
||||
</data>
|
||||
@@ -528,8 +523,8 @@
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>鎖定</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>鎖定後解鎖,請轉到系統托盤解鎖</value>
|
||||
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
|
||||
<value>鎖定後解鎖,請轉到系統托盤解鎖或按下</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>扇形歌詞</value>
|
||||
@@ -568,7 +563,7 @@
|
||||
<value>歌曲標題和藝術家</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>缓动动画类型</value>
|
||||
<value>緩動動畫類型</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
|
||||
<value>播放來源</value>
|
||||
@@ -586,10 +581,10 @@
|
||||
<value>歌詞翻譯</value>
|
||||
</data>
|
||||
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
|
||||
<value>歌詞時間軸偏移(毫秒)</value>
|
||||
<value>歌詞時間軌偏移(毫秒)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>配置翻譯服務</value>
|
||||
<value>LibreTranslate 翻譯服務</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>服務器地址</value>
|
||||
@@ -601,28 +596,28 @@
|
||||
<value>目標語言</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
|
||||
<value>翻译服务由 LibreTranslate 驱动</value>
|
||||
<value>翻譯服務由 LibreTranslate 驅動</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>造訪 https://github.com/LibreTranslate/LibreTranslate 以取得安裝教學及更多資訊(本軟體與此翻譯服務無任何關聯)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestSuccessInfo" xml:space="preserve">
|
||||
<value>服務器測試成功</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestFailedInfo" xml:space="preserve">
|
||||
<value>服務器測試失敗</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
<value>歌詞描邊寬度(僅桌面模式)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>跟随系统</value>
|
||||
<value>跟隨系統</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
|
||||
<value>自動啟動</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
|
||||
<value>歌词描边颜色(仅桌面模式)</value>
|
||||
<value>歌詞描邊顏色(僅桌面模式)</value>
|
||||
</data>
|
||||
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
|
||||
<value>始終顯示在全螢幕應用程式上方</value>
|
||||
@@ -649,7 +644,7 @@
|
||||
<value>未設定翻譯伺服器,請先在設定中進行配置</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>將在切換歌曲時自動重設為 0</value>
|
||||
<value>切換歌曲時重置為 0</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>將優先讀取歌詞內翻譯,若無匹配則向 LibreTranslate 伺服器請求機器翻譯</value>
|
||||
@@ -721,7 +716,7 @@
|
||||
<value>高亮顯示範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>歌詞時間軸同步閾值</value>
|
||||
<value>歌詞時間軌同步閾值</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>當歌詞進度抖動時,請嘗試增加該閾值;更改此值會導致歌詞同步偏差</value>
|
||||
@@ -735,4 +730,193 @@
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>立即加入</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFloatAnimation.Header" xml:space="preserve">
|
||||
<value>浮動動畫</value>
|
||||
</data>
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageLockHotKey.Header" xml:space="preserve">
|
||||
<value>解鎖和鎖定快捷鍵</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
|
||||
<value>LX 音樂服務器</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacement.Header" xml:space="preserve">
|
||||
<value>停靠模式位置</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementTop.Content" xml:space="preserve">
|
||||
<value>頂部</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
|
||||
<value>底部</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
|
||||
<value>啟用翻譯</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
|
||||
<value>僅顯示翻譯</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontOpacity.Header" xml:space="preserve">
|
||||
<value>字體不透明度(非目前播放區域)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFAQ.Header" xml:space="preserve">
|
||||
<value>常見問題與解答</value>
|
||||
</data>
|
||||
<data name="FailToStartLXMusicServer" xml:space="preserve">
|
||||
<value>無法連接到 LX 音樂服務器,請轉到設置 - 高級選項以檢查是否正確輸入鏈接</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>自動隱藏窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>停靠模式或桌面模式下,無歌曲正在播放時,自動隱藏窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockWindowHeight.Header" xml:space="preserve">
|
||||
<value>窗口高度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
|
||||
<value>歌詞字體</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Header" xml:space="preserve">
|
||||
<value>全域拖曳</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
|
||||
<value>將標題列擴展至整個頁面使得在任意非互動區域均可拖曳窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>仿亚克力效果粗糙度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageTitle" xml:space="preserve">
|
||||
<value>音樂庫 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>向 LibreTranslate 請求翻譯失敗,請檢查設置或本機 LibreTranslate 配置</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
|
||||
<value>文件信息</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
|
||||
<value>標題</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
|
||||
<value>藝術家</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
|
||||
<value>專輯</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
|
||||
<value>年</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
|
||||
<value>期間</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
|
||||
<value>比特率</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
|
||||
<value>樣本率</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
|
||||
<value>位深度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
|
||||
<value>格式</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
|
||||
<value>編碼器</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
|
||||
<value>路徑</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>搜索歌曲</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>排序類型</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
|
||||
<value>標題</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
|
||||
<value>專輯</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
|
||||
<value>藝術家</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>未在媒體庫內找到任何歌曲</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||
<value>清除播放</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Content" xml:space="preserve">
|
||||
<value>清除播放隊列</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageScrollToPlayingItem.Content" xml:space="preserve">
|
||||
<value>定位到播放項</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueLoop.Content" xml:space="preserve">
|
||||
<value>列表循環</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSingleLoop.Content" xml:space="preserve">
|
||||
<value>單曲循環</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueRandom.Content" xml:space="preserve">
|
||||
<value>隨機</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageRemoveFromPlayingQueue.Text" xml:space="preserve">
|
||||
<value>從播放列表移除</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueueEmpty.Text" xml:space="preserve">
|
||||
<value>播放隊列是空的</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSelectAll.Content" xml:space="preserve">
|
||||
<value>選擇全部</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToPlayingQueue.Content" xml:space="preserve">
|
||||
<value>新增到播放隊列</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToNext.Text" xml:space="preserve">
|
||||
<value>下一個項目</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToEnd.Text" xml:space="preserve">
|
||||
<value>列表的結尾</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>添加到歌單</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||
<value>建立歌單</value>
|
||||
</data>
|
||||
<data name="SystemTrayMusicGallery.Text" xml:space="preserve">
|
||||
<value>開啟音樂庫</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics 已經在運作</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>所有歌曲</value>
|
||||
</data>
|
||||
<data name="SettingsPageTelegram.Header" xml:space="preserve">
|
||||
<value>Telegram</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Text" xml:space="preserve">
|
||||
<value>歌詞來源</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
|
||||
<value>翻譯來源</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockMonitor.Header" xml:space="preserve">
|
||||
<value>目標顯示器</value>
|
||||
</data>
|
||||
<data name="SystemTrayRestart.Text" xml:space="preserve">
|
||||
<value>重新啟動</value>
|
||||
</data>
|
||||
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
|
||||
<value>重置窗口位置</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
|
||||
<value>此設定不會影響停靠模式,停靠模式將始終保持居中。</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,25 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace BetterLyrics.WinUI3.TemplateSelector;
|
||||
|
||||
public class SongOrderTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate ByTitleTemplate { get; set; }
|
||||
public DataTemplate ByAlbumTemplate { get; set; }
|
||||
public DataTemplate ByArtistTemplate { get; set; }
|
||||
|
||||
public CommonSongProperty SongOrderType { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item)
|
||||
{
|
||||
return SongOrderType switch
|
||||
{
|
||||
CommonSongProperty.Title => ByTitleTemplate,
|
||||
CommonSongProperty.Album => ByAlbumTemplate,
|
||||
CommonSongProperty.Artist => ByArtistTemplate,
|
||||
_ => ByTitleTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,26 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class BaseViewModel : ObservableRecipient, IDisposable
|
||||
public partial class BaseViewModel : ObservableRecipient
|
||||
{
|
||||
private protected readonly DispatcherQueue _dispatcherQueue =
|
||||
DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
private protected readonly DispatcherQueue _dispatcherQueue;
|
||||
private protected readonly DispatcherQueueTimer _dispatcherQueueTimer;
|
||||
private protected readonly ISettingsService _settingsService;
|
||||
|
||||
public BaseViewModel(ISettingsService settingsService)
|
||||
{
|
||||
IsActive = true;
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
_dispatcherQueueTimer = _dispatcherQueue.CreateTimer();
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,38 +9,113 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsPageViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<bool>>
|
||||
public partial class LyricsPageViewModel : BaseViewModel,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<TimeSpan>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsSearchProvider?>>,
|
||||
IRecipient<PropertyChangedMessage<TranslationSearchProvider?>>
|
||||
{
|
||||
private readonly IPlaybackService _playbackService;
|
||||
private readonly ThrottleHelper _timelineThrottle = new(TimeSpan.FromSeconds(1));
|
||||
|
||||
private LyricsDisplayType? _preferredDisplayTypeBeforeSwitchToNonStandardMode;
|
||||
private bool _isDockMode = false;
|
||||
private bool _isDesktopMode = false;
|
||||
|
||||
private int _lyricsStandardFontSize = 8;
|
||||
private int _lyricsDockFontSize = 8;
|
||||
private int _lyricsDesktopFontSize = 8;
|
||||
|
||||
public LyricsPageViewModel(ISettingsService settingsService, IPlaybackService playbackService) : base(settingsService)
|
||||
{
|
||||
IsFirstRun = _settingsService.IsFirstRun;
|
||||
IsTranslationEnabled = _settingsService.IsTranslationEnabled;
|
||||
PreferredDisplayType = _settingsService.PreferredDisplayType;
|
||||
DisplayType = _settingsService.DisplayType;
|
||||
ResetPositionOffsetOnSongChanged = _settingsService.ResetPositionOffsetOnSongChanged;
|
||||
PositionOffset = _settingsService.PositionOffset;
|
||||
IsImmersiveMode = _settingsService.IsImmersiveMode;
|
||||
ShowTranslationOnly = _settingsService.ShowTranslationOnly;
|
||||
|
||||
UpdateHintMessageFontSize();
|
||||
|
||||
LyricsFontFamily = _settingsService.LyricsFontFamily;
|
||||
|
||||
OnIsImmersiveModeChanged(IsImmersiveMode);
|
||||
|
||||
//Volume = SystemVolumeHelper.GetMasterVolume();
|
||||
//SystemVolumeHelper.VolumeChanged += SystemVolumeHelper_VolumeChanged;
|
||||
|
||||
_playbackService = playbackService;
|
||||
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
|
||||
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
||||
_playbackService.TimelineChanged += PlaybackService_TimelineChanged;
|
||||
|
||||
IsSongPlaying = _playbackService.IsPlaying;
|
||||
}
|
||||
|
||||
private void PlaybackService_TimelineChanged(object? sender, Events.TimelineChangedEventArgs e)
|
||||
{
|
||||
SongDurationSeconds = (int)e.End.TotalSeconds;
|
||||
}
|
||||
|
||||
//private void SystemVolumeHelper_VolumeChanged(int volume)
|
||||
//{
|
||||
// Volume = volume;
|
||||
//}
|
||||
|
||||
private void PlaybackService_IsPlayingChanged(object? sender, Events.IsPlayingChangedEventArgs e)
|
||||
{
|
||||
IsSongPlaying = e.IsPlaying;
|
||||
}
|
||||
|
||||
private void PlaybackService_SongInfoChanged(object? sender, Events.SongInfoChangedEventArgs e)
|
||||
{
|
||||
SongInfo = e.SongInfo;
|
||||
PositionOffset = 0; // Reset position offset when song changes
|
||||
TrySwitchToPreferredDisplayType(e.SongInfo);
|
||||
SongDurationSeconds = SongInfo?.Duration ?? 0;
|
||||
if (ResetPositionOffsetOnSongChanged)
|
||||
{
|
||||
PositionOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double TimelinePositionSeconds { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int SongDurationSeconds { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int Volume { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string LyricsFontFamily { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int HintMessageFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsImmersiveMode { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial float BottomCommandGridOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial float BottomCommandFlyoutTriggerOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsDisplayType DisplayType { get; set; } = LyricsDisplayType.PlaceholderOnly;
|
||||
public partial LyricsDisplayType DisplayType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsFirstRun { get; set; }
|
||||
@@ -48,28 +123,48 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial bool IsWelcomeTeachingTipOpen { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsDisplayType PreferredDisplayType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial SongInfo? SongInfo { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int PositionOffset { get; set; } = 0;
|
||||
public partial int PositionOffset { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsTranslationEnabled { get; set; }
|
||||
|
||||
partial void OnIsTranslationEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsTranslationEnabled = value;
|
||||
}
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool ShowTranslationOnly { get; set; }
|
||||
|
||||
partial void OnPreferredDisplayTypeChanged(LyricsDisplayType value)
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsSongPlaying { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsSearchProvider? LyricsSearchProvider { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial TranslationSearchProvider? TranslationSearchProvider { get; set; } = null;
|
||||
|
||||
private void UpdateHintMessageFontSize()
|
||||
{
|
||||
_settingsService.PreferredDisplayType = value;
|
||||
if (_isDockMode)
|
||||
{
|
||||
HintMessageFontSize = _settingsService.LyricsDockFontSize;
|
||||
}
|
||||
else if (_isDesktopMode)
|
||||
{
|
||||
HintMessageFontSize = _settingsService.LyricsDesktopFontSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
HintMessageFontSize = _settingsService.LyricsStandardFontSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
@@ -78,13 +173,33 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
|
||||
{
|
||||
SetNonStandardModePreferredDisplayType(message.NewValue);
|
||||
TrySwitchToPreferredDisplayType(SongInfo);
|
||||
_isDockMode = message.NewValue;
|
||||
if (message.NewValue)
|
||||
{
|
||||
DisplayType = LyricsDisplayType.LyricsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayType = _settingsService.DisplayType;
|
||||
}
|
||||
UpdateHintMessageFontSize();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
|
||||
{
|
||||
SetNonStandardModePreferredDisplayType(message.NewValue);
|
||||
TrySwitchToPreferredDisplayType(SongInfo);
|
||||
_isDesktopMode = message.NewValue;
|
||||
if (message.NewValue)
|
||||
{
|
||||
DisplayType = LyricsDisplayType.LyricsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayType = _settingsService.DisplayType;
|
||||
}
|
||||
UpdateHintMessageFontSize();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsImmersiveMode))
|
||||
{
|
||||
IsImmersiveMode = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,41 +207,31 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[RelayCommand]
|
||||
private static void OpenSettingsWindow()
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<SettingsWindow>();
|
||||
WindowHelper.OpenWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
private void SetNonStandardModePreferredDisplayType(bool isEnabled)
|
||||
[RelayCommand]
|
||||
private async Task PlaySongAsync()
|
||||
{
|
||||
if (isEnabled)
|
||||
{
|
||||
_preferredDisplayTypeBeforeSwitchToNonStandardMode = PreferredDisplayType;
|
||||
PreferredDisplayType = LyricsDisplayType.LyricsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode ?? LyricsDisplayType.SplitView;
|
||||
}
|
||||
await _playbackService.PlayAsync();
|
||||
}
|
||||
|
||||
private void TrySwitchToPreferredDisplayType(SongInfo? songInfo)
|
||||
[RelayCommand]
|
||||
private async Task PauseSongAsync()
|
||||
{
|
||||
LyricsDisplayType displayType;
|
||||
await _playbackService.PauseAsync();
|
||||
}
|
||||
|
||||
if (songInfo == null)
|
||||
{
|
||||
displayType = LyricsDisplayType.PlaceholderOnly;
|
||||
}
|
||||
else if (PreferredDisplayType is LyricsDisplayType preferredDisplayType)
|
||||
{
|
||||
displayType = preferredDisplayType;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayType = LyricsDisplayType.SplitView;
|
||||
}
|
||||
|
||||
DisplayType = displayType;
|
||||
[RelayCommand]
|
||||
private async Task PreviousSongAsync()
|
||||
{
|
||||
await _playbackService.PreviousAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task NextSongAsync()
|
||||
{
|
||||
await _playbackService.NextAsync();
|
||||
}
|
||||
|
||||
partial void OnIsFirstRunChanged(bool value)
|
||||
@@ -134,5 +239,108 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
IsWelcomeTeachingTipOpen = value;
|
||||
_settingsService.IsFirstRun = false;
|
||||
}
|
||||
|
||||
partial void OnIsTranslationEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsTranslationEnabled = value;
|
||||
}
|
||||
|
||||
partial void OnPositionOffsetChanged(int value)
|
||||
{
|
||||
_settingsService.PositionOffset = value;
|
||||
}
|
||||
|
||||
partial void OnIsImmersiveModeChanged(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
BottomCommandGridOpacity = 0f;
|
||||
BottomCommandFlyoutTriggerOpacity = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
BottomCommandGridOpacity = 1f;
|
||||
BottomCommandFlyoutTriggerOpacity = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnShowTranslationOnlyChanged(bool value)
|
||||
{
|
||||
_settingsService.ShowTranslationOnly = value;
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsStandardFontSize))
|
||||
{
|
||||
UpdateHintMessageFontSize();
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsDockFontSize))
|
||||
{
|
||||
UpdateHintMessageFontSize();
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsDesktopFontSize))
|
||||
{
|
||||
UpdateHintMessageFontSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontFamily))
|
||||
{
|
||||
LyricsFontFamily = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//partial void OnVolumeChanged(int value)
|
||||
//{
|
||||
// SystemVolumeHelper.SetMasterVolume(value);
|
||||
//}
|
||||
|
||||
public void Receive(PropertyChangedMessage<TimeSpan> message)
|
||||
{
|
||||
if (message.Sender is LyricsRendererViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.TotalTime))
|
||||
{
|
||||
if (_timelineThrottle.CanTrigger())
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
TimelinePositionSeconds = message.NewValue.TotalSeconds;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsSearchProvider?> message)
|
||||
{
|
||||
if (message.Sender is LyricsRendererViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsSearchProvider))
|
||||
{
|
||||
LyricsSearchProvider = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<TranslationSearchProvider?> message)
|
||||
{
|
||||
if (message.Sender is LyricsRendererViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.TranslationSearchProvider))
|
||||
{
|
||||
TranslationSearchProvider = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
@@ -20,16 +21,23 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_isDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
_albumArtBgOpacity = _settingsService.CoverOverlayOpacity;
|
||||
_albumArtBgBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
||||
_coverAcrylicEffectAmount = _settingsService.CoverAcrylicEffectAmount;
|
||||
|
||||
_lyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
|
||||
_lyricsFgFontColorType = _settingsService.LyricsFgFontColorType;
|
||||
|
||||
_lyricsTextFormat.FontWeight = _settingsService.LyricsFontWeight.ToFontWeight();
|
||||
|
||||
_lyricsTextFormat.FontFamily = _artistTextFormat.FontFamily = _titleTextFormat.FontFamily = _settingsService.LyricsFontFamily;
|
||||
|
||||
_lyricsAlignmentType = _settingsService.LyricsAlignmentType;
|
||||
_lyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
|
||||
_lyricsLineSpacingFactor = _settingsService.LyricsLineSpacingFactor;
|
||||
_lyricsFontSize = _settingsService.LyricsFontSize;
|
||||
|
||||
_lyricsStandardFontSize = _settingsService.LyricsStandardFontSize;
|
||||
_lyricsDockFontSize = _settingsService.LyricsDockFontSize;
|
||||
_lyricsDesktopFontSize = _settingsService.LyricsDesktopFontSize;
|
||||
|
||||
_lyricsBlurAmount = _settingsService.LyricsBlurAmount;
|
||||
_isLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
|
||||
_lyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
|
||||
@@ -41,8 +49,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_lyricsBgTheme = _settingsService.LyricsBackgroundTheme;
|
||||
|
||||
_isFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
|
||||
|
||||
// 歌词描边
|
||||
_lyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
|
||||
_lyricsStrokeFontColorType = _settingsService.LyricsStrokeFontColorType;
|
||||
_customStrokeFontColor = _settingsService.LyricsCustomStrokeFontColor;
|
||||
|
||||
_isTranslationEnabled = _settingsService.IsTranslationEnabled;
|
||||
_showTranslationOnly = _settingsService.ShowTranslationOnly;
|
||||
_isLibreTranslateEnabled = _settingsService.IsLibreTranslateEnabled;
|
||||
_targetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
|
||||
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
|
||||
|
||||
@@ -50,6 +65,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
_canvasYScrollTransition.SetDuration(_settingsService.LyricsScrollDuration / 1000f);
|
||||
_canvasYScrollTransition.SetEasingType(_settingsService.LyricsScrollEasingType);
|
||||
_defaultOpacity = _settingsService.LyricsBgFontOpacity / 100f;
|
||||
|
||||
_isLyricsFloatAnimationEnabled = _settingsService.IsLyricsFloatAnimationEnabled;
|
||||
_displayType = _displayTypeReceived = _settingsService.DisplayType;
|
||||
|
||||
_libWatcherService.MusicLibraryFilesChanged +=
|
||||
LibWatcherService_MusicLibraryFilesChanged;
|
||||
@@ -57,7 +76,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
||||
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
|
||||
_playbackService.AlbumArtChangedChanged += PlaybackService_AlbumArtChangedChanged;
|
||||
_playbackService.PositionChanged += PlaybackService_PositionChanged;
|
||||
_playbackService.TimelineChanged += PlaybackService_TimelineChanged;
|
||||
|
||||
_isPlaying = _playbackService.IsPlaying;
|
||||
|
||||
UpdateColorConfig();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Brushes;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
@@ -8,17 +7,15 @@ using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Graphics.Effects;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
@@ -33,28 +30,20 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
DrawBlurredLyrics(control, blurredLyricsDs);
|
||||
}
|
||||
|
||||
if (_lastAlbumArtSwBitmap != null && _lastAlbumArtCanvasBitmap == null)
|
||||
{
|
||||
_lastAlbumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _lastAlbumArtSwBitmap);
|
||||
}
|
||||
if (_albumArtSwBitmap != null && _albumArtCanvasBitmap == null)
|
||||
{
|
||||
_albumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _albumArtSwBitmap);
|
||||
}
|
||||
|
||||
using var combined = new CanvasCommandList(control);
|
||||
using var combinedDs = combined.CreateDrawingSession();
|
||||
|
||||
if (_isDockMode)
|
||||
{
|
||||
DrawImmersiveBackground(control, combinedDs, 0f);
|
||||
FillBackgroundColor(control, combinedDs, _immersiveBgColorTransition.Value, 0f, _immersiveBgOpacityTransition.Value);
|
||||
}
|
||||
else if (_isDesktopMode)
|
||||
{
|
||||
DrawImmersiveBackground(control, combinedDs, 12f);
|
||||
FillBackgroundColor(control, combinedDs, _immersiveBgColorTransition.Value, 0f, _immersiveBgOpacityTransition.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
FillBackgroundColor(control, combinedDs, _albumArtAccentColorTransition.Value, 0f, _albumArtBgOpacity / 100f);
|
||||
DrawAlbumArtBackground(control, combinedDs);
|
||||
}
|
||||
|
||||
@@ -67,6 +56,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
if (_isDebugOverlayEnabled)
|
||||
{
|
||||
_drawFrameCount++;
|
||||
|
||||
var currentPlayingLine = _lyricsDataArr
|
||||
.ElementAtOrDefault(_langIndex)
|
||||
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
@@ -74,7 +65,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (currentPlayingLine != null)
|
||||
{
|
||||
GetLinePlayingProgress(
|
||||
currentPlayingLine,
|
||||
_playingLineIndex,
|
||||
out int charStartIndex,
|
||||
out int charLength,
|
||||
out float charProgress
|
||||
@@ -82,131 +73,61 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
ds.DrawText(
|
||||
$"[DEBUG]\n" +
|
||||
$"Cur playing {_playingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
|
||||
$"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
|
||||
$"Cur time {_totalTime + _positionOffset}\n" +
|
||||
$"Lang size {_lyricsDataArr.Count}\n" +
|
||||
$"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
|
||||
new Vector2(10, 10),
|
||||
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
|
||||
$"Canvas size: {_canvasWidth}x{_canvasHeight}\n" +
|
||||
$"FPS (Draw): {_displayedDrawFrameCount}\n" +
|
||||
$"Playing line: {_playingLineIndex}\n" +
|
||||
$"Syllable start idx: {charStartIndex}\n" +
|
||||
$"Syllable len: {charLength}\n" +
|
||||
$"Syllable prog: {charProgress}\n" +
|
||||
$"Visible lines: [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
|
||||
$"Total line count: {GetMaxLyricsLineIndexBoundaries().Item2 + 1}\n" +
|
||||
$"Cur time: {TotalTime + _positionOffset}\n" +
|
||||
$"Lang size: {_lyricsDataArr.Count}\n" +
|
||||
$"Song duration: {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}\n" +
|
||||
$"Y offset: {_canvasYScrollTransition.Value}",
|
||||
new Vector2(10, 40),
|
||||
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White,
|
||||
_debugTextFormat
|
||||
);
|
||||
}
|
||||
|
||||
//for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
//{
|
||||
// LyricsLine? line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
// if (line != null)
|
||||
// {
|
||||
// ds.DrawText(
|
||||
// $"[{i}] {line.OriginalText} {line.HighlightOpacityTransition.Value}",
|
||||
// new Vector2(10, 30 + (i - _startVisibleLineIndex) * 20),
|
||||
// ThemeTypeSent == ElementTheme.Light ? Colors.Black : Colors.White
|
||||
// );
|
||||
// }
|
||||
//}
|
||||
if (_drawFrameStopwatch?.Elapsed.TotalSeconds >= 1.0)
|
||||
{
|
||||
_displayedDrawFrameCount = _drawFrameCount;
|
||||
_drawFrameStopwatch?.Restart();
|
||||
_drawFrameCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBackgroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
|
||||
private void DrawBackgroundImgae(OpacityEffect effect, CanvasDrawingSession ds, CanvasBitmap canvasBitmap)
|
||||
{
|
||||
float imageWidth = (float)canvasBitmap.Size.Width;
|
||||
float imageHeight = (float)canvasBitmap.Size.Height;
|
||||
|
||||
float targetSize = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2)) * 1.4f;
|
||||
float targetSize = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2));
|
||||
float scaleFactor = targetSize / MathF.Min(imageWidth, imageHeight);
|
||||
|
||||
float x = _canvasWidth / 2 - imageWidth * scaleFactor / 2;
|
||||
float y = _canvasHeight / 2 - imageHeight * scaleFactor / 2;
|
||||
|
||||
// Original source: https://zhuanlan.zhihu.com/p/37178216
|
||||
float gain = _lyricsBgBrightnessTransition.Value;
|
||||
|
||||
float whiteX = 1 - 0.5f * gain;
|
||||
float whiteY = 0.5f + 0.5f * gain;
|
||||
float blackX = 0.5f - 0.5f * gain;
|
||||
float blackY = 0 + 0.5f * gain;
|
||||
|
||||
ds.DrawImage(new OpacityEffect
|
||||
{
|
||||
Source = new BrightnessEffect
|
||||
{
|
||||
Source = new ScaleEffect
|
||||
{
|
||||
Scale = new Vector2(scaleFactor),
|
||||
Source = canvasBitmap,
|
||||
},
|
||||
WhitePoint = new Vector2(whiteX, whiteY),
|
||||
BlackPoint = new Vector2(blackX, blackY),
|
||||
},
|
||||
Opacity = opacity,
|
||||
}, new Vector2(x, y)
|
||||
);
|
||||
ds.DrawImage(effect, new Vector2(x, y));
|
||||
}
|
||||
|
||||
private void DrawForegroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
|
||||
private void DrawForegroundImgae(OpacityEffect effect, CanvasDrawingSession ds)
|
||||
{
|
||||
if (opacity == 0) return;
|
||||
|
||||
float imageWidth = (float)canvasBitmap.Size.Width;
|
||||
float imageHeight = (float)canvasBitmap.Size.Height;
|
||||
|
||||
float scaleFactor = _albumArtSize / Math.Min(imageWidth, imageHeight);
|
||||
if (scaleFactor < 0.01f) return;
|
||||
|
||||
float cornerRadius = _albumArtCornerRadius / 100f * _albumArtSize / 2;
|
||||
|
||||
using var cornerRadiusMask = new CanvasCommandList(control.Device);
|
||||
using var cornerRadiusMaskDs = cornerRadiusMask.CreateDrawingSession();
|
||||
cornerRadiusMaskDs.FillRoundedRectangle(
|
||||
new Rect(0, 0, imageWidth * scaleFactor, imageHeight * scaleFactor),
|
||||
cornerRadius, cornerRadius, Colors.White
|
||||
);
|
||||
|
||||
ds.DrawImage(new OpacityEffect
|
||||
{
|
||||
Source = new AlphaMaskEffect
|
||||
{
|
||||
Source = new ScaleEffect
|
||||
{
|
||||
Scale = new Vector2(scaleFactor),
|
||||
Source = canvasBitmap,
|
||||
},
|
||||
AlphaMask = cornerRadiusMask,
|
||||
},
|
||||
Opacity = opacity,
|
||||
}, new Vector2(_albumArtXTransition.Value, _albumArtY)
|
||||
);
|
||||
ds.DrawImage(effect, new Vector2(_albumArtXTransition.Value, _albumArtYTransition.Value));
|
||||
}
|
||||
|
||||
private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
if (_albumArtBgEffect == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
|
||||
|
||||
using var overlappedCovers = new CanvasCommandList(control.Device);
|
||||
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
|
||||
|
||||
if (_lastAlbumArtCanvasBitmap != null)
|
||||
{
|
||||
DrawBackgroundImgae(control, overlappedCoversDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
|
||||
}
|
||||
if (_albumArtCanvasBitmap != null)
|
||||
{
|
||||
DrawBackgroundImgae(control, overlappedCoversDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
|
||||
}
|
||||
|
||||
using var coverOverlayEffect = new OpacityEffect
|
||||
{
|
||||
Opacity = _albumArtBgOpacity / 100f,
|
||||
Source = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = _albumArtBgBlurAmount,
|
||||
Source = overlappedCovers,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
Optimization = EffectOptimization.Quality,
|
||||
},
|
||||
};
|
||||
ds.DrawImage(coverOverlayEffect);
|
||||
|
||||
ds.DrawImage(_albumArtBgEffect);
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
@@ -214,13 +135,14 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
using var albumArt = new CanvasCommandList(control.Device);
|
||||
using var albumArtDs = albumArt.CreateDrawingSession();
|
||||
if (_albumArtCanvasBitmap != null)
|
||||
|
||||
if (_lastFgImageEffect != null && !_lastFgImageEffect.IsDisposed() && _lastAlbumArtCanvasBitmap != null)
|
||||
{
|
||||
DrawForegroundImgae(control, albumArtDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
|
||||
DrawForegroundImgae(_lastFgImageEffect, albumArtDs);
|
||||
}
|
||||
if (_lastAlbumArtCanvasBitmap != null)
|
||||
if (_fgImageEffect != null && !_fgImageEffect.IsDisposed() && _albumArtCanvasBitmap != null)
|
||||
{
|
||||
DrawForegroundImgae(control, albumArtDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
|
||||
DrawForegroundImgae(_fgImageEffect, albumArtDs);
|
||||
}
|
||||
|
||||
using var opacity = new CanvasCommandList(control.Device);
|
||||
@@ -229,7 +151,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
Source = albumArt,
|
||||
BlurAmount = 12f,
|
||||
Optimization = EffectOptimization.Quality,
|
||||
Optimization = EffectOptimization.Speed,
|
||||
});
|
||||
opacityDs.DrawImage(albumArt);
|
||||
|
||||
@@ -254,21 +176,32 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private void DrawSingleTitleAndArtist(ICanvasAnimatedControl control, CanvasDrawingSession ds, string? title, string? artist, float opacity)
|
||||
{
|
||||
CanvasTextLayout titleLayout = new(
|
||||
var maxWidth = _lyricsLayoutOrientation switch
|
||||
{
|
||||
LyricsLayoutOrientation.Horizontal => _albumArtSize,
|
||||
LyricsLayoutOrientation.Vertical => _canvasWidth - _leftMargin - _albumArtSize - _rightMargin,
|
||||
_ => 0f
|
||||
};
|
||||
if (maxWidth <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using CanvasTextLayout titleLayout = new(
|
||||
control, title ?? string.Empty,
|
||||
_titleTextFormat, _albumArtSize, _canvasHeight
|
||||
_titleTextFormat, maxWidth, _canvasHeight
|
||||
);
|
||||
CanvasTextLayout artistLayout = new(
|
||||
using CanvasTextLayout artistLayout = new(
|
||||
control, artist ?? string.Empty,
|
||||
_artistTextFormat, _albumArtSize, _canvasHeight
|
||||
_artistTextFormat, maxWidth, _canvasHeight
|
||||
);
|
||||
ds.DrawTextLayout(
|
||||
titleLayout,
|
||||
new Vector2(_albumArtXTransition.Value, _titleY),
|
||||
new Vector2(_titleXTransition.Value, _titleYTransition.Value),
|
||||
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 255 * opacity)));
|
||||
ds.DrawTextLayout(
|
||||
artistLayout,
|
||||
new Vector2(_albumArtXTransition.Value, _titleY + (float)titleLayout.LayoutBounds.Height),
|
||||
new Vector2(_titleXTransition.Value, _titleYTransition.Value + (float)titleLayout.LayoutBounds.Height),
|
||||
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 128 * opacity)));
|
||||
}
|
||||
|
||||
@@ -286,90 +219,47 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (line == null) continue;
|
||||
|
||||
var textLayout = line.CanvasTextLayout;
|
||||
|
||||
if (textLayout == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var position = new Vector2(line.Position.X, line.Position.Y);
|
||||
if (textLayout == null) continue;
|
||||
|
||||
float layoutWidth = (float)textLayout.LayoutBounds.Width;
|
||||
float layoutHeight = (float)textLayout.LayoutBounds.Height;
|
||||
|
||||
if (layoutWidth <= 0 || layoutHeight <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (layoutWidth <= 0 || layoutHeight <= 0) continue;
|
||||
|
||||
float centerX = position.X;
|
||||
float centerY = position.Y + layoutHeight / 2;
|
||||
|
||||
switch (_lyricsAlignmentType)
|
||||
{
|
||||
case TextAlignmentType.Left:
|
||||
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
|
||||
break;
|
||||
case TextAlignmentType.Center:
|
||||
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
|
||||
centerX += _maxLyricsWidth / 2;
|
||||
break;
|
||||
case TextAlignmentType.Right:
|
||||
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
|
||||
centerX += _maxLyricsWidth;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
float yOffset = _canvasYScrollTransition.Value + _canvasHeight / 2 + _lyricsYTransition.Value;
|
||||
|
||||
// 组合变换:缩放 -> 旋转 -> 平移
|
||||
ds.Transform =
|
||||
Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
|
||||
Matrix3x2.CreateScale(line.ScaleTransition.Value, line.CenterPosition)
|
||||
* Matrix3x2.CreateRotation(line.AngleTransition.Value, currentPlayingLine.Position)
|
||||
* Matrix3x2.CreateTranslation(_lyricsXTransition.Value, _canvasYScrollTransition.Value + _canvasHeight / 2);
|
||||
* Matrix3x2.CreateTranslation(_lyricsXTransition.Value, yOffset);
|
||||
|
||||
// Create the background lyrics line with stroke and fill
|
||||
using var bgLyrics = new CanvasCommandList(control.Device);
|
||||
using var bgLyricsDs = bgLyrics.CreateDrawingSession();
|
||||
if (line.BackgroundFontEffect == null || line.ForegroundFontEffect == null) continue;
|
||||
|
||||
// Create the foreground lyrics line with stroke and fill
|
||||
using var fgLyrics = new CanvasCommandList(control.Device);
|
||||
using var fgLyricsDs = fgLyrics.CreateDrawingSession();
|
||||
|
||||
// 创建文字几何体
|
||||
using (var textGeometry = CanvasGeometry.CreateText(textLayout))
|
||||
{
|
||||
if (_isDesktopMode)
|
||||
{
|
||||
bgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 背景描边
|
||||
fgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 前景描边
|
||||
}
|
||||
|
||||
bgLyricsDs.FillGeometry(textGeometry, position, _bgFontColor); // 背景填充
|
||||
fgLyricsDs.FillGeometry(textGeometry, position, _fgFontColor); // 前景填充
|
||||
}
|
||||
using var combined = new CanvasCommandList(control.Device);
|
||||
using var combinedDs = combined.CreateDrawingSession();
|
||||
|
||||
// Mock gradient blurred lyrics layer
|
||||
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层(背景歌词层次)
|
||||
// Current line will not be blurred
|
||||
ds.DrawImage(
|
||||
new GaussianBlurEffect
|
||||
combinedDs.DrawImage(
|
||||
new OpacityEffect
|
||||
{
|
||||
Source = new OpacityEffect { Source = bgLyrics, Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
|
||||
BlurAmount = line.BlurAmountTransition.Value,
|
||||
Optimization = EffectOptimization.Quality,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
Source = new GaussianBlurEffect
|
||||
{
|
||||
Source = line.BackgroundFontEffect,
|
||||
BlurAmount = line.BlurAmountTransition.Value,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
Optimization = EffectOptimization.Speed,
|
||||
},
|
||||
Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value,
|
||||
}
|
||||
);
|
||||
|
||||
if (line.HighlightOpacityTransition.Value != 0)
|
||||
if (line.HighlightOpacityTransition.Value !=0)
|
||||
{
|
||||
// 再叠加高亮行歌词层(前景歌词层)
|
||||
using var mask = new CanvasCommandList(control.Device);
|
||||
@@ -381,7 +271,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (i == _playingLineIndex)
|
||||
{
|
||||
GetLinePlayingProgress(
|
||||
line,
|
||||
i,
|
||||
out int charStartIndex,
|
||||
out int charLength,
|
||||
out float charProgress
|
||||
@@ -398,11 +288,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
var region = regions[j];
|
||||
var rect = new Rect(
|
||||
region.LayoutBounds.X,
|
||||
region.LayoutBounds.Y + position.Y,
|
||||
region.LayoutBounds.Y + line.Position.Y,
|
||||
region.LayoutBounds.Width,
|
||||
region.LayoutBounds.Height
|
||||
);
|
||||
maskDs.FillRectangle(rect, Colors.Black);
|
||||
maskDs.FillRectangle(rect, Color.FromArgb(255, 128, 128, 128));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,20 +305,20 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
// Rects
|
||||
var highlightRect = new Rect(
|
||||
highlightRegion.LayoutBounds.X,
|
||||
highlightRegion.LayoutBounds.Y + position.Y,
|
||||
highlightRegion.LayoutBounds.Y + line.Position.Y,
|
||||
highlightWidth,
|
||||
highlightRegion.LayoutBounds.Height
|
||||
);
|
||||
|
||||
var fadeInRect = new Rect(
|
||||
highlightRect.Right - fadingWidth,
|
||||
highlightRegion.LayoutBounds.Y + position.Y,
|
||||
highlightRegion.LayoutBounds.Y + line.Position.Y,
|
||||
fadingWidth,
|
||||
highlightRegion.LayoutBounds.Height
|
||||
);
|
||||
var fadeOutRect = new Rect(
|
||||
highlightRect.Right,
|
||||
highlightRegion.LayoutBounds.Y + position.Y,
|
||||
highlightRegion.LayoutBounds.Y + line.Position.Y,
|
||||
fadingWidth,
|
||||
highlightRegion.LayoutBounds.Height
|
||||
);
|
||||
@@ -447,7 +337,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
fadingWidth
|
||||
);
|
||||
|
||||
maskDs.FillRectangle(highlightRect, Colors.White);
|
||||
maskDs.FillRectangle(highlightRect, Color.FromArgb(255, 128, 128, 128));
|
||||
maskDs.FillRectangle(fadeOutRect, fadeOutBrush);
|
||||
|
||||
highlightMaskDs.FillRectangle(fadeInRect, fadeInBrush);
|
||||
@@ -456,7 +346,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
else
|
||||
{
|
||||
float height = 0f;
|
||||
var regions = textLayout.GetCharacterRegions(0, string.Join("", line.CharTimings.Select(x => x.Text)).Length);
|
||||
var regions = textLayout.GetCharacterRegions(0, line.OriginalText.Length);
|
||||
if (regions.Length > 0)
|
||||
{
|
||||
height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top;
|
||||
@@ -465,7 +355,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
maskDs.FillRectangle(
|
||||
new Rect(
|
||||
textLayout.LayoutBounds.X,
|
||||
position.Y,
|
||||
line.Position.Y,
|
||||
textLayout.LayoutBounds.Width,
|
||||
height
|
||||
),
|
||||
@@ -473,66 +363,89 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
);
|
||||
}
|
||||
|
||||
ds.DrawImage(
|
||||
new OpacityEffect
|
||||
using var opacityEffect = new OpacityEffect
|
||||
{
|
||||
Source = new BlendEffect
|
||||
{
|
||||
Source = new BlendEffect
|
||||
{
|
||||
Background = _isLyricsGlowEffectEnabled
|
||||
Background = _isLyricsGlowEffectEnabled
|
||||
? new GaussianBlurEffect
|
||||
{
|
||||
Source = new AlphaMaskEffect
|
||||
{
|
||||
Source = fgLyrics,
|
||||
Source = line.ForegroundFontEffect,
|
||||
AlphaMask = _lyricsGlowEffectScope switch
|
||||
{
|
||||
LineRenderingType.CurrentChar => highlightMask,
|
||||
LineRenderingType.LineStartToCurrentChar => mask,
|
||||
LineRenderingType.CurrentLine => fgLyrics,
|
||||
LineRenderingType.CurrentLine => line.ForegroundFontEffect,
|
||||
_ => mask,
|
||||
},
|
||||
},
|
||||
BlurAmount = _lyricsGlowEffectAmount,
|
||||
Optimization = EffectOptimization.Quality,
|
||||
Optimization = EffectOptimization.Speed,
|
||||
}
|
||||
: new CanvasCommandList(control.Device),
|
||||
Foreground = new AlphaMaskEffect
|
||||
Foreground = new AlphaMaskEffect
|
||||
{
|
||||
Source = line.ForegroundFontEffect,
|
||||
AlphaMask = _lyricsHighlightScope switch
|
||||
{
|
||||
Source = fgLyrics,
|
||||
AlphaMask = _lyricsHighlightScope switch
|
||||
{
|
||||
LineRenderingType.CurrentChar => highlightMask,
|
||||
LineRenderingType.LineStartToCurrentChar => mask,
|
||||
LineRenderingType.CurrentLine => fgLyrics,
|
||||
_ => mask,
|
||||
},
|
||||
LineRenderingType.CurrentChar => highlightMask,
|
||||
LineRenderingType.LineStartToCurrentChar => mask,
|
||||
LineRenderingType.CurrentLine => line.ForegroundFontEffect,
|
||||
_ => mask,
|
||||
},
|
||||
},
|
||||
Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
|
||||
},
|
||||
Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
|
||||
};
|
||||
|
||||
combinedDs.DrawImage(opacityEffect);
|
||||
|
||||
if (i == _playingLineIndex)
|
||||
{
|
||||
if (_isLyricsFloatAnimationEnabled)
|
||||
{
|
||||
ds.DrawImage(new DisplacementMapEffect
|
||||
{
|
||||
Source = combined,
|
||||
Displacement = mask,
|
||||
XChannelSelect = EffectChannelSelect.Red,
|
||||
YChannelSelect = EffectChannelSelect.Alpha,
|
||||
Amount = 1f,
|
||||
});
|
||||
}
|
||||
);
|
||||
else
|
||||
{
|
||||
ds.DrawImage(combined);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ds.DrawImage(combined);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ds.DrawImage(combined);
|
||||
}
|
||||
|
||||
// Reset scale
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
|
||||
line.DisposeFontEffects();
|
||||
line.DisposeTextGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawImmersiveBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds, float radius)
|
||||
private void FillBackgroundColor(ICanvasAnimatedControl control, CanvasDrawingSession ds, Color color, float radius, float opacity)
|
||||
{
|
||||
CanvasCommandList list = new(control.Device);
|
||||
using var listDs = list.CreateDrawingSession();
|
||||
listDs.FillRoundedRectangle(
|
||||
ds.FillRoundedRectangle(
|
||||
new Rect(0, 0, _canvasWidth, _canvasHeight),
|
||||
radius,
|
||||
radius,
|
||||
_immersiveBgTransition.Value
|
||||
color.WithAlpha((byte)(opacity * 255))
|
||||
);
|
||||
ds.DrawImage(new OpacityEffect
|
||||
{
|
||||
Source = list,
|
||||
Opacity = _immersiveBgOpacityTransition.Value
|
||||
});
|
||||
}
|
||||
|
||||
private CanvasLinearGradientBrush GetHorizontalFillBrush(
|
||||
@@ -542,16 +455,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
float width
|
||||
)
|
||||
{
|
||||
return new CanvasLinearGradientBrush(
|
||||
control,
|
||||
stops
|
||||
.Select(stops => new CanvasGradientStop
|
||||
{
|
||||
Position = stops.position,
|
||||
Color = Color.FromArgb((byte)(stops.opacity * 255), 0, 0, 0),
|
||||
})
|
||||
.ToArray()
|
||||
)
|
||||
return new CanvasLinearGradientBrush(control, stops.Select(stops => new CanvasGradientStop
|
||||
{
|
||||
Position = stops.position,
|
||||
Color = Color.FromArgb((byte)(stops.opacity * 255), 128, 128, 128),
|
||||
}).ToArray())
|
||||
{
|
||||
StartPoint = new Vector2(startX, 0),
|
||||
EndPoint = new Vector2(startX + width, 0),
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
{
|
||||
private OpacityEffect? _lastBgImageEffect;
|
||||
private OpacityEffect? _bgImageEffect;
|
||||
|
||||
private OpacityEffect? _lastFgImageEffect;
|
||||
private OpacityEffect? _fgImageEffect;
|
||||
|
||||
private CanvasCommandList? _albumArtBgEffect;
|
||||
|
||||
private OpacityEffect CreateBgImageEffect(CanvasBitmap canvasBitmap, float opacity)
|
||||
{
|
||||
float imageWidth = (float)canvasBitmap.Size.Width;
|
||||
float imageHeight = (float)canvasBitmap.Size.Height;
|
||||
|
||||
float targetSize = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2));
|
||||
float scaleFactor = targetSize / MathF.Min(imageWidth, imageHeight);
|
||||
|
||||
// Original source: https://zhuanlan.zhihu.com/p/37178216
|
||||
float gain = _lyricsBgBrightnessTransition.Value;
|
||||
|
||||
float whiteX = 1 - 0.5f * gain;
|
||||
float whiteY = 0.5f + 0.5f * gain;
|
||||
float blackX = 0.5f - 0.5f * gain;
|
||||
float blackY = 0 + 0.5f * gain;
|
||||
|
||||
return new OpacityEffect
|
||||
{
|
||||
Source = new BrightnessEffect
|
||||
{
|
||||
Source = new ScaleEffect
|
||||
{
|
||||
Scale = new Vector2(scaleFactor),
|
||||
Source = canvasBitmap,
|
||||
},
|
||||
WhitePoint = new Vector2(whiteX, whiteY),
|
||||
BlackPoint = new Vector2(blackX, blackY),
|
||||
},
|
||||
Opacity = opacity,
|
||||
};
|
||||
}
|
||||
|
||||
private OpacityEffect? CreateFgImageEffect(ICanvasAnimatedControl control, CanvasBitmap canvasBitmap, float opacity)
|
||||
{
|
||||
// TODO 最大化/还原时图片大小未跟随改变
|
||||
if (opacity == 0) return null;
|
||||
|
||||
float imageWidth = (float)canvasBitmap.Size.Width;
|
||||
float imageHeight = (float)canvasBitmap.Size.Height;
|
||||
|
||||
float scaleFactor = _albumArtSize / Math.Min(imageWidth, imageHeight);
|
||||
if (scaleFactor < 0.01f) return null;
|
||||
|
||||
float cornerRadius = _albumArtCornerRadius / 100f * _albumArtSize / 2;
|
||||
|
||||
var cornerRadiusMask = new CanvasCommandList(control);
|
||||
using var cornerRadiusMaskDs = cornerRadiusMask.CreateDrawingSession();
|
||||
cornerRadiusMaskDs.FillRoundedRectangle(
|
||||
new Rect(0, 0, imageWidth * scaleFactor, imageHeight * scaleFactor),
|
||||
cornerRadius, cornerRadius, Colors.White
|
||||
);
|
||||
|
||||
return new OpacityEffect
|
||||
{
|
||||
Source = new AlphaMaskEffect
|
||||
{
|
||||
Source = new ScaleEffect
|
||||
{
|
||||
Scale = new Vector2(scaleFactor),
|
||||
Source = canvasBitmap,
|
||||
},
|
||||
AlphaMask = cornerRadiusMask,
|
||||
},
|
||||
Opacity = opacity,
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateAlbumArtBgEffect(ICanvasAnimatedControl control)
|
||||
{
|
||||
_albumArtBgEffect?.Dispose();
|
||||
_albumArtBgEffect = null;
|
||||
|
||||
using var overlappedCovers = new CanvasCommandList(control);
|
||||
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
|
||||
|
||||
if (_lastBgImageEffect != null && !_lastBgImageEffect.IsDisposed() && _lastAlbumArtCanvasBitmap != null)
|
||||
{
|
||||
DrawBackgroundImgae(_lastBgImageEffect, overlappedCoversDs, _lastAlbumArtCanvasBitmap);
|
||||
}
|
||||
if (_bgImageEffect != null && !_bgImageEffect.IsDisposed() && _albumArtCanvasBitmap != null)
|
||||
{
|
||||
DrawBackgroundImgae(_bgImageEffect, overlappedCoversDs, _albumArtCanvasBitmap);
|
||||
}
|
||||
|
||||
using var blurredCover = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = _albumArtBgBlurAmount,
|
||||
Source = overlappedCovers,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
Optimization = EffectOptimization.Speed,
|
||||
};
|
||||
|
||||
using var combined = new CanvasCommandList(control);
|
||||
using var combinedDs = combined.CreateDrawingSession();
|
||||
|
||||
if (_coverAcrylicEffectAmount > 0 && _coverAcrylicNoiseCanvasBitmap != null)
|
||||
{
|
||||
// 应用亚克力噪点效果
|
||||
combinedDs.DrawImage(new BlendEffect
|
||||
{
|
||||
Mode = BlendEffectMode.SoftLight,
|
||||
Background = blurredCover,
|
||||
Foreground = new OpacityEffect
|
||||
{
|
||||
Source = _coverAcrylicNoiseCanvasBitmap,
|
||||
Opacity = _coverAcrylicEffectAmount / 100f,
|
||||
},
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
combinedDs.DrawImage(blurredCover);
|
||||
}
|
||||
|
||||
_albumArtBgEffect = new CanvasCommandList(control);
|
||||
using var albumArtBgDs = _albumArtBgEffect.CreateDrawingSession();
|
||||
albumArtBgDs.DrawImage(new OpacityEffect
|
||||
{
|
||||
Opacity = _albumArtBgOpacity / 100f,
|
||||
Source = combined,
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateLastBgImageEffect()
|
||||
{
|
||||
_lastBgImageEffect?.Dispose();
|
||||
_lastBgImageEffect = null;
|
||||
if (_lastAlbumArtCanvasBitmap != null)
|
||||
{
|
||||
_lastBgImageEffect = CreateBgImageEffect(_lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBgImageEffect()
|
||||
{
|
||||
_bgImageEffect?.Dispose();
|
||||
_bgImageEffect = null;
|
||||
if (_albumArtCanvasBitmap != null)
|
||||
{
|
||||
_bgImageEffect = CreateBgImageEffect(_albumArtCanvasBitmap, _albumArtBgTransition.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLastFgImageEffect(ICanvasAnimatedControl control)
|
||||
{
|
||||
_lastFgImageEffect?.Dispose();
|
||||
_lastFgImageEffect = null;
|
||||
if (_lastAlbumArtCanvasBitmap != null)
|
||||
{
|
||||
_lastFgImageEffect = CreateFgImageEffect(control, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFgImageEffect(ICanvasAnimatedControl control)
|
||||
{
|
||||
_fgImageEffect?.Dispose();
|
||||
_fgImageEffect = null;
|
||||
if (_albumArtCanvasBitmap != null)
|
||||
{
|
||||
_fgImageEffect = CreateFgImageEffect(control, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,14 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
: IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<float>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<Color>>,
|
||||
@@ -23,13 +25,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<EasingType>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>>>
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LocalMediaFolder>>>
|
||||
{
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>> message)
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<LocalMediaFolder>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LocalLyricsFolders))
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LocalMediaFolders))
|
||||
{
|
||||
// Music lib changed, re-fetch lyrics
|
||||
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
|
||||
@@ -68,6 +70,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.IsDebugOverlayEnabled))
|
||||
{
|
||||
_isDebugOverlayEnabled = message.NewValue;
|
||||
_isDebugOverlayEnabledChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLyricsGlowEffectEnabled))
|
||||
{
|
||||
@@ -78,6 +81,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_isFanLyricsEnabled = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLyricsFloatAnimationEnabled))
|
||||
{
|
||||
_isLyricsFloatAnimationEnabled = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLibreTranslateEnabled))
|
||||
{
|
||||
_isLibreTranslateEnabled = message.NewValue;
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsWindowViewModel)
|
||||
{
|
||||
@@ -85,20 +97,25 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_isDockMode = message.NewValue;
|
||||
UpdateColorConfig();
|
||||
UpdateImmersiveBackgroundOpacity();
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
|
||||
{
|
||||
_isDesktopMode = message.NewValue;
|
||||
UpdateColorConfig();
|
||||
UpdateImmersiveBackgroundOpacity();
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsLyricsWindowLocked))
|
||||
{
|
||||
_isLyricsWindowLocked = message.NewValue;
|
||||
UpdateImmersiveBackgroundOpacity();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsMouseWithinWindow))
|
||||
{
|
||||
_isMouseWithinWindow = message.NewValue;
|
||||
_immersiveBgOpacityTransition.StartTransition(_isDesktopMode ? (_isMouseWithinWindow ? 1f : 0f) : 1f);
|
||||
UpdateImmersiveBackgroundOpacity();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsPageViewModel)
|
||||
@@ -109,6 +126,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_logger.LogInformation("Translation enabled state changed: {IsEnabled}", _isTranslationEnabled);
|
||||
UpdateTranslations();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsPageViewModel.ShowTranslationOnly))
|
||||
{
|
||||
_showTranslationOnly = message.NewValue;
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +140,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.ActivatedWindowAccentColor))
|
||||
{
|
||||
_immersiveBgTransition.StartTransition(message.NewValue);
|
||||
_immersiveBgColorTransition.StartTransition(message.NewValue);
|
||||
_environmentalColor = message.NewValue;
|
||||
UpdateColorConfig();
|
||||
}
|
||||
@@ -162,6 +184,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.CoverImageRadius))
|
||||
{
|
||||
_albumArtCornerRadius = message.NewValue;
|
||||
_isAlbumArtCornerRadiusChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayOpacity))
|
||||
{
|
||||
@@ -171,6 +194,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_albumArtBgBlurAmount = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverAcrylicEffectAmount))
|
||||
{
|
||||
_coverAcrylicEffectAmount = message.NewValue;
|
||||
_isCoverAcrylicEffectAmountChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsVerticalEdgeOpacity))
|
||||
{
|
||||
_lyricsVerticalEdgeOpacity = message.NewValue;
|
||||
@@ -181,9 +209,19 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_lyricsBlurAmount = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsStandardFontSize))
|
||||
{
|
||||
_lyricsFontSize = message.NewValue;
|
||||
_lyricsStandardFontSize = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsDockFontSize))
|
||||
{
|
||||
_lyricsDockFontSize = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsDesktopFontSize))
|
||||
{
|
||||
_lyricsDesktopFontSize = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.SelectedTargetLanguageIndex))
|
||||
@@ -195,6 +233,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontStrokeWidth))
|
||||
{
|
||||
_lyricsFontStrokeWidth = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsScrollDuration))
|
||||
{
|
||||
@@ -204,6 +243,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_timelineSyncThreshold = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBgFontOpacity))
|
||||
{
|
||||
_defaultOpacity = message.NewValue / 100f;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsPageViewModel)
|
||||
{
|
||||
@@ -236,6 +280,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsAlignmentType))
|
||||
{
|
||||
_lyricsAlignmentType = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.SongInfoAlignmentType))
|
||||
{
|
||||
@@ -306,5 +351,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontFamily))
|
||||
{
|
||||
_lyricsTextFormat.FontFamily = _artistTextFormat.FontFamily = _titleTextFormat.FontFamily = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
private readonly ValueTransition<float> _canvasYScrollTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.5f,
|
||||
easingType: EasingType.EaseInOutCubic
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutSine
|
||||
);
|
||||
|
||||
private readonly ValueTransition<Color> _immersiveBgTransition = new(
|
||||
private readonly ValueTransition<Color> _immersiveBgColorTransition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
);
|
||||
|
||||
private readonly ValueTransition<Color> _albumArtAccentColorTransition = new(
|
||||
initialValue: Colors.Transparent,
|
||||
durationSeconds: 0.3f,
|
||||
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
|
||||
@@ -26,12 +32,31 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private readonly ValueTransition<float> _immersiveBgOpacityTransition = new(
|
||||
initialValue: 1f,
|
||||
durationSeconds: 0.2f
|
||||
durationSeconds: 0.3f
|
||||
);
|
||||
|
||||
private readonly ValueTransition<float> _titleXTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutBack
|
||||
);
|
||||
|
||||
private readonly ValueTransition<float> _titleYTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutBack
|
||||
);
|
||||
|
||||
private readonly ValueTransition<float> _lyricsXTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutBack
|
||||
);
|
||||
|
||||
private readonly ValueTransition<float> _lyricsYTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutBack
|
||||
);
|
||||
|
||||
private readonly ValueTransition<float> _lyricsOpacityTransition = new(
|
||||
@@ -51,7 +76,14 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private readonly ValueTransition<float> _albumArtXTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutBack
|
||||
);
|
||||
|
||||
private readonly ValueTransition<float> _albumArtYTransition = new(
|
||||
initialValue: 0f,
|
||||
durationSeconds: 0.3f,
|
||||
easingType: EasingType.EaseInOutBack
|
||||
);
|
||||
|
||||
private readonly ValueTransition<float> _songInfoOpacityTransition = new(
|
||||
|
||||
@@ -6,9 +6,14 @@ using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
@@ -19,17 +24,25 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private bool _isCanvasHeightChanged = false;
|
||||
|
||||
private bool _isDisplayTypeChanged = false;
|
||||
|
||||
private bool _isLyricsLayoutOrientationChanged = false;
|
||||
|
||||
private bool _isPlayingLineChanged = false;
|
||||
private bool _isVisibleLinesBoundaryChanged = false;
|
||||
|
||||
private bool _isDebugOverlayEnabledChanged = false;
|
||||
|
||||
private bool _albumArtChanged = false;
|
||||
private bool _isCoverAcrylicEffectAmountChanged = false;
|
||||
|
||||
private bool _isAlbumArtCornerRadiusChanged = true;
|
||||
|
||||
public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
|
||||
{
|
||||
_elapsedTime = args.Timing.ElapsedTime;
|
||||
|
||||
if (_isPlaying)
|
||||
{
|
||||
_totalTime += _elapsedTime;
|
||||
TotalTime += _elapsedTime;
|
||||
}
|
||||
|
||||
var playingLineIndex = GetCurrentPlayingLineIndex();
|
||||
@@ -44,11 +57,19 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_displayType = _displayTypeReceived;
|
||||
_playingLineIndex = playingLineIndex;
|
||||
|
||||
_immersiveBgOpacityTransition.Update(_elapsedTime);
|
||||
_immersiveBgTransition.Update(_elapsedTime);
|
||||
_albumArtBgTransition.Update(_elapsedTime);
|
||||
_lyricsBgBrightnessTransition.Update(_elapsedTime);
|
||||
_songInfoOpacityTransition.Update(_elapsedTime);
|
||||
if (_isDebugOverlayEnabledChanged)
|
||||
{
|
||||
if (_isDebugOverlayEnabled)
|
||||
{
|
||||
_drawFrameStopwatch = Stopwatch.StartNew();
|
||||
}
|
||||
else
|
||||
{
|
||||
_drawFrameStopwatch?.Stop();
|
||||
_drawFrameStopwatch = null;
|
||||
}
|
||||
_isDebugOverlayEnabledChanged = false;
|
||||
}
|
||||
|
||||
if (_isDynamicCoverOverlayEnabled)
|
||||
{
|
||||
@@ -56,53 +77,150 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_rotateAngle %= MathF.PI * 2;
|
||||
}
|
||||
|
||||
if (_isCanvasHeightChanged)
|
||||
if (_isCanvasWidthChanged)
|
||||
{
|
||||
_albumArtY = 36 + (_canvasHeight - 36 * 2) * 3 / 16f;
|
||||
if (_canvasWidth < 450)
|
||||
{
|
||||
_lyricsLayoutOrientation = LyricsLayoutOrientation.Vertical;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsLayoutOrientation = LyricsLayoutOrientation.Horizontal;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isCanvasWidthChanged || _isCanvasHeightChanged)
|
||||
{
|
||||
_albumArtSize = MathF.Min(
|
||||
(_canvasHeight - _topMargin - _bottomMargin) * 8.5f / 16,
|
||||
(_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2);
|
||||
_albumArtSize = MathF.Max(0, _albumArtSize);
|
||||
|
||||
_titleY = _albumArtY + _albumArtSize * 1.05f;
|
||||
_isCoverAcrylicEffectAmountChanged = true;
|
||||
}
|
||||
|
||||
if (_isDisplayTypeChanged || _isCanvasWidthChanged)
|
||||
if (_isDisplayTypeChanged || _isCanvasWidthChanged || _isCanvasHeightChanged)
|
||||
{
|
||||
bool jumpTo = !_isDisplayTypeChanged && _isCanvasWidthChanged;
|
||||
switch (_displayType)
|
||||
bool jumpTo = !_isDisplayTypeChanged && (_isCanvasWidthChanged || _isCanvasHeightChanged);
|
||||
switch (_lyricsLayoutOrientation)
|
||||
{
|
||||
case LyricsDisplayType.AlbumArtOnly:
|
||||
_lyricsOpacityTransition.StartTransition(0f, jumpTo);
|
||||
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_albumArtXTransition.StartTransition(_canvasWidth / 2 - _albumArtSize / 2, jumpTo);
|
||||
case LyricsLayoutOrientation.Horizontal:
|
||||
_albumArtSize = MathF.Min((_canvasHeight - _topMargin - _bottomMargin) * 8.5f / 16, (_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2);
|
||||
_albumArtSize = MathF.Max(0, _albumArtSize);
|
||||
_albumArtYTransition.StartTransition((_canvasHeight - _albumArtSize * 1.05f - _titleTextFormat.FontSize - _artistTextFormat.FontSize) / 2, jumpTo);
|
||||
_titleYTransition.StartTransition(_albumArtYTransition.TargetValue + _albumArtSize * 1.05f, jumpTo);
|
||||
_lyricsYTransition.StartTransition(0, jumpTo);
|
||||
switch (_displayType)
|
||||
{
|
||||
case LyricsDisplayType.AlbumArtOnly:
|
||||
_lyricsOpacityTransition.StartTransition(0f, jumpTo);
|
||||
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_albumArtXTransition.StartTransition(_canvasWidth / 2 - _albumArtSize / 2, jumpTo);
|
||||
_titleXTransition.StartTransition(_albumArtXTransition.TargetValue, jumpTo);
|
||||
break;
|
||||
case LyricsDisplayType.LyricsOnly:
|
||||
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_albumArtOpacityTransition.StartTransition(0f, jumpTo);
|
||||
_lyricsXTransition.StartTransition(_leftMargin, jumpTo);
|
||||
break;
|
||||
case LyricsDisplayType.SplitView:
|
||||
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_lyricsXTransition.StartTransition((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 + _leftMargin + _middleMargin, jumpTo);
|
||||
_albumArtXTransition.StartTransition(_leftMargin + ((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 - _albumArtSize) / 2, jumpTo);
|
||||
_titleXTransition.StartTransition(_albumArtXTransition.TargetValue, jumpTo);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case LyricsDisplayType.LyricsOnly:
|
||||
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_albumArtOpacityTransition.StartTransition(0f, jumpTo);
|
||||
case LyricsLayoutOrientation.Vertical:
|
||||
_albumArtSize = 64;
|
||||
_lyricsXTransition.StartTransition(_leftMargin, jumpTo);
|
||||
break;
|
||||
case LyricsDisplayType.SplitView:
|
||||
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_lyricsXTransition.StartTransition((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 + _leftMargin + _middleMargin, jumpTo);
|
||||
_albumArtXTransition.StartTransition(_leftMargin + ((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 - _albumArtSize) / 2, jumpTo);
|
||||
break;
|
||||
case LyricsDisplayType.PlaceholderOnly:
|
||||
_albumArtXTransition.StartTransition(_leftMargin, jumpTo);
|
||||
_titleXTransition.StartTransition(_leftMargin + _albumArtSize * 1.2f, jumpTo);
|
||||
switch (_displayType)
|
||||
{
|
||||
case LyricsDisplayType.AlbumArtOnly:
|
||||
_lyricsOpacityTransition.StartTransition(0f, jumpTo);
|
||||
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_albumArtYTransition.StartTransition((_canvasHeight - _albumArtSize) / 2, jumpTo);
|
||||
_titleYTransition.StartTransition(_albumArtYTransition.TargetValue, jumpTo);
|
||||
break;
|
||||
case LyricsDisplayType.LyricsOnly:
|
||||
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_albumArtOpacityTransition.StartTransition(0f, jumpTo);
|
||||
_lyricsYTransition.StartTransition(0, jumpTo);
|
||||
break;
|
||||
case LyricsDisplayType.SplitView:
|
||||
_albumArtYTransition.StartTransition(_topMargin, jumpTo);
|
||||
_titleYTransition.StartTransition(_topMargin, jumpTo);
|
||||
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
|
||||
_lyricsYTransition.StartTransition(_albumArtSize, jumpTo);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_lyricsXTransition.Update(_elapsedTime);
|
||||
_albumArtXTransition.Update(_elapsedTime);
|
||||
_lyricsOpacityTransition.Update(_elapsedTime);
|
||||
_albumArtOpacityTransition.Update(_elapsedTime);
|
||||
if (_isAlbumArtCornerRadiusChanged)
|
||||
{
|
||||
UpdateLastFgImageEffect(control);
|
||||
UpdateFgImageEffect(control);
|
||||
_isAlbumArtCornerRadiusChanged = false;
|
||||
}
|
||||
|
||||
// 背景图切换计算
|
||||
// 将当前背景图放到 _lastAlbumArtSwBitmap 中 并设置不透明度为 1
|
||||
// 将新的背景图放到 _albumArtSwBitmap 中 并设置不透明度为 0
|
||||
// 这样可以实现背景图的连贯渐变效果
|
||||
if (_albumArtChanged || _isCanvasHeightChanged || _isCanvasWidthChanged ||
|
||||
_lyricsBgBrightnessTransition.IsTransitioning ||
|
||||
_albumArtBgTransition.IsTransitioning)
|
||||
{
|
||||
// 必须先在此处重置动画
|
||||
if (_albumArtChanged)
|
||||
{
|
||||
_albumArtBgTransition.Reset(0f);
|
||||
_albumArtBgTransition.StartTransition(1f);
|
||||
}
|
||||
// 更新 last
|
||||
if (_albumArtChanged)
|
||||
{
|
||||
if (_lastAlbumArtSwBitmap != null)
|
||||
{
|
||||
_lastAlbumArtCanvasBitmap?.Dispose();
|
||||
_lastAlbumArtCanvasBitmap = null;
|
||||
_lastAlbumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _lastAlbumArtSwBitmap);
|
||||
}
|
||||
}
|
||||
UpdateLastFgImageEffect(control);
|
||||
UpdateLastBgImageEffect();
|
||||
// 更新 current
|
||||
if (_albumArtChanged)
|
||||
{
|
||||
if (_albumArtSwBitmap != null)
|
||||
{
|
||||
_albumArtCanvasBitmap?.Dispose();
|
||||
_albumArtCanvasBitmap = null;
|
||||
_albumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _albumArtSwBitmap);
|
||||
}
|
||||
}
|
||||
UpdateFgImageEffect(control);
|
||||
UpdateBgImageEffect();
|
||||
// 更新叠加的背景效果
|
||||
UpdateAlbumArtBgEffect(control);
|
||||
}
|
||||
|
||||
if (_isCoverAcrylicEffectAmountChanged)
|
||||
{
|
||||
UpdateCoverAcrylicOverlay(control);
|
||||
UpdateAlbumArtBgEffect(control);
|
||||
|
||||
_isCoverAcrylicEffectAmountChanged = false;
|
||||
}
|
||||
|
||||
_albumArtChanged = false;
|
||||
|
||||
if (_isCanvasWidthChanged || _lyricsXTransition.IsTransitioning)
|
||||
{
|
||||
@@ -121,9 +239,24 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
UpdateCanvasYScrollOffset(control, false, true);
|
||||
}
|
||||
|
||||
UpdateLinesProps();
|
||||
UpdateLinesProps(control);
|
||||
|
||||
_isLayoutChanged = false;
|
||||
|
||||
_titleXTransition.Update(_elapsedTime);
|
||||
_titleYTransition.Update(_elapsedTime);
|
||||
_lyricsXTransition.Update(_elapsedTime);
|
||||
_lyricsYTransition.Update(_elapsedTime);
|
||||
_albumArtXTransition.Update(_elapsedTime);
|
||||
_albumArtYTransition.Update(_elapsedTime);
|
||||
_lyricsOpacityTransition.Update(_elapsedTime);
|
||||
_albumArtOpacityTransition.Update(_elapsedTime);
|
||||
_immersiveBgOpacityTransition.Update(_elapsedTime);
|
||||
_immersiveBgColorTransition.Update(_elapsedTime);
|
||||
_albumArtAccentColorTransition.Update(_elapsedTime);
|
||||
_albumArtBgTransition.Update(_elapsedTime);
|
||||
_lyricsBgBrightnessTransition.Update(_elapsedTime);
|
||||
_songInfoOpacityTransition.Update(_elapsedTime);
|
||||
}
|
||||
|
||||
private void ReLayout(ICanvasAnimatedControl control)
|
||||
@@ -131,7 +264,18 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (control == null)
|
||||
return;
|
||||
|
||||
_lyricsTextFormat.FontSize = _lyricsFontSize;
|
||||
if (_isDockMode)
|
||||
{
|
||||
_lyricsTextFormat.FontSize = _lyricsDockFontSize;
|
||||
}
|
||||
else if (_isDesktopMode)
|
||||
{
|
||||
_lyricsTextFormat.FontSize = _lyricsDesktopFontSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsTextFormat.FontSize = _lyricsStandardFontSize;
|
||||
}
|
||||
|
||||
float y = 0;
|
||||
|
||||
@@ -145,22 +289,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.CanvasTextLayout != null)
|
||||
{
|
||||
line.CanvasTextLayout.Dispose();
|
||||
line.CanvasTextLayout = null;
|
||||
}
|
||||
|
||||
// Calculate layout bounds
|
||||
line.CanvasTextLayout = new CanvasTextLayout(
|
||||
control,
|
||||
line.DisplayedText,
|
||||
_lyricsTextFormat,
|
||||
_maxLyricsWidth,
|
||||
_canvasHeight
|
||||
);
|
||||
|
||||
line.Position = new Vector2(0, y);
|
||||
line.UpdateTextLayout(control, _lyricsTextFormat, _maxLyricsWidth, _canvasHeight, _isDockMode ? TextAlignmentType.Center : _lyricsAlignmentType);
|
||||
line.UpdateCenterPosition(_maxLyricsWidth, _isDockMode ? TextAlignmentType.Center : _lyricsAlignmentType);
|
||||
|
||||
//line.UpdateTextGeometry();
|
||||
//line.UpdateFontEffect(control, _isDesktopMode, _strokeFontColor, _lyricsFontStrokeWidth, _bgFontColor);
|
||||
|
||||
if (line.CanvasTextLayout == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
y +=
|
||||
(float)line.CanvasTextLayout.LayoutBounds.Height
|
||||
@@ -196,48 +335,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
_canvasYScrollTransition.Update(_elapsedTime);
|
||||
|
||||
int startVisibleLineIndex = -1;
|
||||
int endVisibleLineIndex = -1;
|
||||
|
||||
// Update visible line indices
|
||||
for (int i = startLineIndex; i <= endLineIndex; i++)
|
||||
{
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
var lines = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines;
|
||||
if (lines == null || lines.Count == 0) return;
|
||||
|
||||
if (line == null || line.CanvasTextLayout == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var textLayout = line.CanvasTextLayout;
|
||||
|
||||
if (
|
||||
_canvasYScrollTransition.Value
|
||||
+ _canvasHeight / 2
|
||||
+ line.Position.Y
|
||||
+ textLayout.LayoutBounds.Height
|
||||
>= 0
|
||||
)
|
||||
{
|
||||
if (startVisibleLineIndex == -1)
|
||||
{
|
||||
startVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
if (
|
||||
_canvasYScrollTransition.Value
|
||||
+ _canvasHeight / 2
|
||||
+ line.Position.Y
|
||||
+ textLayout.LayoutBounds.Height
|
||||
>= control.Size.Height
|
||||
)
|
||||
{
|
||||
if (endVisibleLineIndex == -1)
|
||||
{
|
||||
endVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
float offset = _canvasYScrollTransition.Value + _canvasHeight / 2;
|
||||
int startVisibleLineIndex = FindFirstVisibleLine(lines, offset);
|
||||
int endVisibleLineIndex = FindLastVisibleLine(lines, offset, _canvasHeight);
|
||||
|
||||
if (startVisibleLineIndex != -1 && endVisibleLineIndex == -1)
|
||||
{
|
||||
@@ -250,6 +354,52 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_endVisibleLineIndex = endVisibleLineIndex;
|
||||
}
|
||||
|
||||
private int FindFirstVisibleLine(IList<LyricsLine> lines, float offset)
|
||||
{
|
||||
int left = 0, right = lines.Count - 1, result = -1;
|
||||
while (left <= right)
|
||||
{
|
||||
int mid = (left + right) / 2;
|
||||
var line = lines[mid];
|
||||
var layout = line.CanvasTextLayout;
|
||||
if (layout == null) break;
|
||||
float value = offset + line.Position.Y + (float)layout.LayoutBounds.Height;
|
||||
if (value >= 0)
|
||||
{
|
||||
result = mid;
|
||||
right = mid - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = mid + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int FindLastVisibleLine(IList<LyricsLine> lines, float offset, float canvasHeight)
|
||||
{
|
||||
int left = 0, right = lines.Count - 1, result = -1;
|
||||
while (left <= right)
|
||||
{
|
||||
int mid = (left + right) / 2;
|
||||
var line = lines[mid];
|
||||
var layout = line.CanvasTextLayout;
|
||||
if (layout == null) break;
|
||||
float value = offset + line.Position.Y + (float)layout.LayoutBounds.Height;
|
||||
if (value >= canvasHeight)
|
||||
{
|
||||
result = mid;
|
||||
right = mid - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = mid + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void UpdateColorConfig()
|
||||
{
|
||||
if (_isDesktopMode || _isDockMode)
|
||||
@@ -261,45 +411,30 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
ThemeTypeSent = _lyricsBgTheme;
|
||||
}
|
||||
|
||||
float brightness = 0f;
|
||||
|
||||
float brightness;
|
||||
Color grayedEnvironmentalColor = Colors.Transparent;
|
||||
|
||||
switch (ThemeTypeSent)
|
||||
bool isLight = ThemeTypeSent switch
|
||||
{
|
||||
case ElementTheme.Default:
|
||||
switch (Application.Current.RequestedTheme)
|
||||
{
|
||||
case ApplicationTheme.Light:
|
||||
_adaptiveGrayedFontColor = _darkColor;
|
||||
brightness = 0.7f;
|
||||
break;
|
||||
case ApplicationTheme.Dark:
|
||||
_adaptiveGrayedFontColor = _lightColor;
|
||||
brightness = 0.3f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ElementTheme.Light:
|
||||
_adaptiveGrayedFontColor = _darkColor;
|
||||
brightness = 0.7f;
|
||||
break;
|
||||
case ElementTheme.Dark:
|
||||
_adaptiveGrayedFontColor = _lightColor;
|
||||
brightness = 0.3f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ElementTheme.Default => Application.Current.RequestedTheme == ApplicationTheme.Light,
|
||||
ElementTheme.Light => true,
|
||||
ElementTheme.Dark => false,
|
||||
_ => false
|
||||
};
|
||||
|
||||
if (_adaptiveGrayedFontColor == _lightColor)
|
||||
{
|
||||
grayedEnvironmentalColor = _darkColor;
|
||||
} else if (_adaptiveGrayedFontColor == _darkColor)
|
||||
if (isLight)
|
||||
{
|
||||
_adaptiveGrayedFontColor = _darkColor;
|
||||
brightness = 0.7f;
|
||||
grayedEnvironmentalColor = _lightColor;
|
||||
_albumArtAccentColorTransition.StartTransition(_albumArtLightAccentColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
_adaptiveGrayedFontColor = _lightColor;
|
||||
brightness = 0.3f;
|
||||
grayedEnvironmentalColor = _darkColor;
|
||||
_albumArtAccentColorTransition.StartTransition(_albumArtDarkAccentColor);
|
||||
}
|
||||
|
||||
_lyricsBgBrightnessTransition.StartTransition(brightness);
|
||||
@@ -310,7 +445,14 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
_adaptiveColoredFontColor = Helper.ColorHelper.GetForegroundColor(_albumArtAccentColor?.WithBrightness(brightness) ?? Colors.Transparent);
|
||||
if (isLight)
|
||||
{
|
||||
_adaptiveColoredFontColor = _albumArtDarkAccentColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
_adaptiveColoredFontColor = _albumArtLightAccentColor;
|
||||
}
|
||||
}
|
||||
|
||||
switch (_lyricsBgFontColorType)
|
||||
@@ -357,9 +499,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
|
||||
private void UpdateLinesProps()
|
||||
private void UpdateLinesProps(ICanvasAnimatedControl control)
|
||||
{
|
||||
var currentPlayingLine = _lyricsDataArr
|
||||
.ElementAtOrDefault(_langIndex)
|
||||
@@ -373,6 +517,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
if (line == null) continue;
|
||||
|
||||
line.UpdateTextGeometry();
|
||||
line.UpdateFontEffect(control, _isDesktopMode, _strokeFontColor, _lyricsFontStrokeWidth, _bgFontColor);
|
||||
|
||||
if (_isLayoutChanged || _isVisibleLinesBoundaryChanged || _isPlayingLineChanged)
|
||||
{
|
||||
float distanceFromPlayingLine = Math.Abs(line.Position.Y - currentPlayingLine.Position.Y);
|
||||
@@ -399,5 +546,50 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
line.HighlightOpacityTransition.Update(_elapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateImmersiveBackgroundOpacity()
|
||||
{
|
||||
float targetOpacity;
|
||||
if (_isDesktopMode)
|
||||
{
|
||||
if (_isLyricsWindowLocked)
|
||||
{
|
||||
targetOpacity = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isMouseWithinWindow)
|
||||
{
|
||||
targetOpacity = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetOpacity = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetOpacity = 1f;
|
||||
}
|
||||
_immersiveBgOpacityTransition.StartTransition(targetOpacity);
|
||||
}
|
||||
|
||||
private void UpdateCoverAcrylicOverlay(ICanvasAnimatedControl control)
|
||||
{
|
||||
if (_coverAcrylicEffectAmount > 0)
|
||||
{
|
||||
var ret = NoiseOverlayHelper.GenerateNoiseBitmapBGRA((int)_canvasWidth, (int)_canvasHeight);
|
||||
_coverAcrylicNoiseCanvasBitmap?.Dispose();
|
||||
_coverAcrylicNoiseCanvasBitmap = null;
|
||||
_coverAcrylicNoiseCanvasBitmap = CanvasBitmap.CreateFromBytes(
|
||||
control,
|
||||
ret,
|
||||
(int)_canvasWidth,
|
||||
(int)_canvasHeight,
|
||||
Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using ABI.Microsoft.UI.Xaml;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
@@ -30,32 +26,40 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public partial class LyricsRendererViewModel : BaseViewModel
|
||||
{
|
||||
private TimeSpan _elapsedTime = TimeSpan.Zero;
|
||||
private TimeSpan _totalTime = TimeSpan.Zero;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
|
||||
|
||||
private TimeSpan _positionOffset = TimeSpan.Zero;
|
||||
|
||||
private int _songDurationMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
|
||||
|
||||
private Stopwatch? _drawFrameStopwatch;
|
||||
private int _drawFrameCount = 0;
|
||||
private int _displayedDrawFrameCount = 0;
|
||||
|
||||
private SoftwareBitmap? _lastAlbumArtSwBitmap = null;
|
||||
private SoftwareBitmap? _albumArtSwBitmap = null;
|
||||
|
||||
private CanvasBitmap? _lastAlbumArtCanvasBitmap = null;
|
||||
private CanvasBitmap? _albumArtCanvasBitmap = null;
|
||||
|
||||
private CanvasBitmap? _coverAcrylicNoiseCanvasBitmap = null;
|
||||
|
||||
private float _albumArtSize = 0f;
|
||||
private int _albumArtCornerRadius = 0;
|
||||
|
||||
private float _albumArtY = 0f;
|
||||
|
||||
private string? _lastSongTitle;
|
||||
private string? _songTitle;
|
||||
|
||||
private float _titleY = 0f;
|
||||
|
||||
private string? _lastSongArtist;
|
||||
private string? _songArtist;
|
||||
|
||||
private float _canvasWidth = 0f;
|
||||
private float _canvasHeight = 0f;
|
||||
|
||||
private readonly float _defaultOpacity = 0.3f;
|
||||
private float _defaultOpacity;
|
||||
private readonly float _highlightedOpacity = 1.0f;
|
||||
|
||||
private readonly float _defaultScale = 0.75f;
|
||||
@@ -64,6 +68,14 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private readonly float _coverRotateSpeed = 0.003f;
|
||||
private float _rotateAngle = 0f;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsSearchProvider? LyricsSearchProvider { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial TranslationSearchProvider? TranslationSearchProvider { get; set; } = null;
|
||||
|
||||
private TextAlignmentType _lyricsAlignmentType;
|
||||
|
||||
private readonly float _lyricsGlowEffectAmount = 8f;
|
||||
@@ -75,7 +87,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private LineRenderingType _lyricsHighlightScope;
|
||||
|
||||
private int _lyricsFontStrokeWidth;
|
||||
private int _lyricsFontSize;
|
||||
private int _lyricsStandardFontSize;
|
||||
private int _lyricsDockFontSize;
|
||||
private int _lyricsDesktopFontSize;
|
||||
private float _lyricsLineSpacingFactor;
|
||||
|
||||
private LyricsFontColorType _lyricsBgFontColorType;
|
||||
@@ -99,7 +113,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private Color _adaptiveGrayedFontColor = Colors.Transparent;
|
||||
private Color? _adaptiveColoredFontColor = null;
|
||||
|
||||
private Color? _albumArtAccentColor = null;
|
||||
private Color _albumArtLightAccentColor = Colors.Transparent;
|
||||
private Color _albumArtDarkAccentColor = Colors.Transparent;
|
||||
private Color _environmentalColor = Colors.Transparent;
|
||||
|
||||
private Color _lightColor = Colors.White;
|
||||
@@ -131,14 +146,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private bool _isDynamicCoverOverlayEnabled;
|
||||
private bool _isLyricsGlowEffectEnabled;
|
||||
|
||||
private bool _isLyricsFloatAnimationEnabled;
|
||||
|
||||
private bool _isLayoutChanged = true;
|
||||
|
||||
private int _langIndex = 0;
|
||||
|
||||
private List<LyricsData> _lyricsDataArr = [];
|
||||
private List<string> _translationList = [];
|
||||
private bool _isTranslationEnabled = false;
|
||||
private int _targetLanguageIndex = 6;
|
||||
private bool _isTranslationEnabled;
|
||||
private bool _showTranslationOnly;
|
||||
private int _targetLanguageIndex;
|
||||
private bool _isLibreTranslateEnabled;
|
||||
|
||||
private int _timelineSyncThreshold;
|
||||
|
||||
@@ -165,16 +183,25 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
TrimmingSign = CanvasTrimmingSign.Ellipsis,
|
||||
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
|
||||
};
|
||||
private CanvasTextFormat _debugTextFormat = new()
|
||||
{
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.ExtraBlack,
|
||||
};
|
||||
|
||||
private LatestOnlyTaskRunner _refreshLyricsRunner = new();
|
||||
private LatestOnlyTaskRunner _showTranslationsRunner = new();
|
||||
|
||||
private LyricsDisplayType _displayTypeReceived = LyricsDisplayType.PlaceholderOnly;
|
||||
private LyricsDisplayType _displayType = LyricsDisplayType.PlaceholderOnly;
|
||||
private LyricsDisplayType _displayTypeReceived;
|
||||
private LyricsDisplayType _displayType;
|
||||
|
||||
private LyricsLayoutOrientation _lyricsLayoutOrientation;
|
||||
|
||||
private int _albumArtBgBlurAmount;
|
||||
private int _albumArtBgOpacity;
|
||||
|
||||
private int _coverAcrylicEffectAmount;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsTranslating { get; set; } = false;
|
||||
|
||||
@@ -187,32 +214,43 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private int GetCurrentPlayingLineIndex()
|
||||
{
|
||||
var totalMs = TotalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds;
|
||||
if (totalMs < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.FirstOrDefault()?.StartMs) return 0;
|
||||
|
||||
for (int i = 0; i < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.Count; i++)
|
||||
{
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines[i];
|
||||
if (line == null)
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
if (line == null) continue;
|
||||
var nextLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i + 1);
|
||||
if (nextLine != null && line.StartMs <= totalMs && totalMs < nextLine.StartMs)
|
||||
{
|
||||
continue;
|
||||
return i;
|
||||
}
|
||||
if (
|
||||
line.StartMs <= _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds
|
||||
&& _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds <= line.EndMs
|
||||
)
|
||||
else if (nextLine == null && line.StartMs <= totalMs)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
return GetMaxLyricsLineIndexBoundaries().Item2;
|
||||
}
|
||||
|
||||
private void GetLinePlayingProgress(LyricsLine line, out int charStartIndex, out int charLength, out float charProgress)
|
||||
private void GetLinePlayingProgress(int lineIndex, out int charStartIndex, out int charLength, out float charProgress)
|
||||
{
|
||||
charStartIndex = 0;
|
||||
charLength = 0;
|
||||
charProgress = 0f;
|
||||
|
||||
float now = (float)_totalTime.TotalMilliseconds + (float)_positionOffset.TotalMilliseconds;
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(lineIndex);
|
||||
if (line == null) return;
|
||||
var nextLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(lineIndex + 1);
|
||||
|
||||
int lineEndMs;
|
||||
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
|
||||
else if (nextLine != null) lineEndMs = nextLine.StartMs;
|
||||
else lineEndMs = _songDurationMs;
|
||||
|
||||
float now = (float)TotalTime.TotalMilliseconds + (float)_positionOffset.TotalMilliseconds;
|
||||
|
||||
// 1. 还没到本句
|
||||
if (now < line.StartMs)
|
||||
@@ -221,27 +259,37 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
|
||||
// 2. 已经超过本句
|
||||
if (now > line.EndMs)
|
||||
if (now > lineEndMs)
|
||||
{
|
||||
charProgress = 1f;
|
||||
charStartIndex = line.OriginalText.Length - 1;
|
||||
charLength = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 有逐字时间轴
|
||||
if (line.CharTimings != null && line.CharTimings.Count > 0)
|
||||
if (line.LyricsChars != null && line.LyricsChars.Count > 1)
|
||||
{
|
||||
int charTimingsCount = line.CharTimings.Count;
|
||||
int charTimingsCount = line.LyricsChars.Count;
|
||||
for (int i = 0; i < charTimingsCount; i++)
|
||||
{
|
||||
var timing = line.CharTimings[i];
|
||||
var timing = line.LyricsChars[i];
|
||||
var nextTiming = line.LyricsChars.ElementAtOrDefault(i + 1);
|
||||
|
||||
int timingEndMs;
|
||||
if (timing.EndMs != null) timingEndMs = timing.EndMs.Value;
|
||||
else if (nextTiming != null) timingEndMs = nextTiming.StartMs;
|
||||
else timingEndMs = lineEndMs;
|
||||
|
||||
charStartIndex = timing.StartIndex;
|
||||
charLength = timing.Text.Length;
|
||||
|
||||
// 当前时间在某个字的高亮区间
|
||||
if (now >= timing.StartMs && now <= timing.EndMs)
|
||||
if (now >= timing.StartMs && now <= timingEndMs)
|
||||
{
|
||||
charStartIndex = timing.StartIndex;
|
||||
charLength = timing.Text.Length;
|
||||
if (timing.EndMs != timing.StartMs)
|
||||
if (timingEndMs != timing.StartMs)
|
||||
{
|
||||
charProgress = (now - timing.StartMs) / (timing.EndMs - timing.StartMs);
|
||||
charProgress = (now - timing.StartMs) / (timingEndMs - timing.StartMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -249,15 +297,30 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (now > timingEndMs && (nextTiming == null || now < nextTiming?.StartMs))
|
||||
{
|
||||
charProgress = 1f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有逐字时间轴,直接线性
|
||||
charProgress = (now - line.StartMs) / line.DurationMs;
|
||||
charProgress = Math.Clamp(charProgress, 0f, 1f);
|
||||
charStartIndex = 0;
|
||||
charLength = line.OriginalText.Length;
|
||||
// 没有逐字时间轴,均匀分配每个字的高亮时间
|
||||
int textLength = line.OriginalText.Length;
|
||||
if (textLength == 0) return;
|
||||
|
||||
float lineProgress = (now - line.StartMs) / (lineEndMs - line.StartMs);
|
||||
lineProgress = Math.Clamp(lineProgress, 0f, 1f);
|
||||
|
||||
// 计算当前高亮到第几个字
|
||||
float charFloatIndex = lineProgress * textLength;
|
||||
int charIndex = (int)charFloatIndex;
|
||||
charStartIndex = Math.Clamp(charIndex, 0, textLength - 1);
|
||||
charLength = 1;
|
||||
|
||||
// 当前字的进度(0~1)
|
||||
charProgress = charFloatIndex - charIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,11 +352,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_isPlaying = e.IsPlaying;
|
||||
}
|
||||
|
||||
private void PlaybackService_PositionChanged(object? sender, PositionChangedEventArgs e)
|
||||
private void PlaybackService_TimelineChanged(object? sender, TimelineChangedEventArgs e)
|
||||
{
|
||||
if (Math.Abs(_totalTime.TotalMilliseconds - e.Position.TotalMilliseconds) >= _timelineSyncThreshold)
|
||||
if (Math.Abs(TotalTime.TotalMilliseconds - e.Position.TotalMilliseconds) >= _timelineSyncThreshold)
|
||||
{
|
||||
_totalTime = e.Position;
|
||||
TotalTime = e.Position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +372,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_lastSongArtist = _songArtist;
|
||||
_songArtist = SongInfo?.Artist;
|
||||
|
||||
_songDurationMs = (int)(SongInfo?.DurationMs ?? TimeSpan.FromMinutes(99).TotalMilliseconds);
|
||||
|
||||
_songInfoOpacityTransition.Reset(0f);
|
||||
_songInfoOpacityTransition.StartTransition(1f);
|
||||
|
||||
@@ -317,7 +382,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
await RefreshLyricsAsync(token);
|
||||
});
|
||||
_totalTime = TimeSpan.Zero;
|
||||
TotalTime = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,16 +390,18 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
if (e.AlbumArtSwBitmap != _albumArtSwBitmap)
|
||||
{
|
||||
//_lastAlbumArtSwBitmap?.Dispose();
|
||||
_lastAlbumArtSwBitmap = null;
|
||||
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
|
||||
_lastAlbumArtCanvasBitmap = null;
|
||||
|
||||
//_albumArtSwBitmap?.Dispose();
|
||||
_albumArtSwBitmap = null;
|
||||
_albumArtSwBitmap = e.AlbumArtSwBitmap;
|
||||
_albumArtCanvasBitmap = null;
|
||||
|
||||
_albumArtAccentColor = e.AlbumArtAccentColor;
|
||||
_albumArtChanged = true;
|
||||
|
||||
_albumArtBgTransition.Reset(0f);
|
||||
_albumArtBgTransition.StartTransition(1f);
|
||||
_albumArtLightAccentColor = e.AlbumArtLightAccentColor ?? Colors.Transparent;
|
||||
_albumArtDarkAccentColor = e.AlbumArtDarkAccentColor ?? Colors.Transparent;
|
||||
|
||||
UpdateColorConfig();
|
||||
}
|
||||
@@ -342,6 +409,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private void UpdateTranslations()
|
||||
{
|
||||
TranslationSearchProvider = null;
|
||||
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
|
||||
_isLayoutChanged = true;
|
||||
|
||||
IsTranslating = true;
|
||||
if (_isTranslationEnabled)
|
||||
{
|
||||
@@ -354,7 +425,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
|
||||
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
|
||||
_langIndex = 0;
|
||||
IsTranslating = false;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
@@ -364,7 +436,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_logger.LogInformation("Showing translation for lyrics...");
|
||||
string targetLangCode = LanguageHelper.GetUserTargetLanguageCode();
|
||||
string originalText = _lyricsDataArr[0].WrappedOriginalText;
|
||||
string? originalText = _lyricsDataArr.FirstOrDefault()?.WrappedOriginalText;
|
||||
if (originalText == null) return;
|
||||
|
||||
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
|
||||
|
||||
if (originalLangCode == targetLangCode)
|
||||
@@ -378,45 +452,79 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr);
|
||||
if (found >= 0)
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found]);
|
||||
if (_showTranslationOnly)
|
||||
{
|
||||
_lyricsDataArr[found].SetDisplayedTextInOriginalText();
|
||||
_langIndex = found;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found], 50);
|
||||
_langIndex = 0;
|
||||
}
|
||||
TranslationSearchProvider = LyricsSearchProvider.ToTranslationSearchProvider();
|
||||
}
|
||||
else
|
||||
else if (_isLibreTranslateEnabled)
|
||||
{
|
||||
var translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
string translated = string.Empty;
|
||||
try
|
||||
{
|
||||
translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
|
||||
if (translated == string.Empty) return;
|
||||
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
|
||||
if (_showTranslationOnly)
|
||||
{
|
||||
_lyricsDataArr[^1] = _lyricsDataArr[0].CreateLyricsDataFrom(translated);
|
||||
_lyricsDataArr[^1].SetDisplayedTextInOriginalText();
|
||||
_langIndex = _lyricsDataArr.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
|
||||
_langIndex = 0;
|
||||
}
|
||||
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(App.ResourceLoader?.GetString("LibreTranslateFailed")!, Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshLyricsAsync(CancellationToken token)
|
||||
{
|
||||
LyricsSearchProvider = null;
|
||||
_logger.LogInformation("Refreshing lyrics...");
|
||||
|
||||
_lyricsDataArr = [LyricsData.GetLoadingPlaceholder()];
|
||||
_isLayoutChanged = true;
|
||||
|
||||
string? lyricsRaw = null;
|
||||
LyricsSearchProvider? lyricsSearchProvider = null;
|
||||
|
||||
if (SongInfo != null)
|
||||
{
|
||||
lyricsRaw = await _lyrcsSearchService.SearchAsync(
|
||||
(lyricsRaw, lyricsSearchProvider) = await _lyrcsSearchService.SearchAsync(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist,
|
||||
SongInfo.Album ?? "",
|
||||
SongInfo.DurationMs ?? 0,
|
||||
token
|
||||
);
|
||||
LyricsSearchProvider = lyricsSearchProvider;
|
||||
_logger.LogInformation("Lyrics search result: {LyricsRaw}", lyricsRaw ?? "null");
|
||||
token.ThrowIfCancellationRequested();
|
||||
_lyricsDataArr = new LyricsParser().Parse(lyricsRaw, (int?)SongInfo?.DurationMs);
|
||||
FillTranslationFromCache(LyricsSearchProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("SongInfo is null, cannot search lyrics.");
|
||||
}
|
||||
|
||||
_lyricsDataArr = new LyricsParser().Parse(lyricsRaw, (int?)SongInfo?.DurationMs);
|
||||
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _lyricsDataArr.Count);
|
||||
|
||||
// This ensures that original lyrics are always shown while waiting for translations
|
||||
@@ -425,5 +533,54 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
UpdateTranslations();
|
||||
}
|
||||
|
||||
private void FillTranslationFromCache(LyricsSearchProvider? provider)
|
||||
{
|
||||
string? translationRaw = null;
|
||||
switch (provider)
|
||||
{
|
||||
case Enums.LyricsSearchProvider.QQ:
|
||||
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.QQTranslationCacheDirectory);
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.Kugou:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.Netease:
|
||||
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.NeteaseTranslationCacheDirectory);
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LrcLib:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.AmllTtmlDb:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalMusicFile:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalLrcFile:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalEslrcFile:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalTtmlFile:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (translationRaw != null)
|
||||
{
|
||||
var translationData = new LyricsParser().Parse(translationRaw, (int?)SongInfo?.DurationMs);
|
||||
if (provider == Enums.LyricsSearchProvider.QQ)
|
||||
{
|
||||
foreach (var data in translationData)
|
||||
{
|
||||
foreach (var item in data.LyricsLines)
|
||||
{
|
||||
if (item.OriginalText == "//")
|
||||
{
|
||||
item.OriginalText = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_lyricsDataArr = _lyricsDataArr.Concat(translationData).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,19 @@ using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
using Windows.UI;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
@@ -24,15 +29,36 @@ namespace BetterLyrics.WinUI3
|
||||
public partial class LyricsWindowViewModel
|
||||
: BaseWindowViewModel,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>
|
||||
IRecipient<PropertyChangedMessage<DockPlacement>>
|
||||
{
|
||||
private readonly IPlaybackService _playbackService = Ioc.Default.GetRequiredService<IPlaybackService>();
|
||||
private ForegroundWindowWatcher? _windowWatcher = null;
|
||||
private bool _ignoreFullscreenWindow = false;
|
||||
private bool _ignoreFullscreenWindow;
|
||||
private bool _hideWindowWhenNotPlaying;
|
||||
|
||||
private DockPlacement _dockPlacement;
|
||||
private int _dockWindowHeight;
|
||||
private string _dockMonitorDeviceName;
|
||||
|
||||
public LyricsWindowViewModel(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
_dockMonitorDeviceName = _settingsService.DockMonitorDeviceName;
|
||||
_ignoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
|
||||
_hideWindowWhenNotPlaying = _settingsService.HideWindowWhenNotPlaying;
|
||||
IsImmersiveMode = _settingsService.IsImmersiveMode;
|
||||
_dockPlacement = _settingsService.DockPlacement;
|
||||
_dockWindowHeight = _settingsService.DockWindowHeight;
|
||||
OnIsImmersiveModeChanged(_settingsService.IsImmersiveMode);
|
||||
|
||||
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
||||
}
|
||||
|
||||
private void PlaybackService_IsPlayingChanged(object? sender, Events.IsPlayingChangedEventArgs e)
|
||||
{
|
||||
UpdateDockWindow();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -51,6 +77,13 @@ namespace BetterLyrics.WinUI3
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsWindowLocked { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsImmersiveMode { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial float TopCommandGridOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ElementTheme ThemeType { get; set; } = ElementTheme.Default;
|
||||
|
||||
@@ -61,6 +94,49 @@ namespace BetterLyrics.WinUI3
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsMouseWithinWindow { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string LockHotKey { get; set; } = "";
|
||||
|
||||
private void UpdateDockWindow()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
if (IsDockMode || IsDesktopMode)
|
||||
{
|
||||
if (_hideWindowWhenNotPlaying && !_playbackService.IsPlaying)
|
||||
{
|
||||
if (IsDockMode)
|
||||
{
|
||||
DockModeHelper.UpdateAppBarHeight(hwnd, _dockMonitorDeviceName, 0, _dockPlacement);
|
||||
}
|
||||
window.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsDockMode)
|
||||
{
|
||||
DockModeHelper.UpdateAppBarHeight(hwnd, _dockMonitorDeviceName, _dockWindowHeight, _dockPlacement);
|
||||
}
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnIsImmersiveModeChanged(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
TopCommandGridOpacity = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
TopCommandGridOpacity = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is SystemTrayViewModel)
|
||||
@@ -79,6 +155,11 @@ namespace BetterLyrics.WinUI3
|
||||
{
|
||||
_ignoreFullscreenWindow = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.HideWindowWhenNotPlaying))
|
||||
{
|
||||
_hideWindowWhenNotPlaying = message.NewValue;
|
||||
UpdateDockWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,20 +178,43 @@ namespace BetterLyrics.WinUI3
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.DockWindowHeight))
|
||||
{
|
||||
if (IsDockMode)
|
||||
_dockWindowHeight = message.NewValue;
|
||||
UpdateDockWindow();
|
||||
}
|
||||
else if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LockHotKeyIndex))
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
DockModeHelper.UpdateAppBarHeight(WindowNative.GetWindowHandle(window), message.NewValue * 4);
|
||||
UpdateLockHotKey(message.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StartWatchWindowColorChange(WindowPixelSampleMode mode)
|
||||
private void UpdateLockHotKey(int hotKeyIndex)
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
GlobalHotKeyHelper.UnregisterAllHotKeys(window);
|
||||
GlobalHotKeyHelper.RegisterHotKey(
|
||||
window,
|
||||
User32.HotKeyModifiers.MOD_CONTROL | User32.HotKeyModifiers.MOD_ALT,
|
||||
(uint)(hotKeyIndex + (int)VirtualKey.A),
|
||||
() =>
|
||||
{
|
||||
if (IsDesktopMode)
|
||||
{
|
||||
ToggleLockWindowCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
);
|
||||
LockHotKey = ((VirtualKey)(hotKeyIndex + (int)VirtualKey.A)).ToString();
|
||||
}
|
||||
|
||||
public void StartWatchWindowColorChange()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
@@ -120,15 +224,18 @@ namespace BetterLyrics.WinUI3
|
||||
hwnd,
|
||||
onWindowChanged =>
|
||||
{
|
||||
if (_ignoreFullscreenWindow && window.AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
_dispatcherQueueTimer.Debounce(() =>
|
||||
{
|
||||
presenter.IsAlwaysOnTop = true;
|
||||
}
|
||||
UpdateAccentColor(hwnd, mode);
|
||||
if (_ignoreFullscreenWindow && window.AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.IsAlwaysOnTop = true;
|
||||
}
|
||||
UpdateAccentColor(hwnd);
|
||||
}, TimeSpan.FromMilliseconds(300));
|
||||
}
|
||||
);
|
||||
_windowWatcher.Start();
|
||||
UpdateAccentColor(hwnd, mode);
|
||||
UpdateAccentColor(hwnd);
|
||||
}
|
||||
|
||||
private void StopWatchWindowColorChange()
|
||||
@@ -137,19 +244,37 @@ namespace BetterLyrics.WinUI3
|
||||
_windowWatcher = null;
|
||||
}
|
||||
|
||||
public void UpdateAccentColor(nint hwnd, WindowPixelSampleMode mode)
|
||||
public void UpdateAccentColor(nint hwnd)
|
||||
{
|
||||
ActivatedWindowAccentColor = Helper.ColorHelper.GetAccentColor(hwnd, mode).ToColor();
|
||||
WindowPixelSampleMode mode = IsDesktopMode ? WindowPixelSampleMode.WindowEdge : _dockPlacement.ToWindowPixelSampleMode();
|
||||
ActivatedWindowAccentColor = Helper.ColorHelper.GetAccentColor(hwnd, _settingsService.DockMonitorDeviceName, mode).ToColor();
|
||||
}
|
||||
|
||||
public void InitLockHotKey()
|
||||
{
|
||||
UpdateLockHotKey(_settingsService.LockHotKeyIndex);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void LockWindow()
|
||||
private void ToggleLockWindow()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
DesktopModeHelper.SetClickThrough(window, true);
|
||||
IsLyricsWindowLocked = true;
|
||||
if (IsLyricsWindowLocked)
|
||||
{
|
||||
DesktopModeHelper.SetClickThrough(window, false);
|
||||
IsLyricsWindowLocked = false;
|
||||
IsImmersiveMode = _settingsService.IsImmersiveMode;
|
||||
}
|
||||
else
|
||||
{
|
||||
DesktopModeHelper.SetClickThrough(window, true);
|
||||
IsLyricsWindowLocked = true;
|
||||
IsImmersiveMode = true;
|
||||
}
|
||||
|
||||
UpdateDockWindow();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -164,7 +289,7 @@ namespace BetterLyrics.WinUI3
|
||||
if (IsDesktopMode)
|
||||
{
|
||||
DesktopModeHelper.Enable(window);
|
||||
StartWatchWindowColorChange(WindowPixelSampleMode.WindowEdge);
|
||||
StartWatchWindowColorChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -183,13 +308,46 @@ namespace BetterLyrics.WinUI3
|
||||
IsDockMode = !IsDockMode;
|
||||
if (IsDockMode)
|
||||
{
|
||||
DockModeHelper.Enable(window, _settingsService.LyricsFontSize * 4);
|
||||
StartWatchWindowColorChange(WindowPixelSampleMode.BelowWindow);
|
||||
window.Restore();
|
||||
DockModeHelper.Enable(window, _dockMonitorDeviceName, _dockWindowHeight, _dockPlacement);
|
||||
StartWatchWindowColorChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
DockModeHelper.Disable(window);
|
||||
}
|
||||
|
||||
UpdateDockWindow();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OnImmersiveToggleButtonEnabledChanged()
|
||||
{
|
||||
_settingsService.IsImmersiveMode = IsImmersiveMode;
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<DockPlacement> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.DockPlacement))
|
||||
{
|
||||
_dockPlacement = message.NewValue;
|
||||
UpdateDockWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.SelectedDockMonitorDeviceName))
|
||||
{
|
||||
_dockMonitorDeviceName = message.NewValue;
|
||||
UpdateDockWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Media;
|
||||
using Windows.Media.Core;
|
||||
using Windows.Media.Playback;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class MusicGalleryViewModel : BaseViewModel,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LocalMediaFolder>>>
|
||||
{
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
private readonly MediaPlayer _mediaPlayer = new();
|
||||
private readonly MediaTimelineController _timelineController = new();
|
||||
private readonly SystemMediaTransportControls _smtc;
|
||||
// All songs
|
||||
private List<Track> _tracks = [];
|
||||
// Songs in current playlist
|
||||
private List<Track> _playlistTracks = [];
|
||||
// Filtered songs based on search query for current playlist
|
||||
private List<Track> _filteredTracks = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsLocalMediaNotFound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Grouped tracks after filtering and sorting for current playlist
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<GroupInfoList> GroupedTracks { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial List<Track> SelectedTracks { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<PlayQueueItem> TrackPlayingQueue { get; set; } = [];
|
||||
|
||||
public PlayQueueItem? PlayingQueueItem => TrackPlayingQueue.ElementAtOrDefault(PlayingSongIndex);
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PlaybackOrder PlaybackOrder { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial CommonSongProperty SongOrderType { get; set; } = CommonSongProperty.Title;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<SongsTabInfo> SongsTabInfoList { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int SelectedSongsTabInfoIndex { get; set; } = 0;
|
||||
|
||||
public SongsTabInfo? SelectedSongsTabInfo => SongsTabInfoList.ElementAtOrDefault(SelectedSongsTabInfoIndex);
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsDataLoading { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Track TrackRightTapped { get; set; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int PlayingSongIndex { get; set; } = -1;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int DisplayedPlayingSongIndex { get; set; } = 0;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string SongSearchQuery { get; set; } = string.Empty;
|
||||
|
||||
public MusicGalleryViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService) : base(settingsService)
|
||||
{
|
||||
SongsTabInfoList.Add(new SongsTabInfo(App.ResourceLoader!.GetString("MusicGalleryPageAllSongs"), "\uE8A9", false, CommonSongProperty.Title, string.Empty));
|
||||
|
||||
RefreshSongs();
|
||||
|
||||
PlaybackOrder = _settingsService.PlaybackOrder;
|
||||
|
||||
_mediaPlayer.MediaOpened += MediaPlayer_MediaOpened;
|
||||
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
|
||||
_timelineController = _mediaPlayer.TimelineController = new();
|
||||
_timelineController.PositionChanged += TimelineController_PositionChanged;
|
||||
_smtc = _mediaPlayer.SystemMediaTransportControls;
|
||||
_mediaPlayer.CommandManager.IsEnabled = false;
|
||||
_smtc.IsPlayEnabled = true;
|
||||
_smtc.IsPauseEnabled = true;
|
||||
_smtc.IsNextEnabled = true;
|
||||
_smtc.IsPreviousEnabled = true;
|
||||
_smtc.ButtonPressed += Smtc_ButtonPressed;
|
||||
_smtc.PlaybackPositionChangeRequested += Smtc_PlaybackPositionChangeRequested;
|
||||
|
||||
_libWatcherService = libWatcherService;
|
||||
_libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged;
|
||||
}
|
||||
|
||||
private void MediaPlayer_MediaEnded(MediaPlayer sender, object args)
|
||||
{
|
||||
PlayNextTrack();
|
||||
}
|
||||
|
||||
public void PlayNextTrack()
|
||||
{
|
||||
switch (PlaybackOrder)
|
||||
{
|
||||
case PlaybackOrder.RepeatAll:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
if (PlayingSongIndex < TrackPlayingQueue.Count - 1)
|
||||
{
|
||||
PlayingSongIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayingSongIndex = 0;
|
||||
}
|
||||
PlayTrack(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
case PlaybackOrder.RepeatOne:
|
||||
_timelineController.Position = TimeSpan.Zero;
|
||||
break;
|
||||
case PlaybackOrder.Shuffle:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
if (TrackPlayingQueue.Count > 0)
|
||||
{
|
||||
PlayingSongIndex = new Random().Next(0, TrackPlayingQueue.Count);
|
||||
}
|
||||
PlayTrack(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayPreviousTrack()
|
||||
{
|
||||
switch (PlaybackOrder)
|
||||
{
|
||||
case PlaybackOrder.RepeatAll:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
if (PlayingSongIndex > 0)
|
||||
{
|
||||
PlayingSongIndex--;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayingSongIndex = TrackPlayingQueue.Count - 1;
|
||||
}
|
||||
PlayTrack(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
case PlaybackOrder.RepeatOne:
|
||||
_timelineController.Position = TimeSpan.Zero;
|
||||
break;
|
||||
case PlaybackOrder.Shuffle:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
if (TrackPlayingQueue.Count > 0)
|
||||
{
|
||||
PlayingSongIndex = new Random().Next(0, TrackPlayingQueue.Count);
|
||||
}
|
||||
PlayTrack(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Smtc_PlaybackPositionChangeRequested(SystemMediaTransportControls sender, PlaybackPositionChangeRequestedEventArgs args)
|
||||
{
|
||||
_timelineController.Position = args.RequestedPlaybackPosition;
|
||||
}
|
||||
|
||||
private void MediaPlayer_MediaOpened(MediaPlayer sender, object args)
|
||||
{
|
||||
_timelineController.Start();
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
|
||||
}
|
||||
|
||||
private void TimelineController_PositionChanged(MediaTimelineController sender, object args)
|
||||
{
|
||||
_smtc.UpdateTimelineProperties(new SystemMediaTransportControlsTimelineProperties()
|
||||
{
|
||||
Position = sender.Position,
|
||||
EndTime = _mediaPlayer.PlaybackSession.NaturalDuration
|
||||
});
|
||||
}
|
||||
|
||||
private void Smtc_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
|
||||
{
|
||||
switch (args.Button)
|
||||
{
|
||||
case SystemMediaTransportControlsButton.Play:
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
|
||||
_timelineController.Resume();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Pause:
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Paused;
|
||||
_timelineController.Pause();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Next:
|
||||
PlayNextTrack();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Previous:
|
||||
PlayPreviousTrack();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, Events.LibChangedEventArgs e)
|
||||
{
|
||||
RefreshSongs();
|
||||
}
|
||||
|
||||
public void RefreshSongs()
|
||||
{
|
||||
_dispatcherQueueTimer.Debounce(() =>
|
||||
{
|
||||
IsDataLoading = true;
|
||||
_tracks.Clear();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
Track track = new(file);
|
||||
if (track.Duration <= 0) continue;
|
||||
_tracks.Add(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
ApplyPlaylist();
|
||||
ApplySongSearchQuery();
|
||||
IsLocalMediaNotFound = !_filteredTracks.Any();
|
||||
ApplySongOrderType();
|
||||
IsDataLoading = false;
|
||||
});
|
||||
});
|
||||
}, TimeSpan.FromMilliseconds(100));
|
||||
}
|
||||
|
||||
public void ApplyPlaylist()
|
||||
{
|
||||
if (SelectedSongsTabInfo?.FilterValue == string.Empty)
|
||||
{
|
||||
_playlistTracks = _tracks;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (SelectedSongsTabInfo?.FilterProperty)
|
||||
{
|
||||
case CommonSongProperty.Title:
|
||||
_playlistTracks = _tracks.Where(t => t.Title.Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
break;
|
||||
case CommonSongProperty.Album:
|
||||
_playlistTracks = _tracks.Where(t => t.Album.Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
break;
|
||||
case CommonSongProperty.Artist:
|
||||
_playlistTracks = _tracks.Where(t => t.Artist.Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ApplySongSearchQuery();
|
||||
IsLocalMediaNotFound = !_filteredTracks.Any();
|
||||
ApplySongOrderType();
|
||||
}
|
||||
|
||||
public void ApplySongSearchQuery()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(SongSearchQuery))
|
||||
{
|
||||
_filteredTracks = _playlistTracks;
|
||||
return;
|
||||
}
|
||||
_filteredTracks = _playlistTracks.Where(t =>
|
||||
t.Title.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
|
||||
t.Artist.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
|
||||
t.Album.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
}
|
||||
|
||||
private void ApplySongOrderType()
|
||||
{
|
||||
switch (SongOrderType)
|
||||
{
|
||||
case CommonSongProperty.Title:
|
||||
GroupedTracks = _filteredTracks.GetGroupedBy(
|
||||
t => LanguageHelper.GetOrderChar(t.Title),
|
||||
o => ((Track)o).Title
|
||||
);
|
||||
break;
|
||||
case CommonSongProperty.Artist:
|
||||
GroupedTracks = _filteredTracks.GetGroupedBy(
|
||||
t => LanguageHelper.GetOrderChar(t.Artist),
|
||||
o => ((Track)o).Artist
|
||||
);
|
||||
break;
|
||||
case CommonSongProperty.Album:
|
||||
GroupedTracks = _filteredTracks.GetGroupedBy(
|
||||
t => LanguageHelper.GetOrderChar(t.Album),
|
||||
o => ((Track)o).Album
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSelectedPlaylist(SongsTabInfo playlist)
|
||||
{
|
||||
var found = SongsTabInfoList.Where(x => x.FilterProperty == playlist.FilterProperty && x.FilterValue == playlist.FilterValue)
|
||||
.ToList().FirstOrDefault();
|
||||
if (found == null)
|
||||
{
|
||||
SongsTabInfoList.Add(playlist);
|
||||
SelectedSongsTabInfoIndex = SongsTabInfoList.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedSongsTabInfoIndex = SongsTabInfoList.IndexOf(found);
|
||||
}
|
||||
ApplyPlaylist();
|
||||
}
|
||||
|
||||
public void PlayTrackAt(int index)
|
||||
{
|
||||
PlayTrack(TrackPlayingQueue.ElementAtOrDefault(index));
|
||||
}
|
||||
|
||||
public void PlayTrack(PlayQueueItem? playQueueItem)
|
||||
{
|
||||
_timelineController.Pause();
|
||||
_mediaPlayer.Source = null;
|
||||
if (playQueueItem == null)
|
||||
{
|
||||
_smtc.IsEnabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var track = playQueueItem.Track;
|
||||
var updater = _smtc.DisplayUpdater;
|
||||
_smtc.IsEnabled = true;
|
||||
_mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(track.Path));
|
||||
updater.AppMediaId = Package.Current.Id.FullName;
|
||||
updater.Type = MediaPlaybackType.Music;
|
||||
updater.MusicProperties.Title = track.Title;
|
||||
updater.MusicProperties.Artist = track.Artist;
|
||||
updater.MusicProperties.AlbumTitle = track.Album;
|
||||
if (track.EmbeddedPictures.FirstOrDefault()?.PictureData is byte[] pictureData)
|
||||
{
|
||||
updater.Thumbnail = ImageHelper.ByteArrayToRandomAccessStreamReference(pictureData);
|
||||
}
|
||||
else
|
||||
{
|
||||
updater.Thumbnail = null;
|
||||
}
|
||||
updater.Update();
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSongOrderTypeChanged(CommonSongProperty value)
|
||||
{
|
||||
ApplySongOrderType();
|
||||
IsLocalMediaNotFound = !_filteredTracks.Any();
|
||||
}
|
||||
|
||||
partial void OnSongSearchQueryChanged(string value)
|
||||
{
|
||||
ApplySongSearchQuery();
|
||||
IsLocalMediaNotFound = !_filteredTracks.Any();
|
||||
ApplySongOrderType();
|
||||
}
|
||||
|
||||
partial void OnPlayingSongIndexChanged(int value)
|
||||
{
|
||||
DisplayedPlayingSongIndex = value + 1;
|
||||
}
|
||||
|
||||
partial void OnPlaybackOrderChanged(PlaybackOrder value)
|
||||
{
|
||||
_settingsService.PlaybackOrder = value;
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<LocalMediaFolder>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LocalMediaFolders))
|
||||
{
|
||||
RefreshSongs();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class SettingsPageViewModel
|
||||
{
|
||||
public SettingsPageViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService, IPlaybackService playbackService, ITranslateService libreTranslateService) : base(settingsService)
|
||||
{
|
||||
_libWatcherService = libWatcherService;
|
||||
_playbackService = playbackService;
|
||||
_libreTranslateService = libreTranslateService;
|
||||
|
||||
IsLibreTranslateEnabled = _settingsService.IsLibreTranslateEnabled;
|
||||
LibreTranslateServer = _settingsService.LibreTranslateServer;
|
||||
SelectedTargetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
|
||||
|
||||
LocalMediaFolders = [.. _settingsService.LocalMediaFolders];
|
||||
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
|
||||
AlbumArtSearchProvidersInfo = [.. _settingsService.AlbumArtSearchProvidersInfo];
|
||||
|
||||
Language = _settingsService.Language;
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
|
||||
AutoStartWindowType = _settingsService.AutoStartWindowType;
|
||||
AutoLockOnDesktopMode = _settingsService.AutoLockOnDesktopMode;
|
||||
|
||||
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
|
||||
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
||||
|
||||
CoverAcrylicEffectAmount = _settingsService.CoverAcrylicEffectAmount;
|
||||
|
||||
LyricsAlignmentType = _settingsService.LyricsAlignmentType;
|
||||
SongInfoAlignmentType = _settingsService.SongInfoAlignmentType;
|
||||
LyricsFontWeight = _settingsService.LyricsFontWeight;
|
||||
LyricsBlurAmount = _settingsService.LyricsBlurAmount;
|
||||
LyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
|
||||
LyricsLineSpacingFactor = _settingsService.LyricsLineSpacingFactor;
|
||||
|
||||
// Font size
|
||||
LyricsStandardFontSize = _settingsService.LyricsStandardFontSize;
|
||||
LyricsDockFontSize = _settingsService.LyricsDockFontSize;
|
||||
LyricsDesktopFontSize = _settingsService.LyricsDesktopFontSize;
|
||||
|
||||
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
|
||||
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
|
||||
LyricsHighlightScope = _settingsService.LyricsHighlightScope;
|
||||
IsFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
|
||||
|
||||
LyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
|
||||
LyricsFgFontColorType = _settingsService.LyricsFgFontColorType;
|
||||
LyricsStrokeFontColorType = _settingsService.LyricsStrokeFontColorType;
|
||||
|
||||
LyricsCustomBgFontColor = _settingsService.LyricsCustomBgFontColor;
|
||||
LyricsCustomFgFontColor = _settingsService.LyricsCustomFgFontColor;
|
||||
LyricsCustomStrokeFontColor = _settingsService.LyricsCustomStrokeFontColor;
|
||||
|
||||
LyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
|
||||
LyricsBackgroundTheme = _settingsService.LyricsBackgroundTheme;
|
||||
MediaSourceProvidersInfo = [.. _settingsService.MediaSourceProvidersInfo];
|
||||
IgnoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
|
||||
|
||||
LyricsScrollEasingType = _settingsService.LyricsScrollEasingType;
|
||||
LyricsScrollDuration = _settingsService.LyricsScrollDuration;
|
||||
TimelineSyncThreshold = _settingsService.TimelineSyncThreshold;
|
||||
|
||||
IsLyricsFloatAnimationEnabled = _settingsService.IsLyricsFloatAnimationEnabled;
|
||||
ResetPositionOffsetOnSongChanged = _settingsService.ResetPositionOffsetOnSongChanged;
|
||||
LockHotKeyIndex = _settingsService.LockHotKeyIndex;
|
||||
|
||||
LXMusicServer = _settingsService.LXMusicServer;
|
||||
DockPlacement = _settingsService.DockPlacement;
|
||||
LyricsBgFontOpacity = _settingsService.LyricsBgFontOpacity;
|
||||
HideWindowWhenNotPlaying = _settingsService.HideWindowWhenNotPlaying;
|
||||
DockWindowHeight = _settingsService.DockWindowHeight;
|
||||
|
||||
SystemFontNames = [.. FontHelper.SystemFontFamilies];
|
||||
SelectedFontFamilyIndex = _settingsService.SelectedFontFamilyIndex;
|
||||
LyricsFontFamily = _settingsService.LyricsFontFamily;
|
||||
IsDragEverywhereEnabled = _settingsService.IsDragEverywhereEnabled;
|
||||
|
||||
MonitorDeviceNames = [.. MonitorHelper.GetAllMonitorDeviceNames()];
|
||||
SelectedDockMonitorDeviceName = _settingsService.DockMonitorDeviceName;
|
||||
|
||||
_playbackService.MediaSourceProvidersInfoChanged += PlaybackService_SessionIdsChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Globalization;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class SettingsPageViewModel
|
||||
{
|
||||
partial void OnDockPlacementChanged(DockPlacement value)
|
||||
{
|
||||
_settingsService.DockPlacement = value;
|
||||
}
|
||||
partial void OnLyricsScrollEasingTypeChanged(EasingType value)
|
||||
{
|
||||
_settingsService.LyricsScrollEasingType = value;
|
||||
}
|
||||
partial void OnLyricsScrollDurationChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsScrollDuration = value;
|
||||
}
|
||||
partial void OnLyricsBackgroundThemeChanged(ElementTheme value)
|
||||
{
|
||||
_settingsService.LyricsBackgroundTheme = value;
|
||||
}
|
||||
partial void OnLyricsFontStrokeWidthChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsFontStrokeWidth = value;
|
||||
}
|
||||
partial void OnIgnoreFullscreenWindowChanged(bool value)
|
||||
{
|
||||
_settingsService.IgnoreFullscreenWindow = value;
|
||||
}
|
||||
partial void OnSelectedTargetLanguageIndexChanged(int value)
|
||||
{
|
||||
_settingsService.SelectedTargetLanguageIndex = value;
|
||||
}
|
||||
partial void OnLibreTranslateServerChanged(string value)
|
||||
{
|
||||
_settingsService.LibreTranslateServer = value;
|
||||
}
|
||||
partial void OnLXMusicServerChanged(string value)
|
||||
{
|
||||
_settingsService.LXMusicServer = value;
|
||||
}
|
||||
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
|
||||
{
|
||||
_settingsService.AutoStartWindowType = value;
|
||||
}
|
||||
partial void OnAutoLockOnDesktopModeChanged(bool value)
|
||||
{
|
||||
_settingsService.AutoLockOnDesktopMode = value;
|
||||
}
|
||||
partial void OnCoverImageRadiusChanged(int value)
|
||||
{
|
||||
_settingsService.CoverImageRadius = value;
|
||||
}
|
||||
partial void OnCoverOverlayBlurAmountChanged(int value)
|
||||
{
|
||||
_settingsService.CoverOverlayBlurAmount = value;
|
||||
}
|
||||
partial void OnCoverAcrylicEffectAmountChanged(int value)
|
||||
{
|
||||
_settingsService.CoverAcrylicEffectAmount = value;
|
||||
}
|
||||
partial void OnCoverOverlayOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.CoverOverlayOpacity = value;
|
||||
}
|
||||
partial void OnIsDynamicCoverOverlayEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsDynamicCoverOverlayEnabled = value;
|
||||
}
|
||||
partial void OnLanguageChanged(Enums.Language value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case Enums.Language.FollowSystem:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "";
|
||||
break;
|
||||
case Enums.Language.English:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "en-US";
|
||||
break;
|
||||
case Enums.Language.SimplifiedChinese:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "zh-CN";
|
||||
break;
|
||||
case Enums.Language.TraditionalChinese:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "zh-TW";
|
||||
break;
|
||||
case Enums.Language.Japanese:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "ja-JP";
|
||||
break;
|
||||
case Enums.Language.Korean:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "ko-KR";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_settingsService.Language = Language;
|
||||
}
|
||||
partial void OnIsFanLyricsEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsFanLyricsEnabled = value;
|
||||
}
|
||||
partial void OnIsLyricsGlowEffectEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsLyricsGlowEffectEnabled = value;
|
||||
}
|
||||
partial void OnLyricsAlignmentTypeChanged(TextAlignmentType value)
|
||||
{
|
||||
_settingsService.LyricsAlignmentType = value;
|
||||
}
|
||||
partial void OnSongInfoAlignmentTypeChanged(TextAlignmentType value)
|
||||
{
|
||||
_settingsService.SongInfoAlignmentType = value;
|
||||
}
|
||||
partial void OnLyricsBlurAmountChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsBlurAmount = value;
|
||||
}
|
||||
partial void OnLyricsCustomBgFontColorChanged(Color value)
|
||||
{
|
||||
_settingsService.LyricsCustomBgFontColor = value;
|
||||
}
|
||||
partial void OnLyricsCustomFgFontColorChanged(Color value)
|
||||
{
|
||||
_settingsService.LyricsCustomFgFontColor = value;
|
||||
}
|
||||
partial void OnLyricsCustomStrokeFontColorChanged(Color value)
|
||||
{
|
||||
_settingsService.LyricsCustomStrokeFontColor = value;
|
||||
}
|
||||
partial void OnLyricsBgFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
_settingsService.LyricsBgFontColorType = value;
|
||||
}
|
||||
partial void OnLyricsFgFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
_settingsService.LyricsFgFontColorType = value;
|
||||
}
|
||||
partial void OnLyricsStrokeFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
_settingsService.LyricsStrokeFontColorType = value;
|
||||
}
|
||||
partial void OnLyricsStandardFontSizeChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsStandardFontSize = value;
|
||||
}
|
||||
partial void OnLyricsDockFontSizeChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsDockFontSize = value;
|
||||
}
|
||||
partial void OnLyricsDesktopFontSizeChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsDesktopFontSize = value;
|
||||
}
|
||||
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
|
||||
{
|
||||
_settingsService.LyricsFontWeight = value;
|
||||
}
|
||||
partial void OnLyricsGlowEffectScopeChanged(LineRenderingType value)
|
||||
{
|
||||
_settingsService.LyricsGlowEffectScope = value;
|
||||
}
|
||||
partial void OnLyricsHighlightScopeChanged(LineRenderingType value)
|
||||
{
|
||||
_settingsService.LyricsHighlightScope = value;
|
||||
}
|
||||
partial void OnLyricsLineSpacingFactorChanged(float value)
|
||||
{
|
||||
_settingsService.LyricsLineSpacingFactor = value;
|
||||
}
|
||||
partial void OnLyricsVerticalEdgeOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsVerticalEdgeOpacity = value;
|
||||
}
|
||||
partial void OnTimelineSyncThresholdChanged(int value)
|
||||
{
|
||||
_settingsService.TimelineSyncThreshold = value;
|
||||
}
|
||||
partial void OnIsLyricsFloatAnimationEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsLyricsFloatAnimationEnabled = value;
|
||||
}
|
||||
partial void OnResetPositionOffsetOnSongChangedChanged(bool value)
|
||||
{
|
||||
_settingsService.ResetPositionOffsetOnSongChanged = value;
|
||||
}
|
||||
partial void OnLyricsBgFontOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsBgFontOpacity = value;
|
||||
}
|
||||
partial void OnHideWindowWhenNotPlayingChanged(bool value)
|
||||
{
|
||||
_settingsService.HideWindowWhenNotPlaying = value;
|
||||
}
|
||||
partial void OnDockWindowHeightChanged(int value)
|
||||
{
|
||||
_settingsService.DockWindowHeight = value;
|
||||
}
|
||||
partial void OnSelectedFontFamilyIndexChanged(int value)
|
||||
{
|
||||
_settingsService.SelectedFontFamilyIndex = value;
|
||||
LyricsFontFamily = SystemFontNames[value];
|
||||
}
|
||||
partial void OnLyricsFontFamilyChanged(string value)
|
||||
{
|
||||
_settingsService.LyricsFontFamily = value;
|
||||
}
|
||||
partial void OnIsDragEverywhereEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsDragEverywhereEnabled = value;
|
||||
|
||||
LyricsWindow? lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow != null)
|
||||
{
|
||||
lyricsWindow.UpdateTitleBarArea();
|
||||
}
|
||||
}
|
||||
partial void OnIsLibreTranslateEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsLibreTranslateEnabled = value;
|
||||
}
|
||||
partial void OnSelectedDockMonitorDeviceNameChanged(string value)
|
||||
{
|
||||
_settingsService.DockMonitorDeviceName = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class SettingsPageViewModel
|
||||
{
|
||||
public string Version { get; set; } = MetadataHelper.AppVersion;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLibreTranslateEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsDragEverywhereEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial string LyricsFontFamily { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<string> SystemFontNames { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int SelectedFontFamilyIndex { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial DockPlacement DockPlacement { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LockHotKeyIndex { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ElementTheme LyricsBackgroundTheme { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AutoStartWindowType AutoStartWindowType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool AutoLockOnDesktopMode { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverImageRadius { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverOverlayBlurAmount { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverOverlayOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDebugOverlayEnabled { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLogEnabled { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDynamicCoverOverlayEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverAcrylicEffectAmount { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Enums.Language Language { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<LocalMediaFolder> LocalMediaFolders { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsFanLyricsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsGlowEffectEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial TextAlignmentType LyricsAlignmentType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial TextAlignmentType SongInfoAlignmentType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsBlurAmount { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial Color LyricsCustomBgFontColor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial Color LyricsCustomFgFontColor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial Color LyricsCustomStrokeFontColor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsBgFontOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontColorType LyricsBgFontColorType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontColorType LyricsFgFontColorType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontColorType LyricsStrokeFontColorType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsStandardFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsDockFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsDesktopFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontWeight LyricsFontWeight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LineRenderingType LyricsGlowEffectScope { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LineRenderingType LyricsHighlightScope { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial float LyricsLineSpacingFactor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsVerticalEdgeOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial object NavViewSelectedItemTag { get; set; } = "App";
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsFloatAnimationEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string LibreTranslateServer { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int SelectedTargetLanguageIndex { get; set; } = 0;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsLibreTranslateServerTesting { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsFontStrokeWidth { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IgnoreFullscreenWindow { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial EasingType LyricsScrollEasingType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsScrollDuration { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int TimelineSyncThreshold { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsLXMusicServerTesting { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial string LXMusicServer { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool HideWindowWhenNotPlaying { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int DockWindowHeight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial string SelectedDockMonitorDeviceName { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<string> MonitorDeviceNames { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,23 +7,17 @@ using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using ShadowViewer.Controls;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Globalization;
|
||||
using Windows.Media.Playback;
|
||||
using Windows.System;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Popups;
|
||||
using WinRT.Interop;
|
||||
using MetadataHelper = BetterLyrics.WinUI3.Helper.MetadataHelper;
|
||||
|
||||
@@ -37,227 +31,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private readonly string _autoStartupTaskId = "AutoStartup";
|
||||
|
||||
public SettingsPageViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService, IPlaybackService playbackService, ITranslateService libreTranslateService) : base(settingsService)
|
||||
{
|
||||
_libWatcherService = libWatcherService;
|
||||
_playbackService = playbackService;
|
||||
_libreTranslateService = libreTranslateService;
|
||||
|
||||
LibreTranslateServer = _settingsService.LibreTranslateServer;
|
||||
SelectedTargetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
|
||||
|
||||
LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
|
||||
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
|
||||
AlbumArtSearchProvidersInfo = [.. _settingsService.AlbumArtSearchProvidersInfo];
|
||||
|
||||
Language = _settingsService.Language;
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
|
||||
AutoStartWindowType = _settingsService.AutoStartWindowType;
|
||||
AutoLockOnDesktopMode = _settingsService.AutoLockOnDesktopMode;
|
||||
|
||||
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
|
||||
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
||||
|
||||
LyricsAlignmentType = _settingsService.LyricsAlignmentType;
|
||||
SongInfoAlignmentType = _settingsService.SongInfoAlignmentType;
|
||||
LyricsFontWeight = _settingsService.LyricsFontWeight;
|
||||
LyricsBlurAmount = _settingsService.LyricsBlurAmount;
|
||||
LyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
|
||||
LyricsLineSpacingFactor = _settingsService.LyricsLineSpacingFactor;
|
||||
LyricsFontSize = _settingsService.LyricsFontSize;
|
||||
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
|
||||
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
|
||||
LyricsHighlightScope = _settingsService.LyricsHighlightScope;
|
||||
IsFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
|
||||
|
||||
LyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
|
||||
LyricsFgFontColorType = _settingsService.LyricsFgFontColorType;
|
||||
LyricsStrokeFontColorType = _settingsService.LyricsStrokeFontColorType;
|
||||
|
||||
LyricsCustomBgFontColor = _settingsService.LyricsCustomBgFontColor;
|
||||
LyricsCustomFgFontColor = _settingsService.LyricsCustomFgFontColor;
|
||||
LyricsCustomStrokeFontColor = _settingsService.LyricsCustomStrokeFontColor;
|
||||
|
||||
LyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
|
||||
LyricsBackgroundTheme = _settingsService.LyricsBackgroundTheme;
|
||||
MediaSourceProvidersInfo = [.. _settingsService.MediaSourceProvidersInfo];
|
||||
IgnoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
|
||||
|
||||
LyricsScrollEasingType = _settingsService.LyricsScrollEasingType;
|
||||
LyricsScrollDuration = _settingsService.LyricsScrollDuration;
|
||||
TimelineSyncThreshold = _settingsService.TimelineSyncThreshold;
|
||||
|
||||
_playbackService.MediaSourceProvidersInfoChanged += PlaybackService_SessionIdsChanged;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
BuildDate = (await Helper.MetadataHelper.GetBuildDate()).ToString("(yyyy/MM/dd HH:mm:ss)");
|
||||
});
|
||||
}
|
||||
|
||||
private void PlaybackService_SessionIdsChanged(object? sender, Events.MediaSourceProvidersInfoEventArgs e)
|
||||
{
|
||||
MediaSourceProvidersInfo = [.. e.MediaSourceProviersInfo];
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ElementTheme LyricsBackgroundTheme { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AutoStartWindowType AutoStartWindowType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool AutoLockOnDesktopMode { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverImageRadius { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverOverlayBlurAmount { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverOverlayOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDebugOverlayEnabled { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLogEnabled { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDynamicCoverOverlayEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Enums.Language Language { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<LocalLyricsFolder> LocalLyricsFolders { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsFanLyricsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsGlowEffectEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial TextAlignmentType LyricsAlignmentType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial TextAlignmentType SongInfoAlignmentType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsBlurAmount { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial Color LyricsCustomBgFontColor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial Color LyricsCustomFgFontColor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial Color LyricsCustomStrokeFontColor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontColorType LyricsBgFontColorType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontColorType LyricsFgFontColorType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontColorType LyricsStrokeFontColorType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontWeight LyricsFontWeight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LineRenderingType LyricsGlowEffectScope { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LineRenderingType LyricsHighlightScope { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial float LyricsLineSpacingFactor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsVerticalEdgeOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial object NavViewSelectedItemTag { get; set; }
|
||||
|
||||
public string Version { get; set; } = MetadataHelper.AppVersion;
|
||||
|
||||
public string BuildDate { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string LibreTranslateServer { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int SelectedTargetLanguageIndex { get; set; } = 0;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsLibreTranslateServerTesting { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsFontStrokeWidth { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IgnoreFullscreenWindow { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial EasingType LyricsScrollEasingType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsScrollDuration { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int TimelineSyncThreshold { get; set; }
|
||||
|
||||
public void OnLyricsSearchProvidersReordered()
|
||||
{
|
||||
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
|
||||
@@ -278,21 +56,21 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
);
|
||||
}
|
||||
|
||||
public void RemoveFolderAsync(LocalLyricsFolder folder)
|
||||
public void RemoveFolderAsync(LocalMediaFolder folder)
|
||||
{
|
||||
LocalLyricsFolders.Remove(folder);
|
||||
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
|
||||
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
|
||||
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
|
||||
LocalMediaFolders.Remove(folder);
|
||||
_settingsService.LocalMediaFolders = [.. LocalMediaFolders];
|
||||
_libWatcherService.UpdateWatchers([.. LocalMediaFolders]);
|
||||
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
|
||||
}
|
||||
|
||||
public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
|
||||
public void ToggleLocalLyricsFolder()
|
||||
{
|
||||
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
|
||||
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
|
||||
_settingsService.LocalMediaFolders = [.. LocalMediaFolders];
|
||||
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
|
||||
}
|
||||
|
||||
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
|
||||
public void ToggleLyricsSearchProvider()
|
||||
{
|
||||
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
|
||||
Broadcast(
|
||||
@@ -325,52 +103,52 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
var normalizedPath = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
|
||||
if (LocalLyricsFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
|
||||
if (LocalMediaFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"));
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"), InfoBarSeverity.Warning);
|
||||
}
|
||||
else if (LocalLyricsFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
|
||||
else if (LocalMediaFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// 添加的文件夹是现有文件夹的子文件夹
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo"));
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo"), InfoBarSeverity.Warning);
|
||||
}
|
||||
else if (LocalLyricsFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
|
||||
else if (LocalMediaFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
|
||||
)
|
||||
{
|
||||
// 添加的文件夹是现有文件夹的父文件夹
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo"));
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo"), InfoBarSeverity.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
LocalLyricsFolders.Add(new LocalLyricsFolder(path, true));
|
||||
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
|
||||
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
|
||||
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
|
||||
LocalMediaFolders.Add(new LocalMediaFolder(path, true));
|
||||
_settingsService.LocalMediaFolders = [.. LocalMediaFolders];
|
||||
_libWatcherService.UpdateWatchers([.. LocalMediaFolders]);
|
||||
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task LaunchProjectGitHubPageAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri(MetadataHelper.GithubUrl));
|
||||
await Windows.System.Launcher.LaunchUriAsync(new Uri(MetadataHelper.GithubUrl));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static async Task OpenCacheFolderAsync()
|
||||
{
|
||||
await Launcher.LaunchFolderPathAsync(PathHelper.CacheFolder);
|
||||
await Windows.System.Launcher.LaunchFolderPathAsync(Helper.PathHelper.CacheFolder);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void RestartApp()
|
||||
{
|
||||
WindowHelper.RestartApp();
|
||||
Helper.WindowHelper.RestartApp();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SelectAndAddFolderAsync(UIElement sender)
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<SettingsWindow>();
|
||||
var window = Helper.WindowHelper.GetWindowByWindowType<SettingsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
var picker = new Windows.Storage.Pickers.FolderPicker();
|
||||
@@ -397,23 +175,54 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
string targetLangCode = LanguageHelper.SupportedTargetLanguages[SelectedTargetLanguageIndex].Code;
|
||||
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, null);
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
|
||||
IsLibreTranslateServerTesting = false;
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), InfoBarSeverity.Success);
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestFailedInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
|
||||
IsLibreTranslateServerTesting = false;
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestFailedInfo"), InfoBarSeverity.Error);
|
||||
});
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
IsLibreTranslateServerTesting = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void LXMusicServerTest()
|
||||
{
|
||||
IsLXMusicServerTesting = true;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
bool testResult = await NetHelper.CheckConnectivity($"{LXMusicServer}/status");
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
if (testResult)
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), InfoBarSeverity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestFailedInfo"), InfoBarSeverity.Error);
|
||||
}
|
||||
IsLXMusicServerTesting = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RefreshMonitorDeviceNames()
|
||||
{
|
||||
MonitorDeviceNames = [.. MonitorHelper.GetAllMonitorDeviceNames()];
|
||||
SelectedDockMonitorDeviceName = MonitorHelper.GetPrimaryMonitorDeviceName();
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleAutoStartupAsync(bool target)
|
||||
{
|
||||
StartupTask startupTask = await StartupTask.GetAsync(_autoStartupTaskId);
|
||||
@@ -445,158 +254,5 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
partial void OnLyricsScrollEasingTypeChanged(EasingType value)
|
||||
{
|
||||
_settingsService.LyricsScrollEasingType = value;
|
||||
}
|
||||
partial void OnLyricsScrollDurationChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsScrollDuration = value;
|
||||
}
|
||||
partial void OnLyricsBackgroundThemeChanged(ElementTheme value)
|
||||
{
|
||||
_settingsService.LyricsBackgroundTheme = value;
|
||||
}
|
||||
partial void OnLyricsFontStrokeWidthChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsFontStrokeWidth = value;
|
||||
}
|
||||
partial void OnIgnoreFullscreenWindowChanged(bool value)
|
||||
{
|
||||
_settingsService.IgnoreFullscreenWindow = value;
|
||||
}
|
||||
partial void OnSelectedTargetLanguageIndexChanged(int value)
|
||||
{
|
||||
_settingsService.SelectedTargetLanguageIndex = value;
|
||||
}
|
||||
partial void OnLibreTranslateServerChanged(string value)
|
||||
{
|
||||
_settingsService.LibreTranslateServer = value;
|
||||
}
|
||||
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
|
||||
{
|
||||
_settingsService.AutoStartWindowType = value;
|
||||
}
|
||||
partial void OnAutoLockOnDesktopModeChanged(bool value)
|
||||
{
|
||||
_settingsService.AutoLockOnDesktopMode = value;
|
||||
}
|
||||
partial void OnCoverImageRadiusChanged(int value)
|
||||
{
|
||||
_settingsService.CoverImageRadius = value;
|
||||
}
|
||||
partial void OnCoverOverlayBlurAmountChanged(int value)
|
||||
{
|
||||
_settingsService.CoverOverlayBlurAmount = value;
|
||||
}
|
||||
partial void OnCoverOverlayOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.CoverOverlayOpacity = value;
|
||||
}
|
||||
partial void OnIsDynamicCoverOverlayEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsDynamicCoverOverlayEnabled = value;
|
||||
}
|
||||
partial void OnLanguageChanged(Enums.Language value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case Enums.Language.FollowSystem:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "";
|
||||
break;
|
||||
case Enums.Language.English:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "en-US";
|
||||
break;
|
||||
case Enums.Language.SimplifiedChinese:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "zh-CN";
|
||||
break;
|
||||
case Enums.Language.TraditionalChinese:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "zh-TW";
|
||||
break;
|
||||
case Enums.Language.Japanese:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "ja-JP";
|
||||
break;
|
||||
case Enums.Language.Korean:
|
||||
ApplicationLanguages.PrimaryLanguageOverride = "ko-KR";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_settingsService.Language = Language;
|
||||
}
|
||||
partial void OnIsFanLyricsEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsFanLyricsEnabled = value;
|
||||
}
|
||||
partial void OnIsLyricsGlowEffectEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsLyricsGlowEffectEnabled = value;
|
||||
}
|
||||
partial void OnLyricsAlignmentTypeChanged(TextAlignmentType value)
|
||||
{
|
||||
_settingsService.LyricsAlignmentType = value;
|
||||
}
|
||||
partial void OnSongInfoAlignmentTypeChanged(TextAlignmentType value)
|
||||
{
|
||||
_settingsService.SongInfoAlignmentType = value;
|
||||
}
|
||||
partial void OnLyricsBlurAmountChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsBlurAmount = value;
|
||||
}
|
||||
partial void OnLyricsCustomBgFontColorChanged(Color value)
|
||||
{
|
||||
_settingsService.LyricsCustomBgFontColor = value;
|
||||
}
|
||||
partial void OnLyricsCustomFgFontColorChanged(Color value)
|
||||
{
|
||||
_settingsService.LyricsCustomFgFontColor = value;
|
||||
}
|
||||
partial void OnLyricsCustomStrokeFontColorChanged(Color value)
|
||||
{
|
||||
_settingsService.LyricsCustomStrokeFontColor = value;
|
||||
}
|
||||
partial void OnLyricsBgFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
_settingsService.LyricsBgFontColorType = value;
|
||||
}
|
||||
partial void OnLyricsFgFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
_settingsService.LyricsFgFontColorType = value;
|
||||
}
|
||||
partial void OnLyricsStrokeFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
_settingsService.LyricsStrokeFontColorType = value;
|
||||
}
|
||||
partial void OnLyricsFontSizeChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsFontSize = value;
|
||||
}
|
||||
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
|
||||
{
|
||||
_settingsService.LyricsFontWeight = value;
|
||||
}
|
||||
partial void OnLyricsGlowEffectScopeChanged(LineRenderingType value)
|
||||
{
|
||||
_settingsService.LyricsGlowEffectScope = value;
|
||||
}
|
||||
partial void OnLyricsHighlightScopeChanged(LineRenderingType value)
|
||||
{
|
||||
_settingsService.LyricsHighlightScope = value;
|
||||
}
|
||||
partial void OnLyricsLineSpacingFactorChanged(float value)
|
||||
{
|
||||
_settingsService.LyricsLineSpacingFactor = value;
|
||||
}
|
||||
partial void OnLyricsVerticalEdgeOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsVerticalEdgeOpacity = value;
|
||||
}
|
||||
partial void OnTimelineSyncThresholdChanged(int value)
|
||||
{
|
||||
_settingsService.TimelineSyncThreshold = value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
@@ -34,13 +37,41 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[RelayCommand]
|
||||
private static void ExitApp()
|
||||
{
|
||||
WindowHelper.ExitAllWindows();
|
||||
WindowHelper.ExitApp();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void RestartApp()
|
||||
{
|
||||
WindowHelper.RestartApp();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void ResetWindowPosition()
|
||||
{
|
||||
LyricsWindow? lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow != null)
|
||||
{
|
||||
lyricsWindow.MoveAndResize(0, 0, 800, 600);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void OpenSettings()
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<SettingsWindow>();
|
||||
WindowHelper.OpenWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void OpenMusicGallery()
|
||||
{
|
||||
WindowHelper.OpenWindow<MusicGalleryWindow>();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void OpenLyricsWindow()
|
||||
{
|
||||
WindowHelper.OpenWindow<LyricsWindow>();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -18,160 +18,384 @@
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid">
|
||||
<Grid x:Name="RootGrid" SizeChanged="RootGrid_SizeChanged">
|
||||
<!-- Lyrics area -->
|
||||
<renderer:LyricsRenderer />
|
||||
|
||||
<!-- No music playing placeholder -->
|
||||
<Grid x:Name="NoMusicPlayingGrid" Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
|
||||
<Grid x:Name="NoMusicPlayingGrid" Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}">
|
||||
<TextBlock
|
||||
x:Uid="MainPageNoMusicPlaying"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource TitleTextBlockStyle}" />
|
||||
FontFamily="{x:Bind ViewModel.LyricsFontFamily, Mode=OneWay}"
|
||||
FontSize="{x:Bind ViewModel.HintMessageFontSize, Mode=OneWay}" />
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SongInfo, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="{x:Null}">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SongInfo, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="{x:Null}">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Grid>
|
||||
|
||||
<!-- Bottom command area -->
|
||||
<Grid
|
||||
x:Name="BottomCommandGrid"
|
||||
Padding="12"
|
||||
Margin="12"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="Transparent"
|
||||
Opacity="0"
|
||||
Opacity="{x:Bind ViewModel.BottomCommandGridOpacity, Mode=OneWay}"
|
||||
PointerEntered="BottomCommandGrid_PointerEntered"
|
||||
PointerExited="BottomCommandGrid_PointerExited">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
|
||||
<StackPanel
|
||||
x:Name="BottomLeftCommandStackPanel"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
</StackPanel>
|
||||
<Grid x:Name="BottomCommandContent">
|
||||
<Grid Padding="3" HorizontalAlignment="Left">
|
||||
<StackPanel
|
||||
x:Name="BottomLeftCommandStackPanel"
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
|
||||
<StackPanel
|
||||
x:Name="BottomCenterCommandStackPanel"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Margin="0,0,0,2"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="2">
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{Binding ElementName=TimelineSlider, Path=Value, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
|
||||
<TextBlock Text="{Binding ElementName=TimelineSlider, Path=Maximum, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
x:Name="BottomRightCommandStackPanel"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
<!-- Position offset -->
|
||||
<Button Click="TimelineOffsetButton_Click" Style="{StaticResource GhostButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph=""
|
||||
RenderTransformOrigin="0.5,0.5">
|
||||
<FontIcon.RenderTransform>
|
||||
<RotateTransform Angle="90" CenterX="0.5" CenterY="0.5" />
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="TimelineOffsetToolTip" x:Uid="LyricsPageTimelineOffsetButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.ContextFlyout>
|
||||
<Flyout x:Name="TimelineOffsetFlyout" ShouldConstrainToRootBounds="False">
|
||||
<StackPanel>
|
||||
<Slider
|
||||
x:Uid="MainPagePositionOffsetSlider"
|
||||
Maximum="5000"
|
||||
Minimum="-5000"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="100"
|
||||
TickFrequency="100"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.PositionOffset, Mode=TwoWay}" />
|
||||
<RelativePanel>
|
||||
<TextBlock
|
||||
RelativePanel.AlignLeftWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Text="{x:Bind ViewModel.PositionOffset, Mode=OneWay}" />
|
||||
<Button
|
||||
Click="PositionOffsetResetButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
RelativePanel.AlignRightWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</RelativePanel>
|
||||
<CheckBox IsChecked="{x:Bind ViewModel.ResetPositionOffsetOnSongChanged, Mode=TwoWay}">
|
||||
<TextBlock x:Uid="LyricsPagePositionOffsetHint" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.ContextFlyout>
|
||||
</Button>
|
||||
|
||||
<!-- Position offset -->
|
||||
<Button Style="{StaticResource GhostButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph=""
|
||||
RenderTransformOrigin="0.5,0.5">
|
||||
<FontIcon.RenderTransform>
|
||||
<RotateTransform Angle="90" CenterX="0.5" CenterY="0.5" />
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="TimelineOffsetToolTip" x:Uid="LyricsPageTimelineOffsetButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel>
|
||||
<Slider
|
||||
x:Uid="MainPagePositionOffsetSlider"
|
||||
Maximum="5000"
|
||||
Minimum="-5000"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="100"
|
||||
TickFrequency="100"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.PositionOffset, Mode=TwoWay}" />
|
||||
<RelativePanel>
|
||||
<TextBlock
|
||||
RelativePanel.AlignLeftWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Text="{x:Bind ViewModel.PositionOffset, Mode=OneWay}" />
|
||||
<Button
|
||||
Click="PositionOffsetResetButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
RelativePanel.AlignRightWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</RelativePanel>
|
||||
<TextBlock x:Uid="LyricsPagePositionOffsetHint" Opacity="0.5" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Translation -->
|
||||
<ToggleButton
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsChecked="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}"
|
||||
Style="{StaticResource GhostToggleButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="TranslationToolTip" x:Uid="LyricsPageTranslationButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</ToggleButton>
|
||||
<Grid Padding="3" HorizontalAlignment="Center">
|
||||
<StackPanel
|
||||
x:Name="BottomCenterCommandStackPanel"
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.PreviousSongCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.PauseSongCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.PlaySongCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.NextSongCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Display type -->
|
||||
<Button
|
||||
x:Name="DisplayTypeSwitchButton"
|
||||
x:Uid="MainPageDisplayTypeSwitcher"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="PresentationTypeToolTip" x:Uid="LyricsPageDisplayTypeButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style TargetType="FlyoutPresenter">
|
||||
<Setter Property="Padding" Value="12,2,12,8" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<RadioButtons MaxColumns="1" SelectedIndex="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<RadioButton x:Uid="MainPageAlbumArtOnly" Click="AlbumArtOnlyRadioButton_Click" />
|
||||
<RadioButton x:Uid="MainPageLyriscOnly" Click="LyricsOnlyRadioButton_Click" />
|
||||
<RadioButton x:Uid="MainPageSplitView" Click="SplitViewRadioButton_Click" />
|
||||
</RadioButtons>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Grid Padding="3" HorizontalAlignment="Right">
|
||||
<StackPanel
|
||||
x:Name="BottomRightCommandStackPanel"
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
|
||||
<!-- Settings -->
|
||||
<Button
|
||||
x:Name="SettingsButton"
|
||||
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="SettingsToolTip" x:Uid="LyricsPageSettingsButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<!-- Volume -->
|
||||
<!--<Button Click="VolumeButton_Click" Style="{StaticResource GhostButtonStyle}">
|
||||
<Grid>
|
||||
|
||||
</StackPanel>
|
||||
-->
|
||||
<!-- Volumn: 0 -->
|
||||
<!--
|
||||
<FontIcon
|
||||
x:Name="VolumeLevel0"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="">
|
||||
<FontIcon.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</FontIcon.OpacityTransition>
|
||||
</FontIcon>
|
||||
|
||||
-->
|
||||
<!-- Volumn: 1-32 -->
|
||||
<!--
|
||||
<FontIcon
|
||||
x:Name="VolumeLevel1"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="">
|
||||
<FontIcon.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</FontIcon.OpacityTransition>
|
||||
</FontIcon>
|
||||
|
||||
-->
|
||||
<!-- Volumn: 33-65 -->
|
||||
<!--
|
||||
<FontIcon
|
||||
x:Name="VolumeLevel2"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="">
|
||||
<FontIcon.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</FontIcon.OpacityTransition>
|
||||
</FontIcon>
|
||||
|
||||
-->
|
||||
<!-- Volumn: 66-100 -->
|
||||
<!--
|
||||
<FontIcon
|
||||
x:Name="VolumeLevel3"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="">
|
||||
<FontIcon.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</FontIcon.OpacityTransition>
|
||||
</FontIcon>
|
||||
</Grid>
|
||||
<Button.ContextFlyout>
|
||||
<Flyout x:Name="VolumeFlyout" ShouldConstrainToRootBounds="False">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.Volume, Mode=OneWay}" />
|
||||
<TextBlock Margin="0,0,14,0" VerticalAlignment="Center" />
|
||||
<Slider
|
||||
Width="150"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="1"
|
||||
TickFrequency="1"
|
||||
TickPlacement="None"
|
||||
Value="{x:Bind ViewModel.Volume, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.DataContext>
|
||||
</Button>-->
|
||||
|
||||
<!-- Lyrics & Translation -->
|
||||
<Button
|
||||
Click="TranslationButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="TranslationToolTip" x:Uid="LyricsPageTranslationButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.ContextFlyout>
|
||||
<Flyout x:Name="TranslationFlyout" ShouldConstrainToRootBounds="False">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsExpander
|
||||
x:Uid="LyricsPageTranslationEnabled"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="True">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}" />
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="LyricsPageTranslationOnly" IsEnabled="{x:Bind ViewModel.IsTranslationEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowTranslationOnly, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock x:Uid="LyricsPageLyricsProviderPrefix" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.LyricsSearchProvider, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock x:Uid="LyricsPageTranslationProviderPrefix" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.ContextFlyout>
|
||||
</Button>
|
||||
|
||||
<!-- Display type -->
|
||||
<Button
|
||||
x:Name="DisplayTypeSwitchButton"
|
||||
x:Uid="MainPageDisplayTypeSwitcher"
|
||||
Click="DisplayTypeSwitchButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="PresentationTypeToolTip" x:Uid="LyricsPageDisplayTypeButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.ContextFlyout>
|
||||
<Flyout x:Name="DisplayTypeSwitchFlyout" ShouldConstrainToRootBounds="false">
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style TargetType="FlyoutPresenter">
|
||||
<Setter Property="Padding" Value="12,2,12,8" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<RadioButtons MaxColumns="1" SelectedIndex="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<RadioButton x:Uid="MainPageAlbumArtOnly" Click="AlbumArtOnlyRadioButton_Click" />
|
||||
<RadioButton x:Uid="MainPageLyriscOnly" Click="LyricsOnlyRadioButton_Click" />
|
||||
<RadioButton x:Uid="MainPageSplitView" Click="SplitViewRadioButton_Click" />
|
||||
</RadioButtons>
|
||||
</Flyout>
|
||||
</Button.ContextFlyout>
|
||||
</Button>
|
||||
|
||||
<!-- Settings -->
|
||||
<Button
|
||||
x:Name="SettingsButton"
|
||||
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="SettingsToolTip" x:Uid="LyricsPageSettingsButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Slider
|
||||
x:Name="TimelineSlider"
|
||||
Margin="0,-32,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Maximum="{x:Bind ViewModel.SongDurationSeconds, Mode=OneWay}"
|
||||
Minimum="0"
|
||||
Style="{StaticResource GhostSliderStyle}"
|
||||
Tapped="TimelineSliderOverlay_Tapped"
|
||||
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}"
|
||||
Value="{x:Bind ViewModel.TimelinePositionSeconds, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Bottom command flyout trigger -->
|
||||
<Grid
|
||||
x:Name="BottomCommandFlyoutTrigger"
|
||||
Height="12"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="Transparent"
|
||||
CornerRadius="3,3,0,0"
|
||||
Opacity="{x:Bind ViewModel.BottomCommandFlyoutTriggerOpacity, Mode=OneWay}"
|
||||
PointerEntered="BottomCommandFlyoutTrigger_PointerEntered"
|
||||
PointerExited="BottomCommandFlyoutTrigger_PointerExited"
|
||||
Tapped="BottomCommandFlyoutTrigger_Tapped">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<Grid
|
||||
x:Name="BottomCommandFlyoutTriggerHint"
|
||||
Width="150"
|
||||
Margin="4"
|
||||
Background="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
CornerRadius="2"
|
||||
Translation="0,0,0">
|
||||
<Grid.TranslationTransition>
|
||||
<Vector3Transition />
|
||||
</Grid.TranslationTransition>
|
||||
</Grid>
|
||||
<Grid.ContextFlyout>
|
||||
<Flyout x:Name="BottomCommandFlyout" ShouldConstrainToRootBounds="False">
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style TargetType="FlyoutPresenter">
|
||||
<Setter Property="MinWidth" Value="450" />
|
||||
<Setter Property="MinHeight" Value="100" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<Grid x:Name="BottomCommandFlyoutContainer" VerticalAlignment="Bottom" />
|
||||
</Flyout>
|
||||
</Grid.ContextFlyout>
|
||||
</Grid>
|
||||
|
||||
<TeachingTip
|
||||
@@ -184,29 +408,64 @@
|
||||
<uc:SystemTray />
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="MusicPlayingStates">
|
||||
<VisualState x:Name="MusicPlaying">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsNotEqualStateTrigger Value="{x:Bind ViewModel.DisplayType, Converter={StaticResource EnumToIntConverter}, Mode=OneWay}" To="3" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="DisplayTypeSwitchButton.Visibility" Value="Visible" />
|
||||
<Setter Target="NoMusicPlayingGrid.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NoMusicPlaying">
|
||||
<!--<VisualStateGroup x:Name="VolumeState">
|
||||
<VisualState x:Name="Volume0">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="Equal"
|
||||
Value="{x:Bind ViewModel.DisplayType, Converter={StaticResource EnumToIntConverter}, Mode=OneWay}"
|
||||
To="3" />
|
||||
Value="{x:Bind ViewModel.Volume, Mode=OneWay}"
|
||||
To="0" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="DisplayTypeSwitchButton.Visibility" Value="Collapsed" />
|
||||
<Setter Target="NoMusicPlayingGrid.Opacity" Value="1" />
|
||||
<Setter Target="VolumeLevel0.Opacity" Value="1" />
|
||||
<Setter Target="VolumeLevel1.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel2.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel3.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualState x:Name="Volume1">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="LessThanOrEqual"
|
||||
Value="{x:Bind ViewModel.Volume, Mode=OneWay}"
|
||||
To="32" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="VolumeLevel0.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel1.Opacity" Value="1" />
|
||||
<Setter Target="VolumeLevel2.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel3.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Volume2">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="LessThanOrEqual"
|
||||
Value="{x:Bind ViewModel.Volume, Mode=OneWay}"
|
||||
To="65" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="VolumeLevel0.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel1.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel2.Opacity" Value="1" />
|
||||
<Setter Target="VolumeLevel3.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Volume3">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="LessThanOrEqual"
|
||||
Value="{x:Bind ViewModel.Volume, Mode=OneWay}"
|
||||
To="100" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="VolumeLevel0.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel1.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel2.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel3.Opacity" Value="1" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>-->
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user