add: auto adaptive (immersive) background for desktop lyrics; fix: change get method (cached in memory if not changed) for settings viewmodel; fix: incorrect display position for desktop lyrics

This commit is contained in:
Zhe Fang
2025-06-14 13:14:38 -04:00
parent def2c9820a
commit cd857a2807
35 changed files with 1130 additions and 593 deletions

View File

@@ -54,7 +54,7 @@ namespace BetterLyrics.WinUI3
_logger = Ioc.Default.GetService<ILogger<App>>()!;
// UnhandledException += App_UnhandledException;
UnhandledException += App_UnhandledException;
}
private static void ConfigureServices()
@@ -72,24 +72,25 @@ namespace BetterLyrics.WinUI3
loggingBuilder.ClearProviders();
loggingBuilder.AddSerilog();
})
// Services
// Services (Singleton)
.AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<IDatabaseService, DatabaseService>()
.AddSingleton<IPlaybackService, PlaybackService>()
// Renderer
.AddSingleton<AlbumArtRenderer>()
.AddSingleton<InAppLyricsRenderer>()
.AddSingleton<DesktopLyricsRenderer>()
// ViewModels
// ViewModels (Transient)
.AddTransient<BaseSettingsViewModel>()
.AddTransient<BaseLyricsViewModel>()
// ViewModels (Singleton)
.AddSingleton<HostViewModel>()
.AddSingleton<AlbumArtViewModel>()
.AddSingleton<MainViewModel>()
.AddSingleton<BaseSettingsViewModel>()
.AddSingleton<GlobalViewModel>()
.AddSingleton<SettingsViewModel>()
.AddSingleton<InAppLyricsViewModel>()
.AddSingleton<DesktopLyricsViewModel>()
.AddSingleton<AlbumArtOverlayViewModel>()
// Renderer (Singleton)
.AddSingleton<AlbumArtRenderer>()
.AddSingleton<InAppLyricsRenderer>()
.AddSingleton<DesktopLyricsRenderer>()
.BuildServiceProvider()
);
}
@@ -111,13 +112,9 @@ namespace BetterLyrics.WinUI3
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var overlayWindow = new OverlayWindow();
overlayWindow.Navigate(typeof(DesktopLyricsPage));
Current.OverlayWindow = overlayWindow;
// Activate the window
MainWindow = new HostWindow();
MainWindow!.Navigate(typeof(MainPage));
MainWindow!.Navigate(typeof(InAppLyricsPage));
MainWindow.Activate();
}
}

View File

@@ -10,6 +10,7 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="Controls\LyricsSettingsControl.xaml" />
<None Remove="Views\DesktopLyricsPage.xaml" />
<None Remove="Views\OverlayWindow.xaml" />
</ItemGroup>
@@ -24,6 +25,8 @@
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
@@ -51,7 +54,9 @@
</Content>
</ItemGroup>
<ItemGroup>
<Folder Include="Controls\" />
<Page Update="Controls\LyricsSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\DesktopLyricsPage.xaml">
@@ -86,7 +91,7 @@
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
<Version>2025.6.0</Version>
<AssemblyVersion>2025.6.13.2318</AssemblyVersion>
<FileVersion>2025.6.13.2318</FileVersion>
<AssemblyVersion>2025.6.14.1656</AssemblyVersion>
<FileVersion>2025.6.14.1656</FileVersion>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,189 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.LyricsSettingsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsLeft" />
<ComboBoxItem x:Uid="SettingsPageLyricsCenter" />
<ComboBoxItem x:Uid="SettingsPageLyricsRight" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsExpander x:Uid="SettingsPageLyricsFontColor" HeaderIcon="{ui:FontIcon Glyph=&#xE8D3;}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="IsExpanded" Value="False" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="1">
<interactivity:ChangePropertyAction PropertyName="IsExpanded" Value="True" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDefault" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDominant" />
</ComboBox>
<controls:SettingsExpander.Items>
<controls:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Vertical">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="1">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<GridView ItemsSource="{x:Bind ViewModel.CoverImageDominantColors, Mode=OneWay}" SelectedIndex="{x:Bind ViewModel.LyricsFontSelectedAccentColorIndex, Mode=TwoWay}">
<GridView.ItemTemplate>
<DataTemplate>
<GridViewItem>
<StackPanel>
<Border
Width="64"
Height="64"
CornerRadius="4">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
<Border.BackgroundTransition>
<BrushTransition />
</Border.BackgroundTransition>
</Border>
<TextBlock
Margin="4,0,4,4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
TextWrapping="WrapWholeWords" />
</StackPanel>
</GridViewItem>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon Glyph=&#xE8E9;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text="{Binding ElementName=LyricsFontSizeSlider, Path=Value, Mode=OneWay}" />
<Slider
x:Name="LyricsFontSizeSlider"
Maximum="96"
Minimum="12"
SnapsTo="Ticks"
StepFrequency="2"
TickFrequency="2"
TickPlacement="Outside"
Value="{x:Bind ViewModel.LyricsFontSize, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon Glyph=&#xF579;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=LyricsLineSpacingFactorSlider, Path=Value, Mode=OneWay}" />
<TextBlock
x:Uid="SettingsPageLyricsLineSpacingFactorUnit"
Margin="0,0,14,0"
VerticalAlignment="Center" />
<Slider
x:Name="LyricsLineSpacingFactorSlider"
Maximum="2"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="0.1"
TickFrequency="0.1"
TickPlacement="Outside"
Value="{x:Bind ViewModel.LyricsLineSpacingFactor, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsVerticalEdgeOpacity" HeaderIcon="{ui:FontIcon Glyph=&#xF573;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=LyricsVerticalEdgeOpacitySlider, Path=Value, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text=" %" />
<Slider
x:Name="LyricsVerticalEdgeOpacitySlider"
Maximum="100"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="1"
TickFrequency="1"
TickPlacement="Outside"
Value="{x:Bind ViewModel.LyricsVerticalEdgeOpacity, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon Glyph=&#xE727;}">
<controls:SettingsCard.Description>
<StackPanel>
<TextBlock x:Uid="SettingsPageLyricsBlurHighGPUUsage" Foreground="{ThemeResource SystemFillColorCautionBrush}" />
<TextBlock x:Uid="SettingsPageLyricsBlurAmountSideEffect" />
</StackPanel>
</controls:SettingsCard.Description>
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text="{Binding ElementName=LyricsBlurAmountSlider, Path=Value, Mode=OneWay}" />
<Slider
x:Name="LyricsBlurAmountSlider"
Maximum="10"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="1"
TickFrequency="1"
TickPlacement="Outside"
Value="{x:Bind ViewModel.LyricsBlurAmount, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsExpander
x:Uid="SettingsPageLyricsGlowEffect"
HeaderIcon="{ui:FontIcon Glyph=&#xE9A9;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLyricsDynamicGlowEffect" IsEnabled="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsLyricsDynamicGlowEffectEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using BetterLyrics.WinUI3.ViewModels.Lyrics;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class LyricsSettingsControl : UserControl
{
public LyricsSettingsControl()
{
InitializeComponent();
}
public ILyricsViewModel ViewModel
{
get => (ILyricsViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(
nameof(ViewModel),
typeof(ILyricsViewModel),
typeof(LyricsSettingsControl),
new PropertyMetadata(null)
);
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI;
namespace BetterLyrics.WinUI3.Helper
{
public static class ColorHelper
{
public static Windows.UI.Color ToWindowsUIColor(this System.Drawing.Color color)
{
return Windows.UI.Color.FromArgb(color.A, color.R, color.G, color.B);
}
public static Color GetInterpolatedColor(
float progress,
Color startColor,
Color targetColor
)
{
byte Lerp(byte a, byte b) => (byte)(a + (progress * (b - a)));
return Color.FromArgb(
Lerp(startColor.A, targetColor.A),
Lerp(startColor.R, targetColor.R),
Lerp(startColor.G, targetColor.G),
Lerp(startColor.B, targetColor.B)
);
}
}
}

View File

@@ -0,0 +1,142 @@
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
{
public class ForegroundWindowWatcherHelper
{
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;
public delegate void WindowChangedHandler(IntPtr hwnd);
private readonly WindowChangedHandler _onWindowChanged;
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;
public ForegroundWindowWatcherHelper(IntPtr selfHwnd, WindowChangedHandler onWindowChanged)
{
_selfHwnd = selfHwnd;
_onWindowChanged = onWindowChanged;
_winEventDelegate = new WinEventDelegate(WinEventProc);
_pollingTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
_pollingTimer.Tick += (_, _) =>
{
if (_currentForeground != IntPtr.Zero && _currentForeground != _selfHwnd)
_onWindowChanged?.Invoke(_currentForeground);
};
}
public void Start()
{
// Hook: foreground changes and minimize end
_hooks.Add(
SetWinEventHook(
EVENT_SYSTEM_FOREGROUND,
EVENT_SYSTEM_MINIMIZEEND,
IntPtr.Zero,
_winEventDelegate,
0,
0,
WINEVENT_OUTOFCONTEXT
)
);
// Hook: window move/resize (location change)
_hooks.Add(
SetWinEventHook(
EVENT_OBJECT_LOCATIONCHANGE,
EVENT_OBJECT_LOCATIONCHANGE,
IntPtr.Zero,
_winEventDelegate,
0,
0,
WINEVENT_OUTOFCONTEXT
)
);
_pollingTimer.Start();
}
public void Stop()
{
foreach (var hook in _hooks)
UnhookWinEvent(hook);
_hooks.Clear();
_pollingTimer.Stop();
}
private void WinEventProc(
IntPtr hWinEventHook,
uint eventType,
IntPtr hwnd,
int idObject,
int idChild,
uint dwEventThread,
uint dwmsEventTime
)
{
if (hwnd == IntPtr.Zero || hwnd == _selfHwnd)
return;
var now = DateTime.Now;
if ((now - _lastEventTime).TotalMilliseconds < ThrottleIntervalMs)
return;
_lastEventTime = now;
if (eventType == EVENT_SYSTEM_FOREGROUND)
{
_currentForeground = hwnd;
_onWindowChanged?.Invoke(hwnd);
}
else if (
(eventType == EVENT_OBJECT_LOCATIONCHANGE || eventType == EVENT_SYSTEM_MINIMIZEEND)
&& hwnd == _currentForeground
)
{
_onWindowChanged?.Invoke(hwnd);
}
}
#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
}
}

View File

@@ -7,48 +7,30 @@ namespace BetterLyrics.WinUI3.Helper
{
public static class WindowColorHelper
{
public static Color GetDominantColorBelow(
IntPtr myHwnd,
int sampleWidth = 64,
int sampleHeight = 64
)
public static Color GetDominantColorBelow(IntPtr myHwnd)
{
// 获取屏幕坐标中,在窗口下方的某个点
if (!GetWindowRect(myHwnd, out RECT myRect))
return Color.Transparent;
POINT pt = new()
{
x = (myRect.Left + myRect.Right) / 2,
y = myRect.Bottom + 1, // 紧贴窗口底部
};
int screenWidth = GetSystemMetrics(SystemMetric.SM_CXSCREEN);
int sampleHeight = 1;
int sampleY = myRect.Bottom + 1;
IntPtr hwndBelow = WindowFromPoint(pt);
if (hwndBelow == myHwnd || hwndBelow == IntPtr.Zero)
return Color.Transparent;
return GetAverageColorFromWindow(hwndBelow, sampleWidth, sampleHeight);
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
}
private static Color GetAverageColorFromWindow(IntPtr hwnd, int width, int height)
private static Color GetAverageColorFromScreenRegion(int x, int y, int width, int height)
{
if (!GetWindowRect(hwnd, out RECT rect))
return Color.Transparent;
int w = Math.Min(width, rect.Right - rect.Left);
int h = Math.Min(height, rect.Bottom - rect.Top);
using Bitmap bmp = new(w, h, PixelFormat.Format32bppArgb);
using Bitmap bmp = new(width, height, PixelFormat.Format32bppArgb);
using Graphics gDest = Graphics.FromImage(bmp);
IntPtr hdcDest = gDest.GetHdc();
IntPtr hdcSrc = GetWindowDC(hwnd);
IntPtr hdcSrc = GetDC(IntPtr.Zero); // Entire screen
BitBlt(hdcDest, 0, 0, w, h, hdcSrc, rect.Left, rect.Top, SRCCOPY);
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, SRCCOPY);
gDest.ReleaseHdc(hdcDest);
ReleaseDC(hwnd, hdcSrc);
ReleaseDC(IntPtr.Zero, hdcSrc);
return ComputeAverageColor(bmp);
}
@@ -80,14 +62,11 @@ namespace BetterLyrics.WinUI3.Helper
#region Win32 Imports & Structs
private const int SRCCOPY = 0x00CC0020;
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(POINT Point);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hWnd);
private static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
@@ -105,11 +84,13 @@ namespace BetterLyrics.WinUI3.Helper
int dwRop
);
[StructLayout(LayoutKind.Sequential)]
private struct POINT
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(SystemMetric smIndex);
private enum SystemMetric
{
public int x;
public int y;
SM_CXSCREEN = 0,
SM_CYSCREEN = 1,
}
[StructLayout(LayoutKind.Sequential)]

View File

@@ -1,17 +1,29 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Views;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.Storage;
using WinRT.Interop;
namespace BetterLyrics.WinUI3.Helper
{
public class WindowHelper
{
public static Window CreateWindow()
public static HostWindow CreateHostWindow()
{
Window newWindow = new Window { SystemBackdrop = new MicaBackdrop() };
HostWindow newWindow = new() { SystemBackdrop = new MicaBackdrop() };
TrackWindow(newWindow);
return newWindow;
}
public static OverlayWindow CreateOverlayWindow()
{
OverlayWindow newWindow = new();
TransparentAppBarHelper.Enable(newWindow, 48);
TrackWindow(newWindow);
return newWindow;
}

View File

@@ -1,6 +0,0 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
namespace BetterLyrics.WinUI3.Messages
{
public class InAppLyricsRelayoutRequestedMessage() : ValueChangedMessage<object?>(null) { }
}

View File

@@ -1,6 +0,0 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
namespace BetterLyrics.WinUI3.Messages
{
public class DesktopLyricsRelayoutRequestedMessage() : ValueChangedMessage<object?>(null) { }
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public enum LyricsType
{
InAppLyrics,
DesktopLyrics,
}
}

View File

@@ -158,7 +158,7 @@ namespace BetterLyrics.WinUI3.Rendering
}
}
if (_viewModel.IsDynamicCoverOverlay)
if (_viewModel.IsDynamicCoverOverlayEnabled)
{
_rotateAngle += _coverRotateSpeed;
_rotateAngle %= MathF.PI * 2;

View File

@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
@@ -15,11 +13,8 @@ using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
using Windows.Foundation;
using Windows.UI;
@@ -57,11 +52,43 @@ namespace BetterLyrics.WinUI3.Rendering
private readonly double _rightMargin = 36;
public double LimitedLineWidth { get; set; } = 0;
private bool _isLimitedLineWidthUpdated = false;
private double _limitedLineWidth = 0;
public double LimitedLineWidth
{
get => _limitedLineWidth;
set
{
if (_limitedLineWidth != value)
{
_limitedLineWidth = value;
_isLimitedLineWidthUpdated = true;
}
}
}
public TimeSpan CurrentTime { get; set; } = TimeSpan.Zero;
public List<LyricsLine> LyricsLines { get; set; } = [];
private bool _isLyricsUpdated = true;
private List<LyricsLine> _lyricsLines = [];
public List<LyricsLine> LyricsLines
{
get => _lyricsLines;
set
{
if (_lyricsLines != value)
{
_lyricsLines = value;
_isLyricsUpdated = true;
}
}
}
private bool _isRelayoutNeeded = true;
private float _fontSize = 0;
private float _lineSpacingFactor = 0;
private readonly ILyricsViewModel _viewModel;
@@ -394,6 +421,13 @@ namespace BetterLyrics.WinUI3.Rendering
// Draw the final composed layer
ds.DrawImage(maskedCombinedBlurredLyrics);
ds.DrawText(
$"{_fontSize}, {_lineSpacingFactor}, {_isRelayoutNeeded}",
10,
10,
Colors.Red
);
}
private void DrawGradientOpacityMask(
@@ -417,19 +451,12 @@ namespace BetterLyrics.WinUI3.Rendering
ds.FillRectangle(new Rect(0, 0, control.Size.Width, control.Size.Height), maskBrush);
}
public async Task ForceToScrollToCurrentPlayingLineAsync()
{
_forceToScroll = true;
await Task.Delay(1);
_forceToScroll = false;
}
public async Task ReLayoutAsync(ICanvasAnimatedControl control)
private void ReLayout(ICanvasAnimatedControl control)
{
if (control == null)
return;
_textFormat.FontSize = _viewModel.LyricsFontSize;
_textFormat.FontSize = _fontSize;
float y = 0;
@@ -451,14 +478,33 @@ namespace BetterLyrics.WinUI3.Rendering
y +=
(float)textLayout.LayoutBounds.Height
/ textLayout.LineCount
* (textLayout.LineCount + _viewModel.LyricsLineSpacingFactor);
* (textLayout.LineCount + _lineSpacingFactor);
}
await ForceToScrollToCurrentPlayingLineAsync();
}
public void Calculate(ICanvasAnimatedControl control)
{
if (
_isLyricsUpdated
|| _isLimitedLineWidthUpdated
|| _fontSize != _viewModel.LyricsFontSize
|| _lineSpacingFactor != _viewModel.LyricsLineSpacingFactor
)
{
_fontSize = _viewModel.LyricsFontSize;
_lineSpacingFactor = _viewModel.LyricsLineSpacingFactor;
_isRelayoutNeeded = true;
_forceToScroll = true;
}
if (_isRelayoutNeeded)
{
ReLayout(control);
_isRelayoutNeeded = false;
_isLyricsUpdated = false;
_isLimitedLineWidthUpdated = false;
}
int currentPlayingLineIndex = GetCurrentPlayingLineIndex();
CalculateScaleAndOpacity(currentPlayingLineIndex);
CalculatePosition(control, currentPlayingLineIndex);
@@ -620,6 +666,7 @@ namespace BetterLyrics.WinUI3.Rendering
if (_forceToScroll && Math.Abs(targetYScrollOffset) >= 1)
{
_totalYScroll = _lastTotalYScroll + targetYScrollOffset;
_forceToScroll = false;
}
_lastTotalYScroll = _totalYScroll;
}

View File

@@ -3,15 +3,73 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.Lyrics;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Windows.UI;
namespace BetterLyrics.WinUI3.Rendering
{
public class DesktopLyricsRenderer : BaseLyricsRenderer
{
public DesktopLyricsRenderer(DesktopLyricsViewModel viewModel)
: base(viewModel) { }
private readonly GlobalViewModel _globalViewModel;
private Color _startColor;
private Color _targetColor;
private float _progress; // From 0 to 1
private const float TransitionSeconds = 0.3f;
private bool _isTransitioning;
public DesktopLyricsRenderer(
DesktopLyricsViewModel viewModel,
GlobalViewModel globalViewModel
)
: base(viewModel)
{
_globalViewModel = globalViewModel;
_startColor = _targetColor =
_globalViewModel.ActivatedWindowAccentColor.ToWindowsUIColor();
_progress = 1f;
}
public void UpdateTransition(float elapsedSeconds)
{
// Detect if the accent color has changed
var currentAccent = _globalViewModel.ActivatedWindowAccentColor.ToWindowsUIColor();
if (currentAccent != _targetColor)
{
_startColor = _isTransitioning
? ColorHelper.GetInterpolatedColor(_progress, _startColor, _targetColor)
: _targetColor;
_targetColor = currentAccent;
_progress = 0f;
_isTransitioning = true;
}
// Update the transition progress
if (_isTransitioning)
{
_progress += elapsedSeconds / TransitionSeconds;
if (_progress >= 1f)
{
_progress = 1f;
_isTransitioning = false;
}
}
}
public void DrawBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
var color = _isTransitioning
? ColorHelper.GetInterpolatedColor(_progress, _startColor, _targetColor)
: _targetColor;
ds.FillRectangle(control.Size.ToRect(), color);
}
}
}

View File

@@ -1,11 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Services.Playback
namespace BetterLyrics.WinUI3.Services.Playback
{
public interface IPlaybackService
{

View File

@@ -16,7 +16,7 @@
// Backdrop
public const int BackdropType = 5; // Acrylic Base
public const bool IsCoverOverlayEnabled = true;
public const bool IsDynamicCoverOverlay = true;
public const bool IsDynamicCoverOverlayEnabled = true;
public const int CoverOverlayOpacity = 100; // 1.0
public const int CoverOverlayBlurAmount = 200;

View File

@@ -22,7 +22,7 @@ namespace BetterLyrics.WinUI3.Services.Settings
// Backdrop
public const string BackdropType = "BackdropType";
public const string IsCoverOverlayEnabled = "IsCoverOverlayEnabled";
public const string IsDynamicCoverOverlay = "IsDynamicCoverOverlay";
public const string IsDynamicCoverOverlayEnabled = "IsDynamicCoverOverlayEnabled";
public const string CoverOverlayOpacity = "CoverOverlayOpacity";
public const string CoverOverlayBlurAmount = "CoverOverlayBlurAmount";

View File

@@ -225,12 +225,9 @@
<data name="SettingsPageTitle" xml:space="preserve">
<value>Settings</value>
</data>
<data name="MainPageTitle" xml:space="preserve">
<data name="InAppLyricsPageTitle" xml:space="preserve">
<value>BetterLyrics</value>
</data>
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>Lyrics style</value>
</data>
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
<value>Alignment</value>
</data>
@@ -279,9 +276,6 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>Immersive mode</value>
</data>
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>Lyrics effect</value>
</data>
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
<value>Album background</value>
</data>
@@ -396,4 +390,13 @@
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<value>Lyrics not found</value>
</data>
<data name="SettingsPageLyrics.Text" xml:space="preserve">
<value>Lyrics style &amp; effect</value>
</data>
<data name="SettingsPageInAppLyrics.Content" xml:space="preserve">
<value>In-app lyrics</value>
</data>
<data name="SettingsPageDesktopLyrics.Content" xml:space="preserve">
<value>Desktop lyrics</value>
</data>
</root>

View File

@@ -225,12 +225,9 @@
<data name="SettingsPageTitle" xml:space="preserve">
<value>设置</value>
</data>
<data name="MainPageTitle" xml:space="preserve">
<data name="InAppLyricsPageTitle" xml:space="preserve">
<value>BetterLyrics</value>
</data>
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>歌词样式</value>
</data>
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
<value>对齐方式</value>
</data>
@@ -279,9 +276,6 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>沉浸模式</value>
</data>
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌词效果</value>
</data>
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
<value>专辑背景</value>
</data>
@@ -396,4 +390,13 @@
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<value>未找到歌词</value>
</data>
<data name="SettingsPageLyrics.Text" xml:space="preserve">
<value>歌词样式与动效</value>
</data>
<data name="SettingsPageInAppLyrics.Content" xml:space="preserve">
<value>应用内歌词</value>
</data>
<data name="SettingsPageDesktopLyrics.Content" xml:space="preserve">
<value>桌面歌词</value>
</data>
</root>

View File

@@ -225,12 +225,9 @@
<data name="SettingsPageTitle" xml:space="preserve">
<value>設定</value>
</data>
<data name="MainPageTitle" xml:space="preserve">
<data name="InAppLyricsPageTitle" xml:space="preserve">
<value>BetterLyrics</value>
</data>
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
<value>歌詞樣式</value>
</data>
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
<value>對齊方式</value>
</data>
@@ -279,9 +276,6 @@
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
<value>沉浸模式</value>
</data>
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
<value>歌詞效果</value>
</data>
<data name="SettingsPageAlbumOverlay.Text" xml:space="preserve">
<value>專輯背景</value>
</data>
@@ -396,4 +390,13 @@
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<value>找不到歌詞</value>
</data>
<data name="SettingsPageLyrics.Text" xml:space="preserve">
<value>歌詞樣式與動效</value>
</data>
<data name="SettingsPageInAppLyrics.Content" xml:space="preserve">
<value>應用程式內歌詞</value>
</data>
<data name="SettingsPageDesktopLyrics.Content" xml:space="preserve">
<value>桌面歌詞</value>
</data>
</root>

View File

@@ -7,37 +7,73 @@ namespace BetterLyrics.WinUI3.ViewModels
public AlbumArtOverlayViewModel(ISettingsService settingsService)
: base(settingsService) { }
private bool? _isCoverOverlayEnabled = null;
public bool IsCoverOverlayEnabled
{
get =>
Get(
get
{
_isCoverOverlayEnabled ??= Get(
SettingsKeys.IsCoverOverlayEnabled,
SettingsDefaultValues.IsCoverOverlayEnabled
);
set => Set(SettingsKeys.IsCoverOverlayEnabled, value);
return _isCoverOverlayEnabled ?? false;
}
set
{
_isCoverOverlayEnabled = value;
Set(SettingsKeys.IsCoverOverlayEnabled, value);
}
}
public bool IsDynamicCoverOverlay
private bool? _isDynamicCoverOverlayEnabled = null;
public bool IsDynamicCoverOverlayEnabled
{
get =>
Get(
SettingsKeys.IsDynamicCoverOverlay,
SettingsDefaultValues.IsDynamicCoverOverlay
get
{
_isDynamicCoverOverlayEnabled ??= Get(
SettingsKeys.IsDynamicCoverOverlayEnabled,
SettingsDefaultValues.IsDynamicCoverOverlayEnabled
);
set => Set(SettingsKeys.IsDynamicCoverOverlay, value);
return _isDynamicCoverOverlayEnabled ?? false;
}
set
{
_isDynamicCoverOverlayEnabled = value;
Set(SettingsKeys.IsDynamicCoverOverlayEnabled, value);
}
}
private int? _coverOverlayOpacity;
public int CoverOverlayOpacity
{
get => Get(SettingsKeys.CoverOverlayOpacity, SettingsDefaultValues.CoverOverlayOpacity);
set => Set(SettingsKeys.CoverOverlayOpacity, value);
get
{
_coverOverlayOpacity ??= Get(
SettingsKeys.CoverOverlayOpacity,
SettingsDefaultValues.CoverOverlayOpacity
);
return _coverOverlayOpacity ?? 0;
}
set
{
_coverOverlayOpacity = value;
Set(SettingsKeys.CoverOverlayOpacity, value);
}
}
private int? _coverOverlayBlurAmount;
public int CoverOverlayBlurAmount
{
get =>
Get(
get
{
_coverOverlayBlurAmount ??= Get(
SettingsKeys.CoverOverlayBlurAmount,
SettingsDefaultValues.CoverOverlayBlurAmount
);
set => Set(SettingsKeys.CoverOverlayBlurAmount, value);
return _coverOverlayBlurAmount ?? 0;
}
set
{
_coverOverlayBlurAmount = value;
Set(SettingsKeys.CoverOverlayBlurAmount, value);
}
}
}
}

View File

@@ -11,12 +11,21 @@ namespace BetterLyrics.WinUI3.ViewModels
{
public class AlbumArtViewModel : BaseSettingsViewModel
{
private int? _coverImageRadius;
public int CoverImageRadius
{
get => Get(SettingsKeys.CoverImageRadius, SettingsDefaultValues.CoverImageRadius);
get
{
_coverImageRadius ??= Get(
SettingsKeys.CoverImageRadius,
SettingsDefaultValues.CoverImageRadius
);
return _coverImageRadius ?? 0;
}
set
{
Set(SettingsKeys.CoverImageRadius, value);
_coverImageRadius = value;
WeakReferenceMessenger.Default.Send(new AlbumArtCornerRadiusChangedMessage(value));
}
}

View File

@@ -53,6 +53,9 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
private bool _isPlaying = false;
[ObservableProperty]
private System.Drawing.Color _activatedWindowAccentColor = System.Drawing.Color.Transparent;
public GlobalViewModel(ISettingsService settingsService)
: base(settingsService)
{

View File

@@ -1,115 +1,203 @@
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Playback;
using BetterLyrics.WinUI3.Services.Settings;
using BetterLyrics.WinUI3.ViewModels.Lyrics;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml.Documents;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class DesktopLyricsViewModel : BaseLyricsViewModel, ILyricsViewModel
{
private LyricsAlignmentType? _lyricsAlignmentType;
public LyricsAlignmentType LyricsAlignmentType
{
get =>
(LyricsAlignmentType)Get(
get
{
_lyricsAlignmentType ??= (LyricsAlignmentType)Get(
SettingsKeys.DesktopLyricsAlignmentType,
SettingsDefaultValues.DesktopLyricsAlignmentType
);
set => Set(SettingsKeys.DesktopLyricsAlignmentType, (int)value);
return _lyricsAlignmentType ?? 0;
}
set
{
Set(SettingsKeys.DesktopLyricsAlignmentType, (int)value);
_lyricsAlignmentType = value;
}
}
private int? _lyricsBlurAmount;
public int LyricsBlurAmount
{
get =>
Get(
get
{
_lyricsBlurAmount ??= Get(
SettingsKeys.DesktopLyricsBlurAmount,
SettingsDefaultValues.DesktopLyricsBlurAmount
);
set => Set(SettingsKeys.DesktopLyricsBlurAmount, value);
return _lyricsBlurAmount ?? 0;
}
set
{
Set(SettingsKeys.DesktopLyricsBlurAmount, value);
_lyricsBlurAmount = value;
}
}
private int? _lyricsVerticalEdgeOpacity;
public int LyricsVerticalEdgeOpacity
{
get =>
Get(
get
{
_lyricsVerticalEdgeOpacity ??= Get(
SettingsKeys.DesktopLyricsVerticalEdgeOpacity,
SettingsDefaultValues.DesktopLyricsVerticalEdgeOpacity
);
set => Set(SettingsKeys.DesktopLyricsVerticalEdgeOpacity, value);
return _lyricsVerticalEdgeOpacity ?? 0;
}
set
{
Set(SettingsKeys.DesktopLyricsVerticalEdgeOpacity, value);
_lyricsVerticalEdgeOpacity = value;
}
}
private float? _lyricsLineSpacingFactor;
public float LyricsLineSpacingFactor
{
get =>
Get(
get
{
_lyricsLineSpacingFactor ??= Get(
SettingsKeys.DesktopLyricsLineSpacingFactor,
SettingsDefaultValues.DesktopLyricsLineSpacingFactor
);
return _lyricsLineSpacingFactor ?? 0;
}
set
{
Set(SettingsKeys.DesktopLyricsLineSpacingFactor, value);
WeakReferenceMessenger.Default.Send(new DesktopLyricsRelayoutRequestedMessage());
_lyricsLineSpacingFactor = value;
}
}
private int? _lyricsFontSize;
public int LyricsFontSize
{
get =>
Get(
get
{
_lyricsFontSize ??= Get(
SettingsKeys.DesktopLyricsFontSize,
SettingsDefaultValues.DesktopLyricsFontSize
);
return _lyricsFontSize ?? 0;
}
set
{
Set(SettingsKeys.DesktopLyricsFontSize, value);
WeakReferenceMessenger.Default.Send(new DesktopLyricsRelayoutRequestedMessage());
_lyricsFontSize = value;
}
}
private bool? _isLyricsGlowEffectEnabled;
public bool IsLyricsGlowEffectEnabled
{
get =>
Get(
get
{
_isLyricsGlowEffectEnabled ??= Get(
SettingsKeys.IsDesktopLyricsGlowEffectEnabled,
SettingsDefaultValues.IsDesktopLyricsGlowEffectEnabled
);
set => Set(SettingsKeys.IsDesktopLyricsGlowEffectEnabled, value);
return _isLyricsGlowEffectEnabled ?? false;
}
set
{
Set(SettingsKeys.IsDesktopLyricsGlowEffectEnabled, value);
_isLyricsGlowEffectEnabled = value;
}
}
private bool? _isLyricsDynamicGlowEffectEnabled;
public bool IsLyricsDynamicGlowEffectEnabled
{
get =>
Get(
get
{
_isLyricsDynamicGlowEffectEnabled ??= Get(
SettingsKeys.IsDesktopLyricsDynamicGlowEffectEnabled,
SettingsDefaultValues.IsDesktopLyricsDynamicGlowEffectEnabled
);
set => Set(SettingsKeys.IsDesktopLyricsDynamicGlowEffectEnabled, value);
return _isLyricsDynamicGlowEffectEnabled ?? false;
}
set
{
Set(SettingsKeys.IsDesktopLyricsDynamicGlowEffectEnabled, value);
_isLyricsDynamicGlowEffectEnabled = value;
}
}
private LyricsFontColorType? _lyricsFontColorType;
public LyricsFontColorType LyricsFontColorType
{
get =>
(LyricsFontColorType)Get(
get
{
_lyricsFontColorType ??= (LyricsFontColorType)Get(
SettingsKeys.DesktopLyricsFontColorType,
SettingsDefaultValues.DesktopLyricsFontColorType
);
return _lyricsFontColorType ?? 0;
}
set
{
Set(SettingsKeys.DesktopLyricsFontColorType, (int)value);
_lyricsFontColorType = value;
WeakReferenceMessenger.Default.Send(new LyricsFontColorChangedMessage());
}
}
private int? _lyricsFontSelectedAccentColorIndex;
public int LyricsFontSelectedAccentColorIndex
{
get =>
Get(
get
{
_lyricsFontSelectedAccentColorIndex ??= Get(
SettingsKeys.DesktopLyricsFontSelectedAccentColorIndex,
SettingsDefaultValues.DesktopLyricsFontSelectedAccentColorIndex
);
return _lyricsFontSelectedAccentColorIndex ?? 0;
}
set
{
if (value >= 0)
{
Set(SettingsKeys.DesktopLyricsFontSelectedAccentColorIndex, value);
_lyricsFontSelectedAccentColorIndex = value;
WeakReferenceMessenger.Default.Send(new LyricsFontColorChangedMessage());
}
}
}
public DesktopLyricsViewModel(ISettingsService settingsService)
: base(settingsService) { }
[ObservableProperty]
private bool _isSettingsPopupOpened = false;
[RelayCommand]
private void ToggleSettingsPopup()
{
IsSettingsPopupOpened = !IsSettingsPopupOpened;
}
private readonly IPlaybackService _playbackService;
public DesktopLyricsViewModel(
ISettingsService settingsService,
IPlaybackService playbackService
)
: base(settingsService)
{
_playbackService = playbackService;
_playbackService.ReSendingMessages();
}
}
}

View File

@@ -1,117 +1,283 @@
using System.Collections.ObjectModel;
using System.Linq;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using BetterLyrics.WinUI3;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Playback;
using BetterLyrics.WinUI3.Services.Settings;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.Lyrics;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI;
using Windows.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Media.Imaging;
namespace BetterInAppLyrics.WinUI3.ViewModels
{
public partial class InAppLyricsViewModel : BaseLyricsViewModel, ILyricsViewModel
{
private LyricsAlignmentType? _lyricsAlignmentType;
public LyricsAlignmentType LyricsAlignmentType
{
get =>
(LyricsAlignmentType)Get(
get
{
_lyricsAlignmentType ??= (LyricsAlignmentType)Get(
SettingsKeys.InAppLyricsAlignmentType,
SettingsDefaultValues.InAppLyricsAlignmentType
);
set => Set(SettingsKeys.InAppLyricsAlignmentType, (int)value);
return _lyricsAlignmentType ?? 0;
}
set
{
Set(SettingsKeys.InAppLyricsAlignmentType, (int)value);
_lyricsAlignmentType = value;
}
}
private int? _lyricsBlurAmount;
public int LyricsBlurAmount
{
get =>
Get(
get
{
_lyricsBlurAmount ??= Get(
SettingsKeys.InAppLyricsBlurAmount,
SettingsDefaultValues.InAppLyricsBlurAmount
);
set => Set(SettingsKeys.InAppLyricsBlurAmount, value);
return _lyricsBlurAmount ?? 0;
}
set
{
Set(SettingsKeys.InAppLyricsBlurAmount, value);
_lyricsBlurAmount = value;
}
}
private int? _lyricsVerticalEdgeOpacity;
public int LyricsVerticalEdgeOpacity
{
get =>
Get(
get
{
_lyricsVerticalEdgeOpacity ??= Get(
SettingsKeys.InAppLyricsVerticalEdgeOpacity,
SettingsDefaultValues.InAppLyricsVerticalEdgeOpacity
);
set => Set(SettingsKeys.InAppLyricsVerticalEdgeOpacity, value);
return _lyricsVerticalEdgeOpacity ?? 0;
}
set
{
Set(SettingsKeys.InAppLyricsVerticalEdgeOpacity, value);
_lyricsVerticalEdgeOpacity = value;
}
}
private float? _lyricsLineSpacingFactor;
public float LyricsLineSpacingFactor
{
get =>
Get(
get
{
_lyricsLineSpacingFactor ??= Get(
SettingsKeys.InAppLyricsLineSpacingFactor,
SettingsDefaultValues.InAppLyricsLineSpacingFactor
);
return _lyricsLineSpacingFactor ?? 0;
}
set
{
Set(SettingsKeys.InAppLyricsLineSpacingFactor, value);
WeakReferenceMessenger.Default.Send(new InAppLyricsRelayoutRequestedMessage());
_lyricsLineSpacingFactor = value;
}
}
private int? _lyricsFontSize;
public int LyricsFontSize
{
get => Get(SettingsKeys.InAppLyricsFontSize, SettingsDefaultValues.InAppLyricsFontSize);
get
{
_lyricsFontSize ??= Get(
SettingsKeys.InAppLyricsFontSize,
SettingsDefaultValues.InAppLyricsFontSize
);
return _lyricsFontSize ?? 0;
}
set
{
Set(SettingsKeys.InAppLyricsFontSize, value);
WeakReferenceMessenger.Default.Send(new InAppLyricsRelayoutRequestedMessage());
_lyricsFontSize = value;
}
}
private bool? _isLyricsGlowEffectEnabled;
public bool IsLyricsGlowEffectEnabled
{
get =>
Get(
get
{
_isLyricsGlowEffectEnabled ??= Get(
SettingsKeys.IsInAppLyricsGlowEffectEnabled,
SettingsDefaultValues.IsInAppLyricsGlowEffectEnabled
);
set => Set(SettingsKeys.IsInAppLyricsGlowEffectEnabled, value);
return _isLyricsGlowEffectEnabled ?? false;
}
set
{
Set(SettingsKeys.IsInAppLyricsGlowEffectEnabled, value);
_isLyricsGlowEffectEnabled = value;
}
}
private bool? _isLyricsDynamicGlowEffectEnabled;
public bool IsLyricsDynamicGlowEffectEnabled
{
get =>
Get(
get
{
_isLyricsDynamicGlowEffectEnabled ??= Get(
SettingsKeys.IsInAppLyricsDynamicGlowEffectEnabled,
SettingsDefaultValues.IsInAppLyricsDynamicGlowEffectEnabled
);
set => Set(SettingsKeys.IsInAppLyricsDynamicGlowEffectEnabled, value);
return _isLyricsDynamicGlowEffectEnabled ?? false;
}
set
{
Set(SettingsKeys.IsInAppLyricsDynamicGlowEffectEnabled, value);
_isLyricsDynamicGlowEffectEnabled = value;
}
}
private LyricsFontColorType? _lyricsFontColorType;
public LyricsFontColorType LyricsFontColorType
{
get =>
(LyricsFontColorType)Get(
get
{
_lyricsFontColorType ??= (LyricsFontColorType)Get(
SettingsKeys.InAppLyricsFontColorType,
SettingsDefaultValues.InAppLyricsFontColorType
);
return _lyricsFontColorType ?? 0;
}
set
{
Set(SettingsKeys.InAppLyricsFontColorType, (int)value);
_lyricsFontColorType = value;
WeakReferenceMessenger.Default.Send(new LyricsFontColorChangedMessage());
}
}
private int? _lyricsFontSelectedAccentColorIndex;
public int LyricsFontSelectedAccentColorIndex
{
get =>
Get(
get
{
_lyricsFontSelectedAccentColorIndex ??= Get(
SettingsKeys.InAppLyricsFontSelectedAccentColorIndex,
SettingsDefaultValues.InAppLyricsFontSelectedAccentColorIndex
);
return _lyricsFontSelectedAccentColorIndex ?? 0;
}
set
{
if (value >= 0)
{
Set(SettingsKeys.InAppLyricsFontSelectedAccentColorIndex, value);
_lyricsFontSelectedAccentColorIndex = value;
WeakReferenceMessenger.Default.Send(new LyricsFontColorChangedMessage());
}
}
}
public InAppLyricsViewModel(ISettingsService settingsService)
//
[ObservableProperty]
private BitmapImage? _coverImage;
[ObservableProperty]
private SongInfo? _songInfo = null;
private DisplayType? _preferredDisplayType = DisplayType.SplitView;
[ObservableProperty]
private bool _aboutToUpdateUI;
private bool _isImmersiveMode = false;
public bool IsImmersiveMode
{
get => _isImmersiveMode;
set
{
_isImmersiveMode = value;
OnPropertyChanged();
WeakReferenceMessenger.Default.Send(new IsImmersiveModeChangedMessage(value));
if (value)
WeakReferenceMessenger.Default.Send(
new ShowNotificatonMessage(
new Notification(
App.ResourceLoader!.GetString("MainPageEnterImmersiveModeHint"),
isForeverDismissable: true,
relatedSettingsKeyName: SettingsKeys.NeverShowEnterImmersiveModeMessage
)
)
);
}
}
public InAppLyricsViewModel(
ISettingsService settingsService,
IPlaybackService playbackService
)
: base(settingsService) { }
public async Task UpdateSongInfoUI(SongInfo? songInfo)
{
AboutToUpdateUI = true;
await Task.Delay(AnimationHelper.StoryboardDefaultDuration);
SongInfo = songInfo;
await Task.Delay(1);
CoverImage =
(songInfo?.AlbumArt == null)
? null
: await ImageHelper.GetBitmapImageFromBytesAsync(songInfo.AlbumArt);
DisplayType displayType;
if (songInfo == null)
{
displayType = DisplayType.PlaceholderOnly;
}
else if (_preferredDisplayType is DisplayType preferredDisplayType)
{
displayType = preferredDisplayType;
}
else
{
displayType = DisplayType.SplitView;
}
WeakReferenceMessenger.Default.Send(new DisplayTypeChangedMessage(displayType));
AboutToUpdateUI = false;
}
public void OpenMatchedFileFolderInFileExplorer(string path)
{
Process.Start(
new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"/select,\"{path}\"",
UseShellExecute = true,
}
);
}
[RelayCommand]
private void OnDisplayTypeChanged(object value)
{
int index = Convert.ToInt32(value);
_preferredDisplayType = (DisplayType)index;
WeakReferenceMessenger.Default.Send(new DisplayTypeChangedMessage((DisplayType)index));
}
}
}

View File

@@ -1,122 +0,0 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Playback;
using BetterLyrics.WinUI3.Services.Settings;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Media.Imaging;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class MainViewModel : ObservableObject
{
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
[ObservableProperty]
private BitmapImage? _coverImage;
[ObservableProperty]
private SongInfo? _songInfo = null;
private DisplayType? _preferredDisplayType = DisplayType.SplitView;
[ObservableProperty]
private bool _aboutToUpdateUI;
private bool _isImmersiveMode = false;
public bool IsImmersiveMode
{
get => _isImmersiveMode;
set
{
_isImmersiveMode = value;
OnPropertyChanged();
WeakReferenceMessenger.Default.Send(new IsImmersiveModeChangedMessage(value));
if (value)
WeakReferenceMessenger.Default.Send(
new ShowNotificatonMessage(
new Notification(
App.ResourceLoader!.GetString("MainPageEnterImmersiveModeHint"),
isForeverDismissable: true,
relatedSettingsKeyName: SettingsKeys.NeverShowEnterImmersiveModeMessage
)
)
);
}
}
public MainViewModel(IPlaybackService playbackService)
{
WeakReferenceMessenger.Default.Register<MainViewModel, SongInfoChangedMessage>(
this,
(r, m) =>
{
_dispatcherQueue.TryEnqueue(
DispatcherQueuePriority.High,
async () => await UpdateSongInfoUI(m.Value)
);
}
);
}
private async Task UpdateSongInfoUI(SongInfo? songInfo)
{
AboutToUpdateUI = true;
await Task.Delay(AnimationHelper.StoryboardDefaultDuration);
SongInfo = songInfo;
await Task.Delay(1);
CoverImage =
(songInfo?.AlbumArt == null)
? null
: await ImageHelper.GetBitmapImageFromBytesAsync(songInfo.AlbumArt);
DisplayType displayType;
if (songInfo == null)
{
displayType = DisplayType.PlaceholderOnly;
}
else if (_preferredDisplayType is DisplayType preferredDisplayType)
{
displayType = preferredDisplayType;
}
else
{
displayType = DisplayType.SplitView;
}
WeakReferenceMessenger.Default.Send(new DisplayTypeChangedMessage(displayType));
AboutToUpdateUI = false;
}
public void OpenMatchedFileFolderInFileExplorer(string path)
{
Process.Start(
new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = $"/select,\"{path}\"",
UseShellExecute = true,
}
);
}
[RelayCommand]
private void OnDisplayTypeChanged(object value)
{
int index = Convert.ToInt32(value);
_preferredDisplayType = (DisplayType)index;
WeakReferenceMessenger.Default.Send(new DisplayTypeChangedMessage((DisplayType)index));
}
}
}

View File

@@ -128,13 +128,11 @@ namespace BetterLyrics.WinUI3.ViewModels
picker.FileTypeFilter.Add("*");
var hwnd = WindowNative.GetWindowHandle(App.Current.MainWindow);
var hwnd = WindowNative.GetWindowHandle(App.Current.SettingsWindow);
InitializeWithWindow.Initialize(picker, hwnd);
var folder = await picker.PickSingleFolderAsync();
App.Current.SettingsWindow!.AppWindow.MoveInZOrderAtTop();
if (folder != null)
{
await AddFolderAsync(folder.Path);

View File

@@ -5,8 +5,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
mc:Ignorable="d">
<Grid>
@@ -19,5 +21,40 @@
Draw="LyricsCanvas_Draw"
Paused="{x:Bind GlobalSettingsViewModel.IsPlaying, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
Update="LyricsCanvas_Update" />
<Grid
x:Name="RightCommandGrid"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Opacity="0">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<Button
x:Name="SettingsButton"
Command="{x:Bind ViewModel.ToggleSettingsPopupCommand}"
Style="{StaticResource GhostButtonStyle}">
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="&#xF8B0;" />
</Button>
<Popup
x:Name="SettingsPopup"
DesiredPlacement="Bottom"
IsOpen="{x:Bind ViewModel.IsSettingsPopupOpened, Mode=OneWay}">
<uc:LyricsSettingsControl ViewModel="{x:Bind ViewModel, Mode=OneWay}" />
</Popup>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction
PropertyName="Opacity"
TargetObject="{Binding ElementName=RightCommandGrid}"
Value="1" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction
PropertyName="Opacity"
TargetObject="{Binding ElementName=RightCommandGrid}"
Value="0" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
</Grid>
</Page>

View File

@@ -1,19 +1,12 @@
using System.Diagnostics;
using System.Drawing;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Rendering;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using WinRT.Interop;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -27,28 +20,18 @@ namespace BetterLyrics.WinUI3.Views
{
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private double _limitedLineWidth = 0;
private readonly DesktopLyricsRenderer _lyricsRenderer =
Ioc.Default.GetService<DesktopLyricsRenderer>()!;
public DesktopLyricsViewModel ViewModel => (DesktopLyricsViewModel)DataContext;
private GlobalViewModel GlobalSettingsViewModel { get; set; } =
Ioc.Default.GetService<GlobalViewModel>()!;
public DesktopLyricsPage()
{
this.InitializeComponent();
WeakReferenceMessenger.Default.Register<
DesktopLyricsPage,
DesktopLyricsRelayoutRequestedMessage
>(
this,
async (r, m) =>
{
await _lyricsRenderer.ReLayoutAsync(LyricsCanvas);
}
);
DataContext = Ioc.Default.GetService<DesktopLyricsViewModel>();
WeakReferenceMessenger.Default.Register<DesktopLyricsPage, SongInfoChangedMessage>(
this,
@@ -56,10 +39,9 @@ namespace BetterLyrics.WinUI3.Views
{
_dispatcherQueue.TryEnqueue(
DispatcherQueuePriority.High,
async () =>
() =>
{
_lyricsRenderer.LyricsLines = m.Value?.LyricsLines ?? [];
await _lyricsRenderer.ReLayoutAsync(LyricsCanvas);
}
);
}
@@ -86,6 +68,8 @@ namespace BetterLyrics.WinUI3.Views
)
{
using var ds = args.DrawingSession;
_lyricsRenderer.UpdateTransition((float)args.Timing.ElapsedTime.TotalSeconds);
_lyricsRenderer.DrawBackground(sender, ds);
_lyricsRenderer.Draw(sender, ds);
}

View File

@@ -64,7 +64,7 @@ namespace BetterLyrics.WinUI3.Views
private void UpdateBackdrop(BackdropType? backdropType)
{
if (RootFrame.SourcePageType == typeof(MainPage))
if (RootFrame.SourcePageType == typeof(InAppLyricsPage))
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(
backdropType ?? GlobalSettingsViewModel.BackdropType
);
@@ -90,14 +90,7 @@ namespace BetterLyrics.WinUI3.Views
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
if (RootFrame.CurrentSourcePageType == typeof(MainPage))
{
App.Current.Exit();
}
else if (RootFrame.CurrentSourcePageType == typeof(SettingsPage))
{
App.Current.SettingsWindow!.AppWindow.Hide();
}
Close();
}
private void MaximiseButton_Click(object sender, RoutedEventArgs e)

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="BetterLyrics.WinUI3.Views.MainPage"
x:Class="BetterLyrics.WinUI3.Views.InAppLyricsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
@@ -22,7 +22,6 @@
x:Name="LyricsCanvas"
Draw="LyricsCanvas_Draw"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Loaded="LyricsCanvas_Loaded"
Paused="{x:Bind GlobalSettingsViewModel.IsPlaying, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
Update="LyricsCanvas_Update" />
<Grid.OpacityTransition>

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Drawing;
using BetterInAppLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
@@ -28,11 +29,11 @@ namespace BetterLyrics.WinUI3.Views
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
public sealed partial class InAppLyricsPage : Page
{
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
public MainViewModel ViewModel => (MainViewModel)DataContext;
public InAppLyricsViewModel ViewModel => (InAppLyricsViewModel)DataContext;
private GlobalViewModel GlobalSettingsViewModel { get; set; } =
Ioc.Default.GetService<GlobalViewModel>()!;
@@ -43,18 +44,20 @@ namespace BetterLyrics.WinUI3.Views
private readonly AlbumArtRenderer _albumArtRenderer =
Ioc.Default.GetService<AlbumArtRenderer>()!;
private readonly ILogger<MainPage> _logger = Ioc.Default.GetService<ILogger<MainPage>>()!;
private readonly ILogger<InAppLyricsPage> _logger = Ioc.Default.GetService<
ILogger<InAppLyricsPage>
>()!;
public AlbumArtViewModel AlbumArtViewModel { get; set; } =
Ioc.Default.GetService<AlbumArtViewModel>()!;
public MainPage()
public InAppLyricsPage()
{
this.InitializeComponent();
Debug.WriteLine("hashcode for InAppLyricsRenderer: " + _lyricsRenderer.GetHashCode());
DataContext = Ioc.Default.GetService<MainViewModel>();
DataContext = Ioc.Default.GetService<InAppLyricsViewModel>();
// set lyrics font color
@@ -63,7 +66,10 @@ namespace BetterLyrics.WinUI3.Views
WelcomeTeachingTip.IsOpen = true;
}
WeakReferenceMessenger.Default.Register<MainPage, AlbumArtCornerRadiusChangedMessage>(
WeakReferenceMessenger.Default.Register<
InAppLyricsPage,
AlbumArtCornerRadiusChangedMessage
>(
this,
(r, m) =>
{
@@ -71,15 +77,7 @@ namespace BetterLyrics.WinUI3.Views
}
);
WeakReferenceMessenger.Default.Register<MainPage, InAppLyricsRelayoutRequestedMessage>(
this,
async (r, m) =>
{
await _lyricsRenderer.ReLayoutAsync(LyricsCanvas);
}
);
WeakReferenceMessenger.Default.Register<MainPage, SongInfoChangedMessage>(
WeakReferenceMessenger.Default.Register<InAppLyricsPage, SongInfoChangedMessage>(
this,
(r, m) =>
{
@@ -87,14 +85,14 @@ namespace BetterLyrics.WinUI3.Views
DispatcherQueuePriority.High,
async () =>
{
await ViewModel.UpdateSongInfoUI(m.Value);
_lyricsRenderer.LyricsLines = m.Value?.LyricsLines ?? [];
await _lyricsRenderer.ReLayoutAsync(LyricsCanvas);
}
);
}
);
WeakReferenceMessenger.Default.Register<MainPage, PlayingPositionChangedMessage>(
WeakReferenceMessenger.Default.Register<InAppLyricsPage, PlayingPositionChangedMessage>(
this,
(r, m) =>
{
@@ -154,22 +152,10 @@ namespace BetterLyrics.WinUI3.Views
private void SettingsButton_Click(object sender, RoutedEventArgs e)
{
if (App.Current.SettingsWindow is null)
{
var settingsWindow = new HostWindow();
settingsWindow.Navigate(typeof(SettingsPage));
App.Current.SettingsWindow = settingsWindow;
}
var settingsAppWindow = App.Current.SettingsWindow.AppWindow;
if (settingsAppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.Restore();
}
settingsAppWindow.Show();
settingsAppWindow.MoveInZOrderAtTop();
var settingsWindow = WindowHelper.CreateHostWindow();
settingsWindow.Navigate(typeof(SettingsPage));
settingsWindow.Activate();
App.Current.SettingsWindow = settingsWindow;
}
private void WelcomeTeachingTip_Closed(TeachingTip sender, TeachingTipClosedEventArgs args)
@@ -203,10 +189,9 @@ namespace BetterLyrics.WinUI3.Views
BottomCommandGrid.Opacity = 0;
}
private async void LyricsPlaceholderGrid_SizeChanged(object sender, SizeChangedEventArgs e)
private void LyricsPlaceholderGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
_lyricsRenderer.LimitedLineWidth = e.NewSize.Width;
await _lyricsRenderer.ReLayoutAsync(LyricsCanvas);
}
private void OpenMatchedFileButton_Click(object sender, RoutedEventArgs e)
@@ -221,49 +206,18 @@ namespace BetterLyrics.WinUI3.Views
private void DesktopLyricsToggleButton_Checked(object sender, RoutedEventArgs e)
{
TransparentAppBarHelper.Enable(App.Current.OverlayWindow!, 48);
Color color = WindowColorHelper.GetDominantColorBelow(
WindowNative.GetWindowHandle(App.Current.OverlayWindow!)
);
var config = new SystemBackdropConfiguration
{
IsInputActive = true,
Theme = SystemBackdropTheme.Default,
};
var micaController = new MicaController();
micaController.TintColor = Windows.UI.Color.FromArgb(
color.A,
color.R,
color.G,
color.B
); // 指定自定义颜色
micaController.TintOpacity = 0.7f;
micaController.FallbackColor = Colors.Black;
// 配置 Backdrop假设你已经满足系统要求
micaController.AddSystemBackdropTarget(
(
App.Current.OverlayWindow!
).As<Microsoft.UI.Composition.ICompositionSupportsSystemBackdrop>()
);
micaController.SetSystemBackdropConfiguration(config);
var overlayWindow = WindowHelper.CreateOverlayWindow();
overlayWindow.Navigate(typeof(DesktopLyricsPage));
overlayWindow.Activate();
App.Current.OverlayWindow = overlayWindow;
}
private void DesktopLyricsToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
var overlayAppWindow = App.Current.OverlayWindow!.AppWindow;
overlayAppWindow.Hide();
TransparentAppBarHelper.Disable(App.Current.OverlayWindow);
}
private async void LyricsCanvas_Loaded(object sender, RoutedEventArgs e)
{
await _lyricsRenderer.ReLayoutAsync(LyricsCanvas);
var overlayWindow = App.Current.OverlayWindow!;
TransparentAppBarHelper.Disable(overlayWindow);
overlayWindow.Close();
App.Current.OverlayWindow = null;
}
}
}

View File

@@ -1,8 +1,11 @@
using System;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using WinRT.Interop;
using WinUIEx;
// To learn more about WinUI, the WinUI project structure,
@@ -15,6 +18,9 @@ namespace BetterLyrics.WinUI3.Views
/// </summary>
public sealed partial class OverlayWindow : Window
{
private readonly GlobalViewModel _globalViewModel =
Ioc.Default.GetService<GlobalViewModel>()!;
public OverlayWindow()
{
this.InitializeComponent();
@@ -33,6 +39,8 @@ namespace BetterLyrics.WinUI3.Views
// Hide from taskbar and alt-tab
this.SetIsShownInSwitchers(false);
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
// Transparent window
// SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.DesktopAcrylic);
@@ -44,6 +52,27 @@ namespace BetterLyrics.WinUI3.Views
// Always on top
// ((OverlappedPresenter)AppWindow.Presenter).IsAlwaysOnTop = true;
var hwnd = WindowNative.GetWindowHandle(this);
var windowWatcher = new ForegroundWindowWatcherHelper(
hwnd,
onWindowChanged =>
{
UpdateAccentColor(hwnd);
}
);
windowWatcher.Start();
UpdateAccentColor(hwnd);
}
private void UpdateAccentColor(nint hwnd)
{
_globalViewModel.ActivatedWindowAccentColor = WindowColorHelper.GetDominantColorBelow(
hwnd
);
}
public void Navigate(Type type)

View File

@@ -8,6 +8,7 @@
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:vm="using:BetterLyrics.WinUI3.ViewModels"
mc:Ignorable="d">
@@ -165,7 +166,7 @@
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageDynamicCoverOverlay" IsEnabled="{x:Bind AlbumArtRendererSettingsViewModel.IsCoverOverlayEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind AlbumArtRendererSettingsViewModel.IsDynamicCoverOverlay, Mode=TwoWay}" />
<ToggleSwitch IsOn="{x:Bind AlbumArtRendererSettingsViewModel.IsDynamicCoverOverlayEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageCoverOverlayOpacity" IsEnabled="{x:Bind AlbumArtRendererSettingsViewModel.IsCoverOverlayEnabled, Mode=OneWay}">
@@ -229,177 +230,28 @@
</StackPanel>
</controls:SettingsCard>
<TextBlock x:Uid="SettingsPageLyricsStyle" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<!-- Lyrics style & effect -->
<controls:SettingsCard x:Uid="SettingsPageLyricsAlignment" HeaderIcon="{ui:FontIcon Glyph=&#xE8E3;}">
<ComboBox SelectedIndex="{x:Bind LyricsRendererSettingsViewModel.LyricsAlignmentType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsLeft" />
<ComboBoxItem x:Uid="SettingsPageLyricsCenter" />
<ComboBoxItem x:Uid="SettingsPageLyricsRight" />
</ComboBox>
</controls:SettingsCard>
<TextBlock x:Uid="SettingsPageLyrics" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsExpander x:Uid="SettingsPageLyricsFontColor" HeaderIcon="{ui:FontIcon Glyph=&#xE8D3;}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsRendererSettingsViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="IsExpanded" Value="False" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsRendererSettingsViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="1">
<interactivity:ChangePropertyAction PropertyName="IsExpanded" Value="True" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<ComboBox SelectedIndex="{x:Bind LyricsRendererSettingsViewModel.LyricsFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDefault" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDominant" />
</ComboBox>
<controls:SettingsExpander.Items>
<controls:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Vertical">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsRendererSettingsViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind LyricsRendererSettingsViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="1">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<GridView ItemsSource="{x:Bind LyricsRendererSettingsViewModel.CoverImageDominantColors, Mode=OneWay}" SelectedIndex="{x:Bind LyricsRendererSettingsViewModel.LyricsFontSelectedAccentColorIndex, Mode=TwoWay}">
<GridView.ItemTemplate>
<DataTemplate>
<GridViewItem>
<StackPanel>
<Border
Width="64"
Height="64"
CornerRadius="4">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
<Border.BackgroundTransition>
<BrushTransition />
</Border.BackgroundTransition>
</Border>
<TextBlock
Margin="4,0,4,4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
TextWrapping="WrapWholeWords" />
</StackPanel>
</GridViewItem>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:Segmented
x:Name="LyricsSegmentedControl"
Margin="0,0,0,4"
HorizontalAlignment="Stretch"
SelectedIndex="0">
<controls:SegmentedItem x:Uid="SettingsPageInAppLyrics" Tag="InApp" />
<controls:SegmentedItem x:Uid="SettingsPageDesktopLyrics" Tag="Desktop" />
</controls:Segmented>
<controls:SwitchPresenter Value="{Binding SelectedItem.Tag, ElementName=LyricsSegmentedControl}">
<controls:Case Value="InApp">
<uc:LyricsSettingsControl ViewModel="{x:Bind InAppLyricsViewModel, Mode=OneWay}" />
</controls:Case>
<controls:Case Value="Desktop">
<uc:LyricsSettingsControl ViewModel="{x:Bind DesktopLyricsViewModel, Mode=OneWay}" />
</controls:Case>
</controls:SwitchPresenter>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon Glyph=&#xE8E9;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text="{x:Bind LyricsRendererSettingsViewModel.LyricsFontSize, Mode=OneWay}" />
<Slider
Maximum="96"
Minimum="12"
SnapsTo="Ticks"
StepFrequency="2"
TickFrequency="2"
TickPlacement="Outside"
Value="{x:Bind LyricsRendererSettingsViewModel.LyricsFontSize, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsLineSpacingFactor" HeaderIcon="{ui:FontIcon Glyph=&#xF579;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind LyricsRendererSettingsViewModel.LyricsLineSpacingFactor, Mode=OneWay}" />
<TextBlock
x:Uid="SettingsPageLyricsLineSpacingFactorUnit"
Margin="0,0,14,0"
VerticalAlignment="Center" />
<Slider
Maximum="2"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="0.1"
TickFrequency="0.1"
TickPlacement="Outside"
Value="{x:Bind LyricsRendererSettingsViewModel.LyricsLineSpacingFactor, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<TextBlock x:Uid="SettingsPageLyricsEffect" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<controls:SettingsCard x:Uid="SettingsPageLyricsVerticalEdgeOpacity" HeaderIcon="{ui:FontIcon Glyph=&#xF573;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind LyricsRendererSettingsViewModel.LyricsVerticalEdgeOpacity, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text=" %" />
<Slider
Maximum="100"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="1"
TickFrequency="1"
TickPlacement="Outside"
Value="{x:Bind LyricsRendererSettingsViewModel.LyricsVerticalEdgeOpacity, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBlurAmount" HeaderIcon="{ui:FontIcon Glyph=&#xE727;}">
<controls:SettingsCard.Description>
<StackPanel>
<TextBlock x:Uid="SettingsPageLyricsBlurHighGPUUsage" Foreground="{ThemeResource SystemFillColorCautionBrush}" />
<TextBlock x:Uid="SettingsPageLyricsBlurAmountSideEffect" />
</StackPanel>
</controls:SettingsCard.Description>
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text="{x:Bind LyricsRendererSettingsViewModel.LyricsBlurAmount, Mode=OneWay}" />
<Slider
Maximum="10"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="1"
TickFrequency="1"
TickPlacement="Outside"
Value="{x:Bind LyricsRendererSettingsViewModel.LyricsBlurAmount, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsExpander
x:Uid="SettingsPageLyricsGlowEffect"
HeaderIcon="{ui:FontIcon Glyph=&#xE9A9;}"
IsExpanded="True">
<ToggleSwitch IsOn="{x:Bind LyricsRendererSettingsViewModel.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLyricsDynamicGlowEffect" IsEnabled="{x:Bind LyricsRendererSettingsViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsRendererSettingsViewModel.IsLyricsDynamicGlowEffectEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<!-- About -->
<TextBlock x:Uid="SettingsPageAbout" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />

View File

@@ -18,8 +18,10 @@ namespace BetterLyrics.WinUI3.Views
public SettingsViewModel ViewModel => (SettingsViewModel)DataContext;
public AlbumArtOverlayViewModel AlbumArtRendererSettingsViewModel =>
Ioc.Default.GetService<AlbumArtOverlayViewModel>()!;
public InAppLyricsViewModel LyricsRendererSettingsViewModel =>
public InAppLyricsViewModel InAppLyricsViewModel =>
Ioc.Default.GetService<InAppLyricsViewModel>()!;
public DesktopLyricsViewModel DesktopLyricsViewModel =>
Ioc.Default.GetService<DesktopLyricsViewModel>()!;
public GlobalViewModel GlobalSettingsViewModel =>
Ioc.Default.GetService<GlobalViewModel>()!;
public AlbumArtViewModel AlbumArtViewModel => Ioc.Default.GetService<AlbumArtViewModel>()!;