Compare commits

..

21 Commits

Author SHA1 Message Date
Zhe Fang
14e8d88cd1 update webhook logo 2025-07-17 10:39:32 -04:00
Zhe Fang
79e726db33 fix closing issue 2025-07-17 10:37:59 -04:00
Zhe Fang
d4f1949833 update doc 2025-07-17 09:02:26 -04:00
Zhe Fang
6135f996bf update readme 2025-07-17 08:49:08 -04:00
Zhe Fang
5b97621af4 fix for dock mode (color sampling issue for bottom position) 2025-07-17 08:48:02 -04:00
Zhe Fang
0f084d04f8 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-17 07:52:14 -04:00
Zhe Fang
bcb114a171 fix #39 2025-07-17 07:52:13 -04:00
Zhe Fang
7bbe2a3045 Update releases-to-discord.yml 2025-07-16 21:37:45 -04:00
Zhe Fang
fab4e8fe22 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-16 21:21:40 -04:00
Zhe Fang
bc4a06577e fix 2025-07-16 21:21:38 -04:00
Zhe Fang
22a790fff0 Update FUNDING.yml 2025-07-16 21:19:39 -04:00
Zhe Fang
5e3d0cb78f Create FUNDING.yml 2025-07-16 21:15:20 -04:00
Zhe Fang
59ee2a6fc3 fix #25 2025-07-16 20:23:01 -04:00
Zhe Fang
fd5e752b43 fix #33 2025-07-16 19:22:47 -04:00
Zhe Fang
df31d03da7 fix #34 fix #28 update faq 2025-07-16 16:22:34 -04:00
Zhe Fang
a7ebba4a62 fix #26 fix #27 fix #29 2025-07-16 07:33:43 -04:00
Zhe Fang
6ca1a84a54 update doc 2025-07-14 20:26:58 -04:00
Zhe Fang
bab5a827f6 update readme 2025-07-14 20:10:17 -04:00
Zhe Fang
3b1e0389aa fix #13 , fix #20 , fix #21 , fix #24 2025-07-13 20:32:10 -04:00
Zhe Fang
ae05a55d31 update doc 2025-07-11 21:55:25 -04:00
Zhe Fang
4b6d417db3 update docs 2025-07-11 21:48:16 -04:00
85 changed files with 2401 additions and 1068 deletions

15
.github/FUNDING.yml vendored Normal file
View 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']

View File

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

View File

@@ -1,6 +1,6 @@
on:
release:
types: [published, edited]
types: [published]
jobs:
github-releases-to-discord:
@@ -14,7 +14,7 @@ jobs:
webhook_url: ${{ secrets.WEBHOOK_URL }}
color: "2105893"
username: "Release Changelog"
avatar_url: "https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png"
avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png"
content: "||@everyone||"
footer_title: "Changelog"
reduce_headings: true

View File

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

View File

@@ -82,7 +82,7 @@
<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="10,0" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
@@ -92,11 +92,21 @@
<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>
<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>

View File

@@ -59,6 +59,7 @@ namespace BetterLyrics.WinUI3
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow == null) return;
lyricsWindow.ViewModel.InitLockHotKey();
lyricsWindow.AutoSelectLyricsMode();
}
@@ -84,6 +85,7 @@ namespace BetterLyrics.WinUI3
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>()
.AddSingleton<ITranslateService, TranslateService>()
// Manager
// ViewModels
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>()

View File

@@ -1,118 +1,126 @@
<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>
</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="Assets\Segoe Fluent Icons.ttf" />
<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="3v.EvtSource" Version="2.0.0" />
<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.CoreAudio" 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.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>
<Content Update="Assets\Segoe Fluent Icons.ttf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</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>

View File

@@ -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)
};
}
}
}

View File

@@ -7,6 +7,5 @@ namespace BetterLyrics.WinUI3.Enums
AlbumArtOnly,
LyricsOnly,
SplitView,
PlaceholderOnly,
}
}

View File

@@ -3,6 +3,7 @@
public enum WindowPixelSampleMode
{
BelowWindow,
AboveWindow,
WindowArea,
WindowEdge,
}

View File

@@ -119,6 +119,13 @@ namespace BetterLyrics.WinUI3.Helper
int sampleY = myRect.Bottom + 1;
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
}
case WindowPixelSampleMode.AboveWindow:
{
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
int sampleHeight = 1;
int sampleY = myRect.Top - 1;
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
}
case WindowPixelSampleMode.WindowArea:
{
int width = myRect.Right - myRect.Left;

View File

@@ -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;
@@ -17,8 +19,10 @@ namespace BetterLyrics.WinUI3.Helper
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private static readonly Dictionary<IntPtr, bool> _originalTopmostStates = [];
private static readonly Dictionary<IntPtr, nint> _oldWndProcs = new();
private static readonly Dictionary<IntPtr, (double X, double Y, double Width, double Height)> _originalWindowBounds = [];
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
private static List<Rectangle> _interactiveRects = new();
private delegate nint WndProcDelegate(nint hWnd, uint msg, nint wParam, nint lParam);
@@ -90,6 +94,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>ʽ
@@ -98,6 +103,16 @@ namespace BetterLyrics.WinUI3.Helper
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle | (int)User32.WindowStylesEx.WS_EX_TRANSPARENT | (int)User32.WindowStylesEx.WS_EX_LAYERED);
//// <20><>װ<EFBFBD>Զ<EFBFBD><D4B6><EFBFBD>WndProc
//if (!_oldWndProcs.ContainsKey(hwnd))
//{
// nint newWndProc = Marshal.GetFunctionPointerForDelegate((WndProcDelegate)((hWnd, msg, wParam, lParam) =>
// CustomWndProc(hWnd, msg, wParam, lParam, hwnd)
// ));
// nint oldWndProc = User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWLP_WNDPROC, newWndProc);
// _oldWndProcs[hwnd] = oldWndProc;
//}
}
else
{
@@ -108,7 +123,51 @@ namespace BetterLyrics.WinUI3.Helper
window.SetWindowStyle(style);
_originalWindowStyles.Remove(hwnd);
}
//// <20>ָ<EFBFBD>ԭWndProc
//if (_oldWndProcs.TryGetValue(hwnd, out var oldWndProc))
//{
// User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWLP_WNDPROC, oldWndProc);
// _oldWndProcs.Remove(hwnd);
//}
}
}
private static nint CustomWndProc(nint hWnd, uint msg, nint wParam, nint lParam, IntPtr hwnd)
{
const int WM_NCHITTEST = 0x84;
const int HTCLIENT = 1;
const int HTTRANSPARENT = -1;
if (msg == WM_NCHITTEST)
{
int x = (short)(lParam.ToInt32() & 0xFFFF);
int y = (short)((lParam.ToInt32() >> 16) & 0xFFFF);
// תΪ<D7AA><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
POINT pt = new() { x = x, y = y };
User32.ScreenToClient(hWnd, ref pt);
foreach (var rect in _interactiveRects)
{
if (rect.Contains(pt.x, pt.y))
return HTCLIENT;
}
return HTTRANSPARENT;
}
// <20><><EFBFBD><EFBFBD>ԭWndProc
if (_oldWndProcs.TryGetValue(hwnd, out var oldWndProc))
{
return User32.CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
}
return User32.DefWindowProc(hWnd, msg, wParam, lParam);
}
public static void SetInteractiveRects(IEnumerable<Rectangle> rects)
{
_interactiveRects = rects.ToList();
}
}
}

View File

@@ -1,4 +1,6 @@
using Microsoft.UI.Xaml;
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -20,11 +22,16 @@ namespace BetterLyrics.WinUI3.Helper
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);
RefreshWorkArea();
window.SetWindowStyle(_originalWindowStyle[hwnd]);
_originalWindowStyle.Remove(hwnd);
@@ -42,11 +49,9 @@ namespace BetterLyrics.WinUI3.Helper
);
_originalPositions.Remove(hwnd);
}
UnregisterAppBar(hwnd);
}
public static void Enable(Window window, int appBarHeight)
public static void Enable(Window window, int appBarHeight, DockPlacement dockPlacement)
{
window.SetIsShownInSwitchers(false);
window.ExtendsContentIntoTitleBar = false;
@@ -68,36 +73,44 @@ namespace BetterLyrics.WinUI3.Helper
}
}
RegisterAppBar(hwnd, appBarHeight);
RegisterAppBar(hwnd, appBarHeight, dockPlacement);
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
int y = dockPlacement == DockPlacement.Top ? 0 : screenHeight - appBarHeight;
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
0,
0,
y,
screenWidth,
appBarHeight,
User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
RefreshWorkArea();
}
private static void RegisterAppBar(IntPtr hwnd, int height)
private static void RegisterAppBar(IntPtr hwnd, int height, DockPlacement dockPlacement)
{
if (_registered.Contains(hwnd)) return;
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
int top = dockPlacement == DockPlacement.Top ? 0 : screenHeight - height;
int bottom = dockPlacement == DockPlacement.Top ? height : screenHeight;
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,
Top = top,
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
Bottom = height,
Bottom = bottom,
},
};
@@ -119,40 +132,57 @@ namespace BetterLyrics.WinUI3.Helper
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
_registered.Remove(hwnd);
}
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight)
private static void RefreshWorkArea()
{
if (!_registered.Contains(hwnd))
return;
User32.SendMessage(HWND.HWND_BROADCAST, User32.WindowMessage.WM_SETTINGCHANGE, IntPtr.Zero, IntPtr.Zero);
}
Shell32.APPBARDATA abd = new()
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight, DockPlacement dockPlacement)
{
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;
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
int top = dockPlacement == DockPlacement.Top ? 0 : screenHeight - newHeight;
int bottom = dockPlacement == DockPlacement.Top ? newHeight : screenHeight;
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 = 0,
Top = top,
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
Bottom = bottom,
},
};
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, 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 ? 0 : screenHeight - newHeight;
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
0,
y,
User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
newHeight,
User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
RefreshWorkArea();
}, TimeSpan.FromMilliseconds(100));
}
}
}

View File

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

View File

@@ -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;
@@ -30,62 +35,33 @@ namespace BetterLyrics.WinUI3.Helper
return stream;
}
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(string text, int width, int height)
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(int width, int height)
{
var device = CanvasDevice.GetSharedDevice();
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 HslToColor(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
// 绘制线性渐变背景
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 System.Numerics.Vector2(0, 0),
EndPoint = new System.Numerics.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[]
@@ -100,9 +76,34 @@ namespace BetterLyrics.WinUI3.Helper
}
return buffer;
}
// HSL转Color
static Windows.UI.Color HslToColor(double h, double s, double l)
{
h = h / 360.0;
double r = l, g = l, b = l;
if (s != 0)
{
double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
double p = 2 * l - q;
r = HueToRgb(p, q, h + 1.0 / 3.0);
g = HueToRgb(p, q, h);
b = HueToRgb(p, q, h - 1.0 / 3.0);
}
return Windows.UI.Color.FromArgb(255, (byte)(r * 255), (byte)(g * 255), (byte)(b * 255));
}
static double HueToRgb(double p, double q, double t)
{
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1.0 / 6.0) return p + (q - p) * 6 * t;
if (t < 1.0 / 2.0) return q;
if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6;
return p;
}
}
public static List<Color> GetAccentColorsFromByte(byte[] bytes)
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes)
{
// 使用 ImageSharp 读取图片
using var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(bytes);
@@ -137,7 +138,6 @@ namespace BetterLyrics.WinUI3.Helper
.ToList();
}
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
//{
// var stream = new InMemoryRandomAccessStream();
@@ -188,5 +188,35 @@ 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).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();
}
}
}

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Windows.Globalization;
namespace BetterLyrics.WinUI3.Services
{
@@ -90,28 +91,38 @@ namespace BetterLyrics.WinUI3.Services
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;
}
}
}

View File

@@ -128,9 +128,8 @@ namespace BetterLyrics.WinUI3.Helper
var line = new LyricsLine
{
StartMs = start,
EndMs = 0, // 稍后统一修正
OriginalText = text,
CharTimings = [],
LyricsChars = [],
};
if (syllables != null && syllables.Count > 0)
{
@@ -139,11 +138,10 @@ namespace BetterLyrics.WinUI3.Helper
{
var (charStart, charText) = syllables[j];
int startIndex = currentIndex;
line.CharTimings.Add(
new CharTiming
line.LyricsChars.Add(
new LyricsChar
{
StartMs = charStart,
EndMs = 0, // Fixed later
Text = charText ?? "",
StartIndex = startIndex,
}
@@ -170,7 +168,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()
@@ -197,16 +197,18 @@ namespace BetterLyrics.WinUI3.Helper
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 +220,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 +249,9 @@ namespace BetterLyrics.WinUI3.Helper
translationLines.Add(new LyricsLine
{
StartMs = pStartMs,
EndMs = 0,
EndMs = pEndMs,
OriginalText = translationText,
CharTimings = translationCharTimings,
LyricsChars = translationCharTimings,
});
}
}
@@ -336,9 +340,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 +356,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;
}
}
@@ -384,34 +380,6 @@ namespace BetterLyrics.WinUI3.Helper
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)
@@ -423,7 +391,7 @@ namespace BetterLyrics.WinUI3.Helper
StartMs = 0,
EndMs = lines[0].StartMs,
OriginalText = "● ● ●",
CharTimings = [],
LyricsChars = [],
}
);
}

View 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
}
}
}
}

View File

@@ -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(() =>
{
VolumeChanged?.Invoke(_masterVolume);
});
return HRESULT.S_OK;
}
}
}
}

View File

@@ -30,8 +30,9 @@ namespace BetterLyrics.WinUI3.Helper
{
while (_activeWindows.Count > 0)
{
var window = _activeWindows[0];
((Window)window).Close();
var window = (Window)_activeWindows[0];
DockModeHelper.Disable(window);
window.Close();
_activeWindows.Remove(window);
}
App.Current.Exit();

View File

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

View File

@@ -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
{
@@ -35,7 +37,23 @@ namespace BetterLyrics.WinUI3.Models
}
else
{
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationData.LyricsLines[i].OriginalText})";
if (translationData.LanguageCode?.Substring(0, 2) == "zh")
{
string tmp = "";
if (LanguageHelper.GetUserTargetLanguageCode() == "zh-Hant")
{
tmp = ChineseConverter.ConvertToTraditionalChinese(translationData.LyricsLines[i].OriginalText);
}
else if (LanguageHelper.GetUserTargetLanguageCode() == "zh-Hans")
{
tmp = ChineseConverter.ConvertToSimplifiedChinese(translationData.LyricsLines[i].OriginalText);
}
line.DisplayedText = $"{line.OriginalText}\n{tmp}";
}
else
{
line.DisplayedText = $"{line.OriginalText}\n{translationData.LyricsLines[i].OriginalText}";
}
}
i++;
}
@@ -74,7 +92,7 @@ namespace BetterLyrics.WinUI3.Models
StartMs = 0,
EndMs = durationMs,
OriginalText = App.ResourceLoader!.GetString("LyricsNotFound"),
CharTimings = [],
LyricsChars = [],
}]);
}
@@ -87,7 +105,7 @@ namespace BetterLyrics.WinUI3.Models
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = "● ● ●",
DisplayedText = "● ● ●",
CharTimings = [],
LyricsChars = [],
},
]);
}

View File

@@ -21,10 +21,10 @@ namespace BetterLyrics.WinUI3.Models
public Vector2 CenterPosition { get; 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; } = "";

View File

@@ -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;
@@ -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,10 +102,9 @@ 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";
// Make a request to the API
HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();

View File

@@ -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);
}
}

View File

@@ -1,6 +1,7 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
@@ -13,5 +14,11 @@ namespace BetterLyrics.WinUI3.Services
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
Task PlayAsync();
Task PauseAsync();
Task PreviousAsync();
Task NextAsync();
}
}

View File

@@ -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
{
@@ -39,6 +37,8 @@ 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; }
@@ -54,6 +54,8 @@ 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; }
@@ -69,6 +71,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 +87,14 @@ 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; }
}
}

View File

@@ -85,7 +85,7 @@ 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);
@@ -105,7 +105,7 @@ namespace BetterLyrics.WinUI3.Services
cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
return cachedLyrics;
return (cachedLyrics, provider.Provider);
}
}
@@ -155,11 +155,11 @@ namespace BetterLyrics.WinUI3.Services
FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
}
return searchedLyrics;
return (searchedLyrics, provider.Provider);
}
}
return null;
return (null, null);
}
private async Task<string?> SearchFile(string title, string artist, LyricsFormat format)
@@ -327,6 +327,17 @@ 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)

View File

@@ -7,6 +7,7 @@ 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 System;
@@ -14,6 +15,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
@@ -29,14 +31,20 @@ namespace BetterLyrics.WinUI3.Services
{
private readonly IAlbumArtSearchService _albumArtSearchService;
private readonly ILogger<PlaybackService> _logger;
private readonly string _lxMusicId = "cn.toside.music.desktop";
private EventSourceReader? _sse = null;
private readonly MediaManager _mediaManager = 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 AlbumArtChangedEventArgs _albumArtChangedEventArgs = new();
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
@@ -115,14 +123,16 @@ namespace BetterLyrics.WinUI3.Services
RecordMediaSourceProviderInfo(mediaSession);
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return;
bool isPlaying = playbackInfo.PlaybackStatus switch
{
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
_ => false,
};
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
{
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(playbackInfo.PlaybackStatus switch
{
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
_ => false,
}));
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(isPlaying));
}
);
}
@@ -140,6 +150,15 @@ namespace BetterLyrics.WinUI3.Services
token.ThrowIfCancellationRequested();
if (id == _lxMusicId)
{
StartSSE();
}
else
{
StopSSE();
}
_cachedSongInfo = new SongInfo
{
Title = mediaProperties.Title,
@@ -232,10 +251,12 @@ namespace BetterLyrics.WinUI3.Services
if (bytes == null)
{
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync($"{_cachedSongInfo!.Artist} - {_cachedSongInfo.Title}", 400, 400);
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(400, 400);
token.ThrowIfCancellationRequested();
}
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
token.ThrowIfCancellationRequested();
@@ -248,13 +269,88 @@ namespace BetterLyrics.WinUI3.Services
_albumArtChangedEventArgs.AlbumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
_dispatcherQueue.TryEnqueue(() =>
{
AlbumArtChangedChanged?.Invoke(this, _albumArtChangedEventArgs);
});
}
private void StartSSE()
{
_sse = new EventSourceReader(new Uri($"{_settingsService.LXMusicServer}/subscribe-player-status?filter=progress")).Start();
_sse.MessageReceived += Sse_MessageReceived;
_sse.Disconnected += Sse_Disconnected;
}
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)
{
var data = JsonSerializer.Deserialize(e.Message, Serialization.SourceGenerationContext.Default.JsonElement);
if (data.TryGetDouble(out double positionSeconds))
{
if (_cachedSongInfo?.SourceAppUserModelId == _lxMusicId)
{
PositionChanged?.Invoke(this, new PositionChangedEventArgs(TimeSpan.FromSeconds(positionSeconds)));
}
}
}
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 void Receive(PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>> message)
{
if (message.Sender is SettingsPageViewModel)

View File

@@ -41,6 +41,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";
@@ -71,9 +72,12 @@ namespace BetterLyrics.WinUI3.Services
private const string MediaSourceProvidersInfoKey = "MediaSourceProvidersInfo";
private const string IsTranslationEnabledKey = "IsTranslationEnabled";
private const string ShowTranslationOnlyKey = "ShowTranslationOnly";
private const string LibreTranslateServerKey = "LibreTranslateServer";
private const string SelectedTargetLanguageIndexKey = "SelectedTargetLanguageIndex";
private const string LXMusicServerKey = "LXMusicServer";
private const string LyricsBackgroundThemeKey = "LyricsBackgroundTheme";
private const string IgnoreFullscreenWindowKey = "IgnoreFullscreenWindow";
private const string PreferredDisplayTypeKey = "PreferredDisplayTypeKey";
@@ -83,6 +87,16 @@ namespace BetterLyrics.WinUI3.Services
public const string TimelineSyncThresholdKey = "TimelineSyncThreshold";
private const string IsLyricsFloatAnimationEnabledKey = "IsLyricsFloatAnimationEnabled";
private const string ResetPositionOffsetOnSongChangedKey = "ResetPositionOffsetOnSongChanged";
private const string PositionOffsetKey = "PositionOffset";
private const string LockHotKeyIndexKey = "LockHotKeyIndex";
private const string DockPlacementKey = "DockPlacement";
private const string LyricsBgFontOpacityKey = "LyricsBgFontOpacity";
private readonly ApplicationDataContainer _localSettings;
public SettingsService()
@@ -152,6 +166,7 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(StandardWindowWidthKey, 1600);
SetDefault(AutoLockOnDesktopModeKey, false);
SetDefault(IsImmersiveModeKey, false);
// App behavior
SetDefault(AutoStartWindowTypeKey, (int)AutoStartWindowType.StandardMode);
// Album art
@@ -185,8 +200,11 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(IsFanLyricsEnabledKey, false);
SetDefault(LibreTranslateServerKey, "");
SetDefault(IsTranslationEnabledKey, false);
SetDefault(SelectedTargetLanguageIndexKey, 6);
SetDefault(IsTranslationEnabledKey, true);
SetDefault(ShowTranslationOnlyKey, false);
SetDefault(SelectedTargetLanguageIndexKey, LanguageHelper.GetDefaultTargetLanguageIndex());
SetDefault(LXMusicServerKey, "");
SetDefault(LyricsFontStrokeWidthKey, 3);
SetDefault(IgnoreFullscreenWindowKey, false);
@@ -195,6 +213,38 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(LyricsScrollEasingTypeKey, (int)EasingType.EaseInOutQuad);
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%
}
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 +259,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);
@@ -511,6 +561,12 @@ namespace BetterLyrics.WinUI3.Services
set => SetValue(SelectedTargetLanguageIndexKey, value);
}
public string LXMusicServer
{
get => GetValue<string>(LXMusicServerKey)!;
set => SetValue(LXMusicServerKey, value);
}
public bool IgnoreFullscreenWindow
{
get => GetValue<bool>(IgnoreFullscreenWindowKey);
@@ -523,6 +579,30 @@ 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 int PositionOffset
{
get => GetValue<int>(PositionOffsetKey);
set => SetValue(PositionOffsetKey, value);
}
public bool IsImmersiveMode
{
get => GetValue<bool>(IsImmersiveModeKey);
set => SetValue(IsImmersiveModeKey, value);
}
private T? GetValue<T>(string key)
{
if (_localSettings.Values.TryGetValue(key, out object? value))

View File

@@ -18,7 +18,7 @@ namespace BetterLyrics.WinUI3.Services
{
private readonly HttpClient _httpClient;
public TranslateService(ISettingsService settingsService) :base(settingsService)
public TranslateService(ISettingsService settingsService) : base(settingsService)
{
_httpClient = new HttpClient();
}
@@ -76,18 +76,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
}
}
}
}

View File

@@ -288,9 +288,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 +301,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 +418,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>
@@ -528,8 +532,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>
@@ -606,10 +610,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 +653,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 +739,37 @@
<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>
</root>

View File

@@ -288,9 +288,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 +301,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 +418,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>
@@ -528,8 +532,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>
@@ -606,10 +610,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 +653,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 +739,37 @@
<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>
</root>

View File

@@ -288,9 +288,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 +301,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 +418,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>
@@ -528,8 +532,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>
@@ -606,10 +610,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 +653,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 +739,37 @@
<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>
</root>

View File

@@ -288,9 +288,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 +301,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 +418,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>
@@ -528,8 +532,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>
@@ -606,10 +610,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 +653,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 +739,37 @@
<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>
</root>

View File

@@ -288,9 +288,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 +301,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 +335,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 +418,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>
@@ -528,8 +532,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>
@@ -606,10 +610,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 +653,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 +739,37 @@
<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>
</root>

View File

@@ -10,7 +10,6 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using System.Diagnostics;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.ViewModels
@@ -19,28 +18,60 @@ namespace BetterLyrics.WinUI3.ViewModels
{
private readonly IPlaybackService _playbackService;
private LyricsDisplayType? _preferredDisplayTypeBeforeSwitchToNonStandardMode;
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;
OnIsImmersiveModeChanged(IsImmersiveMode);
//Volume = SystemVolumeHelper.GetMasterVolume();
//SystemVolumeHelper.VolumeChanged += SystemVolumeHelper_VolumeChanged;
_playbackService = playbackService;
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
}
//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);
if (ResetPositionOffsetOnSongChanged)
{
PositionOffset = 0;
}
}
//[ObservableProperty]
//public partial int Volume { get; set; }
[ObservableProperty]
public partial bool IsImmersiveMode { get; set; }
[ObservableProperty]
public partial float BottomCommandGridOpacity { get; set; }
[ObservableProperty]
public partial Thickness BottomCommandGridMargin { get; set; } = new Thickness(12);
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsDisplayType DisplayType { get; set; } = LyricsDisplayType.PlaceholderOnly;
public partial LyricsDisplayType DisplayType { get; set; }
[ObservableProperty]
public partial bool IsFirstRun { get; set; }
@@ -48,29 +79,27 @@ 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)
{
_settingsService.PreferredDisplayType = value;
}
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
[ObservableProperty]
public partial bool IsSongPlaying { get; set; }
public void Receive(PropertyChangedMessage<bool> message)
{
@@ -78,13 +107,30 @@ namespace BetterLyrics.WinUI3.ViewModels
{
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
{
SetNonStandardModePreferredDisplayType(message.NewValue);
TrySwitchToPreferredDisplayType(SongInfo);
if (message.NewValue)
{
DisplayType = LyricsDisplayType.LyricsOnly;
}
else
{
DisplayType = _settingsService.DisplayType;
}
BottomCommandGridMargin = message.NewValue ? new Thickness(0) : new Thickness(12);
}
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
{
SetNonStandardModePreferredDisplayType(message.NewValue);
TrySwitchToPreferredDisplayType(SongInfo);
if (message.NewValue)
{
DisplayType = LyricsDisplayType.LyricsOnly;
}
else
{
DisplayType = _settingsService.DisplayType;
}
}
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsImmersiveMode))
{
IsImmersiveMode = message.NewValue;
}
}
}
@@ -95,38 +141,28 @@ namespace BetterLyrics.WinUI3.ViewModels
WindowHelper.OpenOrShowWindow<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 +170,37 @@ 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;
}
else
{
BottomCommandGridOpacity = 1f;
}
}
partial void OnShowTranslationOnlyChanged(bool value)
{
_settingsService.ShowTranslationOnly = value;
}
//partial void OnVolumeChanged(int value)
//{
// SystemVolumeHelper.SetMasterVolume(value);
//}
}
}

View File

@@ -43,6 +43,7 @@ namespace BetterLyrics.WinUI3.ViewModels
_isFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
_lyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
_isTranslationEnabled = _settingsService.IsTranslationEnabled;
_showTranslationOnly = _settingsService.ShowTranslationOnly;
_targetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
@@ -50,6 +51,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;

View File

@@ -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,12 @@ 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.Linq;
using System.Numerics;
using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.UI;
using Windows.UI.Text;
namespace BetterLyrics.WinUI3.ViewModels
{
@@ -51,7 +45,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else if (_isDesktopMode)
{
DrawImmersiveBackground(control, combinedDs, 12f);
DrawImmersiveBackground(control, combinedDs, 0f);
}
else
{
@@ -74,7 +68,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (currentPlayingLine != null)
{
GetLinePlayingProgress(
currentPlayingLine,
_playingLineIndex,
out int charStartIndex,
out int charLength,
out float charProgress
@@ -202,7 +196,7 @@ namespace BetterLyrics.WinUI3.ViewModels
BlurAmount = _albumArtBgBlurAmount,
Source = overlappedCovers,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Quality,
Optimization = EffectOptimization.Speed,
},
};
ds.DrawImage(coverOverlayEffect);
@@ -286,28 +280,17 @@ 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;
}
if (textLayout == null) continue;
var position = new Vector2(line.Position.X, line.Position.Y);
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;
@@ -329,11 +312,14 @@ namespace BetterLyrics.WinUI3.ViewModels
break;
}
float xOffset = _lyricsXTransition.Value;
float yOffset = _canvasYScrollTransition.Value + _canvasHeight / 2;
// 组合变换:缩放 -> 旋转 -> 平移
ds.Transform =
Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
* Matrix3x2.CreateRotation(line.AngleTransition.Value, currentPlayingLine.Position)
* Matrix3x2.CreateTranslation(_lyricsXTransition.Value, _canvasYScrollTransition.Value + _canvasHeight / 2);
* Matrix3x2.CreateTranslation(xOffset, yOffset);
// Create the background lyrics line with stroke and fill
using var bgLyrics = new CanvasCommandList(control.Device);
@@ -344,22 +330,23 @@ namespace BetterLyrics.WinUI3.ViewModels
using var fgLyricsDs = fgLyrics.CreateDrawingSession();
// 创建文字几何体
using (var textGeometry = CanvasGeometry.CreateText(textLayout))
using var textGeometry = CanvasGeometry.CreateText(textLayout);
if (_isDesktopMode)
{
if (_isDesktopMode)
{
bgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 背景描边
fgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 前景描边
}
bgLyricsDs.FillGeometry(textGeometry, position, _bgFontColor); // 背景填充
fgLyricsDs.FillGeometry(textGeometry, position, _fgFontColor); // 前景填充
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(
combinedDs.DrawImage(
new GaussianBlurEffect
{
Source = new OpacityEffect { Source = bgLyrics, Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
@@ -381,7 +368,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (i == _playingLineIndex)
{
GetLinePlayingProgress(
line,
i,
out int charStartIndex,
out int charLength,
out float charProgress
@@ -402,7 +389,7 @@ namespace BetterLyrics.WinUI3.ViewModels
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
maskDs.FillRectangle(rect, Colors.Black);
maskDs.FillRectangle(rect, Color.FromArgb(255, 128, 128, 128));
}
}
@@ -447,7 +434,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 +443,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, string.Join("", line.LyricsChars.Select(x => x.Text)).Length);
if (regions.Length > 0)
{
height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top;
@@ -473,12 +460,11 @@ 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
@@ -496,21 +482,49 @@ namespace BetterLyrics.WinUI3.ViewModels
Optimization = EffectOptimization.Quality,
}
: new CanvasCommandList(control.Device),
Foreground = new AlphaMaskEffect
Foreground = new AlphaMaskEffect
{
Source = fgLyrics,
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 => fgLyrics,
_ => 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
@@ -548,7 +562,7 @@ namespace BetterLyrics.WinUI3.ViewModels
.Select(stops => new CanvasGradientStop
{
Position = stops.position,
Color = Color.FromArgb((byte)(stops.opacity * 255), 0, 0, 0),
Color = Color.FromArgb((byte)(stops.opacity * 255), 128, 128, 128),
})
.ToArray()
)

View File

@@ -78,6 +78,10 @@ namespace BetterLyrics.WinUI3.ViewModels
_isFanLyricsEnabled = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLyricsFloatAnimationEnabled))
{
_isLyricsFloatAnimationEnabled = message.NewValue;
}
}
else if (message.Sender is LyricsWindowViewModel)
{
@@ -109,6 +113,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();
}
}
}
@@ -204,6 +213,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)
{

View File

@@ -19,7 +19,7 @@ namespace BetterLyrics.WinUI3.ViewModels
private bool _isCanvasHeightChanged = false;
private bool _isDisplayTypeChanged = false;
private bool _isPlayingLineChanged = false;
private bool _isVisibleLinesBoundaryChanged = false;
@@ -92,8 +92,6 @@ namespace BetterLyrics.WinUI3.ViewModels
_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:
break;
default:
break;
}
@@ -297,7 +295,8 @@ namespace BetterLyrics.WinUI3.ViewModels
if (_adaptiveGrayedFontColor == _lightColor)
{
grayedEnvironmentalColor = _darkColor;
} else if (_adaptiveGrayedFontColor == _darkColor)
}
else if (_adaptiveGrayedFontColor == _darkColor)
{
grayedEnvironmentalColor = _lightColor;
}

View File

@@ -9,6 +9,7 @@ using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Helpers.General;
using Lyricify.Lyrics.Providers;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
@@ -33,6 +34,8 @@ namespace BetterLyrics.WinUI3.ViewModels
private TimeSpan _totalTime = TimeSpan.Zero;
private TimeSpan _positionOffset = TimeSpan.Zero;
private int _songDurationMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
private SoftwareBitmap? _lastAlbumArtSwBitmap = null;
private SoftwareBitmap? _albumArtSwBitmap = null;
@@ -55,7 +58,7 @@ namespace BetterLyrics.WinUI3.ViewModels
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;
@@ -131,14 +134,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 int _timelineSyncThreshold;
@@ -169,8 +175,8 @@ namespace BetterLyrics.WinUI3.ViewModels
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 int _albumArtBgBlurAmount;
private int _albumArtBgOpacity;
@@ -189,29 +195,38 @@ namespace BetterLyrics.WinUI3.ViewModels
{
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);
var totalMs = _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds;
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;
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. 还没到本句
@@ -221,27 +236,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 +274,20 @@ 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;
// 没有逐字时间轴,直接线性,模拟逐字高亮
float lineProgress = (now - line.StartMs) / (lineEndMs - line.StartMs);
charStartIndex = (int)(lineProgress * line.OriginalText.Length);
charProgress = lineProgress - (int)(lineProgress);
charLength = 1;
}
}
@@ -309,6 +339,8 @@ namespace BetterLyrics.WinUI3.ViewModels
_lastSongArtist = _songArtist;
_songArtist = SongInfo?.Artist;
_songDurationMs = (int)(SongInfo?.DurationMs ?? TimeSpan.FromMinutes(99).TotalMilliseconds);
_songInfoOpacityTransition.Reset(0f);
_songInfoOpacityTransition.StartTransition(1f);
@@ -342,6 +374,9 @@ namespace BetterLyrics.WinUI3.ViewModels
private void UpdateTranslations()
{
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
_isLayoutChanged = true;
IsTranslating = true;
if (_isTranslationEnabled)
{
@@ -354,7 +389,8 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
_langIndex = 0;
IsTranslating = false;
_isLayoutChanged = true;
}
@@ -378,14 +414,36 @@ 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]);
_langIndex = 0;
}
}
else
{
var translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
token.ThrowIfCancellationRequested();
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
try
{
var translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
token.ThrowIfCancellationRequested();
if (_showTranslationOnly)
{
// TODO
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
_langIndex = 0;
}
else
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
_langIndex = 0;
}
}
catch (Exception) { }
}
}
}
@@ -398,10 +456,11 @@ namespace BetterLyrics.WinUI3.ViewModels
_isLayoutChanged = true;
string? lyricsRaw = null;
LyricsSearchProvider? provider = null;
if (SongInfo != null)
{
lyricsRaw = await _lyrcsSearchService.SearchAsync(
(lyricsRaw, provider) = await _lyrcsSearchService.SearchAsync(
SongInfo.Title,
SongInfo.Artist,
SongInfo.Album ?? "",
@@ -410,13 +469,14 @@ namespace BetterLyrics.WinUI3.ViewModels
);
_logger.LogInformation("Lyrics search result: {LyricsRaw}", lyricsRaw ?? "null");
token.ThrowIfCancellationRequested();
_lyricsDataArr = new LyricsParser().Parse(lyricsRaw, (int?)SongInfo?.DurationMs);
FillTranslationFromCache(provider);
}
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 +485,43 @@ namespace BetterLyrics.WinUI3.ViewModels
UpdateTranslations();
}
private void FillTranslationFromCache(LyricsSearchProvider? provider)
{
string? translationRaw = null;
switch (provider)
{
case LyricsSearchProvider.QQ:
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
break;
case LyricsSearchProvider.Kugou:
break;
case LyricsSearchProvider.Netease:
break;
case LyricsSearchProvider.LrcLib:
break;
case LyricsSearchProvider.AmllTtmlDb:
break;
case LyricsSearchProvider.LocalMusicFile:
break;
case LyricsSearchProvider.LocalLrcFile:
break;
case LyricsSearchProvider.LocalEslrcFile:
break;
case LyricsSearchProvider.LocalTtmlFile:
break;
default:
break;
}
if (translationRaw != null)
{
var translationData = new LyricsParser().Parse(translationRaw, (int?)SongInfo?.DurationMs);
foreach (var data in translationData)
{
data.LyricsLines = data.LyricsLines.Where(line => !string.IsNullOrWhiteSpace(line.OriginalText)).ToList();
}
_lyricsDataArr = _lyricsDataArr.Concat(translationData).ToList();
}
}
}
}

View File

@@ -13,8 +13,11 @@ using CommunityToolkit.Mvvm.Messaging.Messages;
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 +27,24 @@ namespace BetterLyrics.WinUI3
public partial class LyricsWindowViewModel
: BaseWindowViewModel,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<bool>>
IRecipient<PropertyChangedMessage<DockPlacement>>
{
private ForegroundWindowWatcher? _windowWatcher = null;
private bool _ignoreFullscreenWindow = false;
private int _dockWindowMinHeight = 96;
private DockPlacement _dockPlacement;
private int _lyricsFontSize;
public LyricsWindowViewModel(ISettingsService settingsService) : base(settingsService)
{
_ignoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
IsImmersiveMode = _settingsService.IsImmersiveMode;
_dockPlacement = _settingsService.DockPlacement;
_lyricsFontSize = _settingsService.LyricsFontSize;
OnIsImmersiveModeChanged(_settingsService.IsImmersiveMode);
}
[ObservableProperty]
@@ -51,6 +63,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 +80,29 @@ 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;
DockModeHelper.UpdateAppBarHeight(WindowNative.GetWindowHandle(window), Math.Max(_dockWindowMinHeight, _lyricsFontSize * 4), _dockPlacement);
}
partial void OnIsImmersiveModeChanged(bool value)
{
if (value)
{
TopCommandGridOpacity = 0f;
}
else
{
TopCommandGridOpacity = 1f;
}
}
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is SystemTrayViewModel)
@@ -99,18 +141,41 @@ namespace BetterLyrics.WinUI3
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
{
if (IsDockMode)
_lyricsFontSize = 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;
@@ -124,11 +189,11 @@ namespace BetterLyrics.WinUI3
{
presenter.IsAlwaysOnTop = true;
}
UpdateAccentColor(hwnd, mode);
UpdateAccentColor(hwnd);
}
);
_windowWatcher.Start();
UpdateAccentColor(hwnd, mode);
UpdateAccentColor(hwnd);
}
private void StopWatchWindowColorChange()
@@ -137,19 +202,35 @@ namespace BetterLyrics.WinUI3
_windowWatcher = null;
}
public void UpdateAccentColor(nint hwnd, WindowPixelSampleMode mode)
public void UpdateAccentColor(nint hwnd)
{
WindowPixelSampleMode mode = IsDesktopMode ? WindowPixelSampleMode.WindowEdge : _dockPlacement.ToWindowPixelSampleMode();
ActivatedWindowAccentColor = Helper.ColorHelper.GetAccentColor(hwnd, 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;
}
}
[RelayCommand]
@@ -164,7 +245,7 @@ namespace BetterLyrics.WinUI3
if (IsDesktopMode)
{
DesktopModeHelper.Enable(window);
StartWatchWindowColorChange(WindowPixelSampleMode.WindowEdge);
StartWatchWindowColorChange();
}
else
{
@@ -183,13 +264,31 @@ namespace BetterLyrics.WinUI3
IsDockMode = !IsDockMode;
if (IsDockMode)
{
DockModeHelper.Enable(window, _settingsService.LyricsFontSize * 4);
StartWatchWindowColorChange(WindowPixelSampleMode.BelowWindow);
DockModeHelper.Enable(window, Math.Max(_dockWindowMinHeight, _lyricsFontSize * 4), _dockPlacement);
StartWatchWindowColorChange();
}
else
{
DockModeHelper.Disable(window);
}
}
[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();
}
}
}
}
}

View File

@@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using ShadowViewer.Controls;
using System;
using System.Collections.Generic;
@@ -89,6 +90,14 @@ namespace BetterLyrics.WinUI3.ViewModels
LyricsScrollDuration = _settingsService.LyricsScrollDuration;
TimelineSyncThreshold = _settingsService.TimelineSyncThreshold;
IsLyricsFloatAnimationEnabled = _settingsService.IsLyricsFloatAnimationEnabled;
ResetPositionOffsetOnSongChanged = _settingsService.ResetPositionOffsetOnSongChanged;
LockHotKeyIndex = _settingsService.LockHotKeyIndex;
LXMusicServer = _settingsService.LXMusicServer;
DockPlacement = _settingsService.DockPlacement;
LyricsBgFontOpacity = _settingsService.LyricsBgFontOpacity;
_playbackService.MediaSourceProvidersInfoChanged += PlaybackService_SessionIdsChanged;
Task.Run(async () =>
@@ -102,6 +111,14 @@ namespace BetterLyrics.WinUI3.ViewModels
MediaSourceProvidersInfo = [.. e.MediaSourceProviersInfo];
}
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial DockPlacement DockPlacement { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int LockHotKeyIndex { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ElementTheme LyricsBackgroundTheme { get; set; }
@@ -185,6 +202,10 @@ namespace BetterLyrics.WinUI3.ViewModels
[NotifyPropertyChangedRecipients]
public partial Color LyricsCustomStrokeFontColor { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int LyricsBgFontOpacity { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontColorType LyricsBgFontColorType { get; set; }
@@ -224,6 +245,14 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
public partial object NavViewSelectedItemTag { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsLyricsFloatAnimationEnabled { get; set; }
public string Version { get; set; } = MetadataHelper.AppVersion;
public string BuildDate { get; set; } = string.Empty;
@@ -258,6 +287,13 @@ namespace BetterLyrics.WinUI3.ViewModels
[NotifyPropertyChangedRecipients]
public partial int TimelineSyncThreshold { get; set; }
[ObservableProperty]
public partial bool IsLXMusicServerTesting { get; set; } = false;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial string LXMusicServer { get; set; }
public void OnLyricsSearchProvidersReordered()
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
@@ -399,7 +435,7 @@ namespace BetterLyrics.WinUI3.ViewModels
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, null);
_dispatcherQueue.TryEnqueue(() =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
IsLibreTranslateServerTesting = false;
});
}
@@ -407,13 +443,30 @@ namespace BetterLyrics.WinUI3.ViewModels
{
_dispatcherQueue.TryEnqueue(() =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestFailedInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestFailedInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
IsLibreTranslateServerTesting = false;
});
}
});
}
[RelayCommand]
private void LXMusicServerTest()
{
IsLXMusicServerTesting = true;
Task.Run(async () =>
{
bool testResult = await NetHelper.CheckConnectivity($"{LXMusicServer}/status");
_dispatcherQueue.TryEnqueue(() =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(
App.ResourceLoader!.GetString($"SettingsPageServerTest{(testResult ? "Success" : "Failed")}Info"),
testResult ? InfoBarSeverity.Success : InfoBarSeverity.Error);
IsLXMusicServerTesting = false;
});
});
}
public async Task<bool> ToggleAutoStartupAsync(bool target)
{
StartupTask startupTask = await StartupTask.GetAsync(_autoStartupTaskId);
@@ -446,6 +499,10 @@ namespace BetterLyrics.WinUI3.ViewModels
return result;
}
partial void OnDockPlacementChanged(DockPlacement value)
{
_settingsService.DockPlacement = value;
}
partial void OnLyricsScrollEasingTypeChanged(EasingType value)
{
_settingsService.LyricsScrollEasingType = value;
@@ -474,6 +531,10 @@ namespace BetterLyrics.WinUI3.ViewModels
{
_settingsService.LibreTranslateServer = value;
}
partial void OnLXMusicServerChanged(string value)
{
_settingsService.LXMusicServer = value;
}
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
{
_settingsService.AutoStartWindowType = value;
@@ -597,6 +658,17 @@ namespace BetterLyrics.WinUI3.ViewModels
{
_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;
}
}
}

View File

@@ -23,7 +23,7 @@
<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"
@@ -32,145 +32,297 @@
<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="{x:Bind ViewModel.BottomCommandGridMargin, Mode=OneWay}"
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>
<StackPanel
x:Name="BottomCenterCommandStackPanel"
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="6">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
</StackPanel>
<StackPanel
x:Name="BottomRightCommandStackPanel"
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="6">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<!-- Position offset -->
<Button Style="{StaticResource GhostButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xECE7;"
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>
<Grid Padding="3" HorizontalAlignment="Left">
<StackPanel
x:Name="BottomLeftCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<!--<Button Style="{StaticResource GhostButtonStyle}">
<Grid>
-->
<!-- Volumn: 0 -->
<!--
<FontIcon
x:Name="VolumeLevel0"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE74F;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
-->
<!-- Volumn: 1-32 -->
<!--
<FontIcon
x:Name="VolumeLevel1"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE993;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
-->
<!-- Volumn: 33-65 -->
<!--
<FontIcon
x:Name="VolumeLevel2"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE994;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
-->
<!-- Volumn: 66-100 -->
<!--
<FontIcon
x:Name="VolumeLevel3"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE995;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
</Grid>
<Button.Flyout>
<Flyout>
<StackPanel>
<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
x:Uid="MainPagePositionOffsetSlider"
Maximum="5000"
Minimum="-5000"
Width="150"
Maximum="100"
Minimum="0"
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=&#xE777;}"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Style="{StaticResource GhostButtonStyle}" />
</RelativePanel>
<TextBlock x:Uid="LyricsPagePositionOffsetHint" Opacity="0.5" />
StepFrequency="1"
TickFrequency="1"
TickPlacement="None"
Value="{x:Bind ViewModel.Volume, Mode=TwoWay}" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</Button>-->
</StackPanel>
</Grid>
<!-- Translation -->
<ToggleButton
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8C1;}"
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"
Style="{StaticResource CardGridStyle}">
<StackPanel
x:Name="BottomCenterCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<Button
Command="{x:Bind ViewModel.PreviousSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE622;}"
Style="{StaticResource GhostButtonStyle}" />
<Button
Command="{x:Bind ViewModel.PauseSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF8AE;}"
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=&#xF5B0;}"
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=&#xE623;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</Grid>
<!-- Display type -->
<Button
x:Name="DisplayTypeSwitchButton"
x:Uid="MainPageDisplayTypeSwitcher"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF246;}"
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"
Style="{StaticResource CardGridStyle}">
<StackPanel
x:Name="BottomRightCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<!-- Settings -->
<Button
x:Name="SettingsButton"
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE713;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="SettingsToolTip" x:Uid="LyricsPageSettingsButtonToolTip" />
</ToolTipService.ToolTip>
</Button>
<!-- Position offset -->
<Button Click="TimelineOffsetButton_Click" Style="{StaticResource GhostButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xECE7;"
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.DataContext>
<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=&#xE777;}"
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.DataContext>
</Button>
</StackPanel>
<!-- Translation -->
<Button
Click="TranslationButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8BD;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="TranslationToolTip" x:Uid="LyricsPageTranslationButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<Flyout x:Name="TranslationFlyout" ShouldConstrainToRootBounds="False">
<StackPanel>
<ToggleSwitch x:Uid="LyricsPageTranslationEnabled" IsOn="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}" />
<ToggleSwitch
x:Uid="LyricsPageTranslationOnly"
IsEnabled="{x:Bind ViewModel.IsTranslationEnabled, Mode=OneWay}"
IsOn="{x:Bind ViewModel.ShowTranslationOnly, Mode=TwoWay}" />
</StackPanel>
</Flyout>
</Button.DataContext>
</Button>
<!-- Display type -->
<Button
x:Name="DisplayTypeSwitchButton"
x:Uid="MainPageDisplayTypeSwitcher"
Click="DisplayTypeSwitchButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF246;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="PresentationTypeToolTip" x:Uid="LyricsPageDisplayTypeButtonToolTip" />
</ToolTipService.ToolTip>
<Button.DataContext>
<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.DataContext>
</Button>
<!-- Settings -->
<Button
x:Name="SettingsButton"
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF8B0;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="SettingsToolTip" x:Uid="LyricsPageSettingsButtonToolTip" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
</Grid>
@@ -184,29 +336,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>

View File

@@ -1,6 +1,7 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
@@ -12,6 +13,8 @@ namespace BetterLyrics.WinUI3.Views
{
public sealed partial class LyricsPage : Page
{
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
public LyricsPage()
{
this.InitializeComponent();
@@ -28,17 +31,20 @@ namespace BetterLyrics.WinUI3.Views
private void LyricsOnlyRadioButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.LyricsOnly;
ViewModel.DisplayType = LyricsDisplayType.LyricsOnly;
_settingsService.DisplayType = ViewModel.DisplayType;
}
private void AlbumArtOnlyRadioButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.AlbumArtOnly;
ViewModel.DisplayType = LyricsDisplayType.AlbumArtOnly;
_settingsService.DisplayType = ViewModel.DisplayType;
}
private void SplitViewRadioButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.SplitView;
ViewModel.DisplayType = LyricsDisplayType.SplitView;
_settingsService.DisplayType = ViewModel.DisplayType;
}
private void PositionOffsetResetButton_Click(object sender, RoutedEventArgs e)
@@ -48,12 +54,33 @@ namespace BetterLyrics.WinUI3.Views
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
BottomCommandGrid.Opacity = 1;
if (ViewModel.IsImmersiveMode)
{
ViewModel.BottomCommandGridOpacity = 1f;
}
}
private void BottomCommandGrid_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
BottomCommandGrid.Opacity = 0;
if (ViewModel.IsImmersiveMode)
{
ViewModel.BottomCommandGridOpacity = 0f;
}
}
private void DisplayTypeSwitchButton_Click(object sender, RoutedEventArgs e)
{
DisplayTypeSwitchFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void TimelineOffsetButton_Click(object sender, RoutedEventArgs e)
{
TimelineOffsetFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void TranslationButton_Click(object sender, RoutedEventArgs e)
{
TranslationFlyout.ShowAt(BottomRightCommandStackPanel);
}
}
}

View File

@@ -17,137 +17,172 @@
x:Name="RootGrid"
PointerEntered="RootGrid_PointerEntered"
PointerExited="RootGrid_PointerExited"
RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}"
SizeChanged="RootGrid_SizeChanged">
<local:LyricsPage />
<!-- Top command -->
<Grid
x:Name="TopCommandGrid"
Margin="6"
Margin="12"
VerticalAlignment="Top"
Opacity="0"
Opacity="{x:Bind ViewModel.TopCommandGridOpacity, Mode=OneWay}"
PointerEntered="TopCommandGrid_PointerEntered"
PointerExited="TopCommandGrid_PointerExited">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<StackPanel
x:Name="TopLeftCommandStackPanel"
Margin="12"
<Grid
Padding="3"
HorizontalAlignment="Left"
Orientation="Horizontal"
Spacing="6">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
</StackPanel>
Style="{StaticResource CardGridStyle}">
<StackPanel
x:Name="TopLeftCommandStackPanel"
HorizontalAlignment="Left"
Orientation="Horizontal"
Spacing="3">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<ToggleButton
x:Name="ImmersiveButton"
Command="{x:Bind ViewModel.ImmersiveToggleButtonEnabledChangedCommand}"
IsChecked="{x:Bind ViewModel.IsImmersiveMode, Mode=TwoWay}"
Style="{StaticResource TitleBarToggleButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize}"
Glyph="&#xED1A;" />
</ToggleButton>
</StackPanel>
</Grid>
<StackPanel
x:Name="TopRightCommandStackPanel"
<Grid
Padding="3"
HorizontalAlignment="Right"
Orientation="Horizontal">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
Style="{StaticResource CardGridStyle}">
<StackPanel
x:Name="TopRightCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<!-- Look -->
<Button
x:Name="ClickThroughButton"
Command="{x:Bind ViewModel.LockWindowCommand}"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE72E;" />
<ToolTipService.ToolTip>
<ToolTip x:Name="LockToolTip" x:Uid="HostWindowLockToolTip" />
</ToolTipService.ToolTip>
</Button>
<!-- More -->
<Button x:Name="MoreButton" Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE712;" />
<ToolTipService.ToolTip>
<ToolTip x:Name="MoreButtonToolTip" x:Uid="HostWindowMoreButtonToolTip" />
</ToolTipService.ToolTip>
<Button.Flyout>
<MenuFlyout>
<ToggleMenuFlyoutItem
x:Name="AOTFlyoutItem"
x:Uid="BaseWindowAOTFlyoutItem"
Click="AOTFlyoutItem_Click" />
<ToggleMenuFlyoutItem
x:Name="FullScreenFlyoutItem"
x:Uid="BaseWindowFullScreenFlyoutItem"
Click="FullScreenFlyoutItem_Click" />
<ToggleMenuFlyoutItem
x:Name="DockFlyoutItem"
x:Uid="HostWindowDockFlyoutItem"
Command="{x:Bind ViewModel.ToggleDockModeCommand}"
IsChecked="{x:Bind ViewModel.IsDockMode, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="DesktopFlyoutItem"
x:Uid="HostWindowDesktopFlyoutItem"
Command="{x:Bind ViewModel.ToggleDesktopModeCommand}"
IsChecked="{x:Bind ViewModel.IsDesktopMode, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="MiniFlyoutItem"
x:Uid="BaseWindowMiniFlyoutItem"
Click="MiniFlyoutItem_Click" />
<MenuFlyoutItem
x:Name="SettingsFlyoutItem"
x:Uid="HostWindowSettingsFlyoutItem"
Click="SettingsMenuFlyoutItem_Click" />
</MenuFlyout>
</Button.Flyout>
</Button>
<!-- Look -->
<Button
x:Name="ClickThroughButton"
Command="{x:Bind ViewModel.ToggleLockWindowCommand}"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE72E;" />
<ToolTipService.ToolTip>
<ToolTip x:Name="LockToolTip">
<ToolTip.Content>
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="HostWindowLockToolTip" />
<TextBlock
Margin="6,0"
VerticalAlignment="Center"
Opacity="0.7"
Text="Ctrl + Alt + " />
<TextBlock
VerticalAlignment="Center"
Opacity="0.7"
Text="{x:Bind ViewModel.LockHotKey, Mode=OneWay}" />
</StackPanel>
</ToolTip.Content>
</ToolTip>
</ToolTipService.ToolTip>
</Button>
<!-- More -->
<Button x:Name="MoreButton" Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE700;" />
<ToolTipService.ToolTip>
<ToolTip x:Name="MoreButtonToolTip" x:Uid="HostWindowMoreButtonToolTip" />
</ToolTipService.ToolTip>
<Button.Flyout>
<MenuFlyout>
<ToggleMenuFlyoutItem
x:Name="AOTFlyoutItem"
x:Uid="BaseWindowAOTFlyoutItem"
Click="AOTFlyoutItem_Click" />
<ToggleMenuFlyoutItem
x:Name="FullScreenFlyoutItem"
x:Uid="BaseWindowFullScreenFlyoutItem"
Click="FullScreenFlyoutItem_Click" />
<ToggleMenuFlyoutItem
x:Name="DockFlyoutItem"
x:Uid="HostWindowDockFlyoutItem"
Command="{x:Bind ViewModel.ToggleDockModeCommand}"
IsChecked="{x:Bind ViewModel.IsDockMode, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="DesktopFlyoutItem"
x:Uid="HostWindowDesktopFlyoutItem"
Command="{x:Bind ViewModel.ToggleDesktopModeCommand}"
IsChecked="{x:Bind ViewModel.IsDesktopMode, Mode=OneWay}" />
<ToggleMenuFlyoutItem
x:Name="MiniFlyoutItem"
x:Uid="BaseWindowMiniFlyoutItem"
Click="MiniFlyoutItem_Click" />
<MenuFlyoutItem
x:Name="SettingsFlyoutItem"
x:Uid="HostWindowSettingsFlyoutItem"
Click="SettingsMenuFlyoutItem_Click" />
</MenuFlyout>
</Button.Flyout>
</Button>
<!-- Window Minimise -->
<Button
x:Name="MinimiseButton"
Click="MinimiseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE738;" />
</Button>
<!-- Window Maximise -->
<Button
x:Name="MaximiseButton"
Click="MaximiseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE71A;" />
</Button>
<!-- Window Restore -->
<Button
x:Name="RestoreButton"
Click="RestoreButton_Click"
Style="{StaticResource TitleBarButtonStyle}"
Visibility="Collapsed">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE744;" />
</Button>
<!-- Window Close -->
<Button
x:Name="CloseButton"
Click="CloseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE711;" />
</Button>
</StackPanel>
<!-- Window Minimise -->
<Button
x:Name="MinimiseButton"
Click="MinimiseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE654;" />
</Button>
<!-- Window Maximise -->
<Button
x:Name="MaximiseButton"
Click="MaximiseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE655;" />
</Button>
<!-- Window Restore -->
<Button
x:Name="RestoreButton"
Click="RestoreButton_Click"
Style="{StaticResource TitleBarButtonStyle}"
Visibility="Collapsed">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE656;" />
</Button>
<!-- Window Close -->
<Button
x:Name="CloseButton"
Click="CloseButton_Click"
Style="{StaticResource TitleBarButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE653;" />
</Button>
</StackPanel>
</Grid>
</Grid>

View File

@@ -10,13 +10,19 @@ using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
using Vanara.PInvoke;
using Windows.System;
using WinUIEx.Messaging;
namespace BetterLyrics.WinUI3.Views
{
public sealed partial class LyricsWindow : Window
{
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private readonly WindowMessageMonitor _wmm;
public LyricsWindow()
{
@@ -28,6 +34,19 @@ namespace BetterLyrics.WinUI3.Views
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
Title = App.ResourceLoader!.GetString("LyricsPageTitle");
SetTitleBar(TopCommandGrid);
//SetTitleBar(RootGrid);
_wmm = new WindowMessageMonitor(this);
_wmm.WindowMessageReceived += Wmm_WindowMessageReceived;
}
private void Wmm_WindowMessageReceived(object? sender, WindowMessageEventArgs e)
{
if (e.Message.MessageId == (uint)User32.WindowMessage.WM_HOTKEY)
{
int id = (int)e.Message.WParam;
GlobalHotKeyHelper.TryInvokeAction(id);
}
}
public LyricsWindowViewModel ViewModel { get; private set; } = Ioc.Default.GetRequiredService<LyricsWindowViewModel>();
@@ -54,7 +73,7 @@ namespace BetterLyrics.WinUI3.Views
ViewModel.ToggleDesktopModeCommand.Execute(null);
if (autoLook == null && _settingsService.AutoLockOnDesktopMode)
{
ViewModel.LockWindowCommand.Execute(null);
ViewModel.ToggleLockWindowCommand.Execute(null);
}
break;
default:
@@ -138,6 +157,8 @@ namespace BetterLyrics.WinUI3.Views
private void UpdateTitleBarWindowButtonsVisibility()
{
TopCommandGrid.Margin = new Thickness(12);
switch (AppWindow.Presenter.Kind)
{
case AppWindowPresenterKind.Default:
@@ -147,6 +168,7 @@ namespace BetterLyrics.WinUI3.Views
AOTFlyoutItem.Visibility = DesktopFlyoutItem.Visibility = FullScreenFlyoutItem.Visibility = DockFlyoutItem.Visibility =
ClickThroughButton.Visibility = Visibility.Collapsed;
ViewModel.IsImmersiveMode = true;
break;
case AppWindowPresenterKind.FullScreen:
MinimiseButton.Visibility = MaximiseButton.Visibility = RestoreButton.Visibility =
@@ -157,7 +179,6 @@ namespace BetterLyrics.WinUI3.Views
DockFlyoutItem.Visibility =
Visibility.Collapsed;
FullScreenFlyoutItem.IsChecked = true;
break;
case AppWindowPresenterKind.Overlapped:
DockFlyoutItem.Visibility = Visibility.Visible;
@@ -175,6 +196,8 @@ namespace BetterLyrics.WinUI3.Views
MiniFlyoutItem.Visibility =
Visibility.Collapsed;
ViewModel.IsImmersiveMode = true;
TopCommandGrid.Margin = new Thickness();
}
else if (DesktopFlyoutItem.IsChecked)
{
@@ -189,7 +212,6 @@ namespace BetterLyrics.WinUI3.Views
Visibility.Collapsed;
ClickThroughButton.Visibility = Visibility.Visible;
}
else
{
@@ -217,6 +239,8 @@ namespace BetterLyrics.WinUI3.Views
MaximiseButton.Visibility = Visibility.Visible;
RestoreButton.Visibility = Visibility.Collapsed;
}
ViewModel.IsImmersiveMode = _settingsService.IsImmersiveMode;
}
break;
default:
@@ -255,12 +279,18 @@ namespace BetterLyrics.WinUI3.Views
private void TopCommandGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
TopCommandGrid.Opacity = 1;
if (ViewModel.IsImmersiveMode)
{
ViewModel.TopCommandGridOpacity = 1f;
}
}
private void TopCommandGrid_PointerExited(object sender, PointerRoutedEventArgs e)
{
TopCommandGrid.Opacity = 0;
if (ViewModel.IsImmersiveMode)
{
ViewModel.TopCommandGridOpacity = 0f;
}
}
private void TipContainerCenter_Loaded(object sender, RoutedEventArgs e)
@@ -277,5 +307,21 @@ namespace BetterLyrics.WinUI3.Views
{
ViewModel.IsMouseWithinWindow = false;
}
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (ClickThroughButton == null) return;
// <20><>ȡ<EFBFBD><C8A1><EFBFBD>ؼ<EFBFBD><D8BC>ڴ<EFBFBD><DAB4><EFBFBD><EFBFBD>е<EFBFBD>λ<EFBFBD>ã<EFBFBD><C3A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ͻǣ<CFBD>
var transform = ClickThroughButton.TransformToVisual(Content);
var point = transform.TransformPoint(new Windows.Foundation.Point(0, 0));
var btnRect = new Rectangle(
(int)point.X,
(int)point.Y,
(int)ClickThroughButton.ActualWidth,
(int)ClickThroughButton.ActualHeight
);
DesktopModeHelper.SetInteractiveRects([btnRect]);
}
}
}

View File

@@ -134,12 +134,64 @@
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageIgnoreFullscreenWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE967;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IgnoreFullscreenWindow, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Desktop mode -->
<TextBlock x:Uid="SettingsPageAppDesktop" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageAutoLock" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE755;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AutoLockOnDesktopMode, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageIgnoreFullscreenWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE66C;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IgnoreFullscreenWindow, Mode=TwoWay}" />
<controls:SettingsCard x:Uid="SettingsPageLockHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock
Margin="0,0,0,2"
VerticalAlignment="Center"
Text="Ctrl + Alt + " />
<ComboBox SelectedIndex="{x:Bind ViewModel.LockHotKeyIndex, Mode=TwoWay}">
<ComboBoxItem Content="A" />
<ComboBoxItem Content="B" />
<ComboBoxItem Content="C" />
<ComboBoxItem Content="D" />
<ComboBoxItem Content="E" />
<ComboBoxItem Content="F" />
<ComboBoxItem Content="G" />
<ComboBoxItem Content="H" />
<ComboBoxItem Content="I" />
<ComboBoxItem Content="J" />
<ComboBoxItem Content="K" />
<ComboBoxItem Content="L" />
<ComboBoxItem Content="M" />
<ComboBoxItem Content="N" />
<ComboBoxItem Content="O" />
<ComboBoxItem Content="P" />
<ComboBoxItem Content="Q" />
<ComboBoxItem Content="R" />
<ComboBoxItem Content="S" />
<ComboBoxItem Content="T" />
<ComboBoxItem Content="U" />
<ComboBoxItem Content="V" />
<ComboBoxItem Content="W" />
<ComboBoxItem Content="X" />
<ComboBoxItem Content="Y" />
<ComboBoxItem Content="Z" />
</ComboBox>
</StackPanel>
</controls:SettingsCard>
<!-- Dock mode -->
<TextBlock x:Uid="SettingsPageAppDock" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageDockPlacement" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.DockPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageDockPlacementTop" />
<ComboBoxItem x:Uid="SettingsPageDockPlacementBottom" />
</ComboBox>
</controls:SettingsCard>
</StackPanel>
@@ -448,6 +500,25 @@
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBgFontOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE799;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.LyricsBgFontOpacity, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text=" %" />
<Slider
Maximum="100"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="1"
TickFrequency="1"
TickPlacement="Outside"
Value="{x:Bind ViewModel.LyricsBgFontOpacity, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontStrokeWidth" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC12;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
@@ -668,7 +739,7 @@
IsExpanded="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLyricsGlowEffectScope" IsEnabled="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<controls:SettingsCard x:Uid="SettingsPageScope" IsEnabled="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsGlowEffectScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentChar" />
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeLineStartToCurrentChar" />
@@ -678,6 +749,10 @@
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageLyricsFloatAnimation" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8C5;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsLyricsFloatAnimationEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageFan" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEBC5;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsFanLyricsEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
@@ -825,6 +900,10 @@
Glyph=&#xE943;}"
IsClickEnabled="True" />
<controls:SettingsCard x:Uid="SettingsPageFAQ" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE897;}">
<HyperlinkButton Content="https://github.com/jayfunc/BetterLyrics/blob/dev/FAQ/FAQ.md" NavigateUri="https://github.com/jayfunc/BetterLyrics/blob/dev/FAQ/FAQ.md" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageQQGroup" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/QQ.png}">
<Button x:Uid="SettingsPageJoinNowButton" Click="QQGroupButton_Click" />
</controls:SettingsCard>
@@ -871,6 +950,20 @@
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLXMusicServer">
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBox
IsEnabled="{x:Bind ViewModel.IsLXMusicServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
PlaceholderText="http://127.0.0.1:23330"
Text="{x:Bind ViewModel.LXMusicServer, Mode=TwoWay}" />
<Button
x:Uid="SettingsPageServerTestButton"
Command="{x:Bind ViewModel.LXMusicServerTestCommand}"
IsEnabled="{x:Bind ViewModel.IsLXMusicServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</StackPanel>
</controls:SettingsCard>
</StackPanel>
</controls:Case>

View File

@@ -7,14 +7,13 @@ using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Windows.Forms;
using Windows.System;
namespace BetterLyrics.WinUI3.Views
{
public sealed partial class SettingsPage : Page
{
private bool _isUserToggle;
public SettingsPage()
{
this.InitializeComponent();

View File

@@ -1,8 +1,3 @@
---
title: FAQ
layout: FAQ
---
### I couldn't see any button that I can interact with
This app is built with immersive experience, just hover your mouse on the top/bottom area of the app and then you'll see everything.
@@ -11,17 +6,23 @@ This app is built with immersive experience, just hover your mouse on the top/bo
![alt text](image-4.png)
### I have set up all the settings related to translation but there is no translation at all
![alt text](image-8.png)
Please make sure that you have already enable "Translation" function (at the bottom-right of the lyrics page, click to toggle).
### How can I lock the window when switching to desktop mode
![alt text](image-6.png)
Again, hover you mouse on the top, click on the lock icon and you're good to go!
Again, hover you mouse on the top, click on the lock icon and you're good to go! Or, alternatively, press `Ctrl + Alt + U`.
### How can I unlock the window in desktop mode
![alt text](image-7.png)
It's in the system tray, right-click on the icon and you'll see "Unlock the window".
It's in the system tray, right-click on the icon and you'll see "Unlock the window". Or, alternatively, press `Ctrl + Alt + U`.
### There's a delay in lyrics timeline
@@ -41,4 +42,4 @@ Hover your mouse at the very bottom of the app and then click on the very last i
Go to "Advanced options" section, increase the threshold value (marked with the bigger red rectangle) until the lyrics is working properly.
----
---

View File

Before

Width:  |  Height:  |  Size: 510 KiB

After

Width:  |  Height:  |  Size: 510 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 631 KiB

After

Width:  |  Height:  |  Size: 631 KiB

View File

Before

Width:  |  Height:  |  Size: 599 KiB

After

Width:  |  Height:  |  Size: 599 KiB

View File

Before

Width:  |  Height:  |  Size: 859 KiB

After

Width:  |  Height:  |  Size: 859 KiB

View File

Before

Width:  |  Height:  |  Size: 854 KiB

After

Width:  |  Height:  |  Size: 854 KiB

View File

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

BIN
FAQ/image-8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 445 KiB

After

Width:  |  Height:  |  Size: 445 KiB

View File

@@ -1,7 +1,9 @@
<a href="https://github.com/jayfunc/BetterLyrics/blob/dev/README.md">_**Click here to see the English version**_</a>
<a href="https://github.com/jayfunc/BetterLyrics/blob/dev/FAQ/FAQ.md">_**点此处查看常见问题与解答FAQ**_</a>
<div align="center">
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="64"/>
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="64"/>
</div>
<h2 align="center">
@@ -9,121 +11,141 @@ BetterLyrics
</h2>
<h3 align="center">
使用 WinUI 3 构建的流畅动态歌词显示工具
基于 WinUI 3 打造的丝滑动态歌词显示工具
</h3>
---
- QQ[「BetterLyrics」反馈交流群简体中文](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) (1054700388)
- Discord [「BetterLyrics」反馈交流群繁体中文/英文)](https://discord.gg/5yAQPnyCKv)
- [「BetterLyrics」反馈交流群简体中文](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) (QQ群号1054700388)
- [「BetterLyrics」反馈交流群繁体中文/英文)](https://discord.gg/5yAQPnyCKv) (Discord)
---
## 亮点功能
🎉 本项目已被少数派精选推荐!欢迎阅读:[BetterLyrics - 一款专为 Windows 打造的沉浸式流畅歌词显示软件](https://sspai.com/post/101028)
- 动态模糊专辑封面作为背景
- 流畅的歌词淡入/淡出、放大/缩小效果
- 流畅的用户界面随歌曲切换
- 每个字符均支持渐变卡拉 OK带光晕效果
---
## 核心特色
- 动态模糊专辑封面背景
- 丝滑的歌词淡入/淡出、缩放效果
- 歌曲切换时的流畅界面过渡
- 逐字渐变的卡拉OK效果带光晕
- 沉浸式桌面歌词(停靠模式)
- 本地翻译(支持 30 种语言)
- 本地化歌词翻译(支持 30 种语言)
> 项目目前仍在开发中,最新的开发分支可能存在错误和意外行为
> 项目仍在开发中,最新分支可能存在未修复的 Bug 或异常行为
## 支持的歌词来源
- 来自您的本地存储
- 本地资源
- 音乐文件(内嵌歌词)
- [.lrc](https://en.wikipedia.org/wiki/LRC_(file_format)) 文件(包含核心格式增强格式)
- [.lrc](<https://en.wikipedia.org/wiki/LRC_(file_format)>) 文件(标准格式增强格式)
- [.eslrc](https://github.com/ESLyric/release) 文件
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) 文件
(歌词下载,您可以使用 [LDDC](https://github.com/chenmozhijin/LDDC))
(歌词下载推荐工具:[LDDC](https://github.com/chenmozhijin/LDDC)
- 来自在线歌词提供商
- QQ 音乐
- 在线歌词
- QQ音乐
- 网易云音乐
- 酷狗音乐
- [amll-ttml-db](https://github.com/Steve-xmh/amll-ttml-db)
- [LRCLIB](https://lrclib.net/)
## 截图
## 效果展示
![alt text](Screenshots/mode.png)
### 标准模式
![alt text](Screenshots/glow.png)
![标准模式](Screenshots/image.png)
![alt text](Screenshots/glow.gif)
![光晕浮动效果](Screenshots/glow-float.gif)
![alt text](Screenshots/dock.png)
![歌词面板](Screenshots/fan.png)
![alt text](Screenshots/immersive-dock.gif)
![纯歌词模式](Screenshots/lyrics-only.png)
![alt text](Screenshots/dock.gif)
![纯专辑封面模式](Screenshots/album-art-only.png)
![alt text](Screenshots/pip.png)
### 停靠模式
![alt text](Screenshots/settings.png)
![停靠模式1](Screenshots/dock-1.png)
![alt text](Screenshots/fs.png)
![停靠模式2](Screenshots/dock-2.png)
## 演示
### 桌面模式
在 Bilibili 上观看我们的介绍视频(上传于 2025 年 5 月 31 日) [此处](https://b23.tv/QjKkYmL)
![桌面模式1](Screenshots/desktop-1.png)
![桌面模式2](Screenshots/desktop-2.png)
## 演示视频
观看 B 站演示视频2025年7月7日发布[点击此处](https://www.bilibili.com/video/BV1zjGjzfEXh)
## 立即体验
- 稳定版
- 稳定版
<a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct">
<img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/>
<img src="https://get.microsoft.com/images/zh-cn%20dark.svg" width="200"/>
</a>
> **最简单**的获取方式**无限**免费试用或购买(免费版付费版**没有区别**,如果您喜欢,可以购买来支持
> **最便捷**的获取方式,提供**无限**免费试用或购买(免费版付费版**功能完全一致**,购买视为对开发者的支持)
者您也可以从 Google Drive 获取(链接见 [release](https://github.com/jayfunc/BetterLyrics/releases/latest) 页面
通过 Google Drive 获取(链接见[发布页](https://github.com/jayfunc/BetterLyrics/releases)
> 请注意,您正在下载“.zip”文件有关安装指南请参[此文档](How2Install/How2Install.md)
> 下载的是 ".zip" 压缩包,安装指南请参[此文档](How2Install/How2Install.md)
- 最新开发版
- 开发版
可通过 `git clone` 拉取项目源码自行编译
您可以使用 `git clone` 命令克隆此项目并自行构建。
## 已测试播放器
## 已知不支持的音乐播放器
- 酷狗音乐
- **时间轴同步限制**:酷狗不广播时间轴信息,调整进度时歌词无法实时跟随
- Apple Music
- 需在设置中调整时间轴阈值至 600 ms 左右(路径:设置→高级选项)
- foobar2000
- 需配合安装 [foo_mediacontrol](https://github.com/dumbie/foo_mediacontrol) 插件
- Spotify
- QQ音乐
- PotPlayer
- 系统自带媒体播放器
- 网易云音乐
## 非常感谢
## 鸣谢
- [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper)
- 提供 QQ网易、酷狗等平台歌词获取解密和解析功能
- 提供 QQ/网易云/酷狗歌词获取解密
- [LRCLIB](https://lrclib.net/)
- LRCLIB 歌词 API 提供程序
- [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet)
- 用于提取音乐文件中的图片
- 歌词 API 服务
- [ATL.NET](https://github.com/Zeugma440/atldotnet)
- 音乐文件封面提取
- [WinUIEx](https://github.com/dotMorten/WinUIEx)
- 提供访问 Win32 窗口 API 的便捷方法
- Win32 窗口 API 封装
- [TagLib#](https://github.com/mono/taglib-sharp)
- 用于读取原版歌词内容
- [Stackoverflow - 如何在 WPF 中为 Margin 属性设置动画](https://stackoverflow.com/a/21542882/11048731)
- 歌词内容解析
- [Vanara](https://github.com/dahall/Vanara)
- Win32 API 封装库
- [Stackoverflow - WPF边距动画实现](https://stackoverflow.com/a/21542882/11048731)
- [DevWinUI](https://github.com/ghost1372/DevWinUI)
- [Bilibili -WinUI3】SystemBackdropController定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS)
- [cnblogs - .NET App 与 Windows 系统媒体控制(SMTC)交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
- [Win2D中的游戏循环CanvasAnimatedControl](https://www.cnblogs.com/walterlv/p/10236395.html)
- [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
- [CommunityToolkit - 从入门到精通](https://mvvm.coldwind.top/)
- [B - WinUI3系统背景效果教程](https://www.bilibili.com/video/BV1PY4FevEkS)
- [博客园 - .NET应用与SMTC交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
- [Win2D游戏循环实现](https://www.cnblogs.com/walterlv/p/10236395.html)
- [Win2D高级示例](https://github.com/r2d2rigo/Win2D-Samples)
- [CommunityToolkit开发指南](https://mvvm.coldwind.top/)
## 灵感来
## 灵感来
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
- [网易云歌词增强](https://github.com/solstice23/refined-now-playing-netease)
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)
- [椒盐音乐播放器](https://moriafly.com/program/salt-player)
- [MyToolBar任务栏工具](https://github.com/TwilightLemon/MyToolBar)
## Star 历史
## 项目星标历史
[![星盘历史Chart](https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date)](https://www.star-history.com/#jayfunc/BetterLyrics&Date)
[![Star History Chart](https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date)](https://www.star-history.com/#jayfunc/BetterLyrics&Date)
## 欢迎提出任何问题和 PR
## 欢迎反馈与贡献
果您发现错误,请提交至 issues如果您有任何想法请随时在此处分享。
遇问题请提交 Issue如有改进建议欢迎提交 PR

View File

@@ -1,5 +1,7 @@
<a href="https://github.com/jayfunc/BetterLyrics/blob/dev/README.CN.md">_**点此处查看中文说明**_</a>
<a href="https://github.com/jayfunc/BetterLyrics/blob/dev/FAQ/FAQ.md">_**Click here to view frequently asked questions (FAQ)**_</a>
<div align="center">
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="64"/>
</div>
@@ -19,6 +21,10 @@ Your smooth dynamic lyrics display tool built with WinUI 3
---
🎉 This project was featured by SSPAI! Check out the article: [BetterLyrics An immersive and smooth lyrics display tool designed for Windows](https://sspai.com/post/101028)
---
## Highlighted features
- Dynamic blur album art as background
@@ -28,13 +34,13 @@ Your smooth dynamic lyrics display tool built with WinUI 3
- Immersive desktop lyrics (dock mode)
- Local translation (supporting 30 languages)
> This project is still under development now, bugs and unexpected behaviors may be existed in the latest dev branch.
> This project is still under development, bugs and unexpected behaviors may be existed in the latest branch.
## Supported lyrics source
- From your local storage
- Music files (with embedded lyrics)
- [.lrc](https://en.wikipedia.org/wiki/LRC_(file_format)) files (with both core format and enhanced format)
- [.lrc](<https://en.wikipedia.org/wiki/LRC_(file_format)>) files (with both core format and enhanced format)
- [.eslrc](https://github.com/ESLyric/release) files
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) files
@@ -49,27 +55,33 @@ Your smooth dynamic lyrics display tool built with WinUI 3
## Screenshots
![alt text](Screenshots/mode.png)
### Standard mode
![alt text](Screenshots/glow.png)
![alt text](Screenshots/image.png)
![alt text](Screenshots/glow.gif)
![alt text](Screenshots/glow-float.gif)
![alt text](Screenshots/dock.png)
![alt text](Screenshots/fan.png)
![alt text](Screenshots/immersive-dock.gif)
![alt text](Screenshots/lyrics-only.png)
![alt text](Screenshots/dock.gif)
![alt text](Screenshots/album-art-only.png)
![alt text](Screenshots/pip.png)
### Dock mode
![alt text](Screenshots/settings.png)
![alt text](Screenshots/dock-1.png)
![alt text](Screenshots/fs.png)
![alt text](Screenshots/dock-2.png)
### Desktop mode
![alt text](Screenshots/desktop-1.png)
![alt text](Screenshots/desktop-2.png)
## Demonstration
Watch our introduction video (uploaded on 31 May 2025) on Bilibili [here](https://b23.tv/QjKkYmL).
Watch our introduction video (uploaded on 7 July 2025) on Bilibili [here](https://www.bilibili.com/video/BV1zjGjzfEXh).
## Try it now
@@ -81,7 +93,7 @@ Watch our introduction video (uploaded on 31 May 2025) on Bilibili [here](https:
> **Easiest** way to get it. **Unlimited** free trail or purchase (there is **no difference** between free and paid version, if you like you can purchase to support me)
Or alternatively get it from Google Drive (see [release](https://github.com/jayfunc/BetterLyrics/releases/latest) page for the link)
Or alternatively get it from Google Drive (see [release](https://github.com/jayfunc/BetterLyrics/releases) page for the link)
> Please note you are downloading ".zip" file, for guide on how to install it, please kindly follow [this doc](How2Install/How2Install.md).
@@ -89,9 +101,18 @@ Or alternatively get it from Google Drive (see [release](https://github.com/jayf
You can `git clone` this project and build it yourself.
## Known unsupported music player
## Tested music player
- 网易云音乐 NetEase Cloud Music
- Kugou Music
- No timeline information broadcasted, which means when you change timeline position in Kugou Music, BetterLyrics has no way to detect this change.
- Apple Music
- Make sure you have set timeline threshold to around 600 ms in settings (Go to "Settings" - "Advanced option" to change), otherwise, the lyrics will be moving forward and afterward constantly.
- foobar2000
- Make sure you have https://github.com/dumbie/foo_mediacontrol installed with it
- Spofity
- QQ Music
- PotPlayer
- Media Player (System)
## Many thanks to
@@ -105,6 +126,8 @@ You can `git clone` this project and build it yourself.
- Provide easy ways to access Win32 API regarding windowing
- [TagLib#](https://github.com/mono/taglib-sharp)
- Used for reading original lyrics content
- [Vanara](https://github.com/dahall/Vanara)
- Win32 API wrapper
- [Stackoverflow - How to animate Margin property in WPF](https://stackoverflow.com/a/21542882/11048731)
- [DevWinUI](https://github.com/ghost1372/DevWinUI)
- [Bilibili -【WinUI3】SystemBackdropController定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS)
@@ -126,4 +149,4 @@ You can `git clone` this project and build it yourself.
## Any issues and PRs are welcomed
If you find a bug please file it in issues or if you have any ideas feel free to share it here.
If you find a bug please file it in issues or if you have any ideas feel free to share it here.

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

BIN
Screenshots/desktop-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
Screenshots/desktop-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 KiB

BIN
Screenshots/dock-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 KiB

BIN
Screenshots/dock-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

BIN
Screenshots/fan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 KiB

BIN
Screenshots/glow-float.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

BIN
Screenshots/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 MiB

BIN
Screenshots/lyrics-only.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 947 KiB

View File

@@ -1,7 +0,0 @@
source 'https://rubygems.org'
gem "jekyll", "~> 4.4.1" # installed by `gem jekyll`
# gem "webrick" # required when using Ruby >= 3 and Jekyll <= 4.2.2
gem "just-the-docs", "0.10.1" # pinned to the current release
# gem "just-the-docs" # always download the latest release

View File

@@ -1,97 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
base64 (0.2.0)
bigdecimal (3.1.9)
colorator (1.1.0)
concurrent-ruby (1.3.5)
csv (3.3.2)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
eventmachine (1.2.7)
ffi (1.17.1-arm64-darwin)
ffi (1.17.1-x86_64-linux-gnu)
forwardable-extended (2.6.0)
google-protobuf (4.29.3-arm64-darwin)
bigdecimal
rake (>= 13)
google-protobuf (4.29.3-x86_64-linux)
bigdecimal
rake (>= 13)
http_parser.rb (0.8.0)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
jekyll (4.4.1)
addressable (~> 2.4)
base64 (~> 0.2)
colorator (~> 1.0)
csv (~> 3.0)
em-websocket (~> 0.5)
i18n (~> 1.0)
jekyll-sass-converter (>= 2.0, < 4.0)
jekyll-watch (~> 2.0)
json (~> 2.6)
kramdown (~> 2.3, >= 2.3.1)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0)
mercenary (~> 0.3, >= 0.3.6)
pathutil (~> 0.9)
rouge (>= 3.0, < 5.0)
safe_yaml (~> 1.0)
terminal-table (>= 1.8, < 4.0)
webrick (~> 1.7)
jekyll-include-cache (0.2.1)
jekyll (>= 3.7, < 5.0)
jekyll-sass-converter (3.0.0)
sass-embedded (~> 1.54)
jekyll-seo-tag (2.8.0)
jekyll (>= 3.8, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
json (2.9.1)
just-the-docs (0.10.1)
jekyll (>= 3.8.5)
jekyll-include-cache
jekyll-seo-tag (>= 2.0)
rake (>= 12.3.1)
kramdown (2.5.1)
rexml (>= 3.3.9)
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.4)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (6.0.1)
rake (13.2.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rexml (3.4.0)
rouge (4.5.1)
safe_yaml (1.0.5)
sass-embedded (1.83.4-arm64-darwin)
google-protobuf (~> 4.29)
sass-embedded (1.83.4-x86_64-linux-gnu)
google-protobuf (~> 4.29)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
unicode-display_width (2.6.0)
webrick (1.9.1)
PLATFORMS
arm64-darwin
x86_64-linux-gnu
DEPENDENCIES
jekyll (~> 4.4.1)
just-the-docs (= 0.10.1)
BUNDLED WITH
2.5.9

View File

@@ -1,8 +0,0 @@
title: BetterLyrics Docs
description: BetterLyrics Docs
theme: just-the-docs
url: https://jayfunc.github.io/BetterLyrics
aux_links:
Template Repository: https://github.com/just-the-docs/just-the-docs-template

View File

@@ -1,15 +0,0 @@
---
title: Home
layout: home
---
----
[^1]: [It can take up to 10 minutes for changes to your site to publish after you push the changes to GitHub](https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/creating-a-github-pages-site-with-jekyll#creating-your-site).
[Just the Docs]: https://just-the-docs.github.io/just-the-docs/
[GitHub Pages]: https://docs.github.com/en/pages
[README]: https://github.com/just-the-docs/just-the-docs-template/blob/main/README.md
[Jekyll]: https://jekyllrb.com
[GitHub Pages / Actions workflow]: https://github.blog/changelog/2022-07-27-github-pages-custom-github-actions-workflows-beta/
[use this template]: https://github.com/just-the-docs/just-the-docs-template/generate