Compare commits

..

12 Commits

Author SHA1 Message Date
Zhe Fang
1b69493afd Update release-to-telegram.yml 2025-08-03 20:38:37 -04:00
Zhe Fang
c524dc013c Update release-to-telegram.yml 2025-08-03 20:35:37 -04:00
Zhe Fang
9ca5939e57 fix 2025-08-03 20:29:58 -04:00
Zhe Fang
860abd4037 fix 2025-08-03 19:43:14 -04:00
Zhe Fang
618415016f fix 2025-08-03 17:05:40 -04:00
Zhe Fang
bfcba1425d 更新 FAQ.md 中的链接文本
在 FAQ.md 文件中,修改了 FAQ 部分的链接文本。将原有的“点此访问”替换为“Click here to visit”,并在中文、日文和韩文版本中相应更新为“点此前往”、“こちらをクリック”和“여기를 클릭하세요”。
2025-08-02 12:26:36 -04:00
Zhe Fang
0dc9ebf18e Revert "Add GitHub stars badge to README files and fix encoding issues in Chinese translations"
This reverts commit 35ca28ac7b.
2025-08-02 12:24:30 -04:00
Zhe Fang
366d396b93 revert 2025-08-02 12:22:09 -04:00
Zhe Fang
35ca28ac7b Add GitHub stars badge to README files and fix encoding issues in Chinese translations 2025-08-02 12:11:16 -04:00
Zhe Fang
1505933107 update FAQ links for consistency in language and clarity 2025-08-02 11:36:51 -04:00
Zhe Fang
958227d0f2 refactor font size settings and update lyrics renderer initialization; add FAQ documentation 2025-08-02 11:35:09 -04:00
Zhe Fang
b1978fec09 update version to 1.0.40.0 and refactor image drawing methods for better opacity handling 2025-08-02 09:15:28 -04:00
38 changed files with 938 additions and 458 deletions

View File

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

View File

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

View File

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

View File

@@ -81,7 +81,10 @@
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="TitleBarToggleButtonStyle" TargetType="ToggleButton">
<Style
x:Key="TitleBarToggleButtonStyle"
BasedOn="{StaticResource ToggleButtonRevealStyle}"
TargetType="ToggleButton">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" />
@@ -302,4 +305,5 @@
<FontFamily x:Key="IconFontFamily">ms-appx:///Assets/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -107,6 +107,12 @@ namespace BetterLyrics.WinUI3.Helper
return t * t * (3f - 2f * t);
}
public static float CubicBezier(float t, float p0, float p1, float p2, float p3)
{
float u = 1 - t;
return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
}
public static float Linear(float t) => t;
}
}

View File

@@ -102,7 +102,7 @@ namespace BetterLyrics.WinUI3.Helper
{
if (!_isTransitioning) return;
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
_progress += (float)(elapsedTime.TotalSeconds / _durationSeconds);
if (_progress >= 1f)
{
_progress = 1f;

View File

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

View File

@@ -1,9 +1,14 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System.Collections.Generic;
using System.Numerics;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas.Text;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models
{
@@ -16,9 +21,9 @@ namespace BetterLyrics.WinUI3.Models
public ValueTransition<float> OpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> ScaleTransition { get; set; } = new(initialValue: 0.95f, durationSeconds: _animationDuration);
public CanvasTextLayout? CanvasTextLayout { get; set; }
public CanvasTextLayout? CanvasTextLayout { get; private set; }
public Vector2 CenterPosition { get; set; }
public Vector2 CenterPosition { get; private set; }
public Vector2 Position { get; set; }
public List<LyricsChar> LyricsChars { get; set; } = [];
@@ -29,5 +34,68 @@ namespace BetterLyrics.WinUI3.Models
public string DisplayedText { get; set; } = "";
public string OriginalText { get; set; } = "";
public CanvasGeometry? TextGeometry { get; private set; }
public CanvasCommandList? BackgroundFontEffect { get; private set; }
public CanvasCommandList? ForegroundFontEffect { get; private set; }
public void UpdateCenterPosition(float maxWidth, TextAlignmentType type)
{
if (CanvasTextLayout == null)
{
return;
}
float centerY = Position.Y + (float)CanvasTextLayout.LayoutBounds.Height;
CenterPosition = type switch
{
TextAlignmentType.Left => new Vector2(Position.X, centerY),
TextAlignmentType.Center => new Vector2(Position.X + maxWidth / 2, centerY),
TextAlignmentType.Right => new Vector2(Position.X + maxWidth, centerY),
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
};
}
public void UpdateTextLayout(ICanvasAnimatedControl control, CanvasTextFormat textFormat, float maxWidth, float maxHeight, TextAlignmentType type)
{
CanvasTextLayout?.Dispose();
CanvasTextLayout = null;
CanvasTextLayout = new CanvasTextLayout(control, DisplayedText, textFormat, maxWidth, maxHeight);
CanvasTextLayout.HorizontalAlignment = type.ToCanvasHorizontalAlignment();
}
public void UpdateTextGeometry()
{
TextGeometry?.Dispose();
TextGeometry = null;
if (CanvasTextLayout == null)
{
return;
}
TextGeometry = CanvasGeometry.CreateText(CanvasTextLayout);
}
public void UpdateFontEffect(ICanvasAnimatedControl control, bool drawStroke, Color strokeColor, int strokeWidth, Color fontColor)
{
BackgroundFontEffect?.Dispose();
BackgroundFontEffect = null;
ForegroundFontEffect?.Dispose();
ForegroundFontEffect = null;
if (TextGeometry == null)
{
return;
}
BackgroundFontEffect = new CanvasCommandList(control);
using var bgFontEffectDs = BackgroundFontEffect.CreateDrawingSession();
ForegroundFontEffect = new CanvasCommandList(control);
using var fgFontEffectDs = ForegroundFontEffect.CreateDrawingSession();
if (drawStroke)
{
bgFontEffectDs.DrawGeometry(TextGeometry, Position, strokeColor, strokeWidth); // 描边
fgFontEffectDs.DrawGeometry(TextGeometry, Position, strokeColor, strokeWidth); // 描边
}
bgFontEffectDs.FillGeometry(TextGeometry, Position, fontColor); // 填充
fgFontEffectDs.FillGeometry(TextGeometry, Position, fontColor); // 填充
}
}
}

View File

@@ -11,6 +11,8 @@ using EvtSource;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -292,15 +294,15 @@ namespace BetterLyrics.WinUI3.Services
var decoder = await BitmapDecoder.CreateAsync(stream);
token.ThrowIfCancellationRequested();
var _albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
token.ThrowIfCancellationRequested();
var _albumArtLightAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, false).FirstOrDefault();
var _albumArtDarkAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, true).FirstOrDefault();
var albumArtLightAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, false).FirstOrDefault();
var albumArtDarkAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, true).FirstOrDefault();
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(_albumArtSwBitmap, _albumArtLightAccentColor, _albumArtDarkAccentColor));
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
});
}

View File

@@ -195,7 +195,7 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(CoverImageRadiusKey, 12); // 12 %
SetDefault(CoverAcrylicEffectAmountKey, 0);
// Lyrics
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Center);
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Left);
SetDefault(SongInfoAlignmentTypeKey, (int)TextAlignmentType.Left);
SetDefault(LyricsFontWeightKey, (int)LyricsFontWeight.Bold);
SetDefault(LyricsBlurAmountKey, 5);
@@ -210,8 +210,8 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(LyricsCustomFgFontColorKey, Colors.White.ToInt());
SetDefault(LyricsCustomStrokeFontColorKey, Colors.White.ToInt());
SetDefault(LyricsStandardFontSizeKey, 28);
SetDefault(LyricsDockFontSizeKey, 20);
SetDefault(LyricsStandardFontSizeKey, 32);
SetDefault(LyricsDockFontSizeKey, 16);
SetDefault(LyricsDesktopFontSizeKey, 28);
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
@@ -233,7 +233,7 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(IgnoreFullscreenWindowKey, false);
SetDefault(PreferredDisplayTypeKey, (int)LyricsDisplayType.SplitView);
SetDefault(LyricsScrollEasingTypeKey, (int)EasingType.EaseInOutQuad);
SetDefault(LyricsScrollEasingTypeKey, (int)EasingType.EaseInOutSine);
SetDefault(LyricsScrollDurationKey, 500); // 500ms
SetDefault(TimelineSyncThresholdKey, 0); // 0ms

View File

@@ -916,4 +916,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
<value>Reset window position</value>
</data>
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
<value>This setting will not affect the dock mode and the dock mode will always remain centered.</value>
</data>
</root>

View File

@@ -916,4 +916,7 @@
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
<value>ウィンドウの位置をリセットします</value>
</data>
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
<value>この設定はドックモードには影響しません。ドックモードは常に中心のままです。</value>
</data>
</root>

View File

@@ -916,4 +916,7 @@
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
<value>창 위치를 재설정합니다</value>
</data>
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
<value>이 설정은 도크 모드에 영향을 미치지 않으며 도크 모드는 항상 중앙에 유지됩니다.</value>
</data>
</root>

View File

@@ -916,4 +916,7 @@
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
<value>重置窗口位置</value>
</data>
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
<value>此设置不会影响停靠模式,停靠模式将始终保持居中。</value>
</data>
</root>

View File

@@ -916,4 +916,7 @@
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
<value>重置窗口位置</value>
</data>
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
<value>此設定不會影響停靠模式,停靠模式將始終保持居中。</value>
</data>
</root>

View File

@@ -49,7 +49,12 @@ namespace BetterLyrics.WinUI3.ViewModels
_lyricsBgTheme = _settingsService.LyricsBackgroundTheme;
_isFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
// 歌词描边
_lyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
_lyricsStrokeFontColorType = _settingsService.LyricsStrokeFontColorType;
_customStrokeFontColor = _settingsService.LyricsCustomStrokeFontColor;
_isTranslationEnabled = _settingsService.IsTranslationEnabled;
_showTranslationOnly = _settingsService.ShowTranslationOnly;
_isLibreTranslateEnabled = _settingsService.IsLibreTranslateEnabled;

View File

@@ -35,15 +35,15 @@ namespace BetterLyrics.WinUI3.ViewModels
if (_isDockMode)
{
FillBackground(control, combinedDs, _immersiveBgTransition.Value, 0f, _immersiveBgOpacityTransition.Value);
FillBackgroundColor(control, combinedDs, _immersiveBgColorTransition.Value, 0f, _immersiveBgOpacityTransition.Value);
}
else if (_isDesktopMode)
{
FillBackground(control, combinedDs, _immersiveBgTransition.Value, 0f, _immersiveBgOpacityTransition.Value);
FillBackgroundColor(control, combinedDs, _immersiveBgColorTransition.Value, 0f, _immersiveBgOpacityTransition.Value);
}
else
{
FillBackground(control, combinedDs, _albumArtAccentColorTransition.Value, 0f, _albumArtBgOpacity / 100f);
FillBackgroundColor(control, combinedDs, _albumArtAccentColorTransition.Value, 0f, _albumArtBgOpacity / 100f);
DrawAlbumArtBackground(control, combinedDs);
}
@@ -56,6 +56,8 @@ namespace BetterLyrics.WinUI3.ViewModels
if (_isDebugOverlayEnabled)
{
_drawFrameCount++;
var currentPlayingLine = _lyricsDataArr
.ElementAtOrDefault(_langIndex)
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
@@ -71,32 +73,33 @@ namespace BetterLyrics.WinUI3.ViewModels
ds.DrawText(
$"[DEBUG]\n" +
$"Cur playing {_playingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
$"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
$"Cur time {TotalTime + _positionOffset}\n" +
$"Lang size {_lyricsDataArr.Count}\n" +
$"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
new Vector2(10, 10),
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
$"Canvas size: {_canvasWidth}x{_canvasHeight}\n" +
$"FPS (Draw): {_displayedDrawFrameCount}\n" +
$"Playing line: {_playingLineIndex}\n" +
$"Syllable start idx: {charStartIndex}\n" +
$"Syllable len: {charLength}\n" +
$"Syllable prog: {charProgress}\n" +
$"Visible lines: [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
$"Total line count: {GetMaxLyricsLineIndexBoundaries().Item2 + 1}\n" +
$"Cur time: {TotalTime + _positionOffset}\n" +
$"Lang size: {_lyricsDataArr.Count}\n" +
$"Song duration: {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
new Vector2(10, 40),
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White,
_debugTextFormat
);
}
//for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
//{
// LyricsLine? line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
// if (line != null)
// {
// ds.DrawText(
// $"[{i}] {line.OriginalText} {line.HighlightOpacityTransition.Value}",
// new Vector2(10, 30 + (i - _startVisibleLineIndex) * 20),
// ThemeTypeSent == ElementTheme.Light ? Colors.Black : Colors.White
// );
// }
//}
if (_drawFrameStopwatch?.Elapsed.TotalSeconds >= 1.0)
{
_displayedDrawFrameCount = _drawFrameCount;
_drawFrameStopwatch?.Restart();
_drawFrameCount = 0;
}
}
}
private void DrawBackgroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
private void DrawBackgroundImgae(OpacityEffect effect, CanvasDrawingSession ds, CanvasBitmap canvasBitmap)
{
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
@@ -107,128 +110,38 @@ namespace BetterLyrics.WinUI3.ViewModels
float x = _canvasWidth / 2 - imageWidth * scaleFactor / 2;
float y = _canvasHeight / 2 - imageHeight * scaleFactor / 2;
// Original source: https://zhuanlan.zhihu.com/p/37178216
float gain = _lyricsBgBrightnessTransition.Value;
float whiteX = 1 - 0.5f * gain;
float whiteY = 0.5f + 0.5f * gain;
float blackX = 0.5f - 0.5f * gain;
float blackY = 0 + 0.5f * gain;
ds.DrawImage(new OpacityEffect
{
Source = new BrightnessEffect
{
Source = new ScaleEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
WhitePoint = new Vector2(whiteX, whiteY),
BlackPoint = new Vector2(blackX, blackY),
},
Opacity = opacity,
}, new Vector2(x, y));
ds.DrawImage(effect, new Vector2(x, y));
}
private void DrawForegroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
private void DrawForegroundImgae(OpacityEffect effect, CanvasDrawingSession ds)
{
if (opacity == 0) return;
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
float scaleFactor = _albumArtSize / Math.Min(imageWidth, imageHeight);
if (scaleFactor < 0.01f) return;
float cornerRadius = _albumArtCornerRadius / 100f * _albumArtSize / 2;
using var cornerRadiusMask = new CanvasCommandList(control.Device);
using var cornerRadiusMaskDs = cornerRadiusMask.CreateDrawingSession();
cornerRadiusMaskDs.FillRoundedRectangle(
new Rect(0, 0, imageWidth * scaleFactor, imageHeight * scaleFactor),
cornerRadius, cornerRadius, Colors.White
);
ds.DrawImage(new OpacityEffect
{
Source = new AlphaMaskEffect
{
Source = new ScaleEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
AlphaMask = cornerRadiusMask,
},
Opacity = opacity,
}, new Vector2(_albumArtXTransition.Value, _albumArtYTransition.Value));
ds.DrawImage(effect, new Vector2(_albumArtXTransition.Value, _albumArtYTransition.Value));
}
private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
//ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
using var overlappedCovers = new CanvasCommandList(control.Device);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
overlappedCoversDs.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
if (_lastAlbumArtCanvasBitmap != null)
if (_albumArtBgEffect == null)
{
DrawBackgroundImgae(control, overlappedCoversDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
}
if (_albumArtCanvasBitmap != null)
{
DrawBackgroundImgae(control, overlappedCoversDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
return;
}
overlappedCoversDs.Transform = Matrix3x2.Identity;
IGraphicsEffectSource blurredCover = new GaussianBlurEffect
{
BlurAmount = _albumArtBgBlurAmount,
Source = overlappedCovers,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
};
// 应用亚克力噪点效果
// TODO: 没有写_coverAcrylicNoiseCanvasBitmap加载的代码
if (_coverAcrylicEffectAmount > 0 && _coverAcrylicNoiseCanvasBitmap != null)
{
blurredCover = new BlendEffect
{
Mode = BlendEffectMode.SoftLight,
Background = blurredCover,
Foreground = new OpacityEffect
{
Source = _coverAcrylicNoiseCanvasBitmap,
Opacity = _coverAcrylicEffectAmount / 100f,
},
};
}
var coverOverlayEffect = new OpacityEffect
{
Opacity = _albumArtBgOpacity / 100f,
Source = blurredCover,
};
ds.DrawImage(coverOverlayEffect);
//ds.Transform = Matrix3x2.Identity;
ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
ds.DrawImage(_albumArtBgEffect);
ds.Transform = Matrix3x2.Identity;
}
private void DrawAlbumArt(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
using var albumArt = new CanvasCommandList(control.Device);
using var albumArtDs = albumArt.CreateDrawingSession();
if (_albumArtCanvasBitmap != null)
if (_lastFgImageEffect != null && !_lastFgImageEffect.IsDisposed() && _lastAlbumArtCanvasBitmap != null)
{
DrawForegroundImgae(control, albumArtDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
DrawForegroundImgae(_lastFgImageEffect, albumArtDs);
}
if (_lastAlbumArtCanvasBitmap != null)
if (_fgImageEffect != null && !_fgImageEffect.IsDisposed() && _albumArtCanvasBitmap != null)
{
DrawForegroundImgae(control, albumArtDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
DrawForegroundImgae(_fgImageEffect, albumArtDs);
}
using var opacity = new CanvasCommandList(control.Device);
@@ -268,6 +181,11 @@ namespace BetterLyrics.WinUI3.ViewModels
LyricsLayoutOrientation.Vertical => _canvasWidth - _leftMargin - _albumArtSize - _rightMargin,
_ => 0f
};
if (maxWidth <= 0)
{
return;
}
using CanvasTextLayout titleLayout = new(
control, title ?? string.Empty,
_titleTextFormat, maxWidth, _canvasHeight
@@ -305,60 +223,20 @@ namespace BetterLyrics.WinUI3.ViewModels
var textLayout = line.CanvasTextLayout;
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;
float centerX = position.X;
float centerY = position.Y + layoutHeight / 2;
switch (_lyricsAlignmentType)
{
case TextAlignmentType.Left:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
break;
case TextAlignmentType.Center:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
centerX += _maxLyricsWidth / 2;
break;
case TextAlignmentType.Right:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
centerX += _maxLyricsWidth;
break;
default:
break;
}
float xOffset = _lyricsXTransition.Value;
float yOffset = _canvasYScrollTransition.Value + _canvasHeight / 2 + _lyricsYTransition.Value;
// 组合变换:缩放 -> 旋转 -> 平移
ds.Transform =
Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
Matrix3x2.CreateScale(line.ScaleTransition.Value, line.CenterPosition)
* Matrix3x2.CreateRotation(line.AngleTransition.Value, currentPlayingLine.Position)
* Matrix3x2.CreateTranslation(xOffset, yOffset);
* Matrix3x2.CreateTranslation(_lyricsXTransition.Value, yOffset);
// Create the background lyrics line with stroke and fill
using var bgLyrics = new CanvasCommandList(control.Device);
using var bgLyricsDs = bgLyrics.CreateDrawingSession();
// Create the foreground lyrics line with stroke and fill
using var fgLyrics = new CanvasCommandList(control.Device);
using var fgLyricsDs = fgLyrics.CreateDrawingSession();
// 创建文字几何体
using var textGeometry = CanvasGeometry.CreateText(textLayout);
if (_isDesktopMode)
{
bgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 背景描边
fgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 前景描边
}
bgLyricsDs.FillGeometry(textGeometry, position, _bgFontColor); // 背景填充
fgLyricsDs.FillGeometry(textGeometry, position, _fgFontColor); // 前景填充
if (line.BackgroundFontEffect == null || line.ForegroundFontEffect == null) continue;
using var combined = new CanvasCommandList(control.Device);
using var combinedDs = combined.CreateDrawingSession();
@@ -367,12 +245,16 @@ namespace BetterLyrics.WinUI3.ViewModels
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层(背景歌词层次)
// Current line will not be blurred
combinedDs.DrawImage(
new GaussianBlurEffect
new OpacityEffect
{
Source = new OpacityEffect { Source = bgLyrics, Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
BlurAmount = line.BlurAmountTransition.Value,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
Source = new GaussianBlurEffect
{
Source = line.BackgroundFontEffect,
BlurAmount = line.BlurAmountTransition.Value,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
},
Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value,
}
);
@@ -405,7 +287,7 @@ namespace BetterLyrics.WinUI3.ViewModels
var region = regions[j];
var rect = new Rect(
region.LayoutBounds.X,
region.LayoutBounds.Y + position.Y,
region.LayoutBounds.Y + line.Position.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
@@ -422,20 +304,20 @@ namespace BetterLyrics.WinUI3.ViewModels
// Rects
var highlightRect = new Rect(
highlightRegion.LayoutBounds.X,
highlightRegion.LayoutBounds.Y + position.Y,
highlightRegion.LayoutBounds.Y + line.Position.Y,
highlightWidth,
highlightRegion.LayoutBounds.Height
);
var fadeInRect = new Rect(
highlightRect.Right - fadingWidth,
highlightRegion.LayoutBounds.Y + position.Y,
highlightRegion.LayoutBounds.Y + line.Position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
var fadeOutRect = new Rect(
highlightRect.Right,
highlightRegion.LayoutBounds.Y + position.Y,
highlightRegion.LayoutBounds.Y + line.Position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
@@ -463,7 +345,6 @@ namespace BetterLyrics.WinUI3.ViewModels
else
{
float height = 0f;
//var regions = textLayout.GetCharacterRegions(0, string.Join("", line.LyricsChars.Select(x => x.Text)).Length);
var regions = textLayout.GetCharacterRegions(0, line.OriginalText.Length);
if (regions.Length > 0)
{
@@ -473,7 +354,7 @@ namespace BetterLyrics.WinUI3.ViewModels
maskDs.FillRectangle(
new Rect(
textLayout.LayoutBounds.X,
position.Y,
line.Position.Y,
textLayout.LayoutBounds.Width,
height
),
@@ -490,12 +371,12 @@ namespace BetterLyrics.WinUI3.ViewModels
{
Source = new AlphaMaskEffect
{
Source = fgLyrics,
Source = line.ForegroundFontEffect,
AlphaMask = _lyricsGlowEffectScope switch
{
LineRenderingType.CurrentChar => highlightMask,
LineRenderingType.LineStartToCurrentChar => mask,
LineRenderingType.CurrentLine => fgLyrics,
LineRenderingType.CurrentLine => line.ForegroundFontEffect,
_ => mask,
},
},
@@ -505,12 +386,12 @@ namespace BetterLyrics.WinUI3.ViewModels
: new CanvasCommandList(control.Device),
Foreground = new AlphaMaskEffect
{
Source = fgLyrics,
Source = line.ForegroundFontEffect,
AlphaMask = _lyricsHighlightScope switch
{
LineRenderingType.CurrentChar => highlightMask,
LineRenderingType.LineStartToCurrentChar => mask,
LineRenderingType.CurrentLine => fgLyrics,
LineRenderingType.CurrentLine => line.ForegroundFontEffect,
_ => mask,
},
},
@@ -553,21 +434,14 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
private void FillBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds, Color color, float radius, float opacity)
private void FillBackgroundColor(ICanvasAnimatedControl control, CanvasDrawingSession ds, Color color, float radius, float opacity)
{
CanvasCommandList list = new(control.Device);
using var listDs = list.CreateDrawingSession();
listDs.FillRoundedRectangle(
ds.FillRoundedRectangle(
new Rect(0, 0, _canvasWidth, _canvasHeight),
radius,
radius,
color
color.WithAlpha((byte)(opacity * 255))
);
ds.DrawImage(new OpacityEffect
{
Source = list,
Opacity = opacity
});
}
private CanvasLinearGradientBrush GetHorizontalFillBrush(

View File

@@ -0,0 +1,185 @@
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using System;
using System.Numerics;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
{
private OpacityEffect? _lastBgImageEffect;
private OpacityEffect? _bgImageEffect;
private OpacityEffect? _lastFgImageEffect;
private OpacityEffect? _fgImageEffect;
private CanvasCommandList? _albumArtBgEffect;
private OpacityEffect CreateBgImageEffect(CanvasBitmap canvasBitmap, float opacity)
{
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
float targetSize = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2));
float scaleFactor = targetSize / MathF.Min(imageWidth, imageHeight);
// Original source: https://zhuanlan.zhihu.com/p/37178216
float gain = _lyricsBgBrightnessTransition.Value;
float whiteX = 1 - 0.5f * gain;
float whiteY = 0.5f + 0.5f * gain;
float blackX = 0.5f - 0.5f * gain;
float blackY = 0 + 0.5f * gain;
return new OpacityEffect
{
Source = new BrightnessEffect
{
Source = new ScaleEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
WhitePoint = new Vector2(whiteX, whiteY),
BlackPoint = new Vector2(blackX, blackY),
},
Opacity = opacity,
};
}
private OpacityEffect? CreateFgImageEffect(ICanvasAnimatedControl control, CanvasBitmap canvasBitmap, float opacity)
{
// TODO 最大化/还原时图片大小未跟随改变
if (opacity == 0) return null;
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
float scaleFactor = _albumArtSize / Math.Min(imageWidth, imageHeight);
if (scaleFactor < 0.01f) return null;
float cornerRadius = _albumArtCornerRadius / 100f * _albumArtSize / 2;
var cornerRadiusMask = new CanvasCommandList(control);
using var cornerRadiusMaskDs = cornerRadiusMask.CreateDrawingSession();
cornerRadiusMaskDs.FillRoundedRectangle(
new Rect(0, 0, imageWidth * scaleFactor, imageHeight * scaleFactor),
cornerRadius, cornerRadius, Colors.White
);
return new OpacityEffect
{
Source = new AlphaMaskEffect
{
Source = new ScaleEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
AlphaMask = cornerRadiusMask,
},
Opacity = opacity,
};
}
private void UpdateAlbumArtBgEffect(ICanvasAnimatedControl control)
{
_albumArtBgEffect?.Dispose();
_albumArtBgEffect = null;
using var overlappedCovers = new CanvasCommandList(control);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
if (_lastBgImageEffect != null && !_lastBgImageEffect.IsDisposed() && _lastAlbumArtCanvasBitmap != null)
{
DrawBackgroundImgae(_lastBgImageEffect, overlappedCoversDs, _lastAlbumArtCanvasBitmap);
}
if (_bgImageEffect != null && !_bgImageEffect.IsDisposed() && _albumArtCanvasBitmap != null)
{
DrawBackgroundImgae(_bgImageEffect, overlappedCoversDs, _albumArtCanvasBitmap);
}
using var blurredCover = new GaussianBlurEffect
{
BlurAmount = _albumArtBgBlurAmount,
Source = overlappedCovers,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
};
using var combined = new CanvasCommandList(control);
using var combinedDs = combined.CreateDrawingSession();
if (_coverAcrylicEffectAmount > 0 && _coverAcrylicNoiseCanvasBitmap != null)
{
// 应用亚克力噪点效果
combinedDs.DrawImage(new BlendEffect
{
Mode = BlendEffectMode.SoftLight,
Background = blurredCover,
Foreground = new OpacityEffect
{
Source = _coverAcrylicNoiseCanvasBitmap,
Opacity = _coverAcrylicEffectAmount / 100f,
},
});
}
else
{
combinedDs.DrawImage(blurredCover);
}
_albumArtBgEffect = new CanvasCommandList(control);
using var albumArtBgDs = _albumArtBgEffect.CreateDrawingSession();
albumArtBgDs.DrawImage(new OpacityEffect
{
Opacity = _albumArtBgOpacity / 100f,
Source = combined,
});
}
private void UpdateLastBgImageEffect()
{
_lastBgImageEffect?.Dispose();
_lastBgImageEffect = null;
if (_lastAlbumArtCanvasBitmap != null)
{
_lastBgImageEffect = CreateBgImageEffect(_lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
}
}
private void UpdateBgImageEffect()
{
_bgImageEffect?.Dispose();
_bgImageEffect = null;
if (_albumArtCanvasBitmap != null)
{
_bgImageEffect = CreateBgImageEffect(_albumArtCanvasBitmap, _albumArtBgTransition.Value);
}
}
private void UpdateLastFgImageEffect(ICanvasAnimatedControl control)
{
_lastFgImageEffect?.Dispose();
_lastFgImageEffect = null;
if (_lastAlbumArtCanvasBitmap != null)
{
_lastFgImageEffect = CreateFgImageEffect(control, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
}
}
private void UpdateFgImageEffect(ICanvasAnimatedControl control)
{
_fgImageEffect?.Dispose();
_fgImageEffect = null;
if (_albumArtCanvasBitmap != null)
{
_fgImageEffect = CreateFgImageEffect(control, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
}
}
}
}

View File

@@ -70,6 +70,7 @@ namespace BetterLyrics.WinUI3.ViewModels
else if (message.PropertyName == nameof(SettingsPageViewModel.IsDebugOverlayEnabled))
{
_isDebugOverlayEnabled = message.NewValue;
_isDebugOverlayEnabledChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLyricsGlowEffectEnabled))
{
@@ -139,7 +140,7 @@ namespace BetterLyrics.WinUI3.ViewModels
{
if (message.PropertyName == nameof(LyricsWindowViewModel.ActivatedWindowAccentColor))
{
_immersiveBgTransition.StartTransition(message.NewValue);
_immersiveBgColorTransition.StartTransition(message.NewValue);
_environmentalColor = message.NewValue;
UpdateColorConfig();
}
@@ -183,6 +184,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (message.PropertyName == nameof(SettingsPageViewModel.CoverImageRadius))
{
_albumArtCornerRadius = message.NewValue;
_isAlbumArtCornerRadiusChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayOpacity))
{
@@ -231,6 +233,7 @@ namespace BetterLyrics.WinUI3.ViewModels
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontStrokeWidth))
{
_lyricsFontStrokeWidth = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsScrollDuration))
{
@@ -277,6 +280,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsAlignmentType))
{
_lyricsAlignmentType = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.SongInfoAlignmentType))
{

View File

@@ -14,11 +14,11 @@ namespace BetterLyrics.WinUI3.ViewModels
{
private readonly ValueTransition<float> _canvasYScrollTransition = new(
initialValue: 0f,
durationSeconds: 0.5f,
easingType: EasingType.EaseInOutCubic
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutSine
);
private readonly ValueTransition<Color> _immersiveBgTransition = new(
private readonly ValueTransition<Color> _immersiveBgColorTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
@@ -32,27 +32,31 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly ValueTransition<float> _immersiveBgOpacityTransition = new(
initialValue: 1f,
durationSeconds: 0.2f
durationSeconds: 0.3f
);
private readonly ValueTransition<float> _titleXTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _titleYTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _lyricsXTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _lyricsYTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _lyricsOpacityTransition = new(
@@ -72,12 +76,14 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly ValueTransition<float> _albumArtXTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _albumArtYTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _songInfoOpacityTransition = new(

View File

@@ -28,6 +28,13 @@ namespace BetterLyrics.WinUI3.ViewModels
private bool _isPlayingLineChanged = false;
private bool _isVisibleLinesBoundaryChanged = false;
private bool _isDebugOverlayEnabledChanged = false;
private bool _albumArtChanged = false;
private bool _isCoverAcrylicEffectAmountChanged = false;
private bool _isAlbumArtCornerRadiusChanged = true;
public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
{
_elapsedTime = args.Timing.ElapsedTime;
@@ -49,19 +56,18 @@ namespace BetterLyrics.WinUI3.ViewModels
_displayType = _displayTypeReceived;
_playingLineIndex = playingLineIndex;
if (_albumArtChanged)
if (_isDebugOverlayEnabledChanged)
{
if (_lastAlbumArtSwBitmap != null)
if (_isDebugOverlayEnabled)
{
_lastAlbumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _lastAlbumArtSwBitmap);
_drawFrameStopwatch = Stopwatch.StartNew();
}
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
if (_albumArtSwBitmap != null)
else
{
_albumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _albumArtSwBitmap);
_drawFrameStopwatch?.Stop();
_drawFrameStopwatch = null;
}
_albumArtChanged = false;
_isDebugOverlayEnabledChanged = false;
}
if (_isDynamicCoverOverlayEnabled)
@@ -87,14 +93,9 @@ namespace BetterLyrics.WinUI3.ViewModels
_isCoverAcrylicEffectAmountChanged = true;
}
if (_isCoverAcrylicEffectAmountChanged)
if (_isDisplayTypeChanged || _isCanvasWidthChanged || _isCanvasHeightChanged)
{
UpdateCoverAcrylicOverlay(control);
}
if (_isDisplayTypeChanged || _isCanvasWidthChanged)
{
bool jumpTo = !_isDisplayTypeChanged && _isCanvasWidthChanged;
bool jumpTo = !_isDisplayTypeChanged && (_isCanvasWidthChanged || _isCanvasHeightChanged);
switch (_lyricsLayoutOrientation)
{
case LyricsLayoutOrientation.Horizontal:
@@ -161,6 +162,65 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
if (_isAlbumArtCornerRadiusChanged)
{
UpdateLastFgImageEffect(control);
UpdateFgImageEffect(control);
_isAlbumArtCornerRadiusChanged = false;
}
// 背景图切换计算
// 将当前背景图放到 _lastAlbumArtSwBitmap 中 并设置不透明度为 1
// 将新的背景图放到 _albumArtSwBitmap 中 并设置不透明度为 0
// 这样可以实现背景图的连贯渐变效果
if (_albumArtChanged || _isCanvasHeightChanged || _isCanvasWidthChanged ||
_lyricsBgBrightnessTransition.IsTransitioning ||
_albumArtBgTransition.IsTransitioning)
{
// 必须先在此处重置动画
if (_albumArtChanged)
{
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
}
// 更新 last
if (_albumArtChanged)
{
if (_lastAlbumArtSwBitmap != null)
{
_lastAlbumArtCanvasBitmap?.Dispose();
_lastAlbumArtCanvasBitmap = null;
_lastAlbumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _lastAlbumArtSwBitmap);
}
}
UpdateLastFgImageEffect(control);
UpdateLastBgImageEffect();
// 更新 current
if (_albumArtChanged)
{
if (_albumArtSwBitmap != null)
{
_albumArtCanvasBitmap?.Dispose();
_albumArtCanvasBitmap = null;
_albumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _albumArtSwBitmap);
}
}
UpdateFgImageEffect(control);
UpdateBgImageEffect();
// 更新叠加的背景效果
UpdateAlbumArtBgEffect(control);
}
if (_isCoverAcrylicEffectAmountChanged)
{
UpdateCoverAcrylicOverlay(control);
UpdateAlbumArtBgEffect(control);
_isCoverAcrylicEffectAmountChanged = false;
}
_albumArtChanged = false;
if (_isCanvasWidthChanged || _lyricsXTransition.IsTransitioning)
{
_maxLyricsWidth = _canvasWidth - _lyricsXTransition.Value - _rightMargin;
@@ -191,7 +251,7 @@ namespace BetterLyrics.WinUI3.ViewModels
_lyricsOpacityTransition.Update(_elapsedTime);
_albumArtOpacityTransition.Update(_elapsedTime);
_immersiveBgOpacityTransition.Update(_elapsedTime);
_immersiveBgTransition.Update(_elapsedTime);
_immersiveBgColorTransition.Update(_elapsedTime);
_albumArtAccentColorTransition.Update(_elapsedTime);
_albumArtBgTransition.Update(_elapsedTime);
_lyricsBgBrightnessTransition.Update(_elapsedTime);
@@ -228,22 +288,16 @@ namespace BetterLyrics.WinUI3.ViewModels
continue;
}
if (line.CanvasTextLayout != null)
{
line.CanvasTextLayout.Dispose();
line.CanvasTextLayout = null;
}
// Calculate layout bounds
line.CanvasTextLayout = new CanvasTextLayout(
control,
line.DisplayedText,
_lyricsTextFormat,
_maxLyricsWidth,
_canvasHeight
);
line.UpdateTextLayout(control, _lyricsTextFormat, _maxLyricsWidth, _canvasHeight, _isDockMode ? TextAlignmentType.Center : _lyricsAlignmentType);
line.UpdateTextGeometry();
line.Position = new Vector2(0, y);
line.UpdateCenterPosition(_maxLyricsWidth, _isDockMode ? TextAlignmentType.Center : _lyricsAlignmentType);
line.UpdateFontEffect(control, _isDesktopMode, _strokeFontColor, _lyricsFontStrokeWidth, _bgFontColor);
if (line.CanvasTextLayout == null)
{
continue;
}
y +=
(float)line.CanvasTextLayout.LayoutBounds.Height
@@ -443,6 +497,8 @@ namespace BetterLyrics.WinUI3.ViewModels
default:
break;
}
_isLayoutChanged = true;
}
private void UpdateLinesProps()
@@ -519,6 +575,8 @@ namespace BetterLyrics.WinUI3.ViewModels
if (_coverAcrylicEffectAmount > 0)
{
var ret = NoiseOverlayHelper.GenerateNoiseBitmapBGRA((int)_canvasWidth, (int)_canvasHeight);
_coverAcrylicNoiseCanvasBitmap?.Dispose();
_coverAcrylicNoiseCanvasBitmap = null;
_coverAcrylicNoiseCanvasBitmap = CanvasBitmap.CreateFromBytes(
control,
ret,
@@ -527,7 +585,6 @@ namespace BetterLyrics.WinUI3.ViewModels
Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized
);
}
_isCoverAcrylicEffectAmountChanged = false;
}
}
}

View File

@@ -1,24 +1,17 @@
// 2025/6/23 by Zhe Fang
using ABI.Microsoft.UI.Xaml;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Helpers.General;
using Lyricify.Lyrics.Providers;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.Graphics.Display;
using Microsoft.UI;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -42,16 +35,17 @@ namespace BetterLyrics.WinUI3.ViewModels
private int _songDurationMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
private Stopwatch? _drawFrameStopwatch;
private int _drawFrameCount = 0;
private int _displayedDrawFrameCount = 0;
private SoftwareBitmap? _lastAlbumArtSwBitmap = null;
private SoftwareBitmap? _albumArtSwBitmap = null;
private CanvasBitmap? _lastAlbumArtCanvasBitmap = null;
private CanvasBitmap? _albumArtCanvasBitmap = null;
private bool _albumArtChanged = false;
private CanvasBitmap? _coverAcrylicNoiseCanvasBitmap = null;
private bool _isCoverAcrylicEffectAmountChanged = false;
private float _albumArtSize = 0f;
private int _albumArtCornerRadius = 0;
@@ -189,6 +183,11 @@ namespace BetterLyrics.WinUI3.ViewModels
TrimmingSign = CanvasTrimmingSign.Ellipsis,
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
};
private CanvasTextFormat _debugTextFormat = new()
{
FontSize = 12,
FontWeight = FontWeights.ExtraBlack,
};
private LatestOnlyTaskRunner _refreshLyricsRunner = new();
private LatestOnlyTaskRunner _showTranslationsRunner = new();
@@ -392,14 +391,12 @@ namespace BetterLyrics.WinUI3.ViewModels
if (e.AlbumArtSwBitmap != _albumArtSwBitmap)
{
//_lastAlbumArtSwBitmap?.Dispose();
_lastAlbumArtSwBitmap = null;
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
//_lastAlbumArtCanvasBitmap?.Dispose();
_lastAlbumArtCanvasBitmap = null;
//_albumArtSwBitmap?.Dispose();
_albumArtSwBitmap = null;
_albumArtSwBitmap = e.AlbumArtSwBitmap;
//_albumArtCanvasBitmap?.Dispose();
_albumArtCanvasBitmap = null;
_albumArtChanged = true;
@@ -543,12 +540,12 @@ namespace BetterLyrics.WinUI3.ViewModels
switch (provider)
{
case Enums.LyricsSearchProvider.QQ:
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.QQTranslationCacheDirectory);
break;
case Enums.LyricsSearchProvider.Kugou:
break;
case Enums.LyricsSearchProvider.Netease:
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.NeteaseTranslationCacheDirectory);
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.NeteaseTranslationCacheDirectory);
break;
case Enums.LyricsSearchProvider.LrcLib:
break;

View File

@@ -105,18 +105,18 @@ namespace BetterLyrics.WinUI3.ViewModels
if (LocalMediaFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"));
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"), InfoBarSeverity.Warning);
}
else if (LocalMediaFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
{
// 添加的文件夹是现有文件夹的子文件夹
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo"));
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo"), InfoBarSeverity.Warning);
}
else if (LocalMediaFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
)
{
// 添加的文件夹是现有文件夹的父文件夹
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo"));
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo"), InfoBarSeverity.Warning);
}
else
{
@@ -136,19 +136,19 @@ namespace BetterLyrics.WinUI3.ViewModels
[RelayCommand]
private static async Task OpenCacheFolderAsync()
{
await Windows.System.Launcher.LaunchFolderPathAsync(PathHelper.CacheFolder);
await Windows.System.Launcher.LaunchFolderPathAsync(Helper.PathHelper.CacheFolder);
}
[RelayCommand]
private static void RestartApp()
{
WindowHelper.RestartApp();
Helper.WindowHelper.RestartApp();
}
[RelayCommand]
private async Task SelectAndAddFolderAsync(UIElement sender)
{
var window = WindowHelper.GetWindowByWindowType<SettingsWindow>();
var window = Helper.WindowHelper.GetWindowByWindowType<SettingsWindow>();
if (window == null) return;
var picker = new Windows.Storage.Pickers.FolderPicker();
@@ -177,18 +177,20 @@ namespace BetterLyrics.WinUI3.ViewModels
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, null);
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
IsLibreTranslateServerTesting = false;
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), InfoBarSeverity.Success);
});
}
catch (Exception)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestFailedInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
IsLibreTranslateServerTesting = false;
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestFailedInfo"), InfoBarSeverity.Error);
});
}
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
IsLibreTranslateServerTesting = false;
});
});
}
@@ -201,9 +203,14 @@ namespace BetterLyrics.WinUI3.ViewModels
bool testResult = await NetHelper.CheckConnectivity($"{LXMusicServer}/status");
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(
App.ResourceLoader!.GetString($"SettingsPageServerTest{(testResult ? "Success" : "Failed")}Info"),
testResult ? InfoBarSeverity.Success : InfoBarSeverity.Error);
if (testResult)
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), InfoBarSeverity.Success);
}
else
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestFailedInfo"), InfoBarSeverity.Error);
}
IsLXMusicServerTesting = false;
});
});

View File

@@ -37,12 +37,7 @@ namespace BetterLyrics.WinUI3.ViewModels
[RelayCommand]
private static void ExitApp()
{
LyricsWindow? lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow != null)
{
DockModeHelper.Disable(lyricsWindow);
}
Environment.Exit(0);
WindowHelper.ExitApp();
}
[RelayCommand]

View File

@@ -113,6 +113,7 @@
x:Name="SettingsFlyoutItem"
x:Uid="SystemTraySettings"
Click="SettingsMenuFlyoutItem_Click" />
<MenuFlyoutItem x:Uid="SystemTrayExit" Click="ExitAppMenuFlyoutItem_Click" />
</MenuFlyout>
</Button.Flyout>
</Button>

View File

@@ -41,8 +41,7 @@ namespace BetterLyrics.WinUI3.Views
private void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
{
DockModeHelper.Disable(this);
Environment.Exit(0);
WindowHelper.ExitApp();
}
public void UpdateTitleBarArea()
@@ -267,11 +266,6 @@ namespace BetterLyrics.WinUI3.Views
}
}
private void TipContainerCenter_Loaded(object sender, RoutedEventArgs e)
{
App.Current.LyricsWindowNotificationPanel = TipContainerCenter;
}
private void RootGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
ViewModel.IsMouseWithinWindow = true;
@@ -308,5 +302,15 @@ namespace BetterLyrics.WinUI3.Views
{
WindowHelper.OpenWindow<MusicGalleryWindow>();
}
private void ExitAppMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
WindowHelper.ExitApp();
}
private void TipContainerCenter_Loaded(object sender, RoutedEventArgs e)
{
App.Current.LyricsWindowNotificationPanel = TipContainerCenter;
}
}
}

View File

@@ -1078,16 +1078,5 @@
</ScrollViewer>
</NavigationView>
<scontrols:NotificationPanel
x:Name="TipContainerCenter"
Margin="0,0,0,52"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Canvas.ZIndex="1"
FlowDirection="RightToLeft"
Loaded="TipContainerCenter_Loaded"
Orientation="Vertical"
Visibility="Collapsed" />
</Grid>
</Page>

View File

@@ -89,11 +89,6 @@ namespace BetterLyrics.WinUI3.Views
}
}
private void TipContainerCenter_Loaded(object sender, RoutedEventArgs e)
{
App.Current.SettingsWindowNotificationPanel = TipContainerCenter;
}
private async void AutoStartupToggleSwitch_Loaded(object sender, RoutedEventArgs e)
{
AutoStartupToggleSwitch.IsOn = await ViewModel.DetectIsAutoStartupEnabledAsync();

View File

@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:scontrols="using:ShadowViewer.Controls"
mc:Ignorable="d">
<Window.SystemBackdrop>
@@ -14,6 +15,17 @@
<Grid x:Name="RootGrid">
<Frame x:Name="RootFrame" />
<scontrols:NotificationPanel
x:Name="TipContainerCenter"
Margin="0,0,0,52"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Canvas.ZIndex="1"
FlowDirection="RightToLeft"
Loaded="TipContainerCenter_Loaded"
Orientation="Vertical"
Visibility="Collapsed" />
</Grid>
</Window>

View File

@@ -20,5 +20,10 @@ namespace BetterLyrics.WinUI3.Views
RootFrame.Navigate(typeof(SettingsPage));
}
private void TipContainerCenter_Loaded(object sender, RoutedEventArgs e)
{
App.Current.SettingsWindowNotificationPanel = TipContainerCenter;
}
}
}

14
FAQ/FAQ.md Normal file
View File

@@ -0,0 +1,14 @@
## FAQ has been moved
[Click here to visit](https://github.com/jayfunc/BetterLyrics?tab=readme-ov-file#faq)
## FAQ 现已迁移
[点此访问](https://github.com/jayfunc/BetterLyrics/blob/dev/README.zh-CN.md#faq)
## FAQ 已移至新位置
[點此前往](https://github.com/jayfunc/BetterLyrics/blob/dev/README.zh-TW.md#faq)
## FAQ は移動しました
[こちらをクリック](https://github.com/jayfunc/BetterLyrics/blob/dev/README.ja.md#faq)
## FAQ가 이동되었습니다
[여기를 클릭하세요](https://github.com/jayfunc/BetterLyrics/blob/dev/README.ko.md#faq)

44
Promotion/en.txt Normal file
View File

@@ -0,0 +1,44 @@
* The free trial version is identical to the paid version — no feature limitations.
🎵 BetterLyrics — Lyrics that move you, visuals that delight. A stunning lyrics display tool for Windows.
Looking for a smooth, dynamic, and immersive way to view lyrics on Windows?
BetterLyrics delivers a whole new visual experience — making every song truly sing on screen.
✨ Key Features
- Beautiful UI with smooth animations
Built with WinUI 3, featuring multiple themes and dynamic backgrounds. Enjoy seamless lyric scrolling, fade effects, and rich visuals.
- Offline translation with support for 30 languages
Automatically reads translation lines embedded in local lyrics files. Multi-language support helps you understand every lyric, no internet needed.
- Supports multiple lyric sources and formats
Reads local lyrics in LRC, ESLRC, TTML, and other formats. Also fetches lyrics online from major sources like QQ Music, NetEase Cloud Music, Kugou, and LrcLib.
- Real-time sync with various music players
Compatible with NetEase Cloud, Kugou, Apple Music, Spotify, PotPlayer, MusicBee, AIMP, and even the default Windows player. Lyrics sync automatically—no setup required.
- Multiple display modes to suit your workflow
Switch easily between immersive full-screen mode, floating desktop lyrics, and smart window docking to fit any listening scenario.
- Auto-hide when playback is paused
The interface stays out of your way — lyrics automatically hide when music stops, keeping your screen clean and distraction-free.
🔧 Extra Features Youll Love
- Fine-tune lyric timing with offset adjustment — perfect for mismatched audio versions
- Toggle translation, customize layout, pin or lock windows — your lyrics, your way
- Active community, frequent updates, and responsive support for user feedback
✅ Perfect For:
- Users who want floating desktop lyrics to elevate their listening experience
- Listeners who use multiple players or need support for various lyric formats
- Fans of beautiful, customizable visuals in their music tools
🎧 Whether you're immersed in music through headphones or want lyrics to stay visible on your screen, BetterLyrics gives you a personalized, immersive way to experience every song.

44
Promotion/ja.txt Normal file
View File

@@ -0,0 +1,44 @@
* 無料体験版と有料版は機能に違いはありません。
🎵 BetterLyrics — 歌詞が心を動かし、目を楽しませる。高品質なWindows用歌詞表示ツール
Windowsで滑らかでダイナミック、没入感のある歌詞表示を体験しませんか
BetterLyricsは全く新しいビジュアル体験を提供し、すべての曲が画面上でまるで「歌っている」ように感じられます。
✨ 主な機能
- 美しいインターフェースと滑らかなアニメーション
WinUI 3をベースに開発され、複数のテーマや動的な背景をサポート。歌詞のスクロールやフェード効果など、視覚的に洗練された演出を楽しめます。
- オフライン翻訳対応、30言語サポート
ローカルの歌詞ファイルに含まれる翻訳を自動で読み込み表示。インターネット接続なしで多言語の歌詞理解をサポートします。
- 多様な歌詞フォーマットとソースに対応
LRC、ESLRC、TTMLなどのローカル歌詞ファイルを読み込み、さらにQQミュージック、NetEaseクラウドミュージック、Kugou、LrcLibなどの主要オンラインソースから歌詞を取得可能です。
- 多くの音楽プレイヤーとリアルタイム同期
NetEaseクラウドミュージック、Kugou、Apple Music、Spotify、PotPlayer、MusicBee、AIMP、そしてWindows標準プレイヤーまで幅広く対応。歌詞は自動的に同期され、手動設定は不要です。
- 多彩な表示モードを自由に切り替え
没入型の全画面モード、デスクトップに浮かぶ歌詞、スマートウィンドウドッキングなど、用途に合わせて切り替え可能。
- 再生停止時には自動で非表示
音楽が一時停止すると歌詞画面が自動的に隠れ、画面を邪魔せずスッキリ使えます。
🔧 便利な追加機能
- 微調整可能な歌詞タイムラインオフセットで、音源のズレをぴったり補正
- 翻訳表示のオンオフ、レイアウトのカスタマイズ、ウィンドウの固定・ロックなどの高度な設定
- 活発なユーザーコミュニティによる継続的なアップデートと迅速なフィードバック対応
✅ こんな方におすすめ
- デスクトップに歌詞を浮かべて、より豊かな音楽体験を求める方
- 複数のプレイヤーや多様な歌詞フォーマットを使いたい方
- 美しくカスタマイズ可能なUIを好む方
🎧 ヘッドホンで没入して聴くときも、デスクトップに歌詞を表示して楽しむときも、BetterLyricsがあなたの音楽体験をより特別なものにします。

45
Promotion/ko.txt Normal file
View File

@@ -0,0 +1,45 @@
* 무료 체험판과 유료 버전은 기능 차이가 전혀 없습니다.
🎵 BetterLyrics — 가사는 눈으로, 감동은 마음으로. 고품질 Windows용 가사 디스플레이 도구
Windows에서 부드럽고 역동적이며 몰입감 있는 가사 표시를 경험해보세요.
BetterLyrics는 완전히 새로운 시각적 음악 경험을 선사합니다. 이제 모든 곡이 화면 위에서 살아 숨쉽니다.
✨ 주요 기능
- 세련된 인터페이스와 부드러운 애니메이션
WinUI 3 기반으로 개발되었으며 다양한 테마와 동적 배경을 지원합니다. 가사 스크롤, 페이드 효과 등 시각적 효과가 매끄럽고 아름답습니다.
- 30개 언어를 지원하는 오프라인 번역 기능
로컬 가사 파일 내에 포함된 번역 내용을 자동으로 읽어와 표시합니다. 인터넷 연결 없이도 다양한 언어의 가사를 이해할 수 있습니다.
- 다양한 가사 형식 및 소스 지원
LRC, ESLRC, TTML 등 로컬 파일 형식을 지원하며, QQ 음악, 넷이즈 클라우드, 쿠거우, LrcLib 등 주요 온라인 소스에서 가사를 자동으로 가져올 수 있습니다.
- 다양한 음악 플레이어와 실시간 동기화
넷이즈, 쿠거우, Apple Music, Spotify, PotPlayer, MusicBee, AIMP, 그리고 Windows 기본 플레이어까지 폭넓게 호환됩니다. 가사는 자동으로 동기화되어 별도 설정 없이도 편리하게 사용 가능합니다.
- 다양한 디스플레이 모드 제공
몰입형 전체 화면, 바탕화면 가사 띄우기, 스마트 윈도우 도킹 등 다양한 사용 환경에 맞춰 자유롭게 전환할 수 있습니다.
- 자동 숨김 기능으로 깔끔한 화면 유지
음악이 일시정지되면 가사 창이 자동으로 숨겨져 화면을 방해하지 않고 깔끔함을 유지합니다.
🔧 세심한 부가기능
- 가사 싱크를 미세하게 조정할 수 있는 타임라인 오프셋 기능 제공
- 번역 표시 여부, 레이아웃 설정, 창 고정 및 잠금 등 고급 옵션 지원
- 활발한 사용자 커뮤니티 운영 중이며, 지속적인 업데이트와 피드백 반영
✅ 이런 분께 추천합니다
- 바탕화면에 가사를 띄워 감성적인 음악 감상을 원하는 분
- 다양한 플레이어나 가사 형식을 사용하는 분
- 세련되고 사용자 맞춤형 UI를 선호하는 분
🎧 헤드폰을 쓰고 몰입해서 듣든, 바탕화면에 가사를 띄워 놓고 감상하든 BetterLyrics는 여러분만의 음악 감상 경험을 완성해드립니다.

44
Promotion/zh-CN.txt Normal file
View File

@@ -0,0 +1,44 @@
* 免费试用版与付费版无任何区别
🎵 BetterLyrics —— 曲中人心、词悦双眼,高颜值 Windows 歌词展示工具
想在 Windows 上体验流畅、动态、沉浸的歌词显示效果?
BetterLyrics 打造全新视觉体验,让每一首歌都真正“唱”给你看!
✨ 核心功能亮点
- 界面美观,动画丝滑
基于 WinUI 3 框架开发,支持多种主题风格与动态背景,歌词滚动、淡入淡出等特效令人眼前一亮。
- 可配置接入离线翻译,支持 30 种语言
自动读取本地歌词中的翻译内容,支持多语言自动识别与显示,助你听懂每一句歌词。
- 支持多种歌词来源与格式
可读取本地的 LRC、ESLRC、TTML 等格式歌词文件,同时支持从在线源获取歌词,包括 QQ 音乐、网易云、酷狗音乐与 LrcLib 等主流平台。
- 多播放器实时同步
支持网易云音乐、酷狗音乐、Apple Music、Spotify、PotPlayer、MusicBee、AIMP 等,系统自带播放器也能用,歌词自动同步无需手动干预。
- 多种显示模式随心切换
沉浸式全屏、桌面悬浮歌词、窗口智能停靠,满足你不同场景的需求。
- 自动隐藏,安静陪伴
播放器暂停时自动隐藏歌词界面,不打扰、不占屏,清爽使用体验。
🔧 更多贴心功能
- 歌词时间轴支持偏移微调,解决不同版本音频对不上的问题
- 支持歌词翻译开关、界面布局自由设置、窗口置顶/锁定等高级功能
- 拥有活跃的用户社区,持续更新优化,问题反馈及时响应
✅ 推荐使用场景
- 想要桌面歌词悬浮,提升听歌氛围
- 需要跨平台、多格式的歌词显示支持
- 喜欢细节丰富、界面可自定义的听歌工具
🎧 无论你是用耳机沉浸式聆听还是想让歌词在桌面随时可见BetterLyrics 都能为你打造专属的听歌体验!

44
Promotion/zh-TW.txt Normal file
View File

@@ -0,0 +1,44 @@
* 免費試用版與付費版功能完全一致
🎵 BetterLyrics —— 曲動人心、詞悅雙眼,高顏值 Windows 歌詞顯示工具
想在 Windows 上體驗流暢、動態、沉浸感十足的歌詞顯示效果?
BetterLyrics 為你打造全新視覺享受,讓每一首歌都真正「唱」給你看!
✨ 核心功能亮點
- 介面美觀,動畫流暢
採用 WinUI 3 框架打造,支援多種主題風格與動態背景,歌詞滾動、淡入淡出等特效精緻細膩。
- 可設定離線翻譯,支援 30 種語言
自動讀取本地歌詞中的翻譯內容,支援多語系辨識與顯示,幫你聽懂每一句歌詞。
- 支援多種歌詞來源與格式
可讀取本地的 LRC、ESLRC、TTML 等格式歌詞檔,同時支援從線上來源獲取歌詞,包括 QQ 音樂、網易雲、酷狗音樂與 LrcLib 等主流平台。
- 多播放器即時同步
相容網易雲音樂、酷狗音樂、Apple Music、Spotify、PotPlayer、MusicBee、AIMP 等播放器,甚至支援系統內建播放器,歌詞自動同步無須手動調整。
- 多種顯示模式自由切換
沉浸式全螢幕、桌面浮動歌詞、智慧貼齊窗口等模式,靈活搭配不同使用場景。
- 自動隱藏,安靜陪伴
音樂暫停時自動隱藏歌詞介面,不打擾、不佔螢幕,帶來更清爽的使用體驗。
🔧 更多貼心功能
- 歌詞時間軸支援細微調整,完美對齊不同版本的音訊
- 支援翻譯開關、介面版面自定義、窗口鎖定與置頂等進階選項
- 活躍用戶社群持續參與、版本持續優化、問題回饋即時處理
✅ 適用場景推薦
- 想要桌面浮動歌詞,增強聽歌氛圍
- 需要跨平台、多格式的歌詞支援
- 喜愛細節豐富、介面可自訂的聽歌工具
🎧 無論你是戴上耳機沉浸聆聽還是想讓歌詞隨時浮現在桌面BetterLyrics 都能為你打造專屬的聽歌體驗!

2
README.CN.md Normal file
View File

@@ -0,0 +1,2 @@
## 简体中文文档现已迁移
[点此访问](https://github.com/jayfunc/BetterLyrics/blob/dev/README.zh-CN.md)

BIN
image.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB