mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 19:24:55 +08:00
chore: split renderer viewmodel
This commit is contained in:
@@ -10,6 +10,14 @@
|
||||
<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>
|
||||
<Content Include="Logo.ico" />
|
||||
</ItemGroup>
|
||||
@@ -18,18 +26,12 @@
|
||||
</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.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.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" />
|
||||
@@ -66,7 +68,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Controls\" />
|
||||
<Folder Include="ViewModels\Lyrics\" />
|
||||
</ItemGroup>
|
||||
<!--Disable Trimming for Specific Packages-->
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
@@ -27,15 +27,18 @@ namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLrcLib"
|
||||
),
|
||||
//LyricsSearchProvider.AmllTtmlDb => App.ResourceLoader!.GetString(
|
||||
// "LyricsSearchProviderAmllTtmlDb"
|
||||
//),
|
||||
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLocalLrcFile"
|
||||
),
|
||||
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLocalMusicFile"
|
||||
),
|
||||
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLrcLib"
|
||||
),
|
||||
LyricsSearchProvider.LocalEslrcFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderEslrcFile"
|
||||
),
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LineMaskType
|
||||
{
|
||||
Glow,
|
||||
Highlight,
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
/// </summary>
|
||||
LrcLib,
|
||||
|
||||
//AmllTtmlDb,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the LocalMusicFile
|
||||
/// </summary>
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public ValueTransition(
|
||||
T initialValue,
|
||||
float durationSeconds,
|
||||
Func<T, T, float, T> interpolator = null,
|
||||
Func<T, T, float, T>? interpolator = null,
|
||||
EasingType? easingType = null
|
||||
)
|
||||
{
|
||||
|
||||
@@ -78,8 +78,14 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
/// <summary>
|
||||
/// Gets the OnlineLyricsCacheDirectory
|
||||
/// </summary>
|
||||
public static string OnlineLyricsCacheDirectory =>
|
||||
Path.Combine(CacheFolder, "online-lyrics");
|
||||
public static string LrcLibLyricsCacheDirectory =>
|
||||
Path.Combine(CacheFolder, "lrclib-lyrics");
|
||||
|
||||
public static string AmllTtmlDbLyricsCacheDirectory =>
|
||||
Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
|
||||
|
||||
public static string AmllTtmlDbIndexPath =>
|
||||
Path.Combine(CacheFolder, "amll-ttml-db-index.json");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TestMusicPath
|
||||
@@ -109,7 +115,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
Directory.CreateDirectory(LocalFolder);
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
Directory.CreateDirectory(OnlineLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Xaml;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
using static System.Net.WebRequestMethods;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class DesktopModeHelper
|
||||
{
|
||||
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyles = [];
|
||||
private static readonly Dictionary<IntPtr, bool> _clickThroughStates = [];
|
||||
private static readonly Dictionary<IntPtr, bool> _originalTopmostStates = [];
|
||||
private static readonly Dictionary<IntPtr, nint> _oldWndProcs = [];
|
||||
private static readonly Dictionary<IntPtr, WndProcDelegate> _wndProcDelegates = [];
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><E0BBAF><EFBFBD><EFBFBD>
|
||||
private delegate nint WndProcDelegate(nint hWnd, uint msg, nint wParam, nint lParam);
|
||||
|
||||
private const int WM_NCHITTEST = 0x0084;
|
||||
private const int HTCLIENT = 1;
|
||||
private const int HTTRANSPARENT = -1;
|
||||
private const int GWL_WNDPROC = -4;
|
||||
|
||||
public static void Enable(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ<EFBFBD><CABD><EFBFBD><CDB8><EFBFBD><EFBFBD>
|
||||
if (!_originalWindowStyles.ContainsKey(hwnd))
|
||||
_originalWindowStyles[hwnd] = window.GetWindowStyle();
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬
|
||||
if (!_originalTopmostStates.ContainsKey(hwnd))
|
||||
_originalTopmostStates[hwnd] = IsWindowTopMost(hwnd);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ޱ߿<DEB1><DFBF><EFBFBD><EFBFBD><CDB8>
|
||||
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
|
||||
// <20><><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD>ö<EFBFBD>
|
||||
SetWindowTopMost(hwnd, true);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ȫ<EFBFBD>ִ<EFBFBD>
|
||||
SetClickThrough(window, true);
|
||||
|
||||
// <20><><EFBFBD>þֲ<C3BE><D6B2><EFBFBD>
|
||||
EnablePartialClickThrough(window);
|
||||
}
|
||||
|
||||
public static void Disable(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
// <20>ָ<EFBFBD><D6B8><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><CDB8><EFBFBD><EFBFBD>
|
||||
if (_originalWindowStyles.TryGetValue(hwnd, out var style))
|
||||
{
|
||||
window.SetWindowStyle(style);
|
||||
_originalWindowStyles.Remove(hwnd);
|
||||
}
|
||||
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
// <20>ָ<EFBFBD>TopMost״̬
|
||||
if (_originalTopmostStates.TryGetValue(hwnd, out var wasTopMost))
|
||||
{
|
||||
SetWindowTopMost(hwnd, wasTopMost);
|
||||
_originalTopmostStates.Remove(hwnd);
|
||||
}
|
||||
|
||||
// <20>رյ<D8B1><D5B5><EFBFBD><EFBFBD><EFBFBD>
|
||||
SetClickThrough(window, false);
|
||||
|
||||
// <20>رվֲ<D5BE><D6B2><EFBFBD>
|
||||
DisablePartialClickThrough(window);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7>ö<EFBFBD>
|
||||
/// </summary>
|
||||
private static void SetWindowTopMost(IntPtr hwnd, bool topmost)
|
||||
{
|
||||
const int SWP_NOMOVE = 0x0002;
|
||||
const int SWP_NOSIZE = 0x0001;
|
||||
const int SWP_NOACTIVATE = 0x0010;
|
||||
IntPtr hWndInsertAfter = topmost ? (IntPtr)(-1) : (IntPtr)(1); // HWND_TOPMOST / HWND_NOTOPMOST
|
||||
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
hWndInsertAfter,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20>жϴ<D0B6><CFB4><EFBFBD><EFBFBD>Ƿ<EFBFBD>Ϊ<EFBFBD>ö<EFBFBD>
|
||||
/// </summary>
|
||||
private static bool IsWindowTopMost(IntPtr hwnd)
|
||||
{
|
||||
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
return (exStyle & WS_EX_TOPMOST) == WS_EX_TOPMOST;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20>л<EFBFBD><D0BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>״̬
|
||||
/// </summary>
|
||||
public static void SetClickThrough(Window window, bool enable)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
if (enable)
|
||||
{
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TRANSPARENT | WS_EX_LAYERED);
|
||||
_clickThroughStates[hwnd] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TRANSPARENT);
|
||||
_clickThroughStates[hwnd] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20><>ȡ<EFBFBD><C8A1>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>״̬
|
||||
/// </summary>
|
||||
public static bool GetClickThrough(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
return _clickThroughStates.TryGetValue(hwnd, out var state) && state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD>þֲ<C3BE><D6B2><EFBFBD>
|
||||
/// </summary>
|
||||
public static void EnablePartialClickThrough(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
if (_oldWndProcs.ContainsKey(hwnd))
|
||||
return; // <20>Ѿ<EFBFBD><D1BE><EFBFBD><EFBFBD>
|
||||
|
||||
WndProcDelegate newWndProc = (hWnd, msg, wParam, lParam) =>
|
||||
{
|
||||
if (msg == WM_NCHITTEST)
|
||||
{
|
||||
int x = (short)(lParam.ToInt32() & 0xFFFF);
|
||||
int y = (short)((lParam.ToInt32() >> 16) & 0xFFFF);
|
||||
|
||||
// <20><>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD>ת<EFBFBD><D7AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(
|
||||
Win32Interop.GetWindowIdFromWindow(hwnd)
|
||||
);
|
||||
var windowPos = appWindow.Position;
|
||||
int localX = x - windowPos.X;
|
||||
int localY = y - windowPos.Y;
|
||||
|
||||
if (IsInInteractiveRegion(window, localX, localY))
|
||||
return HTCLIENT;
|
||||
else
|
||||
return HTTRANSPARENT;
|
||||
}
|
||||
return CallWindowProc(_oldWndProcs[hwnd], hWnd, msg, wParam, lParam);
|
||||
};
|
||||
|
||||
nint oldWndProc = SetWindowLongPtr(
|
||||
hwnd,
|
||||
GWL_WNDPROC,
|
||||
Marshal.GetFunctionPointerForDelegate(newWndProc)
|
||||
);
|
||||
_oldWndProcs[hwnd] = oldWndProc;
|
||||
_wndProcDelegates[hwnd] = newWndProc; // <20><>ֹGC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20>رվֲ<D5BE><D6B2><EFBFBD>
|
||||
/// </summary>
|
||||
public static void DisablePartialClickThrough(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
if (_oldWndProcs.TryGetValue(hwnd, out var oldWndProc))
|
||||
{
|
||||
SetWindowLongPtr(hwnd, GWL_WNDPROC, oldWndProc);
|
||||
_oldWndProcs.Remove(hwnd);
|
||||
_wndProcDelegates.Remove(hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20>жϵ<D0B6><CFB5>Ƿ<EFBFBD><C7B7>ڿɽ<DABF><C9BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˴<F2A3A8B4>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD>ϰ벿<CFB0>֣<EFBFBD><D6A3><EFBFBD><EFBFBD>Զ<EFBFBD><D4B6>壩
|
||||
/// </summary>
|
||||
private static bool IsInInteractiveRegion(Window window, int x, int y)
|
||||
{
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϰ벿<CFB0>ֿɽ<D6BF><C9BD><EFBFBD>
|
||||
var bounds = window.Bounds;
|
||||
return y < bounds.Height / 2;
|
||||
}
|
||||
|
||||
#region Win32
|
||||
|
||||
private const int GWL_EXSTYLE = -20;
|
||||
private const int WS_EX_TRANSPARENT = 0x00000020;
|
||||
private const int WS_EX_LAYERED = 0x00080000;
|
||||
private const int WS_EX_TOPMOST = 0x00000008;
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool SetWindowPos(
|
||||
IntPtr hWnd,
|
||||
IntPtr hWndInsertAfter,
|
||||
int X,
|
||||
int Y,
|
||||
int cx,
|
||||
int cy,
|
||||
uint uFlags
|
||||
);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern nint SetWindowLongPtr(nint hWnd, int nIndex, nint dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern nint CallWindowProc(
|
||||
nint lpPrevWndFunc,
|
||||
nint hWnd,
|
||||
uint msg,
|
||||
nint wParam,
|
||||
nint lParam
|
||||
);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ using WinUIEx;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class DockHelper
|
||||
public static class DockModeHelper
|
||||
{
|
||||
private static readonly HashSet<IntPtr> _registered = [];
|
||||
|
||||
@@ -1,92 +1,31 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ForegroundWindowWatcherHelper" />
|
||||
/// </summary>
|
||||
public class ForegroundWindowWatcherHelper
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// Defines the EVENT_OBJECT_LOCATIONCHANGE
|
||||
/// </summary>
|
||||
private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the EVENT_SYSTEM_FOREGROUND
|
||||
/// </summary>
|
||||
private const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the EVENT_SYSTEM_MINIMIZEEND
|
||||
/// </summary>
|
||||
private const uint EVENT_SYSTEM_MINIMIZEEND = 0x0017;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the ThrottleIntervalMs
|
||||
/// </summary>
|
||||
private readonly WinEventDelegate _winEventDelegate;
|
||||
private readonly List<IntPtr> _hooks = new();
|
||||
private IntPtr _currentForeground = IntPtr.Zero;
|
||||
private readonly IntPtr _selfHwnd;
|
||||
private readonly DispatcherTimer _pollingTimer;
|
||||
private DateTime _lastEventTime = DateTime.MinValue;
|
||||
private const int ThrottleIntervalMs = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the WINEVENT_OUTOFCONTEXT
|
||||
/// </summary>
|
||||
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _hooks
|
||||
/// </summary>
|
||||
private readonly List<IntPtr> _hooks = new();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _onWindowChanged
|
||||
/// </summary>
|
||||
public delegate void WindowChangedHandler(IntPtr hwnd);
|
||||
private readonly WindowChangedHandler _onWindowChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _pollingTimer
|
||||
/// </summary>
|
||||
private readonly DispatcherTimer _pollingTimer;
|
||||
private const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
|
||||
private const uint EVENT_SYSTEM_MINIMIZEEND = 0x0017;
|
||||
private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
|
||||
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _selfHwnd
|
||||
/// </summary>
|
||||
private readonly IntPtr _selfHwnd;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _winEventDelegate
|
||||
/// </summary>
|
||||
private readonly WinEventDelegate _winEventDelegate;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _currentForeground
|
||||
/// </summary>
|
||||
private IntPtr _currentForeground = IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _lastEventTime
|
||||
/// </summary>
|
||||
private DateTime _lastEventTime = DateTime.MinValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ForegroundWindowWatcherHelper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="selfHwnd">The selfHwnd<see cref="IntPtr"/></param>
|
||||
/// <param name="onWindowChanged">The onWindowChanged<see cref="WindowChangedHandler"/></param>
|
||||
public ForegroundWindowWatcherHelper(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
|
||||
{
|
||||
_selfHwnd = selfHwnd;
|
||||
@@ -101,43 +40,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delegates
|
||||
|
||||
/// <summary>
|
||||
/// The WindowChangedHandler
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The hwnd<see cref="IntPtr"/></param>
|
||||
public delegate void WindowChangedHandler(IntPtr hwnd);
|
||||
|
||||
/// <summary>
|
||||
/// The WinEventDelegate
|
||||
/// </summary>
|
||||
/// <param name="hWinEventHook">The hWinEventHook<see cref="IntPtr"/></param>
|
||||
/// <param name="eventType">The eventType<see cref="uint"/></param>
|
||||
/// <param name="hwnd">The hwnd<see cref="IntPtr"/></param>
|
||||
/// <param name="idObject">The idObject<see cref="int"/></param>
|
||||
/// <param name="idChild">The idChild<see cref="int"/></param>
|
||||
/// <param name="dwEventThread">The dwEventThread<see cref="uint"/></param>
|
||||
/// <param name="dwmsEventTime">The dwmsEventTime<see cref="uint"/></param>
|
||||
private delegate void WinEventDelegate(
|
||||
IntPtr hWinEventHook,
|
||||
uint eventType,
|
||||
IntPtr hwnd,
|
||||
int idObject,
|
||||
int idChild,
|
||||
uint dwEventThread,
|
||||
uint dwmsEventTime
|
||||
);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The Start
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
// Hook: foreground changes and minimize end
|
||||
@@ -169,9 +71,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_pollingTimer.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Stop
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
foreach (var hook in _hooks)
|
||||
@@ -181,46 +80,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_pollingTimer.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SetWinEventHook
|
||||
/// </summary>
|
||||
/// <param name="eventMin">The eventMin<see cref="uint"/></param>
|
||||
/// <param name="eventMax">The eventMax<see cref="uint"/></param>
|
||||
/// <param name="hmodWinEventProc">The hmodWinEventProc<see cref="IntPtr"/></param>
|
||||
/// <param name="lpfnWinEventProc">The lpfnWinEventProc<see cref="WinEventDelegate"/></param>
|
||||
/// <param name="idProcess">The idProcess<see cref="uint"/></param>
|
||||
/// <param name="idThread">The idThread<see cref="uint"/></param>
|
||||
/// <param name="dwFlags">The dwFlags<see cref="uint"/></param>
|
||||
/// <returns>The <see cref="IntPtr"/></returns>
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SetWinEventHook(
|
||||
uint eventMin,
|
||||
uint eventMax,
|
||||
IntPtr hmodWinEventProc,
|
||||
WinEventDelegate lpfnWinEventProc,
|
||||
uint idProcess,
|
||||
uint idThread,
|
||||
uint dwFlags
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// The UnhookWinEvent
|
||||
/// </summary>
|
||||
/// <param name="hWinEventHook">The hWinEventHook<see cref="IntPtr"/></param>
|
||||
/// <returns>The <see cref="bool"/></returns>
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
|
||||
|
||||
/// <summary>
|
||||
/// The WinEventProc
|
||||
/// </summary>
|
||||
/// <param name="hWinEventHook">The hWinEventHook<see cref="IntPtr"/></param>
|
||||
/// <param name="eventType">The eventType<see cref="uint"/></param>
|
||||
/// <param name="hwnd">The hwnd<see cref="IntPtr"/></param>
|
||||
/// <param name="idObject">The idObject<see cref="int"/></param>
|
||||
/// <param name="idChild">The idChild<see cref="int"/></param>
|
||||
/// <param name="dwEventThread">The dwEventThread<see cref="uint"/></param>
|
||||
/// <param name="dwmsEventTime">The dwmsEventTime<see cref="uint"/></param>
|
||||
private void WinEventProc(
|
||||
IntPtr hWinEventHook,
|
||||
uint eventType,
|
||||
@@ -254,6 +113,30 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
#region WinAPI
|
||||
private delegate void WinEventDelegate(
|
||||
IntPtr hWinEventHook,
|
||||
uint eventType,
|
||||
IntPtr hwnd,
|
||||
int idObject,
|
||||
int idChild,
|
||||
uint dwEventThread,
|
||||
uint dwmsEventTime
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SetWinEventHook(
|
||||
uint eventMin,
|
||||
uint eventMax,
|
||||
IntPtr hmodWinEventProc,
|
||||
WinEventDelegate lpfnWinEventProc,
|
||||
uint idProcess,
|
||||
uint idThread,
|
||||
uint dwFlags
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
@@ -66,7 +66,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
/// <param name="durationMs">The durationMs<see cref="int"/></param>
|
||||
private void ParseLrc(string raw, int durationMs)
|
||||
{
|
||||
var lines = raw.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = raw.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
|
||||
var lrcLines =
|
||||
new List<(int time, string text, List<(int time, string text)> syllables)>();
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
int sec = int.Parse(m.Groups[2].Value);
|
||||
int ms = int.Parse(m.Groups[3].Value.PadRight(3, '0'));
|
||||
lineStartTime = min * 60_000 + sec * 1000 + ms;
|
||||
content = bracketRegex.Replace(line, "").Trim();
|
||||
content = bracketRegex.Replace(line, "");
|
||||
lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
|
||||
}
|
||||
}
|
||||
@@ -147,13 +147,21 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
};
|
||||
if (syllables != null && syllables.Count > 0)
|
||||
{
|
||||
int currentIndex = 0;
|
||||
for (int j = 0; j < syllables.Count; j++)
|
||||
{
|
||||
var (charStart, charText) = syllables[j];
|
||||
int charEnd = (j + 1 < syllables.Count) ? syllables[j + 1].Item1 : 0;
|
||||
int startIndex = currentIndex;
|
||||
line.CharTimings.Add(
|
||||
new CharTiming { StartMs = charStart, EndMs = charEnd }
|
||||
new CharTiming
|
||||
{
|
||||
StartMs = charStart,
|
||||
EndMs = 0, // Fixed later
|
||||
Text = charText ?? "",
|
||||
StartIndex = startIndex,
|
||||
}
|
||||
);
|
||||
currentIndex += charText?.Length ?? 0;
|
||||
}
|
||||
}
|
||||
_multiLangLyricsLines[langIdx].Add(line);
|
||||
@@ -167,20 +175,28 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
for (int i = 0; i < linesInSingleLang.Count; i++)
|
||||
{
|
||||
if (i + 1 < linesInSingleLang.Count)
|
||||
{
|
||||
linesInSingleLang[i].EndMs = linesInSingleLang[i + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
linesInSingleLang[i].EndMs = durationMs;
|
||||
}
|
||||
|
||||
// 修正 CharTimings 的最后一个 EndMs
|
||||
// 修正 CharTimings 的 EndMs
|
||||
var timings = linesInSingleLang[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 = linesInSingleLang[i].EndMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +236,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
)
|
||||
.ToList();
|
||||
|
||||
string text = string.Concat(spans.Select(s => s.Value));
|
||||
string text = string.Concat(spans.Select(s => s));
|
||||
var charTimings = new List<CharTiming>();
|
||||
|
||||
for (int i = 0; i < spans.Count; i++)
|
||||
@@ -244,7 +260,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
|
||||
if (spans.Count == 0)
|
||||
text = p.Value.Trim();
|
||||
text = p.Value;
|
||||
|
||||
singleLangLyricsLine.Add(
|
||||
new LyricsLine
|
||||
@@ -345,7 +361,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = lines[0].StartMs,
|
||||
Text = "",
|
||||
Text = "● ● ●",
|
||||
CharTimings = [],
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,53 +1,12 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="WindowColorHelper" />
|
||||
/// </summary>
|
||||
public static class WindowColorHelper
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SRCCOPY
|
||||
/// </summary>
|
||||
private const int SRCCOPY = 0x00CC0020;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SystemMetric
|
||||
/// </summary>
|
||||
private enum SystemMetric
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the SM_CXSCREEN
|
||||
/// </summary>
|
||||
SM_CXSCREEN = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SM_CYSCREEN
|
||||
/// </summary>
|
||||
SM_CYSCREEN = 1,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// The GetDominantColorBelow
|
||||
/// </summary>
|
||||
/// <param name="myHwnd">The myHwnd<see cref="IntPtr"/></param>
|
||||
/// <returns>The <see cref="Color"/></returns>
|
||||
public static Color GetDominantColorBelow(IntPtr myHwnd)
|
||||
{
|
||||
if (!GetWindowRect(myHwnd, out RECT myRect))
|
||||
@@ -60,37 +19,22 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The BitBlt
|
||||
/// </summary>
|
||||
/// <param name="hdcDest">The hdcDest<see cref="IntPtr"/></param>
|
||||
/// <param name="nXDest">The nXDest<see cref="int"/></param>
|
||||
/// <param name="nYDest">The nYDest<see cref="int"/></param>
|
||||
/// <param name="nWidth">The nWidth<see cref="int"/></param>
|
||||
/// <param name="nHeight">The nHeight<see cref="int"/></param>
|
||||
/// <param name="hdcSrc">The hdcSrc<see cref="IntPtr"/></param>
|
||||
/// <param name="nXSrc">The nXSrc<see cref="int"/></param>
|
||||
/// <param name="nYSrc">The nYSrc<see cref="int"/></param>
|
||||
/// <param name="dwRop">The dwRop<see cref="int"/></param>
|
||||
/// <returns>The <see cref="bool"/></returns>
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern bool BitBlt(
|
||||
IntPtr hdcDest,
|
||||
int nXDest,
|
||||
int nYDest,
|
||||
int nWidth,
|
||||
int nHeight,
|
||||
IntPtr hdcSrc,
|
||||
int nXSrc,
|
||||
int nYSrc,
|
||||
int dwRop
|
||||
);
|
||||
private static Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
|
||||
{
|
||||
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
|
||||
using Graphics gDest = Graphics.FromImage(bmp);
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = GetDC(IntPtr.Zero); // Entire screen
|
||||
|
||||
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, SRCCOPY);
|
||||
|
||||
gDest.ReleaseHdc(hdcDest);
|
||||
ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ComputeAverageColor
|
||||
/// </summary>
|
||||
/// <param name="bmp">The bmp<see cref="Bitmap"/></param>
|
||||
/// <returns>The <see cref="Color"/></returns>
|
||||
private static Color ComputeAverageColor(Bitmap bmp)
|
||||
{
|
||||
long r = 0,
|
||||
@@ -115,95 +59,48 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GetAverageColorFromScreenRegion
|
||||
/// </summary>
|
||||
/// <param name="x">The x<see cref="int"/></param>
|
||||
/// <param name="y">The y<see cref="int"/></param>
|
||||
/// <param name="width">The width<see cref="int"/></param>
|
||||
/// <param name="height">The height<see cref="int"/></param>
|
||||
/// <returns>The <see cref="Color"/></returns>
|
||||
private static Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
|
||||
{
|
||||
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
|
||||
using Graphics gDest = Graphics.FromImage(bmp);
|
||||
#region Win32 Imports & Structs
|
||||
private const int SRCCOPY = 0x00CC0020;
|
||||
|
||||
IntPtr hdcDest = gDest.GetHdc();
|
||||
IntPtr hdcSrc = GetDC(IntPtr.Zero); // Entire screen
|
||||
|
||||
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, SRCCOPY);
|
||||
|
||||
gDest.ReleaseHdc(hdcDest);
|
||||
ReleaseDC(IntPtr.Zero, hdcSrc);
|
||||
|
||||
return ComputeAverageColor(bmp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GetDC
|
||||
/// </summary>
|
||||
/// <param name="hWnd">The hWnd<see cref="IntPtr"/></param>
|
||||
/// <returns>The <see cref="IntPtr"/></returns>
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetDC(IntPtr hWnd);
|
||||
|
||||
/// <summary>
|
||||
/// The GetSystemMetrics
|
||||
/// </summary>
|
||||
/// <param name="smIndex">The smIndex<see cref="SystemMetric"/></param>
|
||||
/// <returns>The <see cref="int"/></returns>
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetSystemMetrics(SystemMetric smIndex);
|
||||
|
||||
/// <summary>
|
||||
/// The GetWindowRect
|
||||
/// </summary>
|
||||
/// <param name="hWnd">The hWnd<see cref="IntPtr"/></param>
|
||||
/// <param name="lpRect">The lpRect<see cref="RECT"/></param>
|
||||
/// <returns>The <see cref="bool"/></returns>
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||
|
||||
/// <summary>
|
||||
/// The ReleaseDC
|
||||
/// </summary>
|
||||
/// <param name="hWnd">The hWnd<see cref="IntPtr"/></param>
|
||||
/// <param name="hDC">The hDC<see cref="IntPtr"/></param>
|
||||
/// <returns>The <see cref="int"/></returns>
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetDC(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
||||
|
||||
#endregion
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern bool BitBlt(
|
||||
IntPtr hdcDest,
|
||||
int nXDest,
|
||||
int nYDest,
|
||||
int nWidth,
|
||||
int nHeight,
|
||||
IntPtr hdcSrc,
|
||||
int nXSrc,
|
||||
int nYSrc,
|
||||
int dwRop
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetSystemMetrics(SystemMetric smIndex);
|
||||
|
||||
private enum SystemMetric
|
||||
{
|
||||
SM_CXSCREEN = 0,
|
||||
SM_CYSCREEN = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="RECT" />
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct RECT
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Bottom
|
||||
/// </summary>
|
||||
public int Bottom;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Left
|
||||
/// </summary>
|
||||
public int Left;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Right
|
||||
/// </summary>
|
||||
public int Right;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Top
|
||||
/// </summary>
|
||||
public int Top;
|
||||
|
||||
#endregion
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ namespace BetterLyrics.WinUI3.Models
|
||||
/// </summary>
|
||||
public int StartMs { get; set; }
|
||||
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
|
||||
@@ -15,6 +14,15 @@ namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the BlurAmountTransition
|
||||
/// </summary>
|
||||
public ValueTransition<float> BlurAmountTransition { get; set; } =
|
||||
new(initialValue: 0f, durationSeconds: 0.3f);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CanvasTextLayout
|
||||
/// </summary>
|
||||
public CanvasTextLayout? CanvasTextLayout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -37,30 +45,8 @@ namespace BetterLyrics.WinUI3.Models
|
||||
/// </summary>
|
||||
public int EndMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the EnteringProgress
|
||||
/// </summary>
|
||||
public float EnteringProgress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ExitingProgress
|
||||
/// </summary>
|
||||
public float ExitingProgress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Opacity
|
||||
/// </summary>
|
||||
public float Opacity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the PlayingProgress
|
||||
/// </summary>
|
||||
public float PlayingProgress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the PlayingState
|
||||
/// </summary>
|
||||
public LyricsPlayingState PlayingState { get; set; }
|
||||
public ValueTransition<float> HighlightOpacityTransition { get; set; } =
|
||||
new(initialValue: 0f, durationSeconds: 0.3f);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Position
|
||||
@@ -68,9 +54,10 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Scale
|
||||
/// Gets or sets the ScaleTransition
|
||||
/// </summary>
|
||||
public float Scale { get; set; }
|
||||
public ValueTransition<float> ScaleTransition { get; set; } =
|
||||
new(initialValue: 0.95f, durationSeconds: 0.3f);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the StartMs
|
||||
@@ -82,9 +69,6 @@ namespace BetterLyrics.WinUI3.Models
|
||||
/// </summary>
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
public ValueTransition<float> BlurAmountTransition { get; set; } =
|
||||
new(initialValue: 0f, durationSeconds: 0.3f);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
/// <summary>
|
||||
/// Defines the _httpClient
|
||||
/// </summary>
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly HttpClient _lrcLibHttpClient;
|
||||
|
||||
private readonly HttpClient _amllTtmlDbHttpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the _settingsService
|
||||
@@ -43,11 +45,12 @@ namespace BetterLyrics.WinUI3.Services
|
||||
public MusicSearchService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.DefaultRequestHeaders.Add(
|
||||
_lrcLibHttpClient = new HttpClient();
|
||||
_lrcLibHttpClient.DefaultRequestHeaders.Add(
|
||||
"User-Agent",
|
||||
$"{AppInfo.AppName} {AppInfo.AppVersion} ({AppInfo.GithubUrl})"
|
||||
);
|
||||
_amllTtmlDbHttpClient = new HttpClient();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -74,7 +77,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
)
|
||||
)
|
||||
{
|
||||
if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
Track track = new(file);
|
||||
var bytes = track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
@@ -114,16 +117,36 @@ namespace BetterLyrics.WinUI3.Services
|
||||
continue;
|
||||
}
|
||||
|
||||
string? cachedLyrics;
|
||||
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
// Check cache first
|
||||
var cachedLyrics = ReadCache(title, artist, LyricsFormat.Lrc);
|
||||
cachedLyrics = ReadCache(
|
||||
title,
|
||||
artist,
|
||||
LyricsFormat.Lrc,
|
||||
AppInfo.LrcLibLyricsCacheDirectory
|
||||
);
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
{
|
||||
return (cachedLyrics, LyricsFormat.Lrc);
|
||||
}
|
||||
break;
|
||||
//case LyricsSearchProvider.AmllTtmlDb:
|
||||
// // Check cache first
|
||||
// cachedLyrics = ReadCache(
|
||||
// title,
|
||||
// artist,
|
||||
// LyricsFormat.Ttml,
|
||||
// AppInfo.AmllTtmlDbLyricsCacheDirectory
|
||||
// );
|
||||
// if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
// {
|
||||
// return (cachedLyrics, LyricsFormat.Ttml);
|
||||
// }
|
||||
// break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -157,7 +180,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
searchedLyrics = await SearchLrcLib(
|
||||
searchedLyrics = await SearchLrcLibAsync(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
@@ -165,6 +188,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
//case LyricsSearchProvider.AmllTtmlDb:
|
||||
// searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
|
||||
// break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -174,8 +200,23 @@ namespace BetterLyrics.WinUI3.Services
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
WriteCache(title, artist, searchedLyrics, LyricsFormat.Lrc);
|
||||
WriteCache(
|
||||
title,
|
||||
artist,
|
||||
searchedLyrics,
|
||||
LyricsFormat.Lrc,
|
||||
AppInfo.LrcLibLyricsCacheDirectory
|
||||
);
|
||||
return (searchedLyrics, LyricsFormat.Lrc);
|
||||
//case LyricsSearchProvider.AmllTtmlDb:
|
||||
// WriteCache(
|
||||
// title,
|
||||
// artist,
|
||||
// searchedLyrics,
|
||||
// LyricsFormat.Ttml,
|
||||
// AppInfo.AmllTtmlDbLyricsCacheDirectory
|
||||
// );
|
||||
// return (searchedLyrics, LyricsFormat.Ttml);
|
||||
case LyricsSearchProvider.LocalMusicFile:
|
||||
return (searchedLyrics, LyricsFormatExtensions.Detect(searchedLyrics));
|
||||
case LyricsSearchProvider.LocalLrcFile:
|
||||
@@ -193,69 +234,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
// 判断相似度
|
||||
|
||||
/// <summary>
|
||||
/// The FuzzyMatch
|
||||
/// </summary>
|
||||
/// <param name="fileName">The fileName<see cref="string"/></param>
|
||||
/// <param name="title">The title<see cref="string"/></param>
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <returns>The <see cref="bool"/></returns>
|
||||
private static bool FuzzyMatch(string fileName, string title, string artist)
|
||||
private static bool MusicMatch(string fileName, string title, string artist)
|
||||
{
|
||||
var normFile = Normalize(fileName);
|
||||
var normTarget1 = Normalize(title + artist);
|
||||
var normTarget2 = Normalize(artist + title);
|
||||
|
||||
int dist1 = LevenshteinDistance(normFile, normTarget1);
|
||||
int dist2 = LevenshteinDistance(normFile, normTarget2);
|
||||
|
||||
return dist1 <= 2 || dist2 <= 2; // 阈值可调整
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The LevenshteinDistance
|
||||
/// </summary>
|
||||
/// <param name="a">The a<see cref="string"/></param>
|
||||
/// <param name="b">The b<see cref="string"/></param>
|
||||
/// <returns>The <see cref="int"/></returns>
|
||||
private static int LevenshteinDistance(string a, string b)
|
||||
{
|
||||
if (string.IsNullOrEmpty(a))
|
||||
return b.Length;
|
||||
if (string.IsNullOrEmpty(b))
|
||||
return a.Length;
|
||||
int[,] d = new int[a.Length + 1, b.Length + 1];
|
||||
for (int i = 0; i <= a.Length; i++)
|
||||
d[i, 0] = i;
|
||||
for (int j = 0; j <= b.Length; j++)
|
||||
d[0, j] = j;
|
||||
for (int i = 1; i <= a.Length; i++)
|
||||
for (int j = 1; j <= b.Length; j++)
|
||||
d[i, j] = Math.Min(
|
||||
Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
|
||||
d[i - 1, j - 1] + (a[i - 1] == b[j - 1] ? 0 : 1)
|
||||
);
|
||||
return d[a.Length, b.Length];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Normalize
|
||||
/// </summary>
|
||||
/// <param name="s">The s<see cref="string"/></param>
|
||||
/// <returns>The <see cref="string"/></returns>
|
||||
private static string Normalize(string s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s))
|
||||
return "";
|
||||
var sb = new StringBuilder();
|
||||
foreach (var c in s.ToLowerInvariant())
|
||||
{
|
||||
if (char.IsLetterOrDigit(c))
|
||||
sb.Append(c);
|
||||
}
|
||||
return sb.ToString();
|
||||
return fileName.Contains(title) && fileName.Contains(artist);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -300,7 +281,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
)
|
||||
)
|
||||
{
|
||||
if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
string? raw = await File.ReadAllTextAsync(
|
||||
file,
|
||||
@@ -337,7 +318,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
)
|
||||
)
|
||||
{
|
||||
if (FuzzyMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
if (MusicMatch(Path.GetFileNameWithoutExtension(file), title, artist))
|
||||
{
|
||||
//Track track = new(file);
|
||||
//var test1 = track.Lyrics.SynchronizedLyrics;
|
||||
@@ -367,12 +348,17 @@ namespace BetterLyrics.WinUI3.Services
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <param name="format">The format<see cref="LyricsFormat"/></param>
|
||||
/// <returns>The <see cref="string?"/></returns>
|
||||
private string? ReadCache(string title, string artist, LyricsFormat format)
|
||||
private string? ReadCache(
|
||||
string title,
|
||||
string artist,
|
||||
LyricsFormat format,
|
||||
string cacheFolderPath
|
||||
)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(
|
||||
AppInfo.OnlineLyricsCacheDirectory,
|
||||
cacheFolderPath,
|
||||
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
|
||||
);
|
||||
if (File.Exists(cacheFilePath))
|
||||
@@ -391,7 +377,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
/// <param name="duration">The duration<see cref="int"/></param>
|
||||
/// <param name="matchMode">The matchMode<see cref="MusicSearchMatchMode"/></param>
|
||||
/// <returns>The <see cref="Task{string?}"/></returns>
|
||||
private async Task<string?> SearchLrcLib(
|
||||
private async Task<string?> SearchLrcLibAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
@@ -412,7 +398,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
+ $"&durationMs={Uri.EscapeDataString(duration.ToString())}";
|
||||
}
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
var response = await _lrcLibHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
@@ -436,6 +422,114 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本地检索 amll-ttml-db 索引并下载歌词内容
|
||||
/// </summary>
|
||||
/// <param name="title">歌曲名</param>
|
||||
/// <param name="artist">歌手名</param>
|
||||
/// <returns>歌词内容字符串,找不到返回 null</returns>
|
||||
private async Task<string?> SearchAmllTtmlDbAsync(string title, string artist)
|
||||
{
|
||||
// 检索本地 JSONL 索引文件,查找 rawLyricFile
|
||||
if (!File.Exists(AppInfo.AmllTtmlDbIndexPath))
|
||||
{
|
||||
var downloadOk = await DownloadAmllTtmlDbIndexAsync();
|
||||
if (!downloadOk || !File.Exists(AppInfo.AmllTtmlDbIndexPath))
|
||||
return null;
|
||||
}
|
||||
|
||||
string? rawLyricFile = null;
|
||||
foreach (var line in File.ReadLines(AppInfo.AmllTtmlDbIndexPath))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(line);
|
||||
var root = doc.RootElement;
|
||||
if (!root.TryGetProperty("metadata", out var metadataArr))
|
||||
continue;
|
||||
string? musicName = null;
|
||||
string? artists = null;
|
||||
foreach (var meta in metadataArr.EnumerateArray())
|
||||
{
|
||||
if (meta.GetArrayLength() != 2)
|
||||
continue;
|
||||
var key = meta[0].GetString();
|
||||
var valueArr = meta[1];
|
||||
if (key == "musicName" && valueArr.GetArrayLength() > 0)
|
||||
musicName = valueArr[0].GetString();
|
||||
if (key == "artists" && valueArr.GetArrayLength() > 0)
|
||||
artists = valueArr[0].GetString();
|
||||
}
|
||||
if (musicName == null || artists == null)
|
||||
continue;
|
||||
|
||||
if (MusicMatch($"{artists} - {musicName}", title, artist))
|
||||
{
|
||||
if (root.TryGetProperty("rawLyricFile", out var rawLyricFileProp))
|
||||
{
|
||||
rawLyricFile = rawLyricFileProp.GetString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawLyricFile))
|
||||
return null;
|
||||
|
||||
// 下载歌词内容
|
||||
var url =
|
||||
$"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
|
||||
try
|
||||
{
|
||||
var response = await _amllTtmlDbHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载 amll-ttml-db 的 JSONL 索引文件到本地缓存目录
|
||||
/// </summary>
|
||||
/// <returns>下载成功返回 true,否则 false</returns>
|
||||
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
|
||||
{
|
||||
const string url =
|
||||
"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
|
||||
try
|
||||
{
|
||||
using var response = await _amllTtmlDbHttpClient.GetAsync(
|
||||
url,
|
||||
HttpCompletionOption.ResponseHeadersRead
|
||||
);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return false;
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync();
|
||||
await using var fs = new FileStream(
|
||||
AppInfo.AmllTtmlDbIndexPath,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
FileShare.None
|
||||
);
|
||||
await stream.CopyToAsync(fs);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The WriteCache
|
||||
/// </summary>
|
||||
@@ -443,12 +537,18 @@ namespace BetterLyrics.WinUI3.Services
|
||||
/// <param name="artist">The artist<see cref="string"/></param>
|
||||
/// <param name="lyrics">The lyrics<see cref="string"/></param>
|
||||
/// <param name="format">The format<see cref="LyricsFormat"/></param>
|
||||
private void WriteCache(string title, string artist, string lyrics, LyricsFormat format)
|
||||
private void WriteCache(
|
||||
string title,
|
||||
string artist,
|
||||
string lyrics,
|
||||
LyricsFormat format,
|
||||
string cacheFolderPath
|
||||
)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(
|
||||
AppInfo.OnlineLyricsCacheDirectory,
|
||||
cacheFolderPath,
|
||||
$"{safeArtist} - {safeTitle}{format.ToFileExtension()}"
|
||||
);
|
||||
File.WriteAllText(cacheFilePath, lyrics);
|
||||
|
||||
@@ -202,7 +202,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
|
||||
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
|
||||
SetDefault(IsLyricsGlowEffectEnabledKey, true);
|
||||
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.CurrentCharOnly);
|
||||
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.UntilCurrentChar);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -411,6 +411,9 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Dock mode</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Desktop mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>Font weight</value>
|
||||
</data>
|
||||
@@ -486,4 +489,7 @@
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>This folder contains added folders, please delete these folders to add the folder</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -411,6 +411,9 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ドックモード</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>フォント重量</value>
|
||||
</data>
|
||||
@@ -486,4 +489,7 @@
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>このフォルダーには追加されたフォルダーが含まれています。これらのフォルダを削除してフォルダーを追加してください</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -411,6 +411,9 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>도크 모드</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>글꼴 무게</value>
|
||||
</data>
|
||||
@@ -486,4 +489,7 @@
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>이 폴더에는 추가 된 폴더가 포함되어 있습니다. 폴더를 추가하려면이 폴더를 삭제하십시오.</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -411,6 +411,9 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>字体粗细</value>
|
||||
</data>
|
||||
@@ -486,4 +489,7 @@
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>该文件夹包含已添加文件夹,请删除这些文件夹以添加该文件夹</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -411,6 +411,9 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>字體粗細</value>
|
||||
</data>
|
||||
@@ -486,4 +489,7 @@
|
||||
<data name="SettingsPagePathIncludingOthersInfo" xml:space="preserve">
|
||||
<value>該文件夾包含已添加文件夾,請刪除這些文件夾以添加該文件夾</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderAmllTtmlDb" xml:space="preserve">
|
||||
<value>amll-ttml-db</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,5 +1,7 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
@@ -7,17 +9,17 @@ using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
using WinUIEx.Messaging;
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
@@ -104,6 +106,10 @@ namespace BetterLyrics.WinUI3
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDockMode { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDesktopMode { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Notification
|
||||
/// </summary>
|
||||
@@ -175,7 +181,7 @@ namespace BetterLyrics.WinUI3
|
||||
{
|
||||
if (IsDockMode)
|
||||
{
|
||||
DockHelper.UpdateAppBarHeight(
|
||||
DockModeHelper.UpdateAppBarHeight(
|
||||
WindowNative.GetWindowHandle(
|
||||
WindowHelper.GetWindowByFramePageType(FramePageType)
|
||||
),
|
||||
@@ -252,15 +258,6 @@ namespace BetterLyrics.WinUI3
|
||||
_watcherHelper = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SwitchInfoBarNeverShowItAgainCheckBox
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="bool"/></param>
|
||||
[RelayCommand]
|
||||
private void SwitchInfoBarNeverShowItAgainCheckBox(bool value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ToggleDockMode
|
||||
/// </summary>
|
||||
@@ -272,16 +269,36 @@ namespace BetterLyrics.WinUI3
|
||||
IsDockMode = !IsDockMode;
|
||||
if (IsDockMode)
|
||||
{
|
||||
DockHelper.Enable(window, _settingsService.LyricsFontSize * 3);
|
||||
DockModeHelper.Enable(window, _settingsService.LyricsFontSize * 3);
|
||||
StartWatchWindowColorChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
DockHelper.Disable(window);
|
||||
DockModeHelper.Disable(window);
|
||||
StopWatchWindowColorChange();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ToggleDesktopMode()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByFramePageType(FramePageType);
|
||||
|
||||
IsDesktopMode = !IsDesktopMode;
|
||||
if (IsDesktopMode)
|
||||
{
|
||||
DesktopModeHelper.Enable(window);
|
||||
WindowHelper.GetWindowByFramePageType(typeof(LyricsPage)).SystemBackdrop =
|
||||
SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
}
|
||||
else
|
||||
{
|
||||
DesktopModeHelper.Disable(window);
|
||||
WindowHelper.GetWindowByFramePageType(typeof(LyricsPage)).SystemBackdrop =
|
||||
SystemBackdropHelper.CreateSystemBackdrop(_settingsService.BackdropType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OnFramePageTypeChanged
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
@@ -11,9 +14,6 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
@@ -199,6 +199,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
TrySwitchToPreferredDisplayType(SongInfo);
|
||||
}
|
||||
else if (message.PropertyName == nameof(HostWindowViewModel.IsDesktopMode))
|
||||
{
|
||||
if (message.NewValue) { }
|
||||
else { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Update
|
||||
/// </summary>
|
||||
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
|
||||
/// <param name="args">The args<see cref="CanvasAnimatedUpdateEventArgs"/></param>
|
||||
public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
|
||||
{
|
||||
if (_isPlaying)
|
||||
{
|
||||
TotalTime += args.Timing.ElapsedTime;
|
||||
}
|
||||
|
||||
ElapsedTime = args.Timing.ElapsedTime;
|
||||
|
||||
if (_immersiveBgTransition.IsTransitioning)
|
||||
{
|
||||
_immersiveBgTransition.Update(ElapsedTime);
|
||||
}
|
||||
|
||||
if (_albumArtBgTransition.IsTransitioning)
|
||||
{
|
||||
_albumArtBgTransition.Update(ElapsedTime);
|
||||
}
|
||||
|
||||
if (IsDynamicCoverOverlayEnabled)
|
||||
{
|
||||
_rotateAngle += _coverRotateSpeed;
|
||||
_rotateAngle %= MathF.PI * 2;
|
||||
}
|
||||
|
||||
if (_limitedLineWidthTransition.IsTransitioning)
|
||||
{
|
||||
_limitedLineWidthTransition.Update(ElapsedTime);
|
||||
_isRelayoutNeeded = true;
|
||||
}
|
||||
|
||||
if (_isRelayoutNeeded)
|
||||
{
|
||||
ReLayout(control);
|
||||
_isRelayoutNeeded = false;
|
||||
}
|
||||
|
||||
UpdateCanvasYScrollOffset(control);
|
||||
UpdateLinesProps();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The UpdateCanvasYScrollOffset
|
||||
/// </summary>
|
||||
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
|
||||
private void UpdateCanvasYScrollOffset(ICanvasAnimatedControl control)
|
||||
{
|
||||
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
||||
|
||||
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
|
||||
|
||||
if (startLineIndex < 0 || endLineIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set _scrollOffsetY
|
||||
LyricsLine? currentPlayingLine = _multiLangLyrics
|
||||
.SafeGet(_langIndex)
|
||||
?.SafeGet(currentPlayingLineIndex);
|
||||
|
||||
var playingTextLayout = currentPlayingLine?.CanvasTextLayout;
|
||||
|
||||
if (currentPlayingLine == null || playingTextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float targetYScrollOffset =
|
||||
(float?)(
|
||||
-currentPlayingLine.Position.Y
|
||||
+ _multiLangLyrics.SafeGet(_langIndex)?[0].Position.Y
|
||||
- playingTextLayout.LayoutBounds.Height / 2
|
||||
) ?? 0f;
|
||||
|
||||
if (!_canvasYScrollTransition.IsTransitioning)
|
||||
{
|
||||
_canvasYScrollTransition.StartTransition(targetYScrollOffset);
|
||||
}
|
||||
|
||||
if (_canvasYScrollTransition.IsTransitioning)
|
||||
{
|
||||
_canvasYScrollTransition.Update(ElapsedTime);
|
||||
}
|
||||
|
||||
_startVisibleLineIndex = _endVisibleLineIndex = -1;
|
||||
|
||||
// Update visible line indices
|
||||
for (int i = startLineIndex; i <= endLineIndex; i++)
|
||||
{
|
||||
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
|
||||
if (line == null || line.CanvasTextLayout == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var textLayout = line.CanvasTextLayout;
|
||||
|
||||
if (
|
||||
_canvasYScrollTransition.Value
|
||||
+ (float)(control.Size.Height / 2)
|
||||
+ line.Position.Y
|
||||
+ textLayout.LayoutBounds.Height
|
||||
>= 0
|
||||
)
|
||||
{
|
||||
if (_startVisibleLineIndex == -1)
|
||||
{
|
||||
_startVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
if (
|
||||
_canvasYScrollTransition.Value
|
||||
+ (float)(control.Size.Height / 2)
|
||||
+ line.Position.Y
|
||||
+ textLayout.LayoutBounds.Height
|
||||
>= control.Size.Height
|
||||
)
|
||||
{
|
||||
if (_endVisibleLineIndex == -1)
|
||||
{
|
||||
_endVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_startVisibleLineIndex != -1 && _endVisibleLineIndex == -1)
|
||||
{
|
||||
_endVisibleLineIndex = endLineIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The UpdateFontColor
|
||||
/// </summary>
|
||||
private protected void UpdateFontColor()
|
||||
{
|
||||
Color fallback = Colors.Transparent;
|
||||
switch (Theme)
|
||||
{
|
||||
case ElementTheme.Default:
|
||||
switch (Application.Current.RequestedTheme)
|
||||
{
|
||||
case ApplicationTheme.Light:
|
||||
fallback = _darkFontColor;
|
||||
break;
|
||||
case ApplicationTheme.Dark:
|
||||
fallback = _lightFontColor;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ElementTheme.Light:
|
||||
fallback = _darkFontColor;
|
||||
break;
|
||||
case ElementTheme.Dark:
|
||||
fallback = _lightFontColor;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (LyricsFontColorType)
|
||||
{
|
||||
case Enums.LyricsFontColorType.Default:
|
||||
_fontColor = fallback;
|
||||
break;
|
||||
case Enums.LyricsFontColorType.Dominant:
|
||||
_fontColor = _albumArtAccentColor ?? fallback;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The UpdateLinesProps
|
||||
/// </summary>
|
||||
/// <param name="source">The source<see cref="List{LyricsLine}?"/></param>
|
||||
/// <param name="defaultOpacity">The defaultOpacity<see cref="float"/></param>
|
||||
private void UpdateLinesProps()
|
||||
{
|
||||
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
||||
|
||||
int halfVisibleLineCount =
|
||||
Math.Max(
|
||||
currentPlayingLineIndex - _startVisibleLineIndex,
|
||||
_endVisibleLineIndex - currentPlayingLineIndex
|
||||
) + 1;
|
||||
|
||||
if (halfVisibleLineCount < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int distanceFromPlayingLine = Math.Abs(i - currentPlayingLineIndex);
|
||||
if (distanceFromPlayingLine > halfVisibleLineCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float distanceZoomFactor = distanceFromPlayingLine / (float)halfVisibleLineCount;
|
||||
|
||||
line.BlurAmountTransition.StartTransition(LyricsBlurAmount * distanceZoomFactor);
|
||||
line.ScaleTransition.StartTransition(
|
||||
_highlightedScale - distanceZoomFactor * (_highlightedScale - _defaultScale)
|
||||
);
|
||||
// Only calculate highlight opacity for the current line and the two lines around it
|
||||
// to avoid unnecessary calculations
|
||||
if (distanceFromPlayingLine <= 1)
|
||||
{
|
||||
line.HighlightOpacityTransition.StartTransition(
|
||||
distanceFromPlayingLine == 0 ? 1 : 0
|
||||
);
|
||||
}
|
||||
|
||||
if (line.ScaleTransition.IsTransitioning)
|
||||
{
|
||||
line.ScaleTransition.Update(ElapsedTime);
|
||||
}
|
||||
if (line.BlurAmountTransition.IsTransitioning)
|
||||
{
|
||||
line.BlurAmountTransition.Update(ElapsedTime);
|
||||
}
|
||||
// Only update highlight opacity for the current line and the two lines around it
|
||||
if (distanceFromPlayingLine <= 1)
|
||||
{
|
||||
if (line.HighlightOpacityTransition.IsTransitioning)
|
||||
{
|
||||
line.HighlightOpacityTransition.Update(ElapsedTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reassigns positions (x,y) to lyrics lines based on the current control size and font size
|
||||
/// </summary>
|
||||
/// <param name="control"></param>
|
||||
private void ReLayout(ICanvasAnimatedControl control)
|
||||
{
|
||||
if (control == null)
|
||||
return;
|
||||
|
||||
_textFormat.FontSize = LyricsFontSize;
|
||||
|
||||
float y = _topMargin;
|
||||
|
||||
// Init Positions
|
||||
for (int i = 0; i < _multiLangLyrics.SafeGet(_langIndex)?.Count; i++)
|
||||
{
|
||||
var line = _multiLangLyrics[_langIndex].SafeGet(i);
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.CanvasTextLayout != null)
|
||||
{
|
||||
line.CanvasTextLayout.Dispose();
|
||||
line.CanvasTextLayout = null;
|
||||
}
|
||||
|
||||
// Calculate layout bounds
|
||||
line.CanvasTextLayout = new CanvasTextLayout(
|
||||
control,
|
||||
line.Text,
|
||||
_textFormat,
|
||||
(float)_limitedLineWidthTransition.Value,
|
||||
(float)control.Size.Height
|
||||
);
|
||||
|
||||
line.Position = new Vector2(0, y);
|
||||
|
||||
y +=
|
||||
(float)line.CanvasTextLayout.LayoutBounds.Height
|
||||
/ line.CanvasTextLayout.LineCount
|
||||
* (line.CanvasTextLayout.LineCount + LyricsLineSpacingFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
: IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<float>>,
|
||||
IRecipient<PropertyChangedMessage<double>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<Color>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsDisplayType>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsFontColorType>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsAlignmentType>>,
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
|
||||
IRecipient<PropertyChangedMessage<LineRenderingType>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The OnLyricsFontColorTypeChanged
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="LyricsFontColorType"/></param>
|
||||
partial void OnLyricsFontColorTypeChanged(LyricsFontColorType value)
|
||||
{
|
||||
UpdateFontColor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OnLyricsFontSizeChanged
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="int"/></param>
|
||||
partial void OnLyricsFontSizeChanged(int value)
|
||||
{
|
||||
_isRelayoutNeeded = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OnLyricsFontWeightChanged
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="LyricsFontWeight"/></param>
|
||||
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
|
||||
{
|
||||
_textFormat.FontWeight = value.ToFontWeight();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OnLyricsLineSpacingFactorChanged
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="float"/></param>
|
||||
partial void OnLyricsLineSpacingFactorChanged(float value)
|
||||
{
|
||||
_isRelayoutNeeded = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OnSongInfoChanged
|
||||
/// </summary>
|
||||
/// <param name="oldValue">The oldValue<see cref="SongInfo?"/></param>
|
||||
/// <param name="newValue">The newValue<see cref="SongInfo?"/></param>
|
||||
async partial void OnSongInfoChanged(SongInfo? oldValue, SongInfo? newValue)
|
||||
{
|
||||
TotalTime = TimeSpan.Zero;
|
||||
|
||||
_lastAlbumArtBitmap = _albumArtBitmap;
|
||||
|
||||
if (newValue?.AlbumArt is byte[] bytes)
|
||||
{
|
||||
_albumArtBitmap = await (
|
||||
await ImageHelper.GetDecoderFromByte(bytes)
|
||||
).GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
|
||||
_albumArtAccentColor = (
|
||||
await ImageHelper.GetAccentColorsFromByte(bytes)
|
||||
).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
_albumArtBitmap = null;
|
||||
_albumArtAccentColor = null;
|
||||
}
|
||||
|
||||
UpdateFontColor();
|
||||
|
||||
_albumArtBgTransition.Reset(0f);
|
||||
_albumArtBgTransition.StartTransition(1f);
|
||||
|
||||
await RefreshLyricsAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OnThemeChanged
|
||||
/// </summary>
|
||||
/// <param name="value">The value<see cref="ElementTheme"/></param>
|
||||
partial void OnThemeChanged(ElementTheme value)
|
||||
{
|
||||
UpdateFontColor();
|
||||
}
|
||||
|
||||
// Receive methods for handling messages from other view models
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{bool}"/></param>
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is SettingsViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsViewModel.IsDynamicCoverOverlayEnabled))
|
||||
{
|
||||
IsDynamicCoverOverlayEnabled = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsViewModel.IsCoverOverlayEnabled))
|
||||
{
|
||||
IsCoverOverlayEnabled = message.NewValue;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.IsLyricsGlowEffectEnabled)
|
||||
)
|
||||
{
|
||||
IsLyricsGlowEffectEnabled = message.NewValue;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is HostWindowViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(HostWindowViewModel.IsDockMode))
|
||||
{
|
||||
IsDockMode = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{Color}"/></param>
|
||||
public void Receive(PropertyChangedMessage<Color> message)
|
||||
{
|
||||
if (message.Sender is HostWindowViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(HostWindowViewModel.ActivatedWindowAccentColor))
|
||||
{
|
||||
_immersiveBgTransition.StartTransition(message.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{double}"/></param>
|
||||
public void Receive(PropertyChangedMessage<double> message)
|
||||
{
|
||||
if (message.Sender is LyricsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsPageViewModel.LimitedLineWidth))
|
||||
{
|
||||
_limitedLineWidthTransition.StartTransition((float)message.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{ElementTheme}"/></param>
|
||||
public void Receive(PropertyChangedMessage<ElementTheme> message)
|
||||
{
|
||||
if (message.Sender is SettingsViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsViewModel.ThemeType))
|
||||
{
|
||||
Theme = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{float}"/></param>
|
||||
public void Receive(PropertyChangedMessage<float> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.LyricsLineSpacingFactor)
|
||||
)
|
||||
{
|
||||
LyricsLineSpacingFactor = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{int}"/></param>
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is SettingsViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
|
||||
{
|
||||
CoverImageRadius = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsViewModel.CoverOverlayOpacity))
|
||||
{
|
||||
CoverOverlayOpacity = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsViewModel.CoverOverlayBlurAmount))
|
||||
{
|
||||
CoverOverlayBlurAmount = message.NewValue;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.LyricsVerticalEdgeOpacity)
|
||||
)
|
||||
{
|
||||
LyricsVerticalEdgeOpacity = message.NewValue;
|
||||
}
|
||||
else if (
|
||||
message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsBlurAmount)
|
||||
)
|
||||
{
|
||||
LyricsBlurAmount = message.NewValue;
|
||||
}
|
||||
else if (
|
||||
message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize)
|
||||
)
|
||||
{
|
||||
LyricsFontSize = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsAlignmentType}"/></param>
|
||||
public void Receive(PropertyChangedMessage<LyricsAlignmentType> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.LyricsAlignmentType)
|
||||
)
|
||||
{
|
||||
LyricsAlignmentType = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsDisplayType}"/></param>
|
||||
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
|
||||
{
|
||||
DisplayType = message.NewValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsFontColorType}"/></param>
|
||||
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.LyricsFontColorType)
|
||||
)
|
||||
{
|
||||
LyricsFontColorType = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsFontWeight}"/></param>
|
||||
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontWeight))
|
||||
{
|
||||
LyricsFontWeight = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{LyricsGlowEffectScope}"/></param>
|
||||
public void Receive(PropertyChangedMessage<LineRenderingType> message)
|
||||
{
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (
|
||||
message.PropertyName
|
||||
== nameof(LyricsSettingsControlViewModel.LyricsGlowEffectScope)
|
||||
)
|
||||
{
|
||||
LyricsGlowEffectScope = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{ObservableCollection{LocalLyricsFolder}}"/></param>
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>> message)
|
||||
{
|
||||
if (message.Sender is SettingsViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsViewModel.LocalLyricsFolders))
|
||||
{
|
||||
// Music lib changed, re-fetch lyrics
|
||||
RefreshLyricsAsync().ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Receive
|
||||
/// </summary>
|
||||
/// <param name="message">The message<see cref="PropertyChangedMessage{ObservableCollection{LyricsSearchProviderInfo}}"/></param>
|
||||
public void Receive(
|
||||
PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>> message
|
||||
)
|
||||
{
|
||||
if (message.Sender is SettingsViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsViewModel.LyricsSearchProvidersInfo))
|
||||
{
|
||||
// Lyrics search providers info changed, re-fetch lyrics
|
||||
RefreshLyricsAsync().ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Brushes;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Draw
|
||||
/// </summary>
|
||||
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
|
||||
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
|
||||
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
if (IsCoverOverlayEnabled)
|
||||
{
|
||||
DrawAlbumArtBackground(control, ds);
|
||||
}
|
||||
|
||||
if (IsDockMode)
|
||||
{
|
||||
DrawImmersiveBackground(control, ds, IsCoverOverlayEnabled);
|
||||
}
|
||||
|
||||
// Blurred lyrics layer
|
||||
using var blurredLyrics = new CanvasCommandList(control);
|
||||
using (var blurredLyricsDs = blurredLyrics.CreateDrawingSession())
|
||||
{
|
||||
switch (DisplayType)
|
||||
{
|
||||
case LyricsDisplayType.AlbumArtOnly:
|
||||
case LyricsDisplayType.PlaceholderOnly:
|
||||
break;
|
||||
case LyricsDisplayType.LyricsOnly:
|
||||
case LyricsDisplayType.SplitView:
|
||||
DrawBlurredLyrics(control, blurredLyricsDs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Masked mock gradient blurred lyrics layer
|
||||
using var maskedBlurredLyrics = new CanvasCommandList(control);
|
||||
using (var maskedBlurredLyricsDs = maskedBlurredLyrics.CreateDrawingSession())
|
||||
{
|
||||
if (LyricsVerticalEdgeOpacity == 100)
|
||||
{
|
||||
maskedBlurredLyricsDs.DrawImage(blurredLyrics);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var mask = new CanvasCommandList(control);
|
||||
using (var maskDs = mask.CreateDrawingSession())
|
||||
{
|
||||
DrawGradientOpacityMask(control, maskDs);
|
||||
}
|
||||
maskedBlurredLyricsDs.DrawImage(
|
||||
new AlphaMaskEffect { Source = blurredLyrics, AlphaMask = mask }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// For desktop mode
|
||||
//ds.DrawImage(
|
||||
// new ShadowEffect
|
||||
// {
|
||||
// Source = maskedBlurredLyrics,
|
||||
// ShadowColor = Colors.Black,
|
||||
// BlurAmount = 8f,
|
||||
// Optimization = EffectOptimization.Quality,
|
||||
// }
|
||||
//);
|
||||
|
||||
ds.DrawImage(maskedBlurredLyrics);
|
||||
|
||||
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
||||
var currentPlayingLine = _multiLangLyrics
|
||||
.SafeGet(_langIndex)
|
||||
?.SafeGet(currentPlayingLineIndex);
|
||||
if (currentPlayingLine != null)
|
||||
{
|
||||
GetLinePlayingProgress(
|
||||
currentPlayingLine,
|
||||
out int charStartIndex,
|
||||
out int charLength,
|
||||
out float charProgress
|
||||
);
|
||||
//ds.DrawText(
|
||||
// $"DEBUG: "
|
||||
// + $"播放行 {currentPlayingLineIndex}, 字符 {charStartIndex}, 长度 {charLength}, 进度 {charProgress}\n"
|
||||
// + $"可见行 [{_startVisibleLineIndex}, {_endVisibleLineIndex}]"
|
||||
// + $"当前时刻 {TotalTime}",
|
||||
// new Vector2(10, 10),
|
||||
// Colors.Red
|
||||
//);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The DrawImgae
|
||||
/// </summary>
|
||||
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
|
||||
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
|
||||
/// <param name="softwareBitmap">The softwareBitmap<see cref="SoftwareBitmap"/></param>
|
||||
/// <param name="opacity">The opacity<see cref="float"/></param>
|
||||
private static void DrawImgae(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
SoftwareBitmap softwareBitmap,
|
||||
float opacity
|
||||
)
|
||||
{
|
||||
using var canvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, softwareBitmap);
|
||||
float imageWidth = (float)canvasBitmap.Size.Width;
|
||||
float imageHeight = (float)canvasBitmap.Size.Height;
|
||||
|
||||
var scaleFactor =
|
||||
(float)Math.Sqrt(Math.Pow(control.Size.Width, 2) + Math.Pow(control.Size.Height, 2))
|
||||
/ Math.Min(imageWidth, imageHeight);
|
||||
|
||||
ds.DrawImage(
|
||||
new OpacityEffect
|
||||
{
|
||||
Source = new ScaleEffect
|
||||
{
|
||||
InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
|
||||
BorderMode = EffectBorderMode.Hard,
|
||||
Scale = new Vector2(scaleFactor),
|
||||
Source = canvasBitmap,
|
||||
},
|
||||
Opacity = opacity,
|
||||
},
|
||||
(float)control.Size.Width / 2 - imageWidth * scaleFactor / 2,
|
||||
(float)control.Size.Height / 2 - imageHeight * scaleFactor / 2
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The DrawAlbumArtBackground
|
||||
/// </summary>
|
||||
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
|
||||
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
|
||||
private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
|
||||
|
||||
var overlappedCovers = new CanvasCommandList(control.Device);
|
||||
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
|
||||
|
||||
if (_albumArtBgTransition.IsTransitioning)
|
||||
{
|
||||
if (_lastAlbumArtBitmap != null)
|
||||
{
|
||||
DrawImgae(
|
||||
control,
|
||||
overlappedCoversDs,
|
||||
_lastAlbumArtBitmap,
|
||||
1 - _albumArtBgTransition.Value
|
||||
);
|
||||
}
|
||||
if (_albumArtBitmap != null)
|
||||
{
|
||||
DrawImgae(
|
||||
control,
|
||||
overlappedCoversDs,
|
||||
_albumArtBitmap,
|
||||
_albumArtBgTransition.Value
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (_albumArtBitmap != null)
|
||||
{
|
||||
DrawImgae(control, overlappedCoversDs, _albumArtBitmap, 1f);
|
||||
}
|
||||
|
||||
using var coverOverlayEffect = new OpacityEffect
|
||||
{
|
||||
Opacity = CoverOverlayOpacity / 100f,
|
||||
Source = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = CoverOverlayBlurAmount,
|
||||
Source = overlappedCovers,
|
||||
},
|
||||
};
|
||||
ds.DrawImage(coverOverlayEffect);
|
||||
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The DrawGradientOpacityMask
|
||||
/// </summary>
|
||||
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
|
||||
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
|
||||
private void DrawGradientOpacityMask(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds
|
||||
)
|
||||
{
|
||||
byte verticalEdgeAlpha = (byte)(255 * LyricsVerticalEdgeOpacity / 100f);
|
||||
using var maskBrush = new CanvasLinearGradientBrush(
|
||||
control,
|
||||
[
|
||||
new() { Position = 0, Color = Color.FromArgb(verticalEdgeAlpha, 0, 0, 0) },
|
||||
new() { Position = 0.5f, Color = Color.FromArgb(255, 0, 0, 0) },
|
||||
new() { Position = 1, Color = Color.FromArgb(verticalEdgeAlpha, 0, 0, 0) },
|
||||
]
|
||||
)
|
||||
{
|
||||
StartPoint = new Vector2(0, 0),
|
||||
EndPoint = new Vector2(0, (float)control.Size.Height),
|
||||
};
|
||||
ds.FillRectangle(new Rect(0, 0, control.Size.Width, control.Size.Height), maskBrush);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The DrawImmersiveBackground
|
||||
/// </summary>
|
||||
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
|
||||
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
|
||||
/// <param name="withGradient">The withGradient<see cref="bool"/></param>
|
||||
private void DrawImmersiveBackground(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
bool withGradient
|
||||
)
|
||||
{
|
||||
ds.FillRectangle(
|
||||
new Rect(0, 0, control.Size.Width, control.Size.Height),
|
||||
new CanvasLinearGradientBrush(
|
||||
control,
|
||||
[
|
||||
new CanvasGradientStop
|
||||
{
|
||||
Position = 0f,
|
||||
Color = withGradient
|
||||
? Color.FromArgb(
|
||||
211,
|
||||
_immersiveBgTransition.Value.R,
|
||||
_immersiveBgTransition.Value.G,
|
||||
_immersiveBgTransition.Value.B
|
||||
)
|
||||
: _immersiveBgTransition.Value,
|
||||
},
|
||||
new CanvasGradientStop
|
||||
{
|
||||
Position = 1,
|
||||
Color = _immersiveBgTransition.Value,
|
||||
},
|
||||
]
|
||||
)
|
||||
{
|
||||
StartPoint = new Vector2(0, 0),
|
||||
EndPoint = new Vector2(0, (float)control.Size.Height),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The DrawLyrics
|
||||
/// </summary>
|
||||
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
|
||||
/// <param name="ds">The ds<see cref="CanvasDrawingSession"/></param>
|
||||
/// <param name="currentLineHighlightType">The currentLineHighlightType<see cref="LyricsHighlightType"/></param>
|
||||
private void DrawBlurredLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
||||
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
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 LyricsAlignmentType.Left:
|
||||
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
|
||||
break;
|
||||
case LyricsAlignmentType.Center:
|
||||
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
|
||||
centerX += (float)_limitedLineWidthTransition.Value / 2;
|
||||
break;
|
||||
case LyricsAlignmentType.Right:
|
||||
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
|
||||
centerX += (float)_limitedLineWidthTransition.Value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
float offsetToLeft =
|
||||
(float)control.Size.Width - _rightMargin - _limitedLineWidthTransition.Value;
|
||||
|
||||
// Scale
|
||||
ds.Transform =
|
||||
Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
|
||||
* Matrix3x2.CreateTranslation(
|
||||
offsetToLeft,
|
||||
_canvasYScrollTransition.Value + (float)(control.Size.Height / 2)
|
||||
);
|
||||
|
||||
// Create the original lyrics line
|
||||
using var lyrics = new CanvasCommandList(control.Device);
|
||||
using var lyricsDs = lyrics.CreateDrawingSession();
|
||||
lyricsDs.DrawTextLayout(textLayout, position, _fontColor);
|
||||
|
||||
// Mock gradient blurred lyrics layer
|
||||
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层
|
||||
// Current line will not be blurred
|
||||
ds.DrawImage(
|
||||
new GaussianBlurEffect
|
||||
{
|
||||
Source = new OpacityEffect { Source = lyrics, Opacity = _defaultOpacity },
|
||||
BlurAmount = line.BlurAmountTransition.Value,
|
||||
Optimization = EffectOptimization.Quality,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
}
|
||||
);
|
||||
|
||||
// 再叠加当前行歌词层
|
||||
// Only draw the current line and the two lines around it
|
||||
// This layer is to highlight the current line
|
||||
// and for fade-in and fade-out effects, two lines around it is also drawn
|
||||
if (Math.Abs(i - currentPlayingLineIndex) <= 1)
|
||||
{
|
||||
using var mask = new CanvasCommandList(control.Device);
|
||||
using var maskDs = mask.CreateDrawingSession();
|
||||
|
||||
using var highlightMask = new CanvasCommandList(control.Device);
|
||||
using var highlightMaskDs = highlightMask.CreateDrawingSession();
|
||||
|
||||
if (i == currentPlayingLineIndex)
|
||||
{
|
||||
GetLinePlayingProgress(
|
||||
line,
|
||||
out int charStartIndex,
|
||||
out int charLength,
|
||||
out float charProgress
|
||||
);
|
||||
var regions = textLayout.GetCharacterRegions(0, charStartIndex);
|
||||
var highlightRegion = textLayout
|
||||
.GetCharacterRegions(charStartIndex, charLength)
|
||||
.FirstOrDefault();
|
||||
if (regions.Length > 0)
|
||||
{
|
||||
// Draw the mask for the current line
|
||||
for (int j = 0; j < regions.Length; j++)
|
||||
{
|
||||
var region = regions[j];
|
||||
var rect = new Rect(
|
||||
region.LayoutBounds.X,
|
||||
region.LayoutBounds.Y + position.Y,
|
||||
region.LayoutBounds.Width,
|
||||
region.LayoutBounds.Height
|
||||
);
|
||||
maskDs.FillRectangle(rect, Colors.Black);
|
||||
}
|
||||
}
|
||||
|
||||
float highlightTotalWidth = (float)highlightRegion.LayoutBounds.Width;
|
||||
// Draw the highlight for the current character
|
||||
float highlightWidth = highlightTotalWidth * charProgress;
|
||||
|
||||
float fadingWidth = (float)highlightRegion.LayoutBounds.Height / 2;
|
||||
|
||||
// Rects
|
||||
var highlightRect = new Rect(
|
||||
highlightRegion.LayoutBounds.X,
|
||||
highlightRegion.LayoutBounds.Y + position.Y,
|
||||
highlightWidth,
|
||||
highlightRegion.LayoutBounds.Height
|
||||
);
|
||||
|
||||
var fadeInRect = new Rect(
|
||||
highlightRect.Right - fadingWidth,
|
||||
highlightRegion.LayoutBounds.Y + position.Y,
|
||||
fadingWidth,
|
||||
highlightRegion.LayoutBounds.Height
|
||||
);
|
||||
var fadeOutRect = new Rect(
|
||||
highlightRect.Right,
|
||||
highlightRegion.LayoutBounds.Y + position.Y,
|
||||
fadingWidth,
|
||||
highlightRegion.LayoutBounds.Height
|
||||
);
|
||||
|
||||
// Brushes
|
||||
using var fadeInBrush = GetHorizontalFillBrush(
|
||||
control,
|
||||
[(0f, 0f), (1f, 1f)],
|
||||
(float)highlightRect.Right - fadingWidth,
|
||||
fadingWidth
|
||||
);
|
||||
using var fadeOutBrush = GetHorizontalFillBrush(
|
||||
control,
|
||||
[(0f, 1f), (1f, 0f)],
|
||||
(float)highlightRect.Right,
|
||||
fadingWidth
|
||||
);
|
||||
|
||||
maskDs.FillRectangle(highlightRect, Colors.White);
|
||||
maskDs.FillRectangle(fadeOutRect, fadeOutBrush);
|
||||
|
||||
highlightMaskDs.FillRectangle(fadeInRect, fadeInBrush);
|
||||
highlightMaskDs.FillRectangle(fadeOutRect, fadeOutBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
maskDs.FillRectangle(
|
||||
new Rect(
|
||||
textLayout.LayoutBounds.X,
|
||||
position.Y,
|
||||
textLayout.LayoutBounds.Width,
|
||||
textLayout.LayoutBounds.Height
|
||||
),
|
||||
Colors.White
|
||||
);
|
||||
}
|
||||
|
||||
ds.DrawImage(
|
||||
new OpacityEffect
|
||||
{
|
||||
Source = new BlendEffect
|
||||
{
|
||||
Background = IsLyricsGlowEffectEnabled
|
||||
? new GaussianBlurEffect
|
||||
{
|
||||
Source = new AlphaMaskEffect
|
||||
{
|
||||
Source = lyrics,
|
||||
AlphaMask = LyricsGlowEffectScope switch
|
||||
{
|
||||
LineRenderingType.UntilCurrentChar => mask,
|
||||
LineRenderingType.CurrentCharOnly => highlightMask,
|
||||
_ => mask,
|
||||
},
|
||||
},
|
||||
BlurAmount = _lyricsGlowEffectAmount,
|
||||
Optimization = EffectOptimization.Quality,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
}
|
||||
: new CanvasCommandList(control.Device),
|
||||
Foreground = new AlphaMaskEffect
|
||||
{
|
||||
Source = lyrics,
|
||||
AlphaMask = mask,
|
||||
},
|
||||
},
|
||||
Opacity = line.HighlightOpacityTransition.Value,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset scale
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GetHorizontalFillBrush
|
||||
/// </summary>
|
||||
/// <param name="control">The control<see cref="ICanvasAnimatedControl"/></param>
|
||||
/// <param name="stopPosition">The stopPosition<see cref="float[]"/></param>
|
||||
/// <param name="stopOpacity">The stopOpacity<see cref="float[]"/></param>
|
||||
/// <param name="startX">The startX<see cref="float"/></param>
|
||||
/// <param name="endX">The endX<see cref="float"/></param>
|
||||
/// <returns>The <see cref="CanvasLinearGradientBrush"/></returns>
|
||||
private CanvasLinearGradientBrush GetHorizontalFillBrush(
|
||||
ICanvasAnimatedControl control,
|
||||
List<(float position, float opacity)> stops,
|
||||
float startX,
|
||||
float width
|
||||
)
|
||||
{
|
||||
return new CanvasLinearGradientBrush(
|
||||
control,
|
||||
stops
|
||||
.Select(stops => new CanvasGradientStop
|
||||
{
|
||||
Position = stops.position,
|
||||
Color = Color.FromArgb((byte)(stops.opacity * 255), 0, 0, 0),
|
||||
})
|
||||
.ToArray()
|
||||
)
|
||||
{
|
||||
StartPoint = new Vector2(startX, 0),
|
||||
EndPoint = new Vector2(startX + width, 0),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -86,6 +86,11 @@
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user