Compare commits

..

1 Commits

Author SHA1 Message Date
Zhe Fang
7bca1d1205 Merge pull request #5 from jayfunc/dev
Add dock mode, improve glow effect, fix bugs ...
2025-06-17 21:50:22 -04:00
36 changed files with 277 additions and 288 deletions

0
.github/workflows/dotnet-desktop.yml vendored Normal file
View File

View File

@@ -11,7 +11,7 @@
<Identity
Name="37412.BetterLyrics"
Publisher="CN=Zhe"
Version="1.0.4.0" />
Version="1.0.3.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

View File

@@ -71,12 +71,12 @@ namespace BetterLyrics.WinUI3
loggingBuilder.ClearProviders();
loggingBuilder.AddSerilog();
})
// Services
// Services (Singleton)
.AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<IDatabaseService, DatabaseService>()
.AddSingleton<IPlaybackService, PlaybackService>()
// ViewModels
.AddTransient<HostWindowViewModel>()
// ViewModels (Singleton)
.AddSingleton<HostWindowViewModel>()
.AddSingleton<SettingsViewModel>()
.AddSingleton<LyricsPageViewModel>()
.AddSingleton<LyricsRendererViewModel>()

View File

@@ -10,9 +10,6 @@
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\CustomTransform.bin" />
</ItemGroup>
<ItemGroup>
<Content Include="Logo.ico" />
</ItemGroup>

View File

@@ -50,9 +50,6 @@ namespace BetterLyrics.WinUI3.Helper
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
private static string CustomShaderFileName => "CustomTransform.bin";
public static string CustomShaderPath => Path.Combine(AssetsFolder, CustomShaderFileName);
public static void EnsureDirectories()
{
Directory.CreateDirectory(LogDirectory);

View File

@@ -177,44 +177,6 @@ namespace BetterLyrics.WinUI3.Helper
int cy,
uint uFlags
);
/// <summary>
/// 更改已注册 AppBar 的高度。
/// </summary>
/// <param name="window">目标窗口</param>
/// <param name="newHeight">新的高度</param>
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight)
{
if (!_registered.Contains(hwnd))
return;
APPBARDATA abd = new()
{
cbSize = Marshal.SizeOf<APPBARDATA>(),
hWnd = hwnd,
uEdge = ABE_TOP,
rc = new RECT
{
left = 0,
top = 0,
right = GetSystemMetrics(SM_CXSCREEN),
bottom = newHeight,
},
};
SHAppBarMessage(ABM_SETPOS, ref abd);
// 同步窗口实际高度
SetWindowPos(
hwnd,
IntPtr.Zero,
0,
0,
GetSystemMetrics(SM_CXSCREEN),
newHeight,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
);
}
#endregion
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public class MathHelper
{
public static List<int> GetAllFactors(int n)
{
var result = new SortedSet<int>();
for (int i = 1; i <= Math.Sqrt(n); i++)
{
if (n % i == 0)
{
result.Add(i);
result.Add(n / i);
}
}
return [.. result];
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
internal class NativeHelper
{
public const int ERROR_SUCCESS = 0;
public const int ERROR_INSUFFICIENT_BUFFER = 122;
public const int APPMODEL_ERROR_NO_PACKAGE = 15700;
[DllImport("api-ms-win-appmodel-runtime-l1-1-1", SetLastError = true)]
[return: MarshalAs(UnmanagedType.U4)]
internal static extern uint GetCurrentPackageId(ref int pBufferLength, out byte pBuffer);
public static bool IsAppPackaged
{
get
{
int bufferSize = 0;
byte byteBuffer = 0;
uint lastError = NativeHelper.GetCurrentPackageId(ref bufferSize, out byteBuffer);
bool isPackaged = true;
if (lastError == NativeHelper.APPMODEL_ERROR_NO_PACKAGE)
{
isPackaged = false;
}
return isPackaged;
}
}
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using BetterLyrics.WinUI3.Views;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using WinRT.Interop;
using WinUIEx;
namespace BetterLyrics.WinUI3.Helper
@@ -41,13 +40,16 @@ namespace BetterLyrics.WinUI3.Helper
{
if (_windowCache.TryGetValue(pageType, out var window))
{
if (window is HostWindow hostWindow)
{
hostWindow.Navigate(pageType);
}
window.TryShow();
}
else
{
var newWindow = new HostWindow();
TrackWindow(newWindow, pageType);
newWindow.ViewModel.FramePageType = pageType;
newWindow.Navigate(pageType);
newWindow.Activate();
}
@@ -117,17 +119,5 @@ namespace BetterLyrics.WinUI3.Helper
window.Hide();
}
}
public static Window GetWindowByFramePageType(Type type)
{
foreach (var cachedWindow in _windowCache)
{
if (cachedWindow.Key == type)
{
return cachedWindow.Value;
}
}
return null;
}
}
}

View File

@@ -33,8 +33,6 @@ namespace BetterLyrics.WinUI3.Services.Database
{
_connection.DeleteAll<MetadataIndex>();
HashSet<string> insertedPaths = new();
foreach (var path in paths)
{
if (Directory.Exists(path))
@@ -43,19 +41,16 @@ namespace BetterLyrics.WinUI3.Services.Database
var file in Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)
)
{
if (!insertedPaths.Contains(file))
{
var track = new Track(file);
_connection.Insert(
new MetadataIndex
{
Path = file,
Title = track.Title,
Artist = track.Artist,
}
);
insertedPaths.Add(file);
}
var fileExtension = Path.GetExtension(file);
var track = new Track(file);
_connection.Insert(
new MetadataIndex
{
Path = file,
Title = track.Title,
Artist = track.Artist,
}
);
}
}
}

View File

@@ -41,9 +41,16 @@ namespace BetterLyrics.WinUI3.Services.Settings
private const string LyricsLineSpacingFactorKey = "LyricsLineSpacingFactor";
private const string LyricsFontSizeKey = "LyricsFontSize";
private const string IsLyricsGlowEffectEnabledKey = "IsLyricsGlowEffectEnabled";
private const string IsLyricsDynamicGlowEffectEnabledKey =
"IsLyricsDynamicGlowEffectEnabled";
private const string LyricsFontColorTypeKey = "LyricsFontColorType";
private const string LyricsGlowEffectScopeKey = "LyricsGlowEffectScope";
// Notification
private const string NeverShowEnterFullScreenMessageKey = "NeverShowEnterFullScreenMessage";
private const string NeverShowEnterImmersiveModeMessageKey =
"NeverShowEnterImmersiveModeMessage";
public bool IsFirstRun
{
get => GetValue<bool>(IsFirstRunKey);
@@ -200,8 +207,11 @@ namespace BetterLyrics.WinUI3.Services.Settings
SetDefault(LyricsFontSizeKey, 28);
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
SetDefault(IsLyricsGlowEffectEnabledKey, true);
SetDefault(IsLyricsGlowEffectEnabledKey, false);
SetDefault(LyricsGlowEffectScopeKey, (int)LyricsGlowEffectScope.CurrentChar);
// Notification
SetDefault(NeverShowEnterFullScreenMessageKey, false);
SetDefault(NeverShowEnterImmersiveModeMessageKey, false);
}
private T? GetValue<T>(string key)

View File

@@ -1,6 +1,4 @@
using System;
using System.Threading.Tasks;
using BetterInAppLyrics.WinUI3.ViewModels;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
@@ -11,27 +9,16 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using H.NotifyIcon.Interop;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
using WinRT.Interop;
using WinUIEx;
namespace BetterLyrics.WinUI3
{
public partial class HostWindowViewModel
: BaseViewModel,
IRecipient<PropertyChangedMessage<TitleBarType>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<BackdropType>>,
IRecipient<PropertyChangedMessage<int>>
IRecipient<PropertyChangedMessage<ElementTheme>>
{
private ForegroundWindowWatcherHelper? _watcherHelper = null;
[ObservableProperty]
public partial Type FramePageType { get; set; }
[ObservableProperty]
public partial ElementTheme ThemeType { get; set; }
@@ -89,39 +76,6 @@ namespace BetterLyrics.WinUI3
);
}
private void StartWatchWindowColorChange()
{
var hwnd = WindowNative.GetWindowHandle(
WindowHelper.GetWindowByFramePageType(FramePageType)
);
_watcherHelper = new ForegroundWindowWatcherHelper(
hwnd,
onWindowChanged =>
{
UpdateAccentColor(hwnd);
}
);
_watcherHelper.Start();
UpdateAccentColor(hwnd);
}
private void StopWatchWindowColorChange()
{
_watcherHelper?.Stop();
_watcherHelper = null;
}
partial void OnFramePageTypeChanged(Type value)
{
if (value != null)
{
var window = WindowHelper.GetWindowByFramePageType(FramePageType);
window.SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(
_settingsService.BackdropType
);
}
}
public void UpdateAccentColor(nint hwnd)
{
ActivatedWindowAccentColor = WindowColorHelper
@@ -162,24 +116,6 @@ namespace BetterLyrics.WinUI3
return null;
}
[RelayCommand]
private void ToggleDockMode()
{
var window = WindowHelper.GetWindowByFramePageType(FramePageType);
IsDockMode = !IsDockMode;
if (IsDockMode)
{
DockHelper.Enable(window, _settingsService.LyricsFontSize * 3);
StartWatchWindowColorChange();
}
else
{
DockHelper.Disable(window);
StopWatchWindowColorChange();
}
}
public void Receive(PropertyChangedMessage<TitleBarType> message)
{
if (message.Sender is SettingsViewModel)
@@ -195,30 +131,5 @@ namespace BetterLyrics.WinUI3
{
ThemeType = message.NewValue;
}
public void Receive(PropertyChangedMessage<BackdropType> message)
{
WindowHelper.GetWindowByFramePageType(FramePageType).SystemBackdrop =
SystemBackdropHelper.CreateSystemBackdrop(message.NewValue);
}
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is LyricsSettingsControlViewModel)
{
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize))
{
if (IsDockMode)
{
DockHelper.UpdateAppBarHeight(
WindowNative.GetWindowHandle(
WindowHelper.GetWindowByFramePageType(FramePageType)
),
message.NewValue * 3
);
}
}
}
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text;
@@ -53,12 +52,12 @@ namespace BetterLyrics.WinUI3.ViewModels
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
//FontFamily = "Segoe UI Mono",
};
public LyricsDisplayType DisplayType { get; set; }
private float _rotateAngle = 0f;
private byte[] _shaderByteCode = File.ReadAllBytes(AppInfo.CustomShaderPath);
private Color ActivatedWindowAccentColor { get; set; } = Colors.Transparent;
@@ -345,8 +344,6 @@ namespace BetterLyrics.WinUI3.ViewModels
var (displayStartLineIndex, displayEndLineIndex) =
GetVisibleLyricsLineIndexBoundaries();
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
for (
int i = displayStartLineIndex;
source?.Count > 0 && i >= 0 && i < source?.Count && i <= displayEndLineIndex;
@@ -466,8 +463,10 @@ namespace BetterLyrics.WinUI3.ViewModels
(float)(control.Size.Width - _rightMargin - LimitedLineWidth),
_totalYScroll + (float)(control.Size.Height / 2)
);
// _logger.LogDebug(_totalYScroll);
ds.DrawTextLayout(textLayout, position, Colors.Transparent);
// Reset scale
ds.Transform = Matrix3x2.Identity;
}
@@ -516,81 +515,52 @@ namespace BetterLyrics.WinUI3.ViewModels
};
}
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,
_currentBgColor.R,
_currentBgColor.G,
_currentBgColor.B
)
: _currentBgColor,
},
new CanvasGradientStop { Position = 1, Color = _currentBgColor },
]
)
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(0, (float)control.Size.Height),
}
);
}
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 (_isTransitioning && _lastSoftwareBitmap != null)
{
DrawImgae(control, overlappedCoversDs, _lastSoftwareBitmap, 1 - _transitionAlpha);
DrawImgae(control, overlappedCoversDs, SoftwareBitmap, _transitionAlpha);
}
else
{
DrawImgae(control, overlappedCoversDs, SoftwareBitmap, 1);
}
using var coverOverlayEffect = new OpacityEffect
{
Opacity = CoverOverlayOpacity / 100f,
Source = new GaussianBlurEffect
{
BlurAmount = CoverOverlayBlurAmount,
Source = overlappedCovers,
},
};
ds.DrawImage(coverOverlayEffect);
ds.Transform = Matrix3x2.Identity;
}
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
bool isAlbumArtOverlayDrawn = IsCoverOverlayEnabled && SoftwareBitmap != null;
if (isAlbumArtOverlayDrawn)
{
DrawAlbumArtBackground(control, ds);
}
if (IsDockMode)
{
DrawImmersiveBackground(control, ds, isAlbumArtOverlayDrawn);
ds.FillRectangle(
new Rect(0, 0, control.Size.Width, control.Size.Height),
_currentBgColor
);
}
else if (IsCoverOverlayEnabled && SoftwareBitmap != null)
{
ds.Transform = Matrix3x2.CreateRotation(
_rotateAngle,
control.Size.ToVector2() * 0.5f
);
var overlappedCovers = new CanvasCommandList(control.Device);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
if (_isTransitioning && _lastSoftwareBitmap != null)
{
DrawImgae(
control,
overlappedCoversDs,
_lastSoftwareBitmap,
1 - _transitionAlpha
);
DrawImgae(control, overlappedCoversDs, SoftwareBitmap, _transitionAlpha);
}
else
{
DrawImgae(control, overlappedCoversDs, SoftwareBitmap, 1);
}
using var coverOverlayEffect = new OpacityEffect
{
Opacity = CoverOverlayOpacity / 100f,
Source = new GaussianBlurEffect
{
BlurAmount = CoverOverlayBlurAmount,
Source = overlappedCovers,
},
};
ds.DrawImage(coverOverlayEffect);
ds.Transform = Matrix3x2.Identity;
}
// Original lyrics only layer
@@ -667,11 +637,11 @@ namespace BetterLyrics.WinUI3.ViewModels
using (var glowedLyricsDs = glowedLyrics.CreateDrawingSession())
{
glowedLyricsDs.DrawImage(
new ShadowEffect
new GaussianBlurEffect
{
Source = modifiedLyrics,
BlurAmount = _lyricsGlowEffectAmount,
ShadowColor = _fontColor,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Quality,
}
);
@@ -679,11 +649,11 @@ namespace BetterLyrics.WinUI3.ViewModels
}
// Mock gradient blurred lyrics layer
using var blurredLyrics = new CanvasCommandList(control);
using var blurredLyricsDs = blurredLyrics.CreateDrawingSession();
using var combinedBlurredLyrics = new CanvasCommandList(control);
using var combinedBlurredLyricsDs = combinedBlurredLyrics.CreateDrawingSession();
if (LyricsBlurAmount == 0)
{
blurredLyricsDs.DrawImage(glowedLyrics);
combinedBlurredLyricsDs.DrawImage(glowedLyrics);
}
else
{
@@ -691,7 +661,7 @@ namespace BetterLyrics.WinUI3.ViewModels
double overlapFactor = 0;
for (double i = 0; i <= 0.5 - step; i += step)
{
using var halfBlurredLyrics = new GaussianBlurEffect
using var blurredLyrics = new GaussianBlurEffect
{
Source = glowedLyrics,
BlurAmount = (float)(LyricsBlurAmount * (1 - i / (0.5 - step))),
@@ -700,7 +670,7 @@ namespace BetterLyrics.WinUI3.ViewModels
};
using var topCropped = new CropEffect
{
Source = halfBlurredLyrics,
Source = blurredLyrics,
SourceRectangle = new Rect(
0,
control.Size.Height * i,
@@ -710,7 +680,7 @@ namespace BetterLyrics.WinUI3.ViewModels
};
using var bottomCropped = new CropEffect
{
Source = halfBlurredLyrics,
Source = blurredLyrics,
SourceRectangle = new Rect(
0,
control.Size.Height * (1 - i - step * (1 + overlapFactor)),
@@ -718,18 +688,21 @@ namespace BetterLyrics.WinUI3.ViewModels
control.Size.Height * step * (1 + overlapFactor)
),
};
blurredLyricsDs.DrawImage(topCropped);
blurredLyricsDs.DrawImage(bottomCropped);
combinedBlurredLyricsDs.DrawImage(topCropped);
combinedBlurredLyricsDs.DrawImage(bottomCropped);
}
}
// Masked mock gradient blurred lyrics layer
using var maskedBlurredLyrics = new CanvasCommandList(control);
using (var maskedBlurredLyricsDs = maskedBlurredLyrics.CreateDrawingSession())
using var maskedCombinedBlurredLyrics = new CanvasCommandList(control);
using (
var maskedCombinedBlurredLyricsDs =
maskedCombinedBlurredLyrics.CreateDrawingSession()
)
{
if (LyricsVerticalEdgeOpacity == 100)
{
maskedBlurredLyricsDs.DrawImage(blurredLyrics);
maskedCombinedBlurredLyricsDs.DrawImage(combinedBlurredLyrics);
}
else
{
@@ -738,14 +711,14 @@ namespace BetterLyrics.WinUI3.ViewModels
{
DrawGradientOpacityMask(control, maskDs);
}
maskedBlurredLyricsDs.DrawImage(
new AlphaMaskEffect { Source = blurredLyrics, AlphaMask = mask }
maskedCombinedBlurredLyricsDs.DrawImage(
new AlphaMaskEffect { Source = combinedBlurredLyrics, AlphaMask = mask }
);
}
}
// Draw the final composed layer
ds.DrawImage(maskedBlurredLyrics);
ds.DrawImage(maskedCombinedBlurredLyrics);
}
private void DrawGradientOpacityMask(
@@ -858,8 +831,12 @@ namespace BetterLyrics.WinUI3.ViewModels
int currentPlayingLineIndex = GetCurrentPlayingLineIndex();
CalculateLinesProps(SongInfo?.LyricsLines, currentPlayingLineIndex, _defaultOpacity);
CalculateCanvasYScrollOffset(control, currentPlayingLineIndex);
CalculateScaleAndOpacity(
SongInfo?.LyricsLines,
currentPlayingLineIndex,
_defaultOpacity
);
CalculatePosition(control, currentPlayingLineIndex);
if (IsLyricsGlowEffectEnabled)
{
@@ -870,10 +847,10 @@ namespace BetterLyrics.WinUI3.ViewModels
case LyricsGlowEffectScope.WholeLyrics:
break;
case LyricsGlowEffectScope.CurrentLine:
CalculateLinesProps(_lyricsForGlowEffect, currentPlayingLineIndex, 0);
CalculateScaleAndOpacity(_lyricsForGlowEffect, currentPlayingLineIndex, 0);
break;
case LyricsGlowEffectScope.CurrentChar:
CalculateLinesProps(_lyricsForGlowEffect, currentPlayingLineIndex, 0);
CalculateScaleAndOpacity(_lyricsForGlowEffect, currentPlayingLineIndex, 0);
break;
default:
break;
@@ -881,7 +858,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
private void CalculateLinesProps(
private void CalculateScaleAndOpacity(
List<LyricsLine>? source,
int currentPlayingLineIndex,
float defaultOpacity
@@ -979,10 +956,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
private void CalculateCanvasYScrollOffset(
ICanvasAnimatedControl control,
int currentPlayingLineIndex
)
private void CalculatePosition(ICanvasAnimatedControl control, int currentPlayingLineIndex)
{
if (currentPlayingLineIndex < 0)
{
@@ -1046,7 +1020,7 @@ namespace BetterLyrics.WinUI3.ViewModels
_startVisibleLineIndex = _endVisibleLineIndex = -1;
// Update visible line indices
// Update Positions
for (
int i = startLineIndex;
i >= 0 && i <= endLineIndex && i < SongInfo?.LyricsLines?.Count;

View File

@@ -12,7 +12,10 @@
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid x:Name="RootGrid" RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
<Grid
x:Name="RootGrid"
Loaded="RootGrid_Loaded"
RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
<Frame
x:Name="RootFrame"
@@ -82,8 +85,7 @@
<ToggleMenuFlyoutItem
x:Name="DockFlyoutItem"
x:Uid="HostWindowDockFlyoutItem"
Command="{x:Bind ViewModel.ToggleDockModeCommand}"
IsChecked="{x:Bind ViewModel.IsDockMode, Mode=OneWay}" />
Click="DockFlyoutItem_Click" />
<ToggleMenuFlyoutItem
x:Name="MiniFlyoutItem"
x:Uid="BaseWindowMiniFlyoutItem"

View File

@@ -1,5 +1,4 @@
using System;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
@@ -26,18 +25,31 @@ namespace BetterLyrics.WinUI3.Views
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class HostWindow : Window
public sealed partial class HostWindow
: Window,
IRecipient<PropertyChangedMessage<BackdropType>>
{
public HostWindowViewModel ViewModel { get; private set; } =
Ioc.Default.GetRequiredService<HostWindowViewModel>();
public HostWindowViewModel ViewModel { get; set; }
private ForegroundWindowWatcherHelper? _watcherHelper = null;
private readonly ISettingsService _settingsService =
Ioc.Default.GetRequiredService<ISettingsService>();
public HostWindow(bool alwaysOnTop = false, bool clickThrough = false)
private readonly bool _listenOnActivatedWindowChange;
public HostWindow(
bool alwaysOnTop = false,
bool clickThrough = false,
bool listenOnActivatedWindowChange = false
)
{
this.InitializeComponent();
ViewModel = Ioc.Default.GetRequiredService<HostWindowViewModel>();
_listenOnActivatedWindowChange = listenOnActivatedWindowChange;
AppWindow.Changed += AppWindow_Changed;
this.HideSystemTitleBarAndSetCustomTitleBar(TopCommandGrid);
@@ -47,8 +59,37 @@ namespace BetterLyrics.WinUI3.Views
ExtendedWindowStyle.Transparent | ExtendedWindowStyle.Layered
);
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(
_settingsService.BackdropType
);
if (alwaysOnTop)
((OverlappedPresenter)AppWindow.Presenter).IsAlwaysOnTop = true;
if (listenOnActivatedWindowChange)
{
StartWatchWindowColorChange();
}
}
private void StartWatchWindowColorChange()
{
var hwnd = WindowNative.GetWindowHandle(this);
_watcherHelper = new ForegroundWindowWatcherHelper(
hwnd,
onWindowChanged =>
{
ViewModel.UpdateAccentColor(hwnd);
}
);
_watcherHelper.Start();
ViewModel.UpdateAccentColor(hwnd);
}
private void StopWatchWindowColorChange()
{
_watcherHelper?.Stop();
_watcherHelper = null;
}
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
@@ -178,7 +219,7 @@ namespace BetterLyrics.WinUI3.Views
if (_settingsService.AutoStartWindowType == AutoStartWindowType.DockMode)
{
DockFlyoutItem.IsChecked = true;
ViewModel.ToggleDockModeCommand.Execute(null);
DockFlyoutItem_Click(null, null);
}
}
}
@@ -220,6 +261,36 @@ namespace BetterLyrics.WinUI3.Views
}
}
public void Receive(PropertyChangedMessage<BackdropType> message)
{
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(message.NewValue);
}
private void RootGrid_Loaded(object sender, RoutedEventArgs e)
{
if (_listenOnActivatedWindowChange)
{
var hwnd = WindowNative.GetWindowHandle(this);
ViewModel.UpdateAccentColor(hwnd);
}
}
private void DockFlyoutItem_Click(object sender, RoutedEventArgs e)
{
if (DockFlyoutItem.IsChecked)
{
DockHelper.Enable(this, 48);
StartWatchWindowColorChange();
}
else
{
DockHelper.Disable(this);
StopWatchWindowColorChange();
}
ViewModel.IsDockMode = DockFlyoutItem.IsChecked;
UpdateTitleBarWindowButtonsVisibility();
}
private void TopCommandGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
if (TopCommandGrid.Opacity == 0)

View File

@@ -36,24 +36,40 @@ We provide more than one setting item to better align with your preference
- Language (English, Simplified Chinese, Traditional Chinese)
## Live demonstration
![alt text](Screenshots/Beelink-SER-8-Moonlight2025-06-0318-35-35-ezgif.com-video-to-gif-converter.gif)
![alt text](Screenshots/BetterLyrics.WinUI3_igDdnc4rzW.gif)
![alt text](Screenshots/Code_HhDCqJZYEZ.gif)
> **Highlighted feature**: Immersive lyrics shown on top of the screen (automatically follow current activated windows's accent color)
Or watch our introduction video「BetterLyrics 阶段性开发成果展示」(uploaded on 31 May 2025) on Bilibili [here](https://b23.tv/QjKkYmL).
## Screenshots
![alt text](Screenshots/mode.png)
### In-app lyrics
![alt text](Screenshots/glow.png)
Non-immersive mode
![alt text](Screenshots/dock.png)
![alt text](Screenshots/Snipaste_2025-06-07_17-36-26.png)
![alt text](Screenshots/pip.png)
Immersive mode
![alt text](Screenshots/Snipaste_2025-06-03_16-47-43.png)
![alt text](Screenshots/settings.png)
Lyrics only
![alt text](Screenshots/fs.png)
![alt text](Screenshots/Snipaste_2025-06-03_17-51-22.png)
## Demonstration
Fullscreen
Watch our introduction video「BetterLyrics 阶段性开发成果展示」(uploaded on 31 May 2025) on Bilibili [here](https://b23.tv/QjKkYmL).
![alt text](Screenshots/Snipaste_2025-06-03_18-36-05.png)
### Desktop lyrics
![alt text](Screenshots/image.png)
## Try it now
@@ -110,7 +126,7 @@ So technically, as long as you are using the music apps (like
## Inspired by
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
- [BetterNCM](https://github.com/std-microblock/BetterNCM)
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

BIN
Screenshots/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 947 KiB