Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db6847b74f | ||
|
|
d510892650 | ||
|
|
a0e51d976e |
@@ -11,7 +11,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.1.0" />
|
||||
Version="1.0.2.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -57,6 +57,34 @@
|
||||
<Setter Property="Margin" Value="1,30,0,6" />
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style x:Key="TitleBarButtonStyle" TargetType="Button">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="16,0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="GhostButtonStyle" TargetType="Button">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="TitleBarToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="16,0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<!-- Dimensions -->
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
@@ -32,12 +32,13 @@ namespace BetterLyrics.WinUI3
|
||||
private readonly ILogger<App> _logger;
|
||||
|
||||
public static new App Current => (App)Application.Current;
|
||||
public MainWindow? MainWindow { get; private set; }
|
||||
public MainWindow? SettingsWindow { get; set; }
|
||||
public BaseWindow? MainWindow { get; private set; }
|
||||
public BaseWindow? SettingsWindow { get; set; }
|
||||
|
||||
public static ResourceLoader? ResourceLoader { get; private set; }
|
||||
|
||||
public static DispatcherQueue DispatcherQueue => DispatcherQueue.GetForCurrentThread();
|
||||
public static DispatcherQueue? DispatcherQueue { get; private set; }
|
||||
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
@@ -47,7 +48,9 @@ namespace BetterLyrics.WinUI3
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
App.ResourceLoader = new ResourceLoader();
|
||||
DispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
DispatcherQueueTimer = DispatcherQueue.CreateTimer();
|
||||
ResourceLoader = new ResourceLoader();
|
||||
|
||||
Helper.AppInfo.EnsureDirectories();
|
||||
ConfigureServices();
|
||||
@@ -67,7 +70,6 @@ namespace BetterLyrics.WinUI3
|
||||
// Register services
|
||||
Ioc.Default.ConfigureServices(
|
||||
new ServiceCollection()
|
||||
.AddSingleton(DispatcherQueue.GetForCurrentThread())
|
||||
.AddLogging(loggingBuilder =>
|
||||
{
|
||||
loggingBuilder.ClearProviders();
|
||||
@@ -77,6 +79,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<SettingsService>()
|
||||
.AddSingleton<DatabaseService>()
|
||||
// ViewModels
|
||||
.AddSingleton<BaseWindowModel>()
|
||||
.AddSingleton<MainViewModel>()
|
||||
.AddSingleton<SettingsViewModel>()
|
||||
.BuildServiceProvider()
|
||||
@@ -101,7 +104,7 @@ namespace BetterLyrics.WinUI3
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
// Activate the window
|
||||
MainWindow = new MainWindow();
|
||||
MainWindow = new BaseWindow();
|
||||
MainWindow!.Navigate(typeof(MainPage));
|
||||
MainWindow.Activate();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="DevWinUI" Version="8.2.0" />
|
||||
<PackageReference Include="DevWinUI" Version="8.3.0" />
|
||||
<PackageReference Include="DevWinUI.Controls" Version="8.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Window
|
||||
x:Class="BetterLyrics.WinUI3.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:media="using:CommunityToolkit.WinUI.Media"
|
||||
SizeChanged="Window_SizeChanged"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid">
|
||||
|
||||
<Frame
|
||||
x:Name="RootFrame"
|
||||
Navigated="RootFrame_Navigated"
|
||||
NavigationFailed="RootFrame_NavigationFailed" />
|
||||
|
||||
<Grid
|
||||
x:Name="TopCommandGrid"
|
||||
Padding="2,0"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
Opacity="0">
|
||||
|
||||
<Grid.Resources>
|
||||
|
||||
<Storyboard x:Name="TopCommandGridFadeInStoryboard">
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="TopCommandGrid"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="1"
|
||||
Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
<Storyboard x:Name="TopCommandGridFadeOutStoryboard">
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="TopCommandGrid"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="0"
|
||||
Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
|
||||
</Grid.Resources>
|
||||
|
||||
<interactivity:Interaction.Behaviors>
|
||||
|
||||
<interactivity:EventTriggerBehavior EventName="PointerEntered">
|
||||
<interactivity:ControlStoryboardAction Storyboard="{StaticResource TopCommandGridFadeInStoryboard}" />
|
||||
</interactivity:EventTriggerBehavior>
|
||||
<interactivity:EventTriggerBehavior EventName="PointerExited">
|
||||
<interactivity:ControlStoryboardAction Storyboard="{StaticResource TopCommandGridFadeOutStoryboard}" />
|
||||
</interactivity:EventTriggerBehavior>
|
||||
|
||||
</interactivity:Interaction.Behaviors>
|
||||
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
|
||||
<ImageIcon
|
||||
Height="24"
|
||||
Margin="16,0"
|
||||
Source="ms-appx:///Assets/Logo.png" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="AppTitleTextBlock"
|
||||
Margin="0,-4,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Title}" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<!-- Always On Top -->
|
||||
<AppBarButton
|
||||
x:Name="AOTButton"
|
||||
Click="AOTButton_Click"
|
||||
LabelPosition="Collapsed">
|
||||
<Grid>
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
<FontIcon
|
||||
x:Name="PinnedFontIcon"
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
Glyph=""
|
||||
Opacity="0">
|
||||
<FontIcon.Resources>
|
||||
<Storyboard x:Key="ShowPinnedFontIconStoryboard">
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="PinnedFontIcon"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="1"
|
||||
Duration="0:0:0.3" />
|
||||
</Storyboard>
|
||||
<Storyboard x:Key="HidePinnedFontIconStoryboard">
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="PinnedFontIcon"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="0"
|
||||
Duration="0:0:0.3" />
|
||||
</Storyboard>
|
||||
</FontIcon.Resources>
|
||||
</FontIcon>
|
||||
</Grid>
|
||||
</AppBarButton>
|
||||
|
||||
<!-- Window Mini -->
|
||||
<AppBarButton
|
||||
x:Name="MiniButton"
|
||||
Click="MiniButton_Click"
|
||||
LabelPosition="Collapsed"
|
||||
Visibility="Collapsed">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</AppBarButton>
|
||||
<!-- Window Unmini -->
|
||||
<AppBarButton
|
||||
x:Name="UnminiButton"
|
||||
Click="UnminiButton_Click"
|
||||
LabelPosition="Collapsed"
|
||||
Visibility="Collapsed">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</AppBarButton>
|
||||
<!-- Window Minimise -->
|
||||
<AppBarButton
|
||||
x:Name="MinimiseButton"
|
||||
Click="MinimiseButton_Click"
|
||||
LabelPosition="Collapsed">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</AppBarButton>
|
||||
<!-- Window Maximise -->
|
||||
<AppBarButton
|
||||
x:Name="MaximiseButton"
|
||||
Click="MaximiseButton_Click"
|
||||
LabelPosition="Collapsed">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</AppBarButton>
|
||||
<!-- Window Restore -->
|
||||
<AppBarButton
|
||||
x:Name="RestoreButton"
|
||||
Click="RestoreButton_Click"
|
||||
LabelPosition="Collapsed"
|
||||
Visibility="Collapsed">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</AppBarButton>
|
||||
<!-- Window Close -->
|
||||
<AppBarButton
|
||||
x:Name="CloseButton"
|
||||
Click="CloseButton_Click"
|
||||
LabelPosition="Collapsed">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</AppBarButton>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
<InfoBar
|
||||
x:Name="HostInfoBar"
|
||||
Margin="18"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
IsClosable="False"
|
||||
Opacity="0">
|
||||
<InfoBar.RenderTransform>
|
||||
<TranslateTransform x:Name="HostInfoBarTransform" Y="20" />
|
||||
</InfoBar.RenderTransform>
|
||||
<InfoBar.Resources>
|
||||
<Storyboard x:Key="InfoBarShowAndHideStoryboard">
|
||||
<!-- Opacity -->
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="HostInfoBar" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:3.6" Value="1" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:3.9" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
|
||||
<!-- Y -->
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="HostInfoBarTransform" Storyboard.TargetProperty="Y">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="20" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:3.6" Value="0" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:3.9" Value="20" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</InfoBar.Resources>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<behaviors:StackedNotificationsBehavior x:Name="NotificationQueue" />
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{Binding ElementName=HostInfoBar, Path=IsOpen, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ControlStoryboardAction Storyboard="{StaticResource InfoBarShowAndHideStoryboard}" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</InfoBar>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,178 +0,0 @@
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using DevWinUI;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
private readonly OverlappedPresenter _presenter;
|
||||
|
||||
private readonly SettingsService _settingsService;
|
||||
|
||||
public static StackedNotificationsBehavior? StackedNotificationsBehavior
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
_settingsService = Ioc.Default.GetService<SettingsService>()!;
|
||||
|
||||
RootGrid.RequestedTheme = (ElementTheme)_settingsService.Theme;
|
||||
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(
|
||||
(BackdropType)_settingsService.BackdropType
|
||||
);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<ThemeChangedMessage>(
|
||||
this,
|
||||
(r, m) =>
|
||||
{
|
||||
RootGrid.RequestedTheme = m.Value;
|
||||
}
|
||||
);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<SystemBackdropChangedMessage>(
|
||||
this,
|
||||
(r, m) =>
|
||||
{
|
||||
SystemBackdrop = null;
|
||||
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(m.Value);
|
||||
}
|
||||
);
|
||||
|
||||
// AppWindow.SetIcon("white_round.ico");
|
||||
StackedNotificationsBehavior = NotificationQueue;
|
||||
|
||||
_presenter = (OverlappedPresenter)AppWindow.Presenter;
|
||||
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
SetTitleBar(TopCommandGrid);
|
||||
}
|
||||
|
||||
public void Navigate(Type type)
|
||||
{
|
||||
RootFrame.Navigate(type);
|
||||
}
|
||||
|
||||
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (RootFrame.CurrentSourcePageType == typeof(MainPage))
|
||||
{
|
||||
(
|
||||
(RootFrame.Content as MainPage)!.FindChild("LyricsCanvas")
|
||||
as CanvasAnimatedControl
|
||||
)!.Paused = true;
|
||||
App.Current.Exit();
|
||||
}
|
||||
else if (RootFrame.CurrentSourcePageType == typeof(SettingsPage))
|
||||
{
|
||||
App.Current.SettingsWindow!.AppWindow.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_presenter.Maximize();
|
||||
//MaximiseButton.Visibility = Visibility.Collapsed;
|
||||
//RestoreButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void MinimiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_presenter.Minimize();
|
||||
}
|
||||
|
||||
private void RestoreButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_presenter.Restore();
|
||||
//MaximiseButton.Visibility = Visibility.Visible;
|
||||
//RestoreButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args)
|
||||
{
|
||||
if (_presenter.State == OverlappedPresenterState.Maximized)
|
||||
{
|
||||
MaximiseButton.Visibility = Visibility.Collapsed;
|
||||
RestoreButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
else if (_presenter.State == OverlappedPresenterState.Restored)
|
||||
{
|
||||
MaximiseButton.Visibility = Visibility.Visible;
|
||||
RestoreButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void MiniButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AppWindow.Resize(new Windows.Graphics.SizeInt32(144, 48));
|
||||
MiniButton.Visibility = Visibility.Collapsed;
|
||||
UnminiButton.Visibility = Visibility.Visible;
|
||||
MinimiseButton.Visibility = Visibility.Collapsed;
|
||||
MaximiseButton.Visibility = Visibility.Collapsed;
|
||||
RestoreButton.Visibility = Visibility.Collapsed;
|
||||
CloseButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void UnminiButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AppWindow.Resize(new Windows.Graphics.SizeInt32(800, 600));
|
||||
MiniButton.Visibility = Visibility.Visible;
|
||||
UnminiButton.Visibility = Visibility.Collapsed;
|
||||
MinimiseButton.Visibility = Visibility.Visible;
|
||||
MaximiseButton.Visibility = Visibility.Visible;
|
||||
RestoreButton.Visibility = Visibility.Collapsed;
|
||||
CloseButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void RootFrame_Navigated(object sender, NavigationEventArgs e)
|
||||
{
|
||||
AppWindow.Title = Title = App.ResourceLoader!.GetString(
|
||||
$"{e.SourcePageType.Name}Title"
|
||||
);
|
||||
}
|
||||
|
||||
private void AOTButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_presenter.IsAlwaysOnTop = !_presenter.IsAlwaysOnTop;
|
||||
string prefix;
|
||||
if (_presenter.IsAlwaysOnTop)
|
||||
{
|
||||
prefix = "Show";
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix = "Hide";
|
||||
}
|
||||
(PinnedFontIcon.Resources[$"{prefix}PinnedFontIconStoryboard"] as Storyboard)!.Begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Messages
|
||||
{
|
||||
public class ShowNotificatonMessage(Notification value)
|
||||
: ValueChangedMessage<Notification>(value) { }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class Notification : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private InfoBarSeverity _severity;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _message;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isForeverDismissable;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _visibility;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _relatedSettingsKeyName;
|
||||
|
||||
public Notification(
|
||||
string? message = null,
|
||||
InfoBarSeverity severity = InfoBarSeverity.Informational,
|
||||
bool isForeverDismissable = false,
|
||||
string? relatedSettingsKeyName = null
|
||||
)
|
||||
{
|
||||
Message = message;
|
||||
Severity = severity;
|
||||
IsForeverDismissable = isForeverDismissable;
|
||||
Visibility = IsForeverDismissable ? Visibility.Visible : Visibility.Collapsed;
|
||||
RelatedSettingsKeyName = relatedSettingsKeyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 TitleBarType
|
||||
{
|
||||
Compact,
|
||||
Extended,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.Graphics.Imaging;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Rendering
|
||||
{
|
||||
public class CoverBackgroundRenderer
|
||||
{
|
||||
private readonly SettingsService _settingsService;
|
||||
|
||||
public float RotateAngle { get; set; } = 0f;
|
||||
|
||||
private SoftwareBitmap? _lastSoftwareBitmap = null;
|
||||
private SoftwareBitmap? _softwareBitmap = null;
|
||||
public SoftwareBitmap? SoftwareBitmap
|
||||
{
|
||||
get => _softwareBitmap;
|
||||
set
|
||||
{
|
||||
if (_softwareBitmap != null)
|
||||
{
|
||||
_lastSoftwareBitmap = _softwareBitmap;
|
||||
_transitionStartTime = DateTimeOffset.Now;
|
||||
_isTransitioning = true;
|
||||
_transitionAlpha = 0f;
|
||||
}
|
||||
|
||||
_softwareBitmap = value;
|
||||
}
|
||||
}
|
||||
|
||||
private float _transitionAlpha = 1f;
|
||||
private TimeSpan _transitionDuration = TimeSpan.FromMilliseconds(1000);
|
||||
private DateTimeOffset _transitionStartTime;
|
||||
private bool _isTransitioning = false;
|
||||
|
||||
public CoverBackgroundRenderer()
|
||||
{
|
||||
_settingsService = Ioc.Default.GetService<SettingsService>()!;
|
||||
}
|
||||
|
||||
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
if (!_settingsService.IsCoverOverlayEnabled || SoftwareBitmap == null)
|
||||
return;
|
||||
|
||||
ds.Transform = Matrix3x2.CreateRotation(RotateAngle, control.Size.ToVector2() * 0.5f);
|
||||
|
||||
var overlappedCovers = new CanvasCommandList(control);
|
||||
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 = _settingsService.CoverOverlayOpacity / 100f,
|
||||
Source = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = _settingsService.CoverOverlayBlurAmount,
|
||||
Source = overlappedCovers,
|
||||
},
|
||||
};
|
||||
ds.DrawImage(coverOverlayEffect);
|
||||
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
private void DrawImgae(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
SoftwareBitmap softwareBitmap,
|
||||
float opacity
|
||||
)
|
||||
{
|
||||
float imageWidth = (float)(softwareBitmap.PixelWidth * 96f / softwareBitmap.DpiX);
|
||||
float imageHeight = (float)(softwareBitmap.PixelHeight * 96f / softwareBitmap.DpiY);
|
||||
var scaleFactor =
|
||||
(float)Math.Sqrt(Math.Pow(control.Size.Width, 2) + Math.Pow(control.Size.Height, 2))
|
||||
/ Math.Min(imageWidth, imageHeight);
|
||||
|
||||
ds.DrawImage(
|
||||
new OpacityEffect
|
||||
{
|
||||
Source = new ScaleEffect
|
||||
{
|
||||
InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
|
||||
BorderMode = EffectBorderMode.Hard,
|
||||
Scale = new Vector2(scaleFactor),
|
||||
Source = CanvasBitmap.CreateFromSoftwareBitmap(control, softwareBitmap),
|
||||
},
|
||||
Opacity = opacity,
|
||||
},
|
||||
(float)control.Size.Width / 2 - imageWidth * scaleFactor / 2,
|
||||
(float)control.Size.Height / 2 - imageHeight * scaleFactor / 2
|
||||
);
|
||||
}
|
||||
|
||||
public void Calculate(ICanvasAnimatedControl control)
|
||||
{
|
||||
if (_isTransitioning)
|
||||
{
|
||||
var elapsed = DateTimeOffset.Now - _transitionStartTime;
|
||||
float progress = (float)(
|
||||
elapsed.TotalMilliseconds / _transitionDuration.TotalMilliseconds
|
||||
);
|
||||
_transitionAlpha = Math.Clamp(progress, 0f, 1f);
|
||||
|
||||
if (_transitionAlpha >= 1f)
|
||||
{
|
||||
_isTransitioning = false;
|
||||
_lastSoftwareBitmap?.Dispose();
|
||||
_lastSoftwareBitmap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Brushes;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Text;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Rendering
|
||||
{
|
||||
public class PureLyricsRenderer
|
||||
{
|
||||
private readonly SettingsService _settingsService;
|
||||
|
||||
private readonly float _defaultOpacity = 0.3f;
|
||||
private readonly float _highlightedOpacity = 1.0f;
|
||||
|
||||
private readonly float _defaultScale = 0.95f;
|
||||
private readonly float _highlightedScale = 1.0f;
|
||||
|
||||
private readonly int _lineEnteringDurationMs = 800;
|
||||
private readonly int _lineExitingDurationMs = 800;
|
||||
private readonly int _lineScrollDurationMs = 800;
|
||||
|
||||
private float _lastTotalYScroll = 0.0f;
|
||||
private float _totalYScroll = 0.0f;
|
||||
|
||||
private int _startVisibleLineIndex = -1;
|
||||
private int _endVisibleLineIndex = -1;
|
||||
|
||||
private bool _forceToScroll = false;
|
||||
|
||||
private readonly double _rightMargin = 36;
|
||||
|
||||
public double LimitedLineWidth { get; set; } = 0;
|
||||
public double CanvasWidth { get; set; } = 0;
|
||||
public double CanvasHeight { get; set; } = 0;
|
||||
|
||||
public TimeSpan CurrentTime { get; set; }
|
||||
|
||||
public List<LyricsLine> LyricsLines { get; set; } = [];
|
||||
|
||||
public PureLyricsRenderer()
|
||||
{
|
||||
_settingsService = Ioc.Default.GetService<SettingsService>()!;
|
||||
}
|
||||
|
||||
private Tuple<int, int> GetVisibleLyricsLineIndexBoundaries()
|
||||
{
|
||||
// _logger.LogDebug($"{_startVisibleLineIndex} {_endVisibleLineIndex}");
|
||||
return new Tuple<int, int>(_startVisibleLineIndex, _endVisibleLineIndex);
|
||||
}
|
||||
|
||||
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
|
||||
{
|
||||
if (LyricsLines.Count == 0)
|
||||
{
|
||||
return new Tuple<int, int>(-1, -1);
|
||||
}
|
||||
|
||||
return new Tuple<int, int>(0, LyricsLines.Count - 1);
|
||||
}
|
||||
|
||||
public void Draw(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
byte r,
|
||||
byte g,
|
||||
byte b
|
||||
)
|
||||
{
|
||||
var (displayStartLineIndex, displayEndLineIndex) =
|
||||
GetVisibleLyricsLineIndexBoundaries();
|
||||
|
||||
for (
|
||||
int i = displayStartLineIndex;
|
||||
LyricsLines.Count > 0
|
||||
&& i >= 0
|
||||
&& i < LyricsLines.Count
|
||||
&& i <= displayEndLineIndex;
|
||||
i++
|
||||
)
|
||||
{
|
||||
var line = LyricsLines[i];
|
||||
|
||||
if (line.TextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float progressPerChar = 1f / line.Text.Length;
|
||||
|
||||
var position = line.Position;
|
||||
|
||||
float centerX = position.X;
|
||||
float centerY = position.Y + (float)line.TextLayout.LayoutBounds.Height / 2;
|
||||
|
||||
switch ((LyricsAlignmentType)_settingsService.LyricsAlignmentType)
|
||||
{
|
||||
case LyricsAlignmentType.Left:
|
||||
line.TextLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
|
||||
break;
|
||||
case LyricsAlignmentType.Center:
|
||||
line.TextLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
|
||||
centerX += (float)LimitedLineWidth / 2;
|
||||
break;
|
||||
case LyricsAlignmentType.Right:
|
||||
line.TextLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
|
||||
centerX += (float)LimitedLineWidth;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
int startIndex = 0;
|
||||
|
||||
// Set brush
|
||||
for (int j = 0; j < line.TextLayout.LineCount; j++)
|
||||
{
|
||||
int count = line.TextLayout.LineMetrics[j].CharacterCount;
|
||||
var regions = line.TextLayout.GetCharacterRegions(startIndex, count);
|
||||
float subLinePlayingProgress = Math.Clamp(
|
||||
(line.PlayingProgress * line.Text.Length - startIndex) / count,
|
||||
0,
|
||||
1
|
||||
);
|
||||
|
||||
using var horizontalFillBrush = new CanvasLinearGradientBrush(
|
||||
control,
|
||||
[
|
||||
new()
|
||||
{
|
||||
Position = 0,
|
||||
Color = Color.FromArgb((byte)(255 * line.Opacity), r, g, b),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Position =
|
||||
subLinePlayingProgress * (1 + progressPerChar)
|
||||
- progressPerChar,
|
||||
Color = Color.FromArgb((byte)(255 * line.Opacity), r, g, b),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Position = subLinePlayingProgress * (1 + progressPerChar),
|
||||
Color = Color.FromArgb((byte)(255 * _defaultOpacity), r, g, b),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Position = 1.5f,
|
||||
Color = Color.FromArgb((byte)(255 * _defaultOpacity), r, g, b),
|
||||
},
|
||||
]
|
||||
)
|
||||
{
|
||||
StartPoint = new Vector2(
|
||||
(float)(regions[0].LayoutBounds.Left + position.X),
|
||||
0
|
||||
),
|
||||
EndPoint = new Vector2(
|
||||
(float)(regions[^1].LayoutBounds.Right + position.X),
|
||||
0
|
||||
),
|
||||
};
|
||||
|
||||
line.TextLayout.SetBrush(startIndex, count, horizontalFillBrush);
|
||||
startIndex += count;
|
||||
}
|
||||
|
||||
// Scale
|
||||
ds.Transform =
|
||||
Matrix3x2.CreateScale(line.Scale, new Vector2(centerX, centerY))
|
||||
* Matrix3x2.CreateTranslation(0, _totalYScroll);
|
||||
// _logger.LogDebug(_totalYScroll);
|
||||
|
||||
ds.DrawTextLayout(line.TextLayout, position, Colors.Transparent);
|
||||
|
||||
// Reset scale
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ForceToScrollToCurrentPlayingLineAsync()
|
||||
{
|
||||
_forceToScroll = true;
|
||||
await Task.Delay(1);
|
||||
_forceToScroll = false;
|
||||
}
|
||||
|
||||
public async Task ReLayoutAsync(ICanvasAnimatedControl control)
|
||||
{
|
||||
if (control == null)
|
||||
return;
|
||||
|
||||
float leftMargin = (float)(CanvasWidth - LimitedLineWidth - _rightMargin);
|
||||
|
||||
using CanvasTextFormat textFormat = new()
|
||||
{
|
||||
FontSize = _settingsService.LyricsFontSize,
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Top,
|
||||
FontWeight = FontWeights.Bold,
|
||||
//FontFamily = "Segoe UI Mono",
|
||||
};
|
||||
float y = (float)CanvasHeight / 2;
|
||||
|
||||
// Init Positions
|
||||
for (int i = 0; i < LyricsLines.Count; i++)
|
||||
{
|
||||
var line = LyricsLines[i];
|
||||
|
||||
// Calculate layout bounds
|
||||
line.TextLayout = new CanvasTextLayout(
|
||||
control.Device,
|
||||
line.Text,
|
||||
textFormat,
|
||||
(float)LimitedLineWidth,
|
||||
(float)CanvasHeight
|
||||
);
|
||||
line.Position = new Vector2(leftMargin, y);
|
||||
|
||||
y +=
|
||||
(float)line.TextLayout.LayoutBounds.Height
|
||||
/ line.TextLayout.LineCount
|
||||
* (line.TextLayout.LineCount + _settingsService.LyricsLineSpacingFactor);
|
||||
}
|
||||
|
||||
await ForceToScrollToCurrentPlayingLineAsync();
|
||||
}
|
||||
|
||||
public void CalculateScaleAndOpacity(int currentPlayingLineIndex)
|
||||
{
|
||||
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
|
||||
|
||||
for (int i = startLineIndex; LyricsLines.Count > 0 && i <= endLineIndex; i++)
|
||||
{
|
||||
var line = LyricsLines[i];
|
||||
|
||||
bool linePlaying = i == currentPlayingLineIndex;
|
||||
|
||||
var lineEnteringDurationMs = Math.Min(line.DurationMs, _lineEnteringDurationMs);
|
||||
var lineExitingDurationMs = _lineExitingDurationMs;
|
||||
if (i + 1 <= endLineIndex)
|
||||
{
|
||||
lineExitingDurationMs = Math.Min(
|
||||
LyricsLines[i + 1].DurationMs,
|
||||
lineExitingDurationMs
|
||||
);
|
||||
}
|
||||
|
||||
float lineEnteringProgress = 0.0f;
|
||||
float lineExitingProgress = 0.0f;
|
||||
|
||||
bool lineEntering = false;
|
||||
bool lineExiting = false;
|
||||
|
||||
float scale = _defaultScale;
|
||||
float opacity = _defaultOpacity;
|
||||
|
||||
float playProgress = 0;
|
||||
|
||||
if (linePlaying)
|
||||
{
|
||||
line.PlayingState = LyricsPlayingState.Playing;
|
||||
|
||||
scale = _highlightedScale;
|
||||
opacity = _highlightedOpacity;
|
||||
|
||||
playProgress =
|
||||
((float)CurrentTime.TotalMilliseconds - line.StartPlayingTimestampMs)
|
||||
/ line.DurationMs;
|
||||
|
||||
var durationFromStartMs =
|
||||
CurrentTime.TotalMilliseconds - line.StartPlayingTimestampMs;
|
||||
lineEntering = durationFromStartMs <= lineEnteringDurationMs;
|
||||
if (lineEntering)
|
||||
{
|
||||
lineEnteringProgress = (float)durationFromStartMs / lineEnteringDurationMs;
|
||||
scale =
|
||||
_defaultScale
|
||||
+ (_highlightedScale - _defaultScale) * (float)lineEnteringProgress;
|
||||
opacity =
|
||||
_defaultOpacity
|
||||
+ (_highlightedOpacity - _defaultOpacity) * (float)lineEnteringProgress;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < currentPlayingLineIndex)
|
||||
{
|
||||
line.PlayingState = LyricsPlayingState.Played;
|
||||
playProgress = 1;
|
||||
|
||||
var durationToEndMs =
|
||||
CurrentTime.TotalMilliseconds - line.EndPlayingTimestampMs;
|
||||
lineExiting = durationToEndMs <= lineExitingDurationMs;
|
||||
if (lineExiting)
|
||||
{
|
||||
lineExitingProgress = (float)durationToEndMs / lineExitingDurationMs;
|
||||
scale =
|
||||
_highlightedScale
|
||||
- (_highlightedScale - _defaultScale) * (float)lineExitingProgress;
|
||||
opacity =
|
||||
_highlightedOpacity
|
||||
- (_highlightedOpacity - _defaultOpacity)
|
||||
* (float)lineExitingProgress;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
line.PlayingState = LyricsPlayingState.NotPlayed;
|
||||
}
|
||||
}
|
||||
|
||||
line.EnteringProgress = lineEnteringProgress;
|
||||
line.ExitingProgress = lineExitingProgress;
|
||||
|
||||
line.Scale = scale;
|
||||
line.Opacity = opacity;
|
||||
|
||||
line.PlayingProgress = playProgress;
|
||||
}
|
||||
}
|
||||
|
||||
public void CalculatePosition(ICanvasAnimatedControl control, int currentPlayingLineIndex)
|
||||
{
|
||||
if (currentPlayingLineIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
|
||||
|
||||
if (startLineIndex < 0 || endLineIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set _scrollOffsetY
|
||||
LyricsLine? currentPlayingLine = LyricsLines?[currentPlayingLineIndex];
|
||||
|
||||
if (currentPlayingLine == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPlayingLine.TextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lineScrollingProgress =
|
||||
(CurrentTime.TotalMilliseconds - currentPlayingLine.StartPlayingTimestampMs)
|
||||
/ Math.Min(_lineScrollDurationMs, currentPlayingLine.DurationMs);
|
||||
|
||||
var targetYScrollOffset = (float)(
|
||||
-currentPlayingLine.Position.Y
|
||||
+ LyricsLines![0].Position.Y
|
||||
- currentPlayingLine.TextLayout.LayoutBounds.Height / 2
|
||||
- _lastTotalYScroll
|
||||
);
|
||||
|
||||
var yScrollOffset =
|
||||
targetYScrollOffset
|
||||
* EasingHelper.SmootherStep((float)Math.Min(1, lineScrollingProgress));
|
||||
|
||||
bool isScrollingNow = lineScrollingProgress <= 1;
|
||||
|
||||
if (isScrollingNow)
|
||||
{
|
||||
_totalYScroll = _lastTotalYScroll + yScrollOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_forceToScroll && Math.Abs(targetYScrollOffset) >= 1)
|
||||
{
|
||||
_totalYScroll = _lastTotalYScroll + targetYScrollOffset;
|
||||
}
|
||||
_lastTotalYScroll = _totalYScroll;
|
||||
}
|
||||
|
||||
_startVisibleLineIndex = _endVisibleLineIndex = -1;
|
||||
|
||||
// Update Positions
|
||||
for (int i = startLineIndex; i >= 0 && i <= endLineIndex; i++)
|
||||
{
|
||||
var line = LyricsLines[i];
|
||||
|
||||
if (_totalYScroll + line.Position.Y + line.TextLayout.LayoutBounds.Height >= 0)
|
||||
{
|
||||
if (_startVisibleLineIndex == -1)
|
||||
{
|
||||
_startVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
if (
|
||||
_totalYScroll + line.Position.Y + line.TextLayout.LayoutBounds.Height
|
||||
>= control.Size.Height
|
||||
)
|
||||
{
|
||||
if (_endVisibleLineIndex == -1)
|
||||
{
|
||||
_endVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_startVisibleLineIndex != -1 && _endVisibleLineIndex == -1)
|
||||
{
|
||||
_endVisibleLineIndex = endLineIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ namespace BetterLyrics.WinUI3.Services.Settings
|
||||
public const int CoverOverlayOpacity = 100; // 1.0
|
||||
public const int CoverOverlayBlurAmount = 200;
|
||||
|
||||
// Title bar
|
||||
public const int TitleBarType = 0;
|
||||
|
||||
// Album art
|
||||
public const int CoverImageRadius = 24;
|
||||
|
||||
@@ -40,5 +43,9 @@ namespace BetterLyrics.WinUI3.Services.Settings
|
||||
public const bool IsLyricsDynamicGlowEffectEnabled = false;
|
||||
public const int LyricsFontColorType = 0; // Default
|
||||
public const int LyricsFontSelectedAccentColorIndex = 0;
|
||||
|
||||
// Notification
|
||||
public const bool NeverShowEnterFullScreenMessage = false;
|
||||
public const bool NeverShowEnterImmersiveModeMessage = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace BetterLyrics.WinUI3.Services.Settings
|
||||
public const string CoverOverlayOpacity = "CoverOverlayOpacity";
|
||||
public const string CoverOverlayBlurAmount = "CoverOverlayBlurAmount";
|
||||
|
||||
// Title bar
|
||||
public const string TitleBarType = "TitleBarType";
|
||||
|
||||
// Album art
|
||||
public const string CoverImageRadius = "CoverImageRadius";
|
||||
|
||||
@@ -40,5 +43,10 @@ namespace BetterLyrics.WinUI3.Services.Settings
|
||||
public const string LyricsFontColorType = "LyricsFontColorType";
|
||||
public const string LyricsFontSelectedAccentColorIndex =
|
||||
"LyricsFontSelectedAccentColorIndex";
|
||||
|
||||
// Notification
|
||||
public const string NeverShowEnterFullScreenMessage = "NeverShowEnterFullScreenMessage";
|
||||
public const string NeverShowEnterImmersiveModeMessage =
|
||||
"NeverShowEnterImmersiveModeMessage";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,28 +33,6 @@ namespace BetterLyrics.WinUI3.Services.Settings
|
||||
_musicLibraries.CollectionChanged += (_, _) => SaveMusicLibraries();
|
||||
}
|
||||
|
||||
private void WatchMultipleDirectories(IEnumerable<string> directories)
|
||||
{
|
||||
foreach (var dir in directories)
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
continue;
|
||||
|
||||
var watcher = new FileSystemWatcher
|
||||
{
|
||||
Path = dir,
|
||||
Filter = "*.*",
|
||||
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFileCreated(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
App.DispatcherQueue.TryEnqueue(() => { });
|
||||
}
|
||||
|
||||
public bool IsFirstRun
|
||||
{
|
||||
get => Get(SettingsKeys.IsFirstRun, SettingsDefaultValues.IsFirstRun);
|
||||
@@ -64,6 +42,9 @@ namespace BetterLyrics.WinUI3.Services.Settings
|
||||
[ObservableProperty]
|
||||
private bool _isRebuildingLyricsIndexDatabase = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isImmersiveMode = false;
|
||||
|
||||
// Theme
|
||||
public int Theme
|
||||
{
|
||||
@@ -172,6 +153,13 @@ namespace BetterLyrics.WinUI3.Services.Settings
|
||||
set => Set(SettingsKeys.CoverOverlayBlurAmount, value);
|
||||
}
|
||||
|
||||
// Title bar
|
||||
public int TitleBarType
|
||||
{
|
||||
get => Get(SettingsKeys.TitleBarType, SettingsDefaultValues.TitleBarType);
|
||||
set => Set(SettingsKeys.TitleBarType, value);
|
||||
}
|
||||
|
||||
// Album art
|
||||
public int CoverImageRadius
|
||||
{
|
||||
@@ -250,6 +238,28 @@ namespace BetterLyrics.WinUI3.Services.Settings
|
||||
}
|
||||
}
|
||||
|
||||
//Notification
|
||||
public bool NeverShowEnterFullScreenMessage
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.NeverShowEnterFullScreenMessage,
|
||||
SettingsDefaultValues.NeverShowEnterFullScreenMessage
|
||||
);
|
||||
set => Set(SettingsKeys.NeverShowEnterFullScreenMessage, value);
|
||||
}
|
||||
public bool NeverShowEnterImmersiveModeMessage
|
||||
{
|
||||
get =>
|
||||
Get(
|
||||
SettingsKeys.NeverShowEnterImmersiveModeMessage,
|
||||
SettingsDefaultValues.NeverShowEnterImmersiveModeMessage
|
||||
);
|
||||
set => Set(SettingsKeys.NeverShowEnterImmersiveModeMessage, value);
|
||||
}
|
||||
|
||||
// Utils
|
||||
|
||||
private T? Get<T>(string key, T? defaultValue = default)
|
||||
{
|
||||
if (_localSettings.Values.TryGetValue(key, out object? value))
|
||||
|
||||
@@ -276,6 +276,9 @@
|
||||
<data name="MainWindowLyricsOnly.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Show lyrics only</value>
|
||||
</data>
|
||||
<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>
|
||||
@@ -309,35 +312,11 @@
|
||||
<data name="SettingsPageRebuildDatabaseDesc.Text" xml:space="preserve">
|
||||
<value>Rebuilding the database, please wait...</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>Let's get started now</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>Welcome to BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageTitleBarTeachingTip.Title" xml:space="preserve">
|
||||
<value>The top area is the title bar</value>
|
||||
</data>
|
||||
<data name="MainPageTitleBarTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>Hover on the top area to show it</value>
|
||||
</data>
|
||||
<data name="MainPageInitDatabaseTeachingTip.Title" xml:space="preserve">
|
||||
<value>Setup lyrics database now</value>
|
||||
</data>
|
||||
<data name="MainPageInitDatabaseTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>Hover on the bottom left corner and then click to open settings page</value>
|
||||
</data>
|
||||
<data name="MainPageBottomCommandTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>Hover on the bottom area to show it</value>
|
||||
</data>
|
||||
<data name="MainPageBottomCommandTeachingTip.Title" xml:space="preserve">
|
||||
<value>The bottom area is the command area</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsOnlyTeachingTip.Title" xml:space="preserve">
|
||||
<value>Toggle "show lyrics only" here</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsOnlyTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>When lyrics exist, you can switch them in the lower right corner</value>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>Let's setup lyrics database now</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>No music playing now</value>
|
||||
@@ -369,4 +348,28 @@
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
<value>Corner radius</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitleBarType.Header" xml:space="preserve">
|
||||
<value>Title bar size</value>
|
||||
</data>
|
||||
<data name="SettingsPageCompactTitleBar.Content" xml:space="preserve">
|
||||
<value>Compact</value>
|
||||
</data>
|
||||
<data name="SettingsPageExtendedTitleBar.Content" xml:space="preserve">
|
||||
<value>Extended</value>
|
||||
</data>
|
||||
<data name="BaseWindowAOTFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Always on top</value>
|
||||
</data>
|
||||
<data name="BaseWindowFullScreenFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Full screen</value>
|
||||
</data>
|
||||
<data name="BaseWindowEnterFullScreenHint" xml:space="preserve">
|
||||
<value>Press Esc to exit full screen mode</value>
|
||||
</data>
|
||||
<data name="MainPageEnterImmersiveModeHint" xml:space="preserve">
|
||||
<value>Hover back again to show the toggle button</value>
|
||||
</data>
|
||||
<data name="BaseWindowHostInfoBarCheckBox.Content" xml:space="preserve">
|
||||
<value>Do not show this message again</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -276,6 +276,9 @@
|
||||
<data name="MainWindowLyricsOnly.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>仅展示歌词</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>沉浸模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>歌词效果</value>
|
||||
</data>
|
||||
@@ -309,36 +312,12 @@
|
||||
<data name="SettingsPageRebuildDatabaseDesc.Text" xml:space="preserve">
|
||||
<value>重构数据库中,请稍候...</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>来看看怎么使用这款应用吧</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>欢迎使用 BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageTitleBarTeachingTip.Title" xml:space="preserve">
|
||||
<value>顶部区域是标题栏</value>
|
||||
</data>
|
||||
<data name="MainPageTitleBarTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>悬停在顶部区域以显示</value>
|
||||
</data>
|
||||
<data name="MainPageInitDatabaseTeachingTip.Title" xml:space="preserve">
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>现在就来初始化歌词数据库吧</value>
|
||||
</data>
|
||||
<data name="MainPageInitDatabaseTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>悬停在左下角后单击以进入设置页面</value>
|
||||
</data>
|
||||
<data name="MainPageBottomCommandTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>悬停在底部区域以显示</value>
|
||||
</data>
|
||||
<data name="MainPageBottomCommandTeachingTip.Title" xml:space="preserve">
|
||||
<value>底部区域是命令栏</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsOnlyTeachingTip.Title" xml:space="preserve">
|
||||
<value>在此处切换“仅展示歌词”</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsOnlyTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>当歌词存在时可在右下角进行切换</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>当前没有正在播放的音乐</value>
|
||||
</data>
|
||||
@@ -369,4 +348,28 @@
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
<value>圆角半径</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitleBarType.Header" xml:space="preserve">
|
||||
<value>标题栏大小</value>
|
||||
</data>
|
||||
<data name="SettingsPageCompactTitleBar.Content" xml:space="preserve">
|
||||
<value>紧凑</value>
|
||||
</data>
|
||||
<data name="SettingsPageExtendedTitleBar.Content" xml:space="preserve">
|
||||
<value>扩展</value>
|
||||
</data>
|
||||
<data name="BaseWindowAOTFlyoutItem.Text" xml:space="preserve">
|
||||
<value>将应用置于顶层</value>
|
||||
</data>
|
||||
<data name="BaseWindowFullScreenFlyoutItem.Text" xml:space="preserve">
|
||||
<value>全屏</value>
|
||||
</data>
|
||||
<data name="BaseWindowEnterFullScreenHint" xml:space="preserve">
|
||||
<value>按 Esc 退出全屏模式</value>
|
||||
</data>
|
||||
<data name="MainPageEnterImmersiveModeHint" xml:space="preserve">
|
||||
<value>再次悬停以显示切换按钮</value>
|
||||
</data>
|
||||
<data name="BaseWindowHostInfoBarCheckBox.Content" xml:space="preserve">
|
||||
<value> 不再显示此消息</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -276,6 +276,9 @@
|
||||
<data name="MainWindowLyricsOnly.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>僅展示歌詞</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>沉浸模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>歌詞效果</value>
|
||||
</data>
|
||||
@@ -309,36 +312,12 @@
|
||||
<data name="SettingsPageRebuildDatabaseDesc.Text" xml:space="preserve">
|
||||
<value>重構資料庫中,請稍候...</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>來看看怎麼使用這款應用程式吧</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>歡迎使用 BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageTitleBarTeachingTip.Title" xml:space="preserve">
|
||||
<value>頂部區域是標題欄</value>
|
||||
</data>
|
||||
<data name="MainPageTitleBarTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>懸停在頂部區域以顯示</value>
|
||||
</data>
|
||||
<data name="MainPageInitDatabaseTeachingTip.Title" xml:space="preserve">
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>現在就來初始化歌詞資料庫吧</value>
|
||||
</data>
|
||||
<data name="MainPageInitDatabaseTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>懸停在左下角後點擊以進入設定頁面</value>
|
||||
</data>
|
||||
<data name="MainPageBottomCommandTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>懸停在底部區域以顯示</value>
|
||||
</data>
|
||||
<data name="MainPageBottomCommandTeachingTip.Title" xml:space="preserve">
|
||||
<value>底部區域是命令列</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsOnlyTeachingTip.Title" xml:space="preserve">
|
||||
<value>在此切換“僅展示歌詞”</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsOnlyTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>當歌詞存在時可在右下角進行切換</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>目前沒有正在播放的音樂</value>
|
||||
</data>
|
||||
@@ -369,4 +348,28 @@
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
<value>圓角半徑</value>
|
||||
</data>
|
||||
<data name="SettingsPageTitleBarType.Header" xml:space="preserve">
|
||||
<value>標題列大小</value>
|
||||
</data>
|
||||
<data name="SettingsPageCompactTitleBar.Content" xml:space="preserve">
|
||||
<value>緊湊</value>
|
||||
</data>
|
||||
<data name="SettingsPageExtendedTitleBar.Content" xml:space="preserve">
|
||||
<value>擴充</value>
|
||||
</data>
|
||||
<data name="BaseWindowAOTFlyoutItem.Text" xml:space="preserve">
|
||||
<value>將應用置於頂層</value>
|
||||
</data>
|
||||
<data name="BaseWindowFullScreenFlyoutItem.Text" xml:space="preserve">
|
||||
<value>全螢幕</value>
|
||||
</data>
|
||||
<data name="BaseWindowEnterFullScreenHint" xml:space="preserve">
|
||||
<value>按 Esc 退出全螢幕模式</value>
|
||||
</data>
|
||||
<data name="MainPageEnterImmersiveModeHint" xml:space="preserve">
|
||||
<value>再次懸停以顯示切換按鈕</value>
|
||||
</data>
|
||||
<data name="BaseWindowHostInfoBarCheckBox.Content" xml:space="preserve">
|
||||
<value>不再顯示此訊息</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class BaseWindowModel : ObservableObject
|
||||
{
|
||||
public SettingsService SettingsService { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private int _titleBarFontSize = 11;
|
||||
|
||||
[ObservableProperty]
|
||||
private Notification _notification = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _showInfoBar = false;
|
||||
|
||||
public BaseWindowModel(SettingsService settingsService)
|
||||
{
|
||||
SettingsService = settingsService;
|
||||
|
||||
WeakReferenceMessenger.Default.Register<ShowNotificatonMessage>(
|
||||
this,
|
||||
async (r, m) =>
|
||||
{
|
||||
Notification = m.Value;
|
||||
if (
|
||||
!Notification.IsForeverDismissable
|
||||
|| AlreadyForeverDismissedThisMessage() == false
|
||||
)
|
||||
{
|
||||
Notification.Visibility = Notification.IsForeverDismissable
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
ShowInfoBar = true;
|
||||
await Task.Delay(AnimationHelper.StackedNotificationsShowingDuration);
|
||||
ShowInfoBar = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SwitchInfoBarNeverShowItAgainCheckBox(bool value)
|
||||
{
|
||||
switch (Notification.RelatedSettingsKeyName)
|
||||
{
|
||||
case SettingsKeys.NeverShowEnterFullScreenMessage:
|
||||
SettingsService.NeverShowEnterFullScreenMessage = value;
|
||||
break;
|
||||
case SettingsKeys.NeverShowEnterImmersiveModeMessage:
|
||||
SettingsService.NeverShowEnterImmersiveModeMessage = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool? AlreadyForeverDismissedThisMessage() =>
|
||||
Notification.RelatedSettingsKeyName switch
|
||||
{
|
||||
SettingsKeys.NeverShowEnterFullScreenMessage =>
|
||||
SettingsService.NeverShowEnterFullScreenMessage,
|
||||
SettingsKeys.NeverShowEnterImmersiveModeMessage =>
|
||||
SettingsService.NeverShowEnterImmersiveModeMessage,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<(List<LyricsLine>, SoftwareBitmap?, uint, uint)> SetSongInfoAsync(
|
||||
public async Task<(List<LyricsLine>, SoftwareBitmap?)> SetSongInfoAsync(
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps
|
||||
)
|
||||
{
|
||||
@@ -201,12 +201,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
return (
|
||||
GetLyrics(track),
|
||||
coverSoftwareBitmap,
|
||||
coverImagePixelWidth,
|
||||
coverImagePixelHeight
|
||||
);
|
||||
return (GetLyrics(track), coverSoftwareBitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Database;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Media;
|
||||
using Windows.Media.Playback;
|
||||
@@ -73,9 +77,12 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
bool existed = SettingsService.MusicLibraries.Any((x) => x == path);
|
||||
if (existed)
|
||||
{
|
||||
MainWindow.StackedNotificationsBehavior?.Show(
|
||||
App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"),
|
||||
Helper.AnimationHelper.StackedNotificationsShowingDuration
|
||||
WeakReferenceMessenger.Default.Send(
|
||||
new ShowNotificatonMessage(
|
||||
new Notification(
|
||||
App.ResourceLoader!.GetString("SettingsPagePathExistedInfo")
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
|
||||
187
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/BaseWindow.xaml
Normal file
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Window
|
||||
x:Class="BetterLyrics.WinUI3.Views.BaseWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
|
||||
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:media="using:CommunityToolkit.WinUI.Media"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid" KeyDown="RootGrid_KeyDown">
|
||||
|
||||
<Frame
|
||||
x:Name="RootFrame"
|
||||
Navigated="RootFrame_Navigated"
|
||||
NavigationFailed="RootFrame_NavigationFailed" />
|
||||
|
||||
<Grid
|
||||
x:Name="TopCommandGrid"
|
||||
Height="{StaticResource TitleBarCompactHeight}"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
|
||||
<interactivity:Interaction.Behaviors>
|
||||
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind WindowModel.SettingsService.IsImmersiveMode, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind WindowModel.SettingsService.IsImmersiveMode, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
|
||||
</interactivity:Interaction.Behaviors>
|
||||
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
|
||||
<ImageIcon
|
||||
x:Name="AppLogoImageIcon"
|
||||
Height="18"
|
||||
Margin="16,0"
|
||||
Source="ms-appx:///Assets/Logo.png" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="AppTitleTextBlock"
|
||||
FontSize="{x:Bind WindowModel.TitleBarFontSize, Mode=OneWay}"
|
||||
FontWeight="SemiBold"
|
||||
Opacity=".5"
|
||||
Text="{x:Bind Title, Mode=OneWay}" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
Opacity=".5"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<Button Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="{x:Bind WindowModel.TitleBarFontSize, Mode=OneWay}"
|
||||
FontWeight="ExtraBold"
|
||||
Glyph="" />
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="AOTFlyoutItem"
|
||||
x:Uid="BaseWindowAOTFlyoutItem"
|
||||
Click="AOTFlyoutItem_Click" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="FullScreenFlyoutItem"
|
||||
x:Uid="BaseWindowFullScreenFlyoutItem"
|
||||
Click="FullScreenFlyoutItem_Click" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
|
||||
<!-- Window Minimise -->
|
||||
<Button
|
||||
x:Name="MinimiseButton"
|
||||
Click="MinimiseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="{x:Bind WindowModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Maximise -->
|
||||
<Button
|
||||
x:Name="MaximiseButton"
|
||||
Click="MaximiseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="{x:Bind WindowModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Restore -->
|
||||
<Button
|
||||
x:Name="RestoreButton"
|
||||
Click="RestoreButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}"
|
||||
Visibility="Collapsed">
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="{x:Bind WindowModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Close -->
|
||||
<Button
|
||||
x:Name="CloseButton"
|
||||
Click="CloseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="{x:Bind WindowModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
<InfoBar
|
||||
x:Name="HostInfoBar"
|
||||
Margin="36"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="{ThemeResource SystemFillColorSolidAttentionBackgroundBrush}"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind WindowModel.ShowInfoBar, Mode=OneWay}"
|
||||
Message="{x:Bind WindowModel.Notification.Message, Mode=OneWay}"
|
||||
Opacity="0"
|
||||
Severity="{x:Bind WindowModel.Notification.Severity, Mode=OneWay}">
|
||||
<InfoBar.RenderTransform>
|
||||
<TranslateTransform x:Name="HostInfoBarTransform" Y="20" />
|
||||
</InfoBar.RenderTransform>
|
||||
<InfoBar.ActionButton>
|
||||
<CheckBox
|
||||
x:Name="HostInfoBarCheckBox"
|
||||
x:Uid="BaseWindowHostInfoBarCheckBox"
|
||||
Command="{x:Bind WindowModel.SwitchInfoBarNeverShowItAgainCheckBoxCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsChecked, Mode=OneWay}"
|
||||
Visibility="{x:Bind WindowModel.Notification.Visibility, Mode=OneWay}" />
|
||||
</InfoBar.ActionButton>
|
||||
<InfoBar.Resources>
|
||||
<Storyboard x:Key="InfoBarShowAndHideStoryboard">
|
||||
<!-- Opacity -->
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="HostInfoBar" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:3.6" Value="1" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:3.9" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
|
||||
<!-- Y -->
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="HostInfoBarTransform" Storyboard.TargetProperty="Y">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="20" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:3.6" Value="0" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:3.9" Value="20" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</InfoBar.Resources>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{Binding ElementName=HostInfoBar, Path=IsOpen, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ControlStoryboardAction Storyboard="{StaticResource InfoBarShowAndHideStoryboard}" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</InfoBar>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
240
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Views/BaseWindow.xaml.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using DevWinUI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
// 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.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class BaseWindow : Window
|
||||
{
|
||||
public BaseWindowModel WindowModel { get; set; }
|
||||
|
||||
public BaseWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
AppWindow.Changed += AppWindow_Changed;
|
||||
|
||||
WindowModel = Ioc.Default.GetService<BaseWindowModel>()!;
|
||||
|
||||
WindowModel.SettingsService.PropertyChanged += SettingsService_PropertyChanged;
|
||||
|
||||
SettingsService_PropertyChanged(
|
||||
null,
|
||||
new System.ComponentModel.PropertyChangedEventArgs(nameof(SettingsService.Theme))
|
||||
);
|
||||
SettingsService_PropertyChanged(
|
||||
null,
|
||||
new System.ComponentModel.PropertyChangedEventArgs(
|
||||
nameof(SettingsService.BackdropType)
|
||||
)
|
||||
);
|
||||
SettingsService_PropertyChanged(
|
||||
null,
|
||||
new System.ComponentModel.PropertyChangedEventArgs(
|
||||
nameof(SettingsService.TitleBarType)
|
||||
)
|
||||
);
|
||||
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
SetTitleBar(TopCommandGrid);
|
||||
}
|
||||
|
||||
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
|
||||
{
|
||||
UpdateTitleBarWindowButtonsVisibility();
|
||||
}
|
||||
|
||||
private void SettingsService_PropertyChanged(
|
||||
object? sender,
|
||||
System.ComponentModel.PropertyChangedEventArgs e
|
||||
)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(SettingsService.Theme):
|
||||
RootGrid.RequestedTheme = (ElementTheme)WindowModel.SettingsService.Theme;
|
||||
break;
|
||||
case nameof(SettingsService.BackdropType):
|
||||
SystemBackdrop = null;
|
||||
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(
|
||||
(BackdropType)WindowModel.SettingsService.BackdropType
|
||||
);
|
||||
break;
|
||||
case nameof(SettingsService.TitleBarType):
|
||||
switch ((TitleBarType)WindowModel.SettingsService.TitleBarType)
|
||||
{
|
||||
case TitleBarType.Compact:
|
||||
TopCommandGrid.Height = (double)
|
||||
App.Current.Resources["TitleBarCompactHeight"];
|
||||
AppLogoImageIcon.Height = 18;
|
||||
WindowModel.TitleBarFontSize = 11;
|
||||
break;
|
||||
case TitleBarType.Extended:
|
||||
TopCommandGrid.Height = (double)
|
||||
App.Current.Resources["TitleBarExpandedHeight"];
|
||||
AppLogoImageIcon.Height = 20;
|
||||
WindowModel.TitleBarFontSize = 14;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Navigate(Type type)
|
||||
{
|
||||
RootFrame.Navigate(type);
|
||||
}
|
||||
|
||||
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.Maximize();
|
||||
UpdateTitleBarWindowButtonsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void MinimiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.Minimize();
|
||||
UpdateTitleBarWindowButtonsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.Restore();
|
||||
UpdateTitleBarWindowButtonsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTitleBarWindowButtonsVisibility()
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter overlappedPresenter)
|
||||
{
|
||||
MinimiseButton.Visibility = AOTFlyoutItem.Visibility = Visibility.Visible;
|
||||
FullScreenFlyoutItem.IsChecked = false;
|
||||
AOTFlyoutItem.IsChecked = overlappedPresenter.IsAlwaysOnTop;
|
||||
|
||||
if (overlappedPresenter.State == OverlappedPresenterState.Maximized)
|
||||
{
|
||||
MaximiseButton.Visibility = Visibility.Collapsed;
|
||||
RestoreButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
else if (overlappedPresenter.State == OverlappedPresenterState.Restored)
|
||||
{
|
||||
MaximiseButton.Visibility = Visibility.Visible;
|
||||
RestoreButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
else if (AppWindow.Presenter is FullScreenPresenter)
|
||||
{
|
||||
MinimiseButton.Visibility =
|
||||
MaximiseButton.Visibility =
|
||||
RestoreButton.Visibility =
|
||||
AOTFlyoutItem.Visibility =
|
||||
Visibility.Collapsed;
|
||||
FullScreenFlyoutItem.IsChecked = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void RootFrame_Navigated(object sender, NavigationEventArgs e)
|
||||
{
|
||||
AppWindow.Title = Title = App.ResourceLoader!.GetString(
|
||||
$"{e.SourcePageType.Name}Title"
|
||||
);
|
||||
}
|
||||
|
||||
private void AOTFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.IsAlwaysOnTop = !presenter.IsAlwaysOnTop;
|
||||
UpdateTitleBarWindowButtonsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void FullScreenFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
switch (AppWindow.Presenter.Kind)
|
||||
{
|
||||
case AppWindowPresenterKind.Default:
|
||||
break;
|
||||
case AppWindowPresenterKind.CompactOverlay:
|
||||
break;
|
||||
case AppWindowPresenterKind.FullScreen:
|
||||
AppWindow.SetPresenter(AppWindowPresenterKind.Overlapped);
|
||||
break;
|
||||
case AppWindowPresenterKind.Overlapped:
|
||||
AppWindow.SetPresenter(AppWindowPresenterKind.FullScreen);
|
||||
WeakReferenceMessenger.Default.Send(
|
||||
new ShowNotificatonMessage(
|
||||
new Models.Notification(
|
||||
App.ResourceLoader!.GetString("BaseWindowEnterFullScreenHint"),
|
||||
isForeverDismissable: true,
|
||||
relatedSettingsKeyName: SettingsKeys.NeverShowEnterFullScreenMessage
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateTitleBarWindowButtonsVisibility();
|
||||
}
|
||||
|
||||
private void RootGrid_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (
|
||||
AppWindow.Presenter is FullScreenPresenter
|
||||
&& e.Key == Windows.System.VirtualKey.Escape
|
||||
)
|
||||
AppWindow.SetPresenter(AppWindowPresenterKind.Overlapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
x:Class="BetterLyrics.WinUI3.Views.MainPage"
|
||||
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"
|
||||
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
@@ -14,7 +15,7 @@
|
||||
NavigationCacheMode="Required"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid" SizeChanged="RootGrid_SizeChanged">
|
||||
<Grid x:Name="RootGrid">
|
||||
<Grid.Resources>
|
||||
<Thickness x:Key="TeachingTipDescriptionMargin">0,16,0,0</Thickness>
|
||||
</Grid.Resources>
|
||||
@@ -34,38 +35,11 @@
|
||||
Draw="LyricsCanvas_Draw"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
Loaded="LyricsCanvas_Loaded"
|
||||
SizeChanged="LyricsCanvas_SizeChanged"
|
||||
Update="LyricsCanvas_Update">
|
||||
|
||||
<canvas:CanvasAnimatedControl.Resources>
|
||||
<Storyboard x:Key="LyricsCanvasFadeInStoryboard">
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LyricsCanvas" Storyboard.TargetProperty="Opacity">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<Storyboard x:Key="LyricsCanvasFadeOutStoryboard">
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="LyricsCanvas" Storyboard.TargetProperty="Opacity">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</canvas:CanvasAnimatedControl.Resources>
|
||||
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ControlStoryboardAction Storyboard="{StaticResource LyricsCanvasFadeOutStoryboard}" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.AboutToUpdateUI, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ControlStoryboardAction Storyboard="{StaticResource LyricsCanvasFadeInStoryboard}" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
|
||||
<canvas:CanvasAnimatedControl.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</canvas:CanvasAnimatedControl.OpacityTransition>
|
||||
</canvas:CanvasAnimatedControl>
|
||||
|
||||
</Grid>
|
||||
@@ -112,6 +86,11 @@
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
|
||||
<Grid
|
||||
x:Name="LyricsPlaceholderGrid"
|
||||
Grid.Column="2"
|
||||
SizeChanged="LyricsPlaceholderGrid_SizeChanged" />
|
||||
|
||||
<Grid
|
||||
x:Name="SongInfoInnerGrid"
|
||||
Grid.Column="0"
|
||||
@@ -349,60 +328,60 @@
|
||||
|
||||
<Grid
|
||||
x:Name="BottomCommandGrid"
|
||||
Padding="2,0"
|
||||
Margin="0,0,4,4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="Transparent"
|
||||
Opacity="0">
|
||||
|
||||
<Grid.Resources>
|
||||
|
||||
<Storyboard x:Name="BottomCommandGridFadeInStoryboard">
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="BottomCommandGrid"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="1"
|
||||
Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
<Storyboard x:Name="BottomCommandGridFadeOutStoryboard" BeginTime="0:0:0.2">
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="BottomCommandGrid"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="0"
|
||||
Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
|
||||
</Grid.Resources>
|
||||
Opacity=".5"
|
||||
PointerEntered="BottomCommandGrid_PointerEntered"
|
||||
PointerExited="BottomCommandGrid_PointerExited">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
|
||||
<interactivity:Interaction.Behaviors>
|
||||
|
||||
<interactivity:EventTriggerBehavior EventName="PointerEntered">
|
||||
<interactivity:ControlStoryboardAction Storyboard="{StaticResource BottomCommandGridFadeInStoryboard}" />
|
||||
</interactivity:EventTriggerBehavior>
|
||||
<interactivity:EventTriggerBehavior EventName="PointerExited">
|
||||
<interactivity:ControlStoryboardAction Storyboard="{StaticResource BottomCommandGridFadeOutStoryboard}" />
|
||||
</interactivity:EventTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind SettingsService.IsImmersiveMode, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0.5" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind SettingsService.IsImmersiveMode, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
|
||||
</interactivity:Interaction.Behaviors>
|
||||
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<AppBarButton
|
||||
x:Name="SettingsButton"
|
||||
Click="SettingsButton_Click"
|
||||
LabelPosition="Collapsed">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</AppBarButton>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" Spacing="4">
|
||||
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<AppBarToggleButton
|
||||
<ToggleButton
|
||||
x:Name="ImmersiveModeButton"
|
||||
x:Uid="MainWindowImmersiveMode"
|
||||
IsChecked="{x:Bind SettingsService.IsImmersiveMode, Mode=TwoWay}"
|
||||
Style="{StaticResource GhostToggleButtonStyle}">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton
|
||||
x:Name="LyricsOnlyButton"
|
||||
x:Uid="MainWindowLyricsOnly"
|
||||
IsChecked="{x:Bind ViewModel.ShowLyricsOnly, Mode=TwoWay}"
|
||||
LabelPosition="Collapsed"
|
||||
Style="{StaticResource GhostToggleButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.LyricsExisted, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</ToggleButton>
|
||||
|
||||
<Button
|
||||
x:Name="SettingsButton"
|
||||
Click="SettingsButton_Click"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</Button>
|
||||
|
||||
<FontIcon FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</AppBarToggleButton>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
@@ -412,55 +391,7 @@
|
||||
x:Uid="MainPageWelcomeTeachingTip"
|
||||
Closed="WelcomeTeachingTip_Closed"
|
||||
IsOpen="False"
|
||||
Target="{x:Bind RootGrid}">
|
||||
<TeachingTip.Content>
|
||||
<TextBlock Margin="{StaticResource TeachingTipDescriptionMargin}" Text="1/5" />
|
||||
</TeachingTip.Content>
|
||||
</TeachingTip>
|
||||
|
||||
<TeachingTip
|
||||
x:Name="TopCommandTeachingTip"
|
||||
x:Uid="MainPageTitleBarTeachingTip"
|
||||
Closed="TopCommandTeachingTip_Closed"
|
||||
IsOpen="False"
|
||||
Target="{x:Bind TopPlaceholder}">
|
||||
<TeachingTip.Content>
|
||||
<TextBlock Margin="{StaticResource TeachingTipDescriptionMargin}" Text="2/5" />
|
||||
</TeachingTip.Content>
|
||||
</TeachingTip>
|
||||
|
||||
<TeachingTip
|
||||
x:Name="BottomCommandTeachingTip"
|
||||
x:Uid="MainPageBottomCommandTeachingTip"
|
||||
Closed="BottomCommandTeachingTip_Closed"
|
||||
IsOpen="False"
|
||||
Target="{x:Bind BottomCommandGrid}">
|
||||
<TeachingTip.Content>
|
||||
<TextBlock Margin="{StaticResource TeachingTipDescriptionMargin}" Text="3/5" />
|
||||
</TeachingTip.Content>
|
||||
</TeachingTip>
|
||||
|
||||
<TeachingTip
|
||||
x:Name="LyricsOnlyTeachingTip"
|
||||
x:Uid="MainPageLyricsOnlyTeachingTip"
|
||||
Closed="LyricsOnlyTeachingTip_Closed"
|
||||
IsOpen="False"
|
||||
Target="{x:Bind LyricsOnlyButton}">
|
||||
<TeachingTip.Content>
|
||||
<TextBlock Margin="{StaticResource TeachingTipDescriptionMargin}" Text="4/5" />
|
||||
</TeachingTip.Content>
|
||||
</TeachingTip>
|
||||
|
||||
<TeachingTip
|
||||
x:Name="InitDatabaseTeachingTip"
|
||||
x:Uid="MainPageInitDatabaseTeachingTip"
|
||||
Closed="InitDatabaseTeachingTip_Closed"
|
||||
IsOpen="False"
|
||||
Target="{x:Bind SettingsButton}">
|
||||
<TeachingTip.Content>
|
||||
<TextBlock Margin="{StaticResource TeachingTipDescriptionMargin}" Text="5/5" />
|
||||
</TeachingTip.Content>
|
||||
</TeachingTip>
|
||||
Target="{x:Bind SettingsButton}" />
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
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;
|
||||
using BetterLyrics.WinUI3.Rendering;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Brushes;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Media;
|
||||
using Windows.Media.Control;
|
||||
using Color = Windows.UI.Color;
|
||||
|
||||
@@ -40,16 +37,10 @@ namespace BetterLyrics.WinUI3.Views
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
public MainViewModel ViewModel => (MainViewModel)DataContext;
|
||||
public SettingsService SettingsService { get; set; }
|
||||
private SettingsService SettingsService { get; set; }
|
||||
|
||||
private List<LyricsLine> _lyricsLines = [];
|
||||
|
||||
private SoftwareBitmap? _coverSoftwareBitmap = null;
|
||||
private uint _coverImagePixelWidth = 0;
|
||||
private uint _coverImagePixelHeight = 0;
|
||||
|
||||
private float _coverBitmapRotateAngle = 0f;
|
||||
private float _coverScaleFactor = 1;
|
||||
private readonly CoverBackgroundRenderer _coverImageAsBackgroundRenderer = new();
|
||||
private readonly PureLyricsRenderer _pureLyricsRenderer = new();
|
||||
|
||||
private readonly float _coverRotateSpeed = 0.003f;
|
||||
|
||||
@@ -59,37 +50,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
private readonly float _lyricsGlowEffectMinBlurAmount = 0f;
|
||||
private readonly float _lyricsGlowEffectMaxBlurAmount = 6f;
|
||||
|
||||
private readonly DispatcherQueueTimer _queueTimer;
|
||||
|
||||
private TimeSpan _currentTime = TimeSpan.Zero;
|
||||
|
||||
private readonly float _defaultOpacity = 0.3f;
|
||||
private readonly float _highlightedOpacity = 1.0f;
|
||||
|
||||
private readonly float _defaultScale = 0.95f;
|
||||
private readonly float _highlightedScale = 1.0f;
|
||||
|
||||
private readonly int _lineEnteringDurationMs = 800;
|
||||
private readonly int _lineExitingDurationMs = 800;
|
||||
private readonly int _lineScrollDurationMs = 800;
|
||||
|
||||
private float _lastTotalYScroll = 0.0f;
|
||||
private float _totalYScroll = 0.0f;
|
||||
|
||||
private double _lyricsAreaWidth = 0.0f;
|
||||
private double _lyricsAreaHeight = 0.0f;
|
||||
|
||||
private readonly double _lyricsCanvasRightMargin = 36;
|
||||
private double _lyricsCanvasLeftMargin = 0;
|
||||
private double _lyricsCanvasMaxTextWidth = 0;
|
||||
|
||||
private int _startVisibleLineIndex = -1;
|
||||
private int _endVisibleLineIndex = -1;
|
||||
|
||||
private bool _forceToScroll = false;
|
||||
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
private GlobalSystemMediaTransportControlsSessionManager? _sessionManager = null;
|
||||
private GlobalSystemMediaTransportControlsSession? _currentSession = null;
|
||||
|
||||
@@ -101,8 +61,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
_queueTimer = _dispatcherQueue.CreateTimer();
|
||||
|
||||
_logger = Ioc.Default.GetService<ILogger<MainPage>>()!;
|
||||
SettingsService = Ioc.Default.GetService<SettingsService>()!;
|
||||
DataContext = Ioc.Default.GetService<MainViewModel>();
|
||||
@@ -118,13 +76,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ForceToScrollToCurrentPlayingLineAsync()
|
||||
{
|
||||
_forceToScroll = true;
|
||||
await Task.Delay(1);
|
||||
_forceToScroll = false;
|
||||
}
|
||||
|
||||
private async void SettingsService_PropertyChanged(
|
||||
object? sender,
|
||||
System.ComponentModel.PropertyChangedEventArgs e
|
||||
@@ -134,8 +85,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
case nameof(SettingsService.LyricsFontSize):
|
||||
case nameof(SettingsService.LyricsLineSpacingFactor):
|
||||
LayoutLyrics();
|
||||
await ForceToScrollToCurrentPlayingLineAsync();
|
||||
await _pureLyricsRenderer.ReLayoutAsync(LyricsCanvas);
|
||||
break;
|
||||
case nameof(SettingsService.IsRebuildingLyricsIndexDatabase):
|
||||
if (!SettingsService.IsRebuildingLyricsIndexDatabase)
|
||||
@@ -154,12 +104,24 @@ namespace BetterLyrics.WinUI3.Views
|
||||
SettingsService.CoverImageRadius / 100f * (CoverImageGrid.ActualHeight / 2)
|
||||
);
|
||||
break;
|
||||
case nameof(SettingsService.IsImmersiveMode):
|
||||
if (SettingsService.IsImmersiveMode)
|
||||
WeakReferenceMessenger.Default.Send(
|
||||
new ShowNotificatonMessage(
|
||||
new Notification(
|
||||
App.ResourceLoader!.GetString("MainPageEnterImmersiveModeHint"),
|
||||
isForeverDismissable: true,
|
||||
relatedSettingsKeyName: SettingsKeys.NeverShowEnterImmersiveModeMessage
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(
|
||||
private async void ViewModel_PropertyChanged(
|
||||
object? sender,
|
||||
System.ComponentModel.PropertyChangedEventArgs e
|
||||
)
|
||||
@@ -167,7 +129,17 @@ namespace BetterLyrics.WinUI3.Views
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(ViewModel.ShowLyricsOnly):
|
||||
RootGrid_SizeChanged(null, null);
|
||||
if (ViewModel.ShowLyricsOnly)
|
||||
{
|
||||
Grid.SetColumn(LyricsPlaceholderGrid, 0);
|
||||
Grid.SetColumnSpan(LyricsPlaceholderGrid, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
Grid.SetColumn(LyricsPlaceholderGrid, 2);
|
||||
Grid.SetColumnSpan(LyricsPlaceholderGrid, 1);
|
||||
}
|
||||
await _pureLyricsRenderer.ReLayoutAsync(LyricsCanvas);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -213,11 +185,11 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
_currentTime = TimeSpan.Zero;
|
||||
_pureLyricsRenderer.CurrentTime = TimeSpan.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
_currentTime = sender.GetTimelineProperties().Position;
|
||||
_pureLyricsRenderer.CurrentTime = sender.GetTimelineProperties().Position;
|
||||
// _logger.LogDebug(_currentTime);
|
||||
}
|
||||
|
||||
@@ -231,7 +203,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
PlaybackInfoChangedEventArgs? args
|
||||
)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
App.DispatcherQueue!.TryEnqueue(
|
||||
DispatcherQueuePriority.Normal,
|
||||
() =>
|
||||
{
|
||||
@@ -242,7 +214,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
|
||||
var playbackState = sender.GetPlaybackInfo().PlaybackStatus;
|
||||
_logger.LogDebug(playbackState.ToString());
|
||||
// _logger.LogDebug(playbackState.ToString());
|
||||
|
||||
switch (playbackState)
|
||||
{
|
||||
@@ -276,7 +248,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
SessionsChangedEventArgs? args
|
||||
)
|
||||
{
|
||||
_logger.LogDebug("SessionManager_SessionsChanged");
|
||||
// _logger.LogDebug("SessionManager_SessionsChanged");
|
||||
}
|
||||
|
||||
private void SessionManager_CurrentSessionChanged(
|
||||
@@ -284,7 +256,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
CurrentSessionChangedEventArgs? args
|
||||
)
|
||||
{
|
||||
_logger.LogDebug("SessionManager_CurrentSessionChanged");
|
||||
// _logger.LogDebug("SessionManager_CurrentSessionChanged");
|
||||
// Unregister events associated with the previous session
|
||||
if (_currentSession != null)
|
||||
{
|
||||
@@ -318,11 +290,11 @@ namespace BetterLyrics.WinUI3.Views
|
||||
MediaPropertiesChangedEventArgs? args
|
||||
)
|
||||
{
|
||||
_queueTimer.Debounce(
|
||||
App.DispatcherQueueTimer!.Debounce(
|
||||
() =>
|
||||
{
|
||||
_logger.LogDebug("CurrentSession_MediaPropertiesChanged");
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
// _logger.LogDebug("CurrentSession_MediaPropertiesChanged");
|
||||
App.DispatcherQueue!.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
async () =>
|
||||
{
|
||||
@@ -330,13 +302,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
null;
|
||||
|
||||
if (_currentSession != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mediaProps = await _currentSession.TryGetMediaPropertiesAsync();
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
mediaProps = await _currentSession.TryGetMediaPropertiesAsync();
|
||||
|
||||
ViewModel.IsAnyMusicSessionExisted = _currentSession != null;
|
||||
|
||||
@@ -344,15 +310,13 @@ namespace BetterLyrics.WinUI3.Views
|
||||
await Task.Delay(AnimationHelper.StoryboardDefaultDuration);
|
||||
|
||||
(
|
||||
_lyricsLines,
|
||||
_coverSoftwareBitmap,
|
||||
_coverImagePixelWidth,
|
||||
_coverImagePixelHeight
|
||||
_pureLyricsRenderer.LyricsLines,
|
||||
_coverImageAsBackgroundRenderer.SoftwareBitmap
|
||||
) = await ViewModel.SetSongInfoAsync(mediaProps);
|
||||
|
||||
// Force to show lyrics and scroll to current line even if the music is not playing
|
||||
LyricsCanvas.Paused = false;
|
||||
await ForceToScrollToCurrentPlayingLineAsync();
|
||||
await _pureLyricsRenderer.ForceToScrollToCurrentPlayingLineAsync();
|
||||
await Task.Delay(1);
|
||||
// Detect and recover the music state
|
||||
CurrentSession_PlaybackInfoChanged(_currentSession, null);
|
||||
@@ -360,7 +324,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
ViewModel.AboutToUpdateUI = false;
|
||||
|
||||
if (_lyricsLines.Count == 0)
|
||||
if (_pureLyricsRenderer.LyricsLines.Count == 0)
|
||||
{
|
||||
Grid.SetColumnSpan(SongInfoInnerGrid, 3);
|
||||
}
|
||||
@@ -375,31 +339,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
);
|
||||
}
|
||||
|
||||
private async void RootGrid_SizeChanged(object? sender, SizeChangedEventArgs? e)
|
||||
{
|
||||
//_queueTimer.Debounce(async () => {
|
||||
|
||||
_lyricsAreaHeight = LyricsGrid.ActualHeight;
|
||||
_lyricsAreaWidth = LyricsGrid.ActualWidth;
|
||||
|
||||
if (SongInfoColumnDefinition.ActualWidth == 0 || ViewModel.ShowLyricsOnly)
|
||||
{
|
||||
_lyricsCanvasLeftMargin = 36;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsCanvasLeftMargin = 36 + SongInfoColumnDefinition.ActualWidth + 36;
|
||||
}
|
||||
|
||||
_lyricsCanvasMaxTextWidth =
|
||||
_lyricsAreaWidth - _lyricsCanvasLeftMargin - _lyricsCanvasRightMargin;
|
||||
|
||||
LayoutLyrics();
|
||||
await ForceToScrollToCurrentPlayingLineAsync();
|
||||
|
||||
//}, TimeSpan.FromMilliseconds(50));
|
||||
}
|
||||
|
||||
// Comsumes GPU related resources
|
||||
private void LyricsCanvas_Draw(
|
||||
ICanvasAnimatedControl sender,
|
||||
@@ -413,16 +352,13 @@ namespace BetterLyrics.WinUI3.Views
|
||||
var b = _lyricsColor.B;
|
||||
|
||||
// Draw (dynamic) cover image as the very first layer
|
||||
if (SettingsService.IsCoverOverlayEnabled && _coverSoftwareBitmap != null)
|
||||
{
|
||||
DrawCoverImage(sender, ds);
|
||||
}
|
||||
_coverImageAsBackgroundRenderer.Draw(sender, ds);
|
||||
|
||||
// Lyrics only layer
|
||||
using var lyrics = new CanvasCommandList(sender);
|
||||
using (var lyricsDs = lyrics.CreateDrawingSession())
|
||||
{
|
||||
DrawLyrics(sender, lyricsDs, r, g, b);
|
||||
_pureLyricsRenderer.Draw(sender, lyricsDs, r, g, b);
|
||||
}
|
||||
|
||||
using var glowedLyrics = new CanvasCommandList(sender);
|
||||
@@ -526,158 +462,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
ds.DrawImage(maskedCombinedBlurredLyrics);
|
||||
}
|
||||
|
||||
private void DrawLyrics(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
byte r,
|
||||
byte g,
|
||||
byte b
|
||||
)
|
||||
{
|
||||
var (displayStartLineIndex, displayEndLineIndex) =
|
||||
GetVisibleLyricsLineIndexBoundaries();
|
||||
|
||||
for (
|
||||
int i = displayStartLineIndex;
|
||||
_lyricsLines.Count > 0
|
||||
&& i >= 0
|
||||
&& i < _lyricsLines.Count
|
||||
&& i <= displayEndLineIndex;
|
||||
i++
|
||||
)
|
||||
{
|
||||
var line = _lyricsLines[i];
|
||||
|
||||
if (line.TextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float progressPerChar = 1f / line.Text.Length;
|
||||
|
||||
var position = line.Position;
|
||||
|
||||
float centerX = position.X;
|
||||
float centerY = position.Y + (float)line.TextLayout.LayoutBounds.Height / 2;
|
||||
|
||||
switch ((LyricsAlignmentType)SettingsService.LyricsAlignmentType)
|
||||
{
|
||||
case LyricsAlignmentType.Left:
|
||||
line.TextLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
|
||||
break;
|
||||
case LyricsAlignmentType.Center:
|
||||
line.TextLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
|
||||
centerX += (float)_lyricsCanvasMaxTextWidth / 2;
|
||||
break;
|
||||
case LyricsAlignmentType.Right:
|
||||
line.TextLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
|
||||
centerX += (float)_lyricsCanvasMaxTextWidth;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
int startIndex = 0;
|
||||
|
||||
// Set brush
|
||||
for (int j = 0; j < line.TextLayout.LineCount; j++)
|
||||
{
|
||||
int count = line.TextLayout.LineMetrics[j].CharacterCount;
|
||||
var regions = line.TextLayout.GetCharacterRegions(startIndex, count);
|
||||
float subLinePlayingProgress = Math.Clamp(
|
||||
(line.PlayingProgress * line.Text.Length - startIndex) / count,
|
||||
0,
|
||||
1
|
||||
);
|
||||
|
||||
using var horizontalFillBrush = new CanvasLinearGradientBrush(
|
||||
control,
|
||||
[
|
||||
new()
|
||||
{
|
||||
Position = 0,
|
||||
Color = Color.FromArgb((byte)(255 * line.Opacity), r, g, b),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Position =
|
||||
subLinePlayingProgress * (1 + progressPerChar)
|
||||
- progressPerChar,
|
||||
Color = Color.FromArgb((byte)(255 * line.Opacity), r, g, b),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Position = subLinePlayingProgress * (1 + progressPerChar),
|
||||
Color = Color.FromArgb((byte)(255 * _defaultOpacity), r, g, b),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Position = 1.5f,
|
||||
Color = Color.FromArgb((byte)(255 * _defaultOpacity), r, g, b),
|
||||
},
|
||||
]
|
||||
)
|
||||
{
|
||||
StartPoint = new Vector2(
|
||||
(float)(regions[0].LayoutBounds.Left + position.X),
|
||||
0
|
||||
),
|
||||
EndPoint = new Vector2(
|
||||
(float)(regions[^1].LayoutBounds.Right + position.X),
|
||||
0
|
||||
),
|
||||
};
|
||||
|
||||
line.TextLayout.SetBrush(startIndex, count, horizontalFillBrush);
|
||||
startIndex += count;
|
||||
}
|
||||
|
||||
// Scale
|
||||
ds.Transform =
|
||||
Matrix3x2.CreateScale(line.Scale, new Vector2(centerX, centerY))
|
||||
* Matrix3x2.CreateTranslation(0, _totalYScroll);
|
||||
// _logger.LogDebug(_totalYScroll);
|
||||
|
||||
ds.DrawTextLayout(line.TextLayout, position, Colors.Transparent);
|
||||
|
||||
// Reset scale
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCoverImage(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
ds.Transform = Matrix3x2.CreateRotation(
|
||||
_coverBitmapRotateAngle,
|
||||
control.Size.ToVector2() * 0.5f
|
||||
);
|
||||
|
||||
using var coverOverlayEffect = new OpacityEffect
|
||||
{
|
||||
Opacity = SettingsService.CoverOverlayOpacity / 100f,
|
||||
Source = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = SettingsService.CoverOverlayBlurAmount,
|
||||
Source = new ScaleEffect
|
||||
{
|
||||
InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
|
||||
BorderMode = EffectBorderMode.Hard,
|
||||
Scale = new Vector2(_coverScaleFactor),
|
||||
Source = CanvasBitmap.CreateFromSoftwareBitmap(
|
||||
control,
|
||||
_coverSoftwareBitmap
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
ds.DrawImage(
|
||||
coverOverlayEffect,
|
||||
(float)control.Size.Width / 2 - _coverImagePixelWidth * _coverScaleFactor / 2,
|
||||
(float)control.Size.Height / 2 - _coverImagePixelHeight * _coverScaleFactor / 2
|
||||
);
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
private void DrawGradientOpacityMask(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasDrawingSession ds,
|
||||
@@ -708,12 +492,12 @@ namespace BetterLyrics.WinUI3.Views
|
||||
CanvasAnimatedUpdateEventArgs args
|
||||
)
|
||||
{
|
||||
_currentTime += args.Timing.ElapsedTime;
|
||||
_pureLyricsRenderer.CurrentTime += args.Timing.ElapsedTime;
|
||||
|
||||
if (SettingsService.IsDynamicCoverOverlay)
|
||||
{
|
||||
_coverBitmapRotateAngle += _coverRotateSpeed;
|
||||
_coverBitmapRotateAngle %= MathF.PI * 2;
|
||||
_coverImageAsBackgroundRenderer.RotateAngle += _coverRotateSpeed;
|
||||
_coverImageAsBackgroundRenderer.RotateAngle %= MathF.PI * 2;
|
||||
}
|
||||
if (SettingsService.IsLyricsDynamicGlowEffectEnabled)
|
||||
{
|
||||
@@ -721,32 +505,24 @@ namespace BetterLyrics.WinUI3.Views
|
||||
_lyricsGlowEffectAngle %= MathF.PI * 2;
|
||||
}
|
||||
|
||||
if (SettingsService.IsCoverOverlayEnabled && _coverSoftwareBitmap != null)
|
||||
{
|
||||
var diagonal = Math.Sqrt(
|
||||
Math.Pow(_lyricsAreaWidth, 2) + Math.Pow(_lyricsAreaHeight, 2)
|
||||
);
|
||||
_coverImageAsBackgroundRenderer.Calculate(sender);
|
||||
|
||||
_coverScaleFactor =
|
||||
(float)diagonal / Math.Min(_coverImagePixelWidth, _coverImagePixelHeight);
|
||||
}
|
||||
|
||||
if (_lyricsLines.LastOrDefault()?.TextLayout == null)
|
||||
if (_pureLyricsRenderer.LyricsLines.LastOrDefault()?.TextLayout == null)
|
||||
{
|
||||
LayoutLyrics();
|
||||
_pureLyricsRenderer.ReLayoutAsync(sender);
|
||||
}
|
||||
|
||||
int currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
||||
UpdateScaleAndOpacity(currentPlayingLineIndex);
|
||||
UpdatePosition(currentPlayingLineIndex);
|
||||
_pureLyricsRenderer.CalculateScaleAndOpacity(currentPlayingLineIndex);
|
||||
_pureLyricsRenderer.CalculatePosition(sender, currentPlayingLineIndex);
|
||||
}
|
||||
|
||||
private int GetCurrentPlayingLineIndex()
|
||||
{
|
||||
for (int i = 0; i < _lyricsLines.Count; i++)
|
||||
for (int i = 0; i < _pureLyricsRenderer.LyricsLines.Count; i++)
|
||||
{
|
||||
var line = _lyricsLines[i];
|
||||
if (line.EndPlayingTimestampMs < _currentTime.TotalMilliseconds)
|
||||
var line = _pureLyricsRenderer.LyricsLines[i];
|
||||
if (line.EndPlayingTimestampMs < _pureLyricsRenderer.CurrentTime.TotalMilliseconds)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -756,239 +532,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
return -1;
|
||||
}
|
||||
|
||||
private Tuple<int, int> GetVisibleLyricsLineIndexBoundaries()
|
||||
{
|
||||
// _logger.LogDebug($"{_startVisibleLineIndex} {_endVisibleLineIndex}");
|
||||
return new Tuple<int, int>(_startVisibleLineIndex, _endVisibleLineIndex);
|
||||
}
|
||||
|
||||
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
|
||||
{
|
||||
if (_lyricsLines.Count == 0)
|
||||
{
|
||||
return new Tuple<int, int>(-1, -1);
|
||||
}
|
||||
|
||||
return new Tuple<int, int>(0, _lyricsLines.Count - 1);
|
||||
}
|
||||
|
||||
private void UpdateScaleAndOpacity(int currentPlayingLineIndex)
|
||||
{
|
||||
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
|
||||
|
||||
for (int i = startLineIndex; _lyricsLines.Count > 0 && i <= endLineIndex; i++)
|
||||
{
|
||||
var line = _lyricsLines[i];
|
||||
|
||||
bool linePlaying = i == currentPlayingLineIndex;
|
||||
|
||||
var lineEnteringDurationMs = Math.Min(line.DurationMs, _lineEnteringDurationMs);
|
||||
var lineExitingDurationMs = _lineExitingDurationMs;
|
||||
if (i + 1 <= endLineIndex)
|
||||
{
|
||||
lineExitingDurationMs = Math.Min(
|
||||
_lyricsLines[i + 1].DurationMs,
|
||||
lineExitingDurationMs
|
||||
);
|
||||
}
|
||||
|
||||
float lineEnteringProgress = 0.0f;
|
||||
float lineExitingProgress = 0.0f;
|
||||
|
||||
bool lineEntering = false;
|
||||
bool lineExiting = false;
|
||||
|
||||
float scale = _defaultScale;
|
||||
float opacity = _defaultOpacity;
|
||||
|
||||
float playProgress = 0;
|
||||
|
||||
if (linePlaying)
|
||||
{
|
||||
line.PlayingState = LyricsPlayingState.Playing;
|
||||
|
||||
scale = _highlightedScale;
|
||||
opacity = _highlightedOpacity;
|
||||
|
||||
playProgress =
|
||||
((float)_currentTime.TotalMilliseconds - line.StartPlayingTimestampMs)
|
||||
/ line.DurationMs;
|
||||
|
||||
var durationFromStartMs =
|
||||
_currentTime.TotalMilliseconds - line.StartPlayingTimestampMs;
|
||||
lineEntering = durationFromStartMs <= lineEnteringDurationMs;
|
||||
if (lineEntering)
|
||||
{
|
||||
lineEnteringProgress = (float)durationFromStartMs / lineEnteringDurationMs;
|
||||
scale =
|
||||
_defaultScale
|
||||
+ (_highlightedScale - _defaultScale) * (float)lineEnteringProgress;
|
||||
opacity =
|
||||
_defaultOpacity
|
||||
+ (_highlightedOpacity - _defaultOpacity) * (float)lineEnteringProgress;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < currentPlayingLineIndex)
|
||||
{
|
||||
line.PlayingState = LyricsPlayingState.Played;
|
||||
playProgress = 1;
|
||||
|
||||
var durationToEndMs =
|
||||
_currentTime.TotalMilliseconds - line.EndPlayingTimestampMs;
|
||||
lineExiting = durationToEndMs <= lineExitingDurationMs;
|
||||
if (lineExiting)
|
||||
{
|
||||
lineExitingProgress = (float)durationToEndMs / lineExitingDurationMs;
|
||||
scale =
|
||||
_highlightedScale
|
||||
- (_highlightedScale - _defaultScale) * (float)lineExitingProgress;
|
||||
opacity =
|
||||
_highlightedOpacity
|
||||
- (_highlightedOpacity - _defaultOpacity)
|
||||
* (float)lineExitingProgress;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
line.PlayingState = LyricsPlayingState.NotPlayed;
|
||||
}
|
||||
}
|
||||
|
||||
line.EnteringProgress = lineEnteringProgress;
|
||||
line.ExitingProgress = lineExitingProgress;
|
||||
|
||||
line.Scale = scale;
|
||||
line.Opacity = opacity;
|
||||
|
||||
line.PlayingProgress = playProgress;
|
||||
}
|
||||
}
|
||||
|
||||
private void LayoutLyrics()
|
||||
{
|
||||
using CanvasTextFormat textFormat = new()
|
||||
{
|
||||
FontSize = SettingsService.LyricsFontSize,
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Top,
|
||||
FontWeight = FontWeights.Bold,
|
||||
//FontFamily = "Segoe UI Mono",
|
||||
};
|
||||
float y = (float)_lyricsAreaHeight / 2;
|
||||
|
||||
// Init Positions
|
||||
for (int i = 0; i < _lyricsLines.Count; i++)
|
||||
{
|
||||
var line = _lyricsLines[i];
|
||||
|
||||
// Calculate layout bounds
|
||||
line.TextLayout = new CanvasTextLayout(
|
||||
LyricsCanvas.Device,
|
||||
line.Text,
|
||||
textFormat,
|
||||
(float)_lyricsCanvasMaxTextWidth,
|
||||
(float)_lyricsAreaHeight
|
||||
);
|
||||
line.Position = new Vector2((float)_lyricsCanvasLeftMargin, y);
|
||||
|
||||
y +=
|
||||
(float)line.TextLayout.LayoutBounds.Height
|
||||
/ line.TextLayout.LineCount
|
||||
* (line.TextLayout.LineCount + SettingsService.LyricsLineSpacingFactor);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePosition(int currentPlayingLineIndex)
|
||||
{
|
||||
if (currentPlayingLineIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
|
||||
|
||||
if (startLineIndex < 0 || endLineIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set _scrollOffsetY
|
||||
LyricsLine? currentPlayingLine = _lyricsLines?[currentPlayingLineIndex];
|
||||
|
||||
if (currentPlayingLine == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPlayingLine.TextLayout == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lineScrollingProgress =
|
||||
(_currentTime.TotalMilliseconds - currentPlayingLine.StartPlayingTimestampMs)
|
||||
/ Math.Min(_lineScrollDurationMs, currentPlayingLine.DurationMs);
|
||||
|
||||
var targetYScrollOffset = (float)(
|
||||
-currentPlayingLine.Position.Y
|
||||
+ _lyricsLines![0].Position.Y
|
||||
- currentPlayingLine.TextLayout.LayoutBounds.Height / 2
|
||||
- _lastTotalYScroll
|
||||
);
|
||||
|
||||
var yScrollOffset =
|
||||
targetYScrollOffset
|
||||
* EasingHelper.SmootherStep((float)Math.Min(1, lineScrollingProgress));
|
||||
|
||||
bool isScrollingNow = lineScrollingProgress <= 1;
|
||||
|
||||
if (isScrollingNow)
|
||||
{
|
||||
_totalYScroll = _lastTotalYScroll + yScrollOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_forceToScroll && Math.Abs(targetYScrollOffset) >= 1)
|
||||
{
|
||||
_totalYScroll = _lastTotalYScroll + targetYScrollOffset;
|
||||
}
|
||||
_lastTotalYScroll = _totalYScroll;
|
||||
}
|
||||
|
||||
_startVisibleLineIndex = _endVisibleLineIndex = -1;
|
||||
|
||||
// Update Positions
|
||||
for (int i = startLineIndex; i >= 0 && i <= endLineIndex; i++)
|
||||
{
|
||||
var line = _lyricsLines[i];
|
||||
|
||||
if (_totalYScroll + line.Position.Y + line.TextLayout.LayoutBounds.Height >= 0)
|
||||
{
|
||||
if (_startVisibleLineIndex == -1)
|
||||
{
|
||||
_startVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
if (
|
||||
_totalYScroll + line.Position.Y + line.TextLayout.LayoutBounds.Height
|
||||
>= _lyricsAreaHeight
|
||||
)
|
||||
{
|
||||
if (_endVisibleLineIndex == -1)
|
||||
{
|
||||
_endVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_startVisibleLineIndex != -1 && _endVisibleLineIndex == -1)
|
||||
{
|
||||
_endVisibleLineIndex = endLineIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private void LyricsCanvas_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InitMediaManager();
|
||||
@@ -998,7 +541,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
if (App.Current.SettingsWindow is null)
|
||||
{
|
||||
var settingsWindow = new MainWindow();
|
||||
var settingsWindow = new BaseWindow();
|
||||
settingsWindow.Navigate(typeof(SettingsPage));
|
||||
App.Current.SettingsWindow = settingsWindow;
|
||||
}
|
||||
@@ -1015,38 +558,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
|
||||
private void WelcomeTeachingTip_Closed(TeachingTip sender, TeachingTipClosedEventArgs args)
|
||||
{
|
||||
TopCommandTeachingTip.IsOpen = true;
|
||||
}
|
||||
|
||||
private void TopCommandTeachingTip_Closed(
|
||||
TeachingTip sender,
|
||||
TeachingTipClosedEventArgs args
|
||||
)
|
||||
{
|
||||
BottomCommandTeachingTip.IsOpen = true;
|
||||
}
|
||||
|
||||
private void BottomCommandTeachingTip_Closed(
|
||||
TeachingTip sender,
|
||||
TeachingTipClosedEventArgs args
|
||||
)
|
||||
{
|
||||
LyricsOnlyTeachingTip.IsOpen = true;
|
||||
}
|
||||
|
||||
private void LyricsOnlyTeachingTip_Closed(
|
||||
TeachingTip sender,
|
||||
TeachingTipClosedEventArgs args
|
||||
)
|
||||
{
|
||||
InitDatabaseTeachingTip.IsOpen = true;
|
||||
}
|
||||
|
||||
private void InitDatabaseTeachingTip_Closed(
|
||||
TeachingTip sender,
|
||||
TeachingTipClosedEventArgs args
|
||||
)
|
||||
{
|
||||
SettingsService.IsFirstRun = false;
|
||||
}
|
||||
@@ -1058,5 +569,36 @@ namespace BetterLyrics.WinUI3.Views
|
||||
CoverArea.ActualHeight
|
||||
);
|
||||
}
|
||||
|
||||
private void BottomCommandGrid_PointerEntered(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e
|
||||
)
|
||||
{
|
||||
if (SettingsService.IsImmersiveMode && BottomCommandGrid.Opacity == 0)
|
||||
BottomCommandGrid.Opacity = .5;
|
||||
}
|
||||
|
||||
private void BottomCommandGrid_PointerExited(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e
|
||||
)
|
||||
{
|
||||
if (SettingsService.IsImmersiveMode && BottomCommandGrid.Opacity == .5)
|
||||
BottomCommandGrid.Opacity = 0;
|
||||
}
|
||||
|
||||
private async void LyricsPlaceholderGrid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
_pureLyricsRenderer.LimitedLineWidth = e.NewSize.Width;
|
||||
await _pureLyricsRenderer.ReLayoutAsync(LyricsCanvas);
|
||||
}
|
||||
|
||||
private async void LyricsCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
_pureLyricsRenderer.CanvasWidth = e.NewSize.Width;
|
||||
_pureLyricsRenderer.CanvasHeight = e.NewSize.Height;
|
||||
await _pureLyricsRenderer.ReLayoutAsync(LyricsCanvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
<Grid Margin="36,72,36,72">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<!-- Music lib -->
|
||||
|
||||
<TextBlock x:Uid="SettingsPageLyricsLib" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsExpander
|
||||
@@ -99,6 +101,8 @@
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- App appearance -->
|
||||
|
||||
<TextBlock x:Uid="SettingsPageAppAppearance" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageTheme" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
@@ -121,6 +125,13 @@
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageTitleBarType" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox x:Name="TitleBarTypeComboBox" SelectedIndex="{x:Bind ViewModel.SettingsService.TitleBarType, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="SettingsPageCompactTitleBar" />
|
||||
<ComboBoxItem x:Uid="SettingsPageExtendedTitleBar" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageLanguage"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
@@ -138,6 +149,8 @@
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<!-- Album art overlay -->
|
||||
|
||||
<TextBlock x:Uid="SettingsPageAlbumOverlay" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsExpander
|
||||
@@ -274,6 +287,9 @@
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding}" />
|
||||
</Border.Background>
|
||||
<Border.BackgroundTransition>
|
||||
<BrushTransition />
|
||||
</Border.BackgroundTransition>
|
||||
</Border>
|
||||
<TextBlock
|
||||
Margin="4,0,4,4"
|
||||
|
||||
64
README.md
@@ -17,7 +17,7 @@ Your smooth dynamic local lyrics display built with WinUI 3
|
||||
- Dynamic blur album art as background
|
||||
- Smooth lyrics fade in/out, zoom in/out effects
|
||||
- Smooth user interface change from song to song
|
||||
- Gradient Karaoke effect on every single character
|
||||
- **Gradient** Karaoke effect on every single character
|
||||
|
||||
Coding in progress...
|
||||
|
||||
@@ -31,7 +31,7 @@ We provide more than one setting item to better align with your preference
|
||||
|
||||
- Album art as background (dynamic, blur amount, opacity)
|
||||
|
||||
- Lyrics (alignment, font size, line spacing, opacity, blur amount, dynamic glow effect)
|
||||
- Lyrics (alignment, font size, font color **(picked from album art accent color)** line spacing, opacity, blur amount, dynamic **glow** effect)
|
||||
|
||||
- Language (English, Simplified Chinese, Traditional Chinese)
|
||||
|
||||
@@ -49,22 +49,26 @@ Or watch our introduction video「BetterLyrics 阶段性开发成果展示」(up
|
||||
|
||||
### Split view
|
||||
|
||||

|
||||
Non-immersive mode
|
||||
|
||||

|
||||
|
||||
Immersive mode
|
||||

|
||||
|
||||
### Lyrics only
|
||||
|
||||

|
||||
|
||||
### Fullscreen
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### Settings
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Download it now
|
||||
|
||||
@@ -74,13 +78,33 @@ Or watch our introduction video「BetterLyrics 阶段性开发成果展示」(up
|
||||
|
||||
> **Easiest** way to get it. **Unlimited** free trail or purchase (there is **no difference** between free and paid version, if you like you can purchase to support me)
|
||||
|
||||
Or alternatively get it from [![]()](https://shorturl.at/jXbd7)
|
||||
Or alternatively get it from Google Drive (see [release](https://github.com/jayfunc/BetterLyrics/releases/latest) page for the link)
|
||||
|
||||
<a href="https://drive.google.com/file/d/1Hh8ijbODIksPmmRYujys7fXngw93Of7I/view?usp=drive_link">
|
||||
<img src="https://pngimg.com/uploads/google_drive/google_drive_PNG9.png" width="100"/>
|
||||
</a>
|
||||
> Please note you are downloading ".zip" file, for guide on how to install it, please kindly follow [this doc](How2Install/How2Install.md).
|
||||
|
||||
> .zip file, please follow [this doc](How2Install/How2Install.md) to properly install it
|
||||
## Setup your app
|
||||
|
||||
This project relies on listening messages from [SMTC](https://learn.microsoft.com/en-ca/windows/uwp/audio-video-camera/integrate-with-systemmediatransportcontrols).
|
||||
So technically, as long as you are using the music apps (like
|
||||
|
||||
- Spotify
|
||||
- Groove Music
|
||||
- Apple Music
|
||||
- Windows Media Player
|
||||
- VLC Media Player
|
||||
- QQ 音乐
|
||||
- 网易云音乐
|
||||
- 酷狗音乐
|
||||
- 酷我音乐
|
||||
|
||||
) which support SMTC, then possibly (I didn't test all of themif you find one fail to listen to, you can open an issue) all you need to do is just load your local music/lyrics lib and you are good to go.
|
||||
|
||||
## Future work
|
||||
|
||||
- Watching file changes
|
||||
When you downloading lyrics (using some other tools or your own scripts) while listening to new musics (non-existed on your local disks), this app can automatically load those new files.
|
||||
|
||||
> Please note: we are not planning support directly load lyrics files via some music software APIs due to copyright issues.
|
||||
|
||||
## Many thanks to
|
||||
|
||||
@@ -102,21 +126,21 @@ Or alternatively get it from [![]()](https://shorturl.at/jXbd7)
|
||||
## Third-party libraries that this project uses
|
||||
|
||||
```
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="DevWinUI" Version="8.2.0" />
|
||||
<PackageReference Include="DevWinUI" Version="8.3.0" />
|
||||
<PackageReference Include="DevWinUI.Controls" Version="8.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.5" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
BIN
Screenshots/Snipaste_2025-06-07_17-32-02.png
Normal file
|
After Width: | Height: | Size: 372 KiB |
BIN
Screenshots/Snipaste_2025-06-07_17-32-17.png
Normal file
|
After Width: | Height: | Size: 394 KiB |
BIN
Screenshots/Snipaste_2025-06-07_17-32-23.png
Normal file
|
After Width: | Height: | Size: 403 KiB |
BIN
Screenshots/Snipaste_2025-06-07_17-36-26.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |