Compare commits

...

82 Commits

Author SHA1 Message Date
Zhe Fang
1d489c68e9 chores: bump to v1.1.194.0 2025-12-15 15:45:25 -05:00
Zhe Fang
90e7fa42d0 chores 2025-12-15 15:36:50 -05:00
Zhe Fang
29a6879e45 chores: remove csharp-kana 2025-12-15 14:58:51 -05:00
Zhe Fang
58499a2d09 chores: update i18n 2025-12-15 14:55:37 -05:00
Zhe Fang
580255699b chores: change romaji method 2025-12-15 14:42:11 -05:00
Zhe Fang
9cac7818f1 fix: system tray left click 2025-12-15 10:45:24 -05:00
Zhe Fang
118668a457 fix: timeline 2025-12-15 07:36:26 -05:00
Zhe Fang
37621dbf2a chores: adjust margin for NowPlayingBar 2025-12-15 07:29:26 -05:00
Zhe Fang
aa7d56f1cb chores: bump to v1.1.190.0 2025-12-14 11:56:24 -05:00
Zhe Fang
8dbe76e790 chores: change bg color for top title bar and now playing bar 2025-12-14 11:45:09 -05:00
Zhe Fang
de6410492e fix: Canvas_CreateResources 2025-12-14 11:20:04 -05:00
Zhe Fang
26df7c7f67 chores: bump to 1.1.188.0 2025-12-14 09:48:01 -05:00
Zhe Fang
3c411374bd chores: undo 2025-12-14 09:40:13 -05:00
Zhe Fang
99f0b9443b fix: spectrum count is not updating when chaning amount 2025-12-14 09:26:19 -05:00
Zhe Fang
a3bc148816 fix: prop clone 2025-12-14 09:18:49 -05:00
Zhe Fang
cea757702b feat: add settings item for always hiding unlock button 2025-12-14 09:17:27 -05:00
Zhe Fang
8938a5c798 feat: add settings item for stop music when closing music gallery window 2025-12-14 08:18:21 -05:00
Zhe Fang
46f4589b64 chores: dispose cover renderer when closed 2025-12-14 08:03:58 -05:00
Zhe Fang
adb02658f4 feat: add cover background 2025-12-14 07:58:41 -05:00
Zhe Fang
3d7e6061e9 fix: lyrics window settings config panel wont auto hide when open again 2025-12-14 06:52:08 -05:00
Zhe Fang
a51220c7b9 fix: playing line top offset out of bounds 2025-12-14 05:51:49 -05:00
Zhe Fang
22b813e687 chores: bump to v1.1.186.0 2025-12-13 17:02:55 -05:00
Zhe Fang
fda94d5020 fix: settings save issue 2025-12-13 16:54:08 -05:00
Zhe Fang
205cbe8fb6 fix: settings storage issue 2025-12-13 16:19:05 -05:00
Zhe Fang
816f7064db fix: fan style lyrics animation 2025-12-13 15:45:41 -05:00
Zhe Fang
132c5267b0 chores: bump to v1.1.184.0 2025-12-13 12:20:20 -05:00
Zhe Fang
4e866818df fix: font family issue 2025-12-13 12:11:50 -05:00
Zhe Fang
9b7b56a0ee Revise acknowledgments in README.CN.md
Updated the acknowledgments section to reflect ongoing support and gratitude towards contributors and users.
2025-12-13 09:46:47 -05:00
Zhe Fang
66f2da0e4c Revise acknowledgments and donation list in README
Updated the acknowledgments section to reflect user support and removed the manual donation list.
2025-12-13 09:46:04 -05:00
Zhe Fang
1735c6a7e6 Add sponsors list and gratitude section to SPONSORS.md 2025-12-13 09:44:10 -05:00
Zhe Fang
8c06c98068 Update README.CN.md to acknowledge supporters
Added a section to thank supporters and users.
2025-12-13 09:36:00 -05:00
Zhe Fang
e2ac4c166c Add acknowledgments for supporters in README
Added a section to acknowledge and thank supporters.
2025-12-13 09:34:22 -05:00
Zhe Fang
728397cafa chores: code cleanup 2025-12-13 08:39:48 -05:00
Zhe Fang
059787a28f chores: bump to v1.1.183.0 2025-12-13 08:16:23 -05:00
Zhe Fang
4c4231b48c fix: white line in fullscreen mode 2025-12-13 07:58:28 -05:00
Zhe Fang
2412927b29 Add Zread badge to README.CN.md 2025-12-12 16:53:45 -05:00
Zhe Fang
f3bdbba83e Add Zread badge to README 2025-12-12 16:53:30 -05:00
Zhe Fang
4c811b12ca Create initial wiki structure for BetterLyrics
Added comprehensive documentation for BetterLyrics, including sections on installation, configuration, architecture, user interface, media integration, and development.
2025-12-12 16:20:42 -05:00
Zhe Fang
933103c57f Update badge links in README.CN.md 2025-12-12 15:55:15 -05:00
Zhe Fang
718e7bdad3 Fix badge formatting in README.md 2025-12-12 15:52:48 -05:00
Zhe Fang
42284b4f45 add: CoverBackgroundRenderer 2025-12-11 15:20:51 -05:00
Zhe Fang
7da8af7c2a fix: lyrics window manager tab not showing 2025-12-11 08:24:51 -05:00
Zhe Fang
a4fc457065 chores: bump to v1.1.181.0 2025-12-11 07:44:20 -05:00
Zhe Fang
d3c2ee592c fix: selectorbar section disappear 2025-12-11 07:19:05 -05:00
Zhe Fang
2eef88523c Modify taskbar.json for layout and style changes
Updated taskbar settings including bounds, opacity, and effects.
2025-12-10 16:17:25 -05:00
Zhe Fang
220b1063ac fix: taskbar mode is not working in release mode 2025-12-10 16:15:58 -05:00
Zhe Fang
28bcd8ddfc chores: remove taskbar mode entry 2025-12-10 14:09:16 -05:00
Zhe Fang
5750bd3ad7 add: more info in thanks control 2025-12-10 11:54:03 -05:00
Zhe Fang
1667c701b0 feat: taskbar mode 2025-12-10 11:09:53 -05:00
Zhe Fang
160398f7ab fix: add null check for Window.AppWindow 2025-12-10 10:51:45 -05:00
Zhe Fang
c25ddf770f fix: when open app with window locked, then unlock the window, title bar and bottom bar will not be interactive 2025-12-10 10:42:06 -05:00
Zhe Fang
44fa3312b2 fix: docked mode ux exp 2025-12-10 07:09:08 -05:00
Zhe Fang
81f3d1f6bf fix: cannot exit app when docked mode is on 2025-12-10 06:54:21 -05:00
Zhe Fang
2f627d7531 feat: add credit list in settings 2025-12-09 19:09:39 -05:00
Zhe Fang
caed76e9b9 fix: chinese to tc or sc 2025-12-09 13:16:00 -05:00
Zhe Fang
a74dc705c9 feat: search for file name in music gallery 2025-12-09 13:02:42 -05:00
Zhe Fang
21eb8fcf9c fix: add apply traditional or simplified chinese condition 2025-12-09 12:22:37 -05:00
Zhe Fang
4662d3d54d fix: auto show and hide lyrics window issue 2025-12-09 11:03:32 -05:00
Zhe Fang
b10f108f93 chores: hide lock button when in fullscreen mode 2025-12-09 10:33:03 -05:00
Zhe Fang
2defe620c7 fix: NowPlayingWIndow titlebar and lock/unlock button 2025-12-09 10:22:34 -05:00
Zhe Fang
315c10c83c fix: translation and roman 2025-12-09 08:23:11 -05:00
Zhe Fang
981bc3f933 fix: fullscreen maximize button error 2025-12-08 23:02:46 -05:00
Zhe Fang
88460899bd fix: listen on lyrics color change 2025-12-08 22:05:20 -05:00
Zhe Fang
8341642658 feat: lyrics opacity settings 2025-12-08 21:40:50 -05:00
Zhe Fang
26f4ff3a58 chores: set host lyrics matching threshold to 40% 2025-12-08 20:30:05 -05:00
Zhe Fang
b1b763c6fe fix: ui color is not updated for the first time when in docked mode or desktop mode 2025-12-08 20:27:37 -05:00
Zhe Fang
28323c39f6 chores: code cleanup 2025-12-08 19:57:49 -05:00
Zhe Fang
b099965715 fix: music gallery init 2025-12-08 19:57:26 -05:00
Zhe Fang
8f9fdc18bb fix: album art sizing issue 2025-12-08 19:37:28 -05:00
Zhe Fang
5a549bd5f5 fix: auto asearch is not using cache for some cases 2025-12-08 18:48:29 -05:00
Zhe Fang
48e94f275b fix: translation 2025-12-08 18:08:16 -05:00
Zhe Fang
691071d725 chores: code cleanup 2025-12-08 16:37:01 -05:00
Zhe Fang
591c1a6d00 fix: lock button status 2025-12-08 15:22:13 -05:00
Zhe Fang
4e6a2df2cb fix 2025-12-08 15:07:12 -05:00
Zhe Fang
8b196e45a4 fix 2025-12-07 22:41:37 -05:00
Zhe Fang
0ddeaef126 fix: auto play only be valid when first opening gallery window 2025-12-07 17:26:41 -05:00
Zhe Fang
e3747c113a feat: multi window 2025-12-07 17:06:50 -05:00
Zhe Fang
1b1449ce3b fix: add scroll bar for playback sources settings 2025-12-05 11:59:55 -05:00
Zhe Fang
ea246a96be fix: album art only paddings 2025-12-05 11:43:39 -05:00
Zhe Fang
28722d325a feat: multi lyrics window 2025-12-04 22:07:02 -05:00
Zhe Fang
7238713ff5 chores: bump to 1.1.167.0 2025-12-04 12:53:45 -05:00
Zhe Fang
66c42f81f2 fix: song timeline offset, lyrics scroll overlay offset 2025-12-04 12:34:46 -05:00
131 changed files with 6083 additions and 3074 deletions

291
.devin/wiki.json Normal file
View File

@@ -0,0 +1,291 @@
{
"repo_notes": [
{
"content": "Always use the latest files in the repo to generate the wiki"
}
],
"pages": [
{
"title": "Overview",
"purpose": "Introduce BetterLyrics, its purpose as a WinUI3 lyrics display application, key features, and supported music players",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Getting Started",
"purpose": "Guide users through installation, initial setup, and basic usage of BetterLyrics",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Installation and Deployment",
"purpose": "Explain how to install BetterLyrics from Microsoft Store or build from source, system requirements, and supported Windows versions",
"parent": "Getting Started",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Initial Configuration",
"purpose": "Walk through first-time setup including media player configuration, folder selection, and basic settings",
"parent": "Getting Started",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Architecture",
"purpose": "Provide technical overview of BetterLyrics' internal architecture, design patterns, and component organization",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Application Entry Point and Dependency Injection",
"purpose": "Document the App.xaml.cs entry point, service registration, and dependency injection container configuration",
"parent": "Architecture",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Service Layer Architecture",
"purpose": "Explain the service-oriented architecture, service interfaces, and their implementations including MediaSessionsService, LyricsSearchService, and SettingsService",
"parent": "Architecture",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Data Models",
"purpose": "Document core data models including LyricsLine, LyricsData, SongInfo, and configuration models",
"parent": "Architecture",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Multi-Window System",
"purpose": "Explain how BetterLyrics manages multiple simultaneous lyrics windows, window lifecycle, and state management",
"parent": "Architecture",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "User Interface",
"purpose": "Document the UI components, windows, and user interaction patterns in BetterLyrics",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Now Playing Window",
"purpose": "Detail the main lyrics display window, its components, and integration with the rendering system",
"parent": "User Interface",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Display Modes and Window Configurations",
"purpose": "Explain different display modes (Standard, Desktop, Docked, Fullscreen, Narrow, Taskbar) and how to configure them",
"parent": "User Interface",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Settings and Customization",
"purpose": "Document the settings interface, configuration options, and how to customize lyrics appearance and behavior",
"parent": "User Interface",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Music Gallery",
"purpose": "Explain the local music library management feature, including playback, playlist management, and integration with media controls",
"parent": "User Interface",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "System Tray and Global Controls",
"purpose": "Document the system tray integration, global hotkeys, and application-wide controls",
"parent": "User Interface",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Lyrics System",
"purpose": "Comprehensive documentation of the lyrics acquisition, processing, and display pipeline",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Lyrics Search and Providers",
"purpose": "Document the lyrics search system, supported providers (QQ Music, Netease, Kugou, LrcLib, Apple Music, local files), and search strategies",
"parent": "Lyrics System",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Parsing and Translation",
"purpose": "Explain how lyrics are parsed from different formats (LRC, QRC, TTML), translation system using LibreTranslate, and metadata matching",
"parent": "Lyrics System",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Rendering Pipeline",
"purpose": "Document the Win2D-based rendering system, LyricsCanvas, PlayingLineRenderer, UnplayingLineRenderer, and LyricsLayoutManager",
"parent": "Lyrics System",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Visual Effects and Animation",
"purpose": "Explain character-level effects (glow, float, scale), background effects (fluid, snow, fog, spectrum), and the animation transition system",
"parent": "Lyrics System",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Media Integration",
"purpose": "Document how BetterLyrics integrates with music players and manages media sessions",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Windows Media Transport Controls Integration",
"purpose": "Explain how BetterLyrics uses Windows SMTC to work universally with media players",
"parent": "Media Integration",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Supported Players and Special Configurations",
"purpose": "List supported media players, document special configurations (Apple Music token, LX Music SSE), and player-specific handling",
"parent": "Media Integration",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Album Art and Theme Colors",
"purpose": "Document album art retrieval from multiple sources, color palette generation (MedianCut, OctTree), and adaptive theming",
"parent": "Media Integration",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Internationalization",
"purpose": "Explain the localization system, supported languages, and how resources are managed",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Development",
"purpose": "Technical documentation for developers contributing to or extending BetterLyrics",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Build Configuration and Deployment",
"purpose": "Document the build process, publish profiles for different architectures, CI/CD pipeline, and packaging for Microsoft Store",
"parent": "Development",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "Helper Utilities and Extensions",
"purpose": "Document utility classes including TaskbarHook, WindowHook, ColorHelper, ImageHelper, and various extension methods",
"parent": "Development",
"page_notes": [
{
"content": ""
}
]
},
{
"title": "External Dependencies and Libraries",
"purpose": "List and explain third-party dependencies including Win2D, NAudio, ATL.NET, FlaUI, and their usage in the application",
"parent": "Development",
"page_notes": [
{
"content": ""
}
]
}
]
}

View File

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

View File

@@ -50,6 +50,7 @@
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
<converter:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
<converter:TransliterationSearchProviderToDisplayNameConverter x:Key="TransliterationSearchProviderToDisplayNameConverter" />
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
<converter:MillisecondsToFormattedTimeConverter x:Key="MillisecondsToFormattedTimeConverter" />
@@ -95,8 +96,7 @@
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="16,9,16,11" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="14,6,14,9" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="GhostButtonStyle" TargetType="Button">
@@ -110,10 +110,10 @@
x:Key="TitleBarToggleButtonStyle"
BasedOn="{StaticResource ToggleButtonRevealStyle}"
TargetType="ToggleButton">
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="16,9,16,11" />
<Setter Property="Padding" Value="14,6,14,9" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">

View File

@@ -6,12 +6,12 @@ using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.DiscordService;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.ResourceService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using BetterLyrics.WinUI3.Services.TranslationService;
using BetterLyrics.WinUI3.Services.TransliterationService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
@@ -21,6 +21,7 @@ using Microsoft.UI.Xaml;
using Microsoft.Windows.ApplicationModel.Resources;
using Serilog;
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -68,8 +69,26 @@ namespace BetterLyrics.WinUI3
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
WindowHook.OpenOrShowWindow<NowPlayingWindow>();
if (Ioc.Default.GetRequiredService<ISettingsService>().AppSettings.MusicGallerySettings.AutoOpen)
var settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
WindowHook.OpenOrShowWindow<SystemTrayWindow>();
if (settingsService.AppSettings.GeneralSettings.AutoStartLyricsWindow)
{
var defaultStatus = settingsService.AppSettings.WindowBoundsRecords.Where(x => x.IsDefault);
if (defaultStatus != null)
{
foreach (var item in defaultStatus)
{
WindowHook.OpenOrShowWindow<NowPlayingWindow>(item);
if (!settingsService.AppSettings.GeneralSettings.MultiNowPlayingWindowMode)
{
break;
}
}
}
}
if (settingsService.AppSettings.MusicGallerySettings.AutoOpen)
{
WindowHook.OpenOrShowWindow<MusicGalleryWindow>();
}
@@ -91,13 +110,13 @@ namespace BetterLyrics.WinUI3
loggingBuilder.AddSerilog();
})
// Services
.AddSingleton<ILiveStatesService, LiveStatesService>()
.AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>()
.AddSingleton<ITranslateService, TranslateService>()
.AddSingleton<ITranslationService, TranslationService>()
.AddSingleton<ITransliterationService, TransliterationService>()
.AddSingleton<ILastFMService, LastFMService>()
.AddSingleton<IResourceService, ResourceService>()
.AddSingleton<IDiscordService, DiscordService>()
@@ -109,13 +128,17 @@ namespace BetterLyrics.WinUI3
.AddSingleton<LyricsWindowSettingsControlViewModel>()
.AddSingleton<LyricsWindowSwitchControlViewModel>()
.AddSingleton<LyricsWindowSwitchWindowViewModel>()
.AddSingleton<NowPlayingWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>()
.AddSingleton<SystemTrayViewModel>()
.AddSingleton<SettingsPageViewModel>()
.AddSingleton<NowPlayingPageViewModel>()
.AddSingleton<MusicGalleryViewModel>()
.AddSingleton<MusicGalleryPageViewModel>()
.AddSingleton<AboutControlViewModel>()
.AddSingleton<MusicGalleryWindowViewModel>()
.AddTransient<NowPlayingWindowViewModel>()
.AddTransient<NowPlayingPageViewModel>()
.AddTransient<NowPlayingBarViewModel>()
.BuildServiceProvider()
);
}

View File

@@ -34,6 +34,7 @@
<None Remove="Controls\LyricsWindowSettingsControl.xaml" />
<None Remove="Controls\LyricsWindowSwitchControl.xaml" />
<None Remove="Controls\MediaSettingsControl.xaml" />
<None Remove="Controls\NowPlayingBar.xaml" />
<None Remove="Controls\PlaybackSettingsControl.xaml" />
<None Remove="Controls\PropertyRow.xaml" />
<None Remove="Controls\ShortcutTextBox.xaml" />
@@ -44,6 +45,7 @@
<None Remove="Views\MusicGalleryPage.xaml" />
<None Remove="Views\MusicGalleryWindow.xaml" />
<None Remove="Views\SettingsWindow.xaml" />
<None Remove="Views\SystemTrayWindow.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Logo.ico" />
@@ -65,27 +67,26 @@
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Triggers" Version="8.2.250402" />
<PackageReference Include="ComputeSharp.D2D1.WinUI" Version="3.2.0" />
<PackageReference Include="csharp-kana" Version="1.0.2" />
<PackageReference Include="csharp-pinyin" Version="1.0.1" />
<PackageReference Include="DevWinUI.Controls" Version="9.6.0" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="F23.StringSimilarity" Version="7.0.0" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.2" />
<PackageReference Include="DevWinUI.Controls" Version="9.7.1" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.6" />
<PackageReference Include="F23.StringSimilarity" Version="7.0.1" />
<PackageReference Include="FlaUI.UIA3" Version="5.0.0" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.4.1" />
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
<PackageReference Include="Interop.UIAutomationClient" Version="10.19041.0" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
<PackageReference Include="NAudio.Wasapi" Version="2.2.1" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="NTextCat" Version="0.3.65" />
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="System.Drawing.Common" Version="10.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.0" />
<PackageReference Include="System.Drawing.Common" Version="10.0.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.1" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="Vanara.PInvoke.DwmApi" Version="4.2.1" />
@@ -113,6 +114,8 @@
</ItemGroup>
<!--Disable Trimming for Specific Packages-->
<ItemGroup>
<TrimmerRootAssembly Include="FlaUI.UIA3" />
<TrimmerRootAssembly Include="Interop.UIAutomationClient" />
<TrimmerRootAssembly Include="NAudio.Wasapi" />
<TrimmerRootAssembly Include="TagLibSharp" />
<TrimmerRootAssembly Include="Vanara.PInvoke.DwmApi" />
@@ -336,6 +339,16 @@
<ItemGroup>
<Folder Include="TemplateSelector\" />
</ItemGroup>
<ItemGroup>
<Page Update="Controls\NowPlayingBar.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\SystemTrayWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\WindowSettingsControl.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -3,6 +3,8 @@
public static class App
{
public const string AppAuthor = "Zhe Fang";
public const string AppAuthorNicknameEN = "jayfunc";
public const string AppAuthorNicknameZH = "摘叶飞镖";
public const string AppName = "BetterLyrics";
public const string AutoStartupTaskId = "AutoStartup";

View File

@@ -4,15 +4,23 @@
{
public const string MicrosoftStore = "https://apps.microsoft.com/detail/9p1wcd1p597r";
public const string GitHub = "https://github.com/jayfunc/BetterLyrics";
public const string ShareHub = $"{GitHub}/blob/dev/ShareHub/index.md";
public const string TermsOfService = $"{GitHub}/blob/dev/TermsofService.md";
public const string PrivacyPolicy = $"{GitHub}/blob/dev/PrivacyPolicy.md";
public const string UserGuide = $"{GitHub}/wiki/User-Guide";
public const string AuthorGitHub = "https://github.com/jayfunc";
public const string BetterLyricsGitHub = $"{AuthorGitHub}/BetterLyrics";
public const string ShareHub = $"{BetterLyricsGitHub}/blob/dev/ShareHub/index.md";
public const string TermsOfService = $"{BetterLyricsGitHub}/blob/dev/TermsofService.md";
public const string PrivacyPolicy = $"{BetterLyricsGitHub}/blob/dev/PrivacyPolicy.md";
public const string UserGuide = $"{BetterLyricsGitHub}/wiki/User-Guide";
public const string AppleMusicCfg = $"{UserGuide}#lyrics-source-configuration";
public const string QQGroup = "https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info";
public const string Discord = "https://discord.gg/5yAQPnyCKv";
public const string Telegram = "https://t.me/+svhSLZ7awPsxNGY1";
public const string BuyMeACoffee = "https://buymeacoffee.com/founchoo";
public const string PayPal = "https://paypal.me/zhefangpay";
public const string Afdian = "https://afdian.com/a/jayfunc";
}
}

View File

@@ -28,30 +28,37 @@
</StackPanel>
</dev:SettingsExpander.Header>
<dev:SettingsExpander.Description>
<StackPanel
Margin="0,2,0,0"
Orientation="Horizontal"
Spacing="2">
<TextBlock Text="©" />
<HyperlinkButton
Margin="0,-1,0,0"
Content="Zhe Fang"
NavigateUri="https://github.com/jayfunc" />
<TextBlock Text="2025" />
<StackPanel Orientation="Horizontal">
<StackPanel
Margin="0,2,0,0"
Orientation="Horizontal"
Spacing="2">
<TextBlock Text="©" />
<HyperlinkButton
Margin="0,-1,0,0"
Content="{x:Bind const:App.AppAuthor}"
NavigateUri="https://github.com/jayfunc" />
<TextBlock Text="2025" />
</StackPanel>
</StackPanel>
</dev:SettingsExpander.Description>
<RichTextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<Paragraph>
<Run x:Uid="SettingsPageVersion" />
<Run Text="{x:Bind helper:MetadataHelper.AppVersion}" />
</Paragraph>
</RichTextBlock>
<StackPanel Orientation="Horizontal" Spacing="6">
<RichTextBlock
Margin="0,-1,0,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}">
<Paragraph>
<Run x:Uid="SettingsPageVersion" />
<Run Text="{x:Bind helper:MetadataHelper.AppVersion}" />
</Paragraph>
</RichTextBlock>
</StackPanel>
<dev:SettingsExpander.Items>
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.GitHub}" />
<HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.BetterLyricsGitHub}" />
<HyperlinkButton x:Uid="UserGuide" NavigateUri="{x:Bind const:Link.UserGuide}" />
<HyperlinkButton x:Uid="PrivacyPolicy" NavigateUri="{x:Bind const:Link.PrivacyPolicy}" />
<HyperlinkButton x:Uid="TermsOfService" NavigateUri="{x:Bind const:Link.TermsOfService}" />
@@ -75,14 +82,14 @@
<StackPanel Spacing="6">
<TextBlock x:Uid="SetingsPageDonation" />
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="Buy Me a Coffee" NavigateUri="https://buymeacoffee.com/founchoo" />
<HyperlinkButton Content="PayPal" NavigateUri="https://paypal.me/zhefangpay" />
<Button
Content="支付宝"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Style="{StaticResource GhostButtonStyle}">
<Button.Flyout>
<Flyout>
<HyperlinkButton Content="Buy Me a Coffee" NavigateUri="{x:Bind const:Link.BuyMeACoffee}" />
<HyperlinkButton Content="PayPal" NavigateUri="{x:Bind const:Link.PayPal}" />
<HyperlinkButton
x:Name="AlipayButton"
Click="AlipayButton_Click"
Content="支付宝">
<HyperlinkButton.ContextFlyout>
<Flyout x:Name="AlipayFlyout">
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="CornerRadius" Value="12" />
@@ -91,14 +98,14 @@
</Flyout.FlyoutPresenterStyle>
<Image Height="300" Source="/Assets/Alipay.jpg" />
</Flyout>
</Button.Flyout>
</Button>
<Button
Content="微信"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Style="{StaticResource GhostButtonStyle}">
<Button.Flyout>
<Flyout>
</HyperlinkButton.ContextFlyout>
</HyperlinkButton>
<HyperlinkButton
x:Name="WeChatButton"
Click="WeChat_Click"
Content="微信">
<HyperlinkButton.ContextFlyout>
<Flyout x:Name="WeChatFlyout">
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="CornerRadius" Value="12" />
@@ -107,8 +114,9 @@
</Flyout.FlyoutPresenterStyle>
<Image Height="300" Source="/Assets/WeChatReward.png" />
</Flyout>
</Button.Flyout>
</Button>
</HyperlinkButton.ContextFlyout>
</HyperlinkButton>
<HyperlinkButton Content="爱发电" NavigateUri="{x:Bind const:Link.Afdian}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="*" />
@@ -120,16 +128,13 @@
</StackPanel>
</dev:SettingsCard>
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
<StackPanel Spacing="6">
<TextBlock x:Uid="SetingsPageContributors" />
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
<HyperlinkButton Content="jayfunc" NavigateUri="https://github.com/jayfunc" />
<HyperlinkButton Content="Raspberry-Monster" NavigateUri="https://github.com/Raspberry-Monster" />
<HyperlinkButton Content="ZHider" NavigateUri="https://github.com/ZHider" />
<HyperlinkButton Content="kusutori" NavigateUri="https://github.com/kusutori" />
</StackPanel>
</StackPanel>
<dev:SettingsCard x:Uid="SettingsPageThanksList">
<Button
Click="Patron_Click"
Content="{ui:FontIcon FontSize=16,
FontFamily={StaticResource IconFontFamily},
Glyph=&#xE7FD;}"
Style="{StaticResource AccentButtonStyle}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
@@ -177,10 +182,6 @@
</dev:SettingsExpander.ItemsHeader>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageDebugOverlay">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDebugOverlayEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageFixedTimeStep" Visibility="Collapsed">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.AdvancedSettings.IsFixedTimeStep, Mode=TwoWay}" />
</dev:SettingsCard>
@@ -198,5 +199,190 @@
</Grid>
</ScrollViewer>
<Grid
x:Name="CreditsReel"
Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"
Opacity="0"
SizeChanged="CreditsReel_SizeChanged"
Tapped="CreditsReel_Tapped"
Visibility="Collapsed">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<ScrollViewer
x:Name="CreditsReelScrollViewer"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollMode="Disabled">
<RichTextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalTextAlignment="Center"
LineHeight="28"
PointerEntered="RichTextBlock_PointerEntered"
PointerExited="RichTextBlock_PointerExited">
<Paragraph x:Name="CreditsReelHeader" />
<!-- 贡献者 -->
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
<Run x:Uid="SetingsPageContributors" />
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/jayfunc">
<Run Text="jayfunc" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/Raspberry-Monster">
<Run Text="Raspberry-Monster" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/ZHider">
<Run Text="ZHider" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/kusutori">
<Run Text="kusutori" />
</Hyperlink>
</Paragraph>
<!-- 赞助 -->
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
<Run x:Uid="SettingsPagePatrons" />
</Paragraph>
<Paragraph>
<Run Text="YE" />
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Dec 3, 2025" />
</Paragraph>
<Paragraph>
<Run Text="**玄" />
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 23, 2025" />
</Paragraph>
<Paragraph>
<Run Text="**智" />
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 21, 2025" />
</Paragraph>
<Paragraph>
<Run Text="*鹤" />
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 17, 2025" />
</Paragraph>
<Paragraph>
<Run Text="借过" />
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 2, 2025" />
</Paragraph>
<Paragraph>
<Run Text="**华" />
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Aug 28, 2025" />
</Paragraph>
<Paragraph>
<Run x:Uid="SettingsPageUserWhoPurchased" />
</Paragraph>
<!-- 特别鸣谢 -->
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
<Run x:Uid="SetingsPageSpecialThanks" />
</Paragraph>
<Paragraph>
<Run x:Uid="SettingsPageYouNowUsing" />
</Paragraph>
<!-- 代码参考 -->
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
<Run x:Uid="SetingsPageDeps" />
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce">
<Run Text="Get album artwork from ITunes (with Python3 or C#)" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://stackoverflow.com/a/32013610/11048731">
<Run Text="FullyObservableCollection" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/Storyteller-Studios/Impressionist">
<Run Text="Impressionist" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/Storyteller-Studios/ColorThief.WinUI3">
<Run Text="ColorThief.WinUI3" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/Johnwikix/SpectrumVisualization">
<Run Text="SpectrumVisualization" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://www.shadertoy.com/view/Mdt3Df">
<Run Text="Snow (as shown in sweden)" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://www.shadertoy.com/view/lllSR2">
<Run Text="w10" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/mo-jinran/Taskbar-Lyrics">
<Run Text="Taskbar-Lyrics" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/jayfunc/BetterLyrics/network/dependencies">
<Run Text="..." />
</Hyperlink>
</Paragraph>
<!-- UI/UX 设计参考 -->
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
<Run x:Uid="SetingsPageUIUXRef" />
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/solstice23/refined-now-playing-netease">
<Run Text="refined-now-playing-netease" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/WXRIW/Lyricify-App">
<Run Text="Lyricify" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://moriafly.com/program/salt-player">
<Run Text="椒盐音乐 Salt Player" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="https://github.com/TwilightLemon/MyToolBar">
<Run Text="MyToolBar" />
</Hyperlink>
</Paragraph>
<Paragraph>
<Hyperlink NavigateUri="">
<Run Text="" />
</Hyperlink>
</Paragraph>
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
<Run Text="{x:Bind const:App.AppName}" />
</Paragraph>
<Paragraph>
<Run Text="Proudly built by" />
<Hyperlink NavigateUri="{x:Bind const:Link.AuthorGitHub}">
<Run Text="{x:Bind const:App.AppAuthor}" />
</Hyperlink>
</Paragraph>
<Paragraph x:Name="CreditsReelFooter" />
</RichTextBlock>
</ScrollViewer>
</Grid>
</Grid>
</UserControl>

View File

@@ -1,6 +1,8 @@
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System.Threading.Tasks;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -9,6 +11,7 @@ namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class AboutControl : UserControl
{
private bool _isCreditsScrolling = false;
public AboutControlViewModel ViewModel => (AboutControlViewModel)DataContext;
public AboutControl()
@@ -16,5 +19,56 @@ namespace BetterLyrics.WinUI3.Controls
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<AboutControlViewModel>();
}
private async void Patron_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
CompositionTarget.Rendering += CompositionTarget_Rendering;
CreditsReel.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
CreditsReel.Opacity = 1;
_isCreditsScrolling = true;
}
private void CompositionTarget_Rendering(object? sender, object e)
{
if (_isCreditsScrolling)
{
CreditsReelScrollViewer.ChangeView(null, CreditsReelScrollViewer.VerticalOffset + 0.5, null);
}
}
private async void CreditsReel_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
{
CreditsReel.Opacity = 0;
await Task.Delay(Constants.Time.AnimationDuration);
CreditsReel.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
CompositionTarget.Rendering -= CompositionTarget_Rendering;
CreditsReelScrollViewer.ChangeView(null, 0, null);
}
private void CreditsReel_SizeChanged(object sender, Microsoft.UI.Xaml.SizeChangedEventArgs e)
{
CreditsReelHeader.LineHeight = e.NewSize.Height;
CreditsReelFooter.LineHeight = e.NewSize.Height / 2;
}
private void RichTextBlock_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
_isCreditsScrolling = false;
}
private void RichTextBlock_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
_isCreditsScrolling = true;
}
private void WeChat_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
WeChatFlyout.ShowAt(WeChatButton);
}
private void AlipayButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
AlipayFlyout.ShowAt(AlipayButton);
}
}
}

View File

@@ -1,18 +1,6 @@
using BetterLyrics.WinUI3.Models.Settings;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

View File

@@ -40,10 +40,9 @@
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.IsAutoCoverImageHeight, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard IsEnabled="{x:Bind AlbumArtLayoutSettings.IsAutoCoverImageHeight, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Maximum="512"
<NumberBox
Minimum="0"
Unit="px"
SpinButtonPlacementMode="Inline"
Value="{x:Bind AlbumArtLayoutSettings.CoverImageHeight, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>

View File

@@ -44,9 +44,9 @@
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- App behavior -->
<!-- Startup -->
<TextBlock x:Uid="SettingsPageAppBehavior" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<TextBlock x:Uid="SettingsPageStartup" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageAutoStart" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF71C;}">
<ToggleSwitch
@@ -59,42 +59,46 @@
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.AutoOpen, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAutoOpenLyricsWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE90B;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.AutoStartLyricsWindow, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- Music gallery window -->
<TextBlock x:Uid="SettingsPageMusicGallery" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageAutoPlayWhenOpenMusicGalleryWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE768;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.AutoPlay, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageStopTrackOnGalleryWindowClosed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE71A;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.StopOnWindowClosed, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageExitOnGalleryWindowClosed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE711;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.MusicGallerySettings.ExitOnWindowClosed, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- Lyrics window -->
<TextBlock x:Uid="SettingsPageLyricsWindow" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageExitOnLyricsWindowClosed" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE711;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ExitOnLyricsWindowClosed, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageListenNewSession" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF270;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ListenOnNewPlaybackSource, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- Playback shortcut -->
<dev:SettingsCard x:Uid="LyricsSearchControlIgnoreCache" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D8;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IgnoreCacheWhenSearching, Mode=TwoWay}" />
</dev:SettingsCard>
<TextBlock x:Uid="SettingsPageShortcut" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPageShowHideHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.ShowOrHideLyricsWindowShortcut, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageBorderlessHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.BorderlessShortcut, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageClickThroughHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.ClickThroughShortcut, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLyricsWindowSwitchHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.LyricsWindowSwitchShortcut, Mode=TwoWay}" />
</dev:SettingsCard>
<!-- Playback shortcut -->
<TextBlock x:Uid="SettingsPagePlaybackShortcut" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsCard x:Uid="SettingsPagePlayOrPauseSongHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEDA7;}">
<local:ShortcutTextBox Shortcut="{x:Bind ViewModel.AppSettings.GeneralSettings.PlayOrPauseShortcut, Mode=TwoWay}" />
</dev:SettingsCard>

View File

@@ -4,12 +4,8 @@ using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -52,7 +48,7 @@ namespace BetterLyrics.WinUI3.Controls
// FontFamilies = fontFamilies;
// });
//});
FontFamilies = FontHelper.SystemFontFamilies.OrderBy(x => x).ToList();
FontFamilies = FontHelper.GetSystemFontFamilies();
}
private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)

View File

@@ -1,19 +1,7 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

View File

@@ -47,6 +47,51 @@
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageAlbumArtLayer"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE93C;}"
IsExpanded="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageOpacity" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayOpacity, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageSpeed" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="50"
Maximum="100"
Minimum="0"
Unit="%"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlaySpeed, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageBlurAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="100"
Maximum="200"
Minimum="0"
Value="{x:Bind LyricsBackgroundSettings.CoverOverlayBlurAmount, Mode=TwoWay}" />
</dev:SettingsCard>
<!--<dev:SettingsCard x:Uid="SettingsPageBackgroundAcrylicEffectAmount" IsEnabled="{x:Bind LyricsBackgroundSettings.IsCoverOverlayEnabled, Mode=OneWay}">
<uc:ExtendedSlider
Default="0"
Maximum="10"
Minimum="0"
Value="{x:Bind LyricsBackgroundSettings.CoverAcrylicEffectAmount, Mode=TwoWay}" />
</dev:SettingsCard>-->
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageFluidLayer"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},

View File

@@ -8,44 +8,48 @@ using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Renderer;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Lyricify.Lyrics.Providers.Web.Netease;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage.Streams;
using Windows.UI;
using static Vanara.PInvoke.Ole32;
namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class LyricsCanvas : UserControl,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<AlbumArtThemeColors>>,
IRecipient<PropertyChangedMessage<TimeSpan>>,
IRecipient<PropertyChangedMessage<LyricsData?>>,
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>,
IRecipient<PropertyChangedMessage<SongInfo?>>,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<double>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<TextAlignmentType>>,
IRecipient<PropertyChangedMessage<SongInfo?>>,
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
IRecipient<PropertyChangedMessage<string>>
IRecipient<PropertyChangedMessage<string>>,
IRecipient<PropertyChangedMessage<IRandomAccessStream?>>
{
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
private readonly ILastFMService _lastFMService = Ioc.Default.GetRequiredService<ILastFMService>();
private readonly LyricsRenderer _lyricsRenderer = new();
private readonly FluidBackgroundRenderer _fluidRenderer = new();
private readonly CoverBackgroundRenderer _coverRenderer = new();
private readonly PureColorBackgroundRenderer _pureColorRenderer = new();
private readonly SnowRenderer _snowRenderer = new();
private readonly FogRenderer _fogRenderer = new();
@@ -96,6 +100,7 @@ namespace BetterLyrics.WinUI3.Controls
easingType: EasingType.EaseInOutSine
);
private TimeSpan _songPositionWithOffset;
private TimeSpan _songPosition; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1>
private TimeSpan _totalPlayedTime; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ػ<EFBFBD><D8BB>ظ<EFBFBD><D8B8><EFBFBD><EFBFBD>ŵ<EFBFBD>ʱ<EFBFBD>
private bool _isLastFMTracked = false;
@@ -106,13 +111,16 @@ namespace BetterLyrics.WinUI3.Controls
private double _renderLyricsHeight = 0;
private double _renderLyricsOpacity = 0;
private LyricsWindowStatus? _lyricsWindowStatus = null;
private AlbumArtThemeColors _albumArtThemeColors = new();
private Point _mousePosition = new(0, 0);
private int _mouseHoverLineIndex = -1;
private bool _isMouseInLyricsArea = false;
private bool _isMousePressing = false;
private bool _isMouseScrolling = false;
private LyricsData? _lyricsData;
private List<RenderLyricsLine>? _renderLyricsLines = null;
private bool _isLayoutChanged = true;
private bool _isMouseScrollingChanged = false;
@@ -123,9 +131,27 @@ namespace BetterLyrics.WinUI3.Controls
public TimeSpan SongPosition => _songPosition;
public double CurrentCanvasYScroll => _canvasYScrollTransition.Value;
public double ActualLyricsHeight => LyricsLayoutManager.CalculateActualHeight(_lyricsData?.LyricsLines);
public double ActualLyricsHeight => LyricsLayoutManager.CalculateActualHeight(_renderLyricsLines);
public int CurrentHoveringLineIndex => _mouseHoverLineIndex;
public LyricsWindowStatus? LyricsWindowStatus
{
get { return (LyricsWindowStatus?)GetValue(LyricsWindowStatusProperty); }
set { SetValue(LyricsWindowStatusProperty, value); }
}
public static readonly DependencyProperty LyricsWindowStatusProperty =
DependencyProperty.Register(nameof(LyricsWindowStatus), typeof(LyricsWindowStatus), typeof(LyricsCanvas), new PropertyMetadata(null, OnDependencyPropertyChanged));
public AlbumArtThemeColors AlbumArtThemeColors
{
get { return (AlbumArtThemeColors)GetValue(AlbumArtThemeColorsProperty); }
set { SetValue(AlbumArtThemeColorsProperty, value); }
}
public static readonly DependencyProperty AlbumArtThemeColorsProperty =
DependencyProperty.Register(nameof(AlbumArtThemeColors), typeof(AlbumArtThemeColors), typeof(LyricsCanvas), new PropertyMetadata(new AlbumArtThemeColors(), OnDependencyPropertyChanged));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><CABC> X <20><><EFBFBD><EFBFBD>
public double LyricsStartX
{
@@ -134,7 +160,7 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty LyricsStartXProperty =
DependencyProperty.Register(nameof(LyricsStartX), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
DependencyProperty.Register(nameof(LyricsStartX), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ Y <20><><EFBFBD><EFBFBD>
public double LyricsStartY
@@ -144,7 +170,7 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty LyricsStartYProperty =
DependencyProperty.Register(nameof(LyricsStartY), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
DependencyProperty.Register(nameof(LyricsStartY), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
public double LyricsWidth
@@ -154,7 +180,7 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty LyricsWidthProperty =
DependencyProperty.Register(nameof(LyricsWidth), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
DependencyProperty.Register(nameof(LyricsWidth), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߶<EFBFBD>
public double LyricsHeight
@@ -164,7 +190,7 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty LyricsHeightProperty =
DependencyProperty.Register(nameof(LyricsHeight), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
DependencyProperty.Register(nameof(LyricsHeight), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͸<EFBFBD><CDB8><EFBFBD><EFBFBD>
public double LyricsOpacity
@@ -174,7 +200,7 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty LyricsOpacityProperty =
DependencyProperty.Register(nameof(LyricsOpacity), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
DependencyProperty.Register(nameof(LyricsOpacity), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
/// <summary>
/// <20>û<EFBFBD><C3BB>ٿ<EFBFBD><D9BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD>ľ<EFBFBD><C4BE><EFBFBD><EBA3A8> 0 <20><>ʼ<EFBFBD>
@@ -186,7 +212,7 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty MouseScrollOffsetProperty =
DependencyProperty.Register(nameof(MouseScrollOffset), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnLayoutPropChanged));
DependencyProperty.Register(nameof(MouseScrollOffset), typeof(double), typeof(LyricsCanvas), new PropertyMetadata(0.0, OnDependencyPropertyChanged));
/// <summary>
/// <20>û<EFBFBD><C3BB><EFBFBD><EFBFBD>굱ǰ<EAB5B1><C7B0>λ<EFBFBD>ã<EFBFBD><C3A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڸ<EFBFBD><DAB8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ͻǣ<CFBD>
@@ -198,7 +224,7 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty MousePositionProperty =
DependencyProperty.Register(nameof(MousePosition), typeof(Point), typeof(LyricsCanvas), new PropertyMetadata(new Point(0, 0), OnLayoutPropChanged));
DependencyProperty.Register(nameof(MousePosition), typeof(Point), typeof(LyricsCanvas), new PropertyMetadata(new Point(0, 0), OnDependencyPropertyChanged));
public bool IsMouseInLyricsArea
{
@@ -207,7 +233,7 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty IsMouseInLyricsAreaProperty =
DependencyProperty.Register(nameof(IsMouseInLyricsArea), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnLayoutPropChanged));
DependencyProperty.Register(nameof(IsMouseInLyricsArea), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnDependencyPropertyChanged));
public bool IsMousePressing
{
@@ -216,7 +242,7 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty IsMousePressingProperty =
DependencyProperty.Register(nameof(IsMousePressing), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnLayoutPropChanged));
DependencyProperty.Register(nameof(IsMousePressing), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnDependencyPropertyChanged));
public bool IsMouseScrolling
{
@@ -225,30 +251,27 @@ namespace BetterLyrics.WinUI3.Controls
}
public static readonly DependencyProperty IsMouseScrollingProperty =
DependencyProperty.Register(nameof(IsMouseScrolling), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnLayoutPropChanged));
DependencyProperty.Register(nameof(IsMouseScrolling), typeof(bool), typeof(LyricsCanvas), new PropertyMetadata(false, OnDependencyPropertyChanged));
public LyricsCanvas()
{
InitializeComponent();
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<int>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<AlbumArtThemeColors>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<TimeSpan>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsData?>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsWindowStatus>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<double>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<bool>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<TextAlignmentType>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<SongInfo?>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<LyricsFontWeight>>(this);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<string>>(this);
WeakReferenceMessenger.Default.RegisterAll(this);
UpdateRenderLyricsLines();
}
private static void OnLayoutPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is LyricsCanvas canvas)
{
if (e.Property == LyricsStartXProperty)
if (e.Property == LyricsWindowStatusProperty)
{
canvas._lyricsWindowStatus = (LyricsWindowStatus)e.NewValue;
canvas._isLayoutChanged = true;
}
else if (e.Property == LyricsStartXProperty)
{
canvas._renderLyricsStartX = Convert.ToDouble(e.NewValue);
canvas._isLayoutChanged = true;
@@ -298,6 +321,18 @@ namespace BetterLyrics.WinUI3.Controls
}
canvas._isMouseScrolling = value;
}
else if (e.Property == AlbumArtThemeColorsProperty)
{
var albumArtThemeColors = (AlbumArtThemeColors)e.NewValue;
canvas._immersiveBgColorTransition.StartTransition(albumArtThemeColors.EnvColor);
canvas._accentColor1Transition.StartTransition(albumArtThemeColors.AccentColor1);
canvas._accentColor2Transition.StartTransition(albumArtThemeColors.AccentColor2);
canvas._accentColor3Transition.StartTransition(albumArtThemeColors.AccentColor3);
canvas._accentColor4Transition.StartTransition(albumArtThemeColors.AccentColor4);
canvas._albumArtThemeColors = albumArtThemeColors;
canvas._isLayoutChanged = true;
}
}
}
@@ -305,25 +340,22 @@ namespace BetterLyrics.WinUI3.Controls
private void Canvas_Draw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
if (_lyricsWindowStatus == null) return;
var bounds = new Rect(0, 0, sender.Size.Width, sender.Size.Height);
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
var albumArtLayout = status.AlbumArtLayoutSettings;
var lyricsBg = status.LyricsBackgroundSettings;
var lyricsStyle = status.LyricsStyleSettings;
var lyricsEffect = status.LyricsEffectSettings;
var albumArtLayout = _lyricsWindowStatus.AlbumArtLayoutSettings;
var lyricsBg = _lyricsWindowStatus.LyricsBackgroundSettings;
var lyricsStyle = _lyricsWindowStatus.LyricsStyleSettings;
var lyricsEffect = _lyricsWindowStatus.LyricsEffectSettings;
double songDuration = _mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0;
bool isForceWordByWord = _settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect;
double fixedSongPositionMs = _songPosition.TotalMilliseconds + (_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
var lyricsThemeColors = _mediaSessionsService.AlbumArtThemeColors;
Color overlayColor;
double finalOpacity;
if (status.IsAdaptToEnvironment)
if (_lyricsWindowStatus.IsAdaptToEnvironment)
{
// <20><><EFBFBD><EFBFBD>Ӧɫ
overlayColor = _immersiveBgColorTransition.Value;
@@ -344,8 +376,8 @@ namespace BetterLyrics.WinUI3.Controls
lyricsBg.IsPureColorOverlayEnabled
);
_fluidRenderer.Opacity = lyricsBg.FluidOverlayOpacity;
_fluidRenderer.IsEnabled = lyricsBg.IsFluidOverlayEnabled;
_coverRenderer.Draw(sender, args.DrawingSession);
_fluidRenderer.Draw(sender, args.DrawingSession);
_snowRenderer.Draw(sender, args.DrawingSession);
@@ -355,7 +387,7 @@ namespace BetterLyrics.WinUI3.Controls
_lyricsRenderer.Draw(
control: sender,
ds: args.DrawingSession,
lyricsData: _lyricsData,
lines: _renderLyricsLines,
playingLineIndex: _playingLineIndex,
mouseHoverLineIndex: _mouseHoverLineIndex,
isMousePressing: _isMousePressing,
@@ -368,21 +400,21 @@ namespace BetterLyrics.WinUI3.Controls
userScrollOffset: _mouseYScrollTransition.Value,
lyricsOpacity: _renderLyricsOpacity,
playingLineTopOffsetFactor: lyricsStyle.PlayingLineTopOffset / 100.0,
windowStatus: status,
strokeColor: lyricsThemeColors.StrokeFontColor,
bgColor: lyricsThemeColors.BgFontColor,
fgColor: lyricsThemeColors.FgFontColor,
windowStatus: _lyricsWindowStatus,
strokeColor: _albumArtThemeColors.StrokeFontColor,
bgColor: _albumArtThemeColors.BgFontColor,
fgColor: _albumArtThemeColors.FgFontColor,
getPlaybackState: (lineIndex) =>
{
if (_lyricsData == null) return new LinePlaybackState();
if (_renderLyricsLines == null) return new LinePlaybackState();
var line = _lyricsData.LyricsLines.ElementAtOrDefault(lineIndex);
var line = _renderLyricsLines.ElementAtOrDefault(lineIndex);
if (line == null) return new LinePlaybackState();
var nextLine = _lyricsData.LyricsLines.ElementAtOrDefault(lineIndex + 1);
var nextLine = _renderLyricsLines.ElementAtOrDefault(lineIndex + 1);
return _synchronizer.GetLinePlayingProgress(
fixedSongPositionMs,
_songPositionWithOffset.TotalMilliseconds,
line,
nextLine,
songDuration,
@@ -403,33 +435,35 @@ namespace BetterLyrics.WinUI3.Controls
style: lyricsBg.SpectrumStyle,
canvasWidth: sender.Size.Width,
canvasHeight: sender.Size.Height,
fillColor: lyricsThemeColors.BgFontColor
fillColor: _albumArtThemeColors.BgFontColor
);
}
#if DEBUG
args.DrawingSession.DrawText(
$"Lyrics render start pos: ({(int)_renderLyricsStartX}, {(int)_renderLyricsStartY})\n" +
$"Lyrics render size: [{(int)_renderLyricsWidth} x {(int)_renderLyricsHeight}]\n" +
$"Lyrics actual height: {LyricsLayoutManager.CalculateActualHeight(_lyricsData?.LyricsLines)}\n" +
$"Playing line (idx): {_playingLineIndex}\n" +
$"Mouse hovering line (idx): {_mouseHoverLineIndex}\n" +
$"Visible lines range (idx): [{_visibleRange.Start}, {_visibleRange.End}]\n" +
$"Total line count: {LyricsLayoutManager.CalculateMaxRange(_lyricsData?.LyricsLines).End + 1}\n" +
$"Played: {TimeSpan.FromMilliseconds(fixedSongPositionMs)} / {TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0)}\n" +
$"Y offset: {_canvasYScrollTransition.Value}\n" +
$"User scroll offset: {_mouseYScrollTransition.Value}",
new Vector2(0, 0), Colors.Red);
//args.DrawingSession.DrawText(
// $"Lyrics render start pos: ({(int)_renderLyricsStartX}, {(int)_renderLyricsStartY})\n" +
// $"Lyrics render size: [{(int)_renderLyricsWidth} x {(int)_renderLyricsHeight}]\n" +
// $"Lyrics actual height: {LyricsLayoutManager.CalculateActualHeight(_renderLyricsLines)}\n" +
// $"Playing line (idx): {_playingLineIndex}\n" +
// $"Mouse hovering line (idx): {_mouseHoverLineIndex}\n" +
// $"Visible lines range (idx): [{_visibleRange.Start}, {_visibleRange.End}]\n" +
// $"Total line count: {LyricsLayoutManager.CalculateMaxRange(_renderLyricsLines).End + 1}\n" +
// $"Played: {_songPosition} / {TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0)}\n" +
// $"Y offset: {_canvasYScrollTransition.Value}\n" +
// $"User scroll offset: {_mouseYScrollTransition.Value}",
// new Vector2(0, 0), Colors.Red);
#endif
}
private void Canvas_Update(ICanvasAnimatedControl sender, CanvasAnimatedUpdateEventArgs args)
{
var lyricsBg = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings;
var lyricsStyle = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings;
var lyricsEffect = _liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings;
var albumArtThemeColors = _mediaSessionsService.AlbumArtThemeColors;
if (_lyricsWindowStatus == null) return;
var lyricsBg = _lyricsWindowStatus.LyricsBackgroundSettings;
var lyricsStyle = _lyricsWindowStatus.LyricsStyleSettings;
var lyricsEffect = _lyricsWindowStatus.LyricsEffectSettings;
var lyricsData = _mediaSessionsService.CurrentLyricsData;
TimeSpan elapsedTime = args.Timing.ElapsedTime;
@@ -447,7 +481,7 @@ namespace BetterLyrics.WinUI3.Controls
#region UpdatePlayingLineIndex
int newPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPosition.TotalMilliseconds, _lyricsData);
int newPlayingIndex = _synchronizer.GetCurrentLineIndex(_songPositionWithOffset.TotalMilliseconds, lyricsData);
bool isPlayingLineChanged = newPlayingIndex != _playingLineIndex;
_playingLineIndex = newPlayingIndex;
@@ -457,7 +491,7 @@ namespace BetterLyrics.WinUI3.Controls
if (isPlayingLineChanged || _isLayoutChanged)
{
var targetScroll = LyricsLayoutManager.CalculateTargetScrollOffset(_lyricsData, _playingLineIndex);
var targetScroll = LyricsLayoutManager.CalculateTargetScrollOffset(_renderLyricsLines, _playingLineIndex);
if (targetScroll.HasValue) _canvasTargetScrollOffset = targetScroll.Value;
_canvasYScrollTransition.SetEasingType(lyricsEffect.LyricsScrollEasingType);
@@ -471,7 +505,7 @@ namespace BetterLyrics.WinUI3.Controls
_mouseYScrollTransition.Update(elapsedTime);
_mouseHoverLineIndex = LyricsLayoutManager.FindMouseHoverLineIndex(
_lyricsData?.LyricsLines,
_renderLyricsLines,
_isMouseInLyricsArea,
_mousePosition,
_canvasYScrollTransition.Value + _mouseYScrollTransition.Value,
@@ -481,7 +515,7 @@ namespace BetterLyrics.WinUI3.Controls
);
_visibleRange = LyricsLayoutManager.CalculateVisibleRange(
_lyricsData?.LyricsLines,
_renderLyricsLines,
_canvasYScrollTransition.Value + _mouseYScrollTransition.Value, // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>λ<EFBFBD><CEBB>
_renderLyricsStartY,
_renderLyricsHeight,
@@ -489,20 +523,21 @@ namespace BetterLyrics.WinUI3.Controls
lyricsStyle.PlayingLineTopOffset / 100.0
);
var maxRange = LyricsLayoutManager.CalculateMaxRange(_lyricsData?.LyricsLines);
var maxRange = LyricsLayoutManager.CalculateMaxRange(_renderLyricsLines);
_animator.UpdateLines(
_lyricsData,
_renderLyricsLines,
_isMouseScrolling ? maxRange.Start : _visibleRange.Start,
_isMouseScrolling ? maxRange.End : _visibleRange.End,
_playingLineIndex,
sender.Size.Height,
_canvasTargetScrollOffset,
lyricsStyle.PlayingLineTopOffset / 100.0,
_liveStatesService.LiveStates.LyricsWindowStatus.LyricsEffectSettings,
_lyricsWindowStatus.LyricsStyleSettings,
_lyricsWindowStatus.LyricsEffectSettings,
_canvasYScrollTransition,
albumArtThemeColors.BgFontColor,
albumArtThemeColors.FgFontColor,
_albumArtThemeColors.BgFontColor,
_albumArtThemeColors.FgFontColor,
elapsedTime,
_isMouseScrolling,
_isLayoutChanged,
@@ -522,16 +557,21 @@ namespace BetterLyrics.WinUI3.Controls
_isLayoutChanged = false;
if (_fluidRenderer.IsEnabled)
{
_fluidRenderer.UpdateColors(
_accentColor1Transition.Value,
_accentColor2Transition.Value,
_accentColor3Transition.Value,
_accentColor4Transition.Value
);
_fluidRenderer.Update(elapsedTime);
}
_fluidRenderer.IsEnabled = lyricsBg.IsFluidOverlayEnabled;
_fluidRenderer.Opacity = lyricsBg.FluidOverlayOpacity / 100.0;
_fluidRenderer.UpdateColors(
_accentColor1Transition.Value,
_accentColor2Transition.Value,
_accentColor3Transition.Value,
_accentColor4Transition.Value
);
_fluidRenderer.Update(elapsedTime);
_coverRenderer.IsEnabled = lyricsBg.IsCoverOverlayEnabled;
_coverRenderer.Opacity = lyricsBg.CoverOverlayOpacity;
_coverRenderer.BlurAmount = lyricsBg.CoverOverlayBlurAmount;
_coverRenderer.Speed = lyricsBg.CoverOverlaySpeed;
_coverRenderer.Update(elapsedTime);
_snowRenderer.IsEnabled = lyricsBg.IsSnowFlakeOverlayEnabled;
_snowRenderer.Amount = lyricsBg.SnowFlakeOverlayAmount / 100f;
@@ -558,19 +598,29 @@ namespace BetterLyrics.WinUI3.Controls
private void Canvas_Unloaded(object sender, RoutedEventArgs e)
{
Canvas.RemoveFromVisualTree();
Canvas = null;
_fluidRenderer.Dispose();
_coverRenderer.Dispose();
_snowRenderer.Dispose();
_fogRenderer.Dispose();
_spectrumRenderer.Dispose();
_renderLyricsLines = null;
DisposeAnalyzer();
Canvas.RemoveFromVisualTree();
Canvas = null;
}
private async void Canvas_CreateResources(CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
args.TrackAsyncAction(_fluidRenderer.LoadResourcesAsync().AsAsyncAction());
var tasks = new Task[]
{
_fluidRenderer.LoadResourcesAsync(),
ReloadCoverBackgroundResourcesAsync()
};
args.TrackAsyncAction(Task.WhenAll(tasks).AsAsyncAction());
_snowRenderer.LoadResources();
_fogRenderer.LoadResources();
@@ -591,12 +641,12 @@ namespace BetterLyrics.WinUI3.Controls
private void TriggerRelayout()
{
if (_lyricsData == null || !_isLayoutChanged) return;
if (_renderLyricsLines == null || !_isLayoutChanged || _lyricsWindowStatus == null) return;
LyricsLayoutManager.MeasureAndArrange(
resourceCreator: Canvas,
lyricsData: _lyricsData,
status: _liveStatesService.LiveStates.LyricsWindowStatus,
lines: _renderLyricsLines,
status: _lyricsWindowStatus,
appSettings: _settingsService.AppSettings,
canvasWidth: Canvas.Size.Width,
canvasHeight: Canvas.Size.Height,
@@ -611,6 +661,7 @@ namespace BetterLyrics.WinUI3.Controls
{
_songPosition += elapsedTime;
_totalPlayedTime += elapsedTime;
_songPositionWithOffset = _songPosition + TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
CheckAndScrobbleLastFM();
}
}
@@ -632,26 +683,32 @@ namespace BetterLyrics.WinUI3.Controls
private void ResetPlaybackState()
{
_totalPlayedTime = TimeSpan.Zero;
_songPosition = TimeSpan.Zero;
_totalPlayedTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
public void Receive(PropertyChangedMessage<AlbumArtThemeColors> message)
private void UpdateRenderLyricsLines()
{
if (message.Sender is IMediaSessionsService)
_renderLyricsLines = null;
_renderLyricsLines = _mediaSessionsService.CurrentLyricsData?.LyricsLines.Select(x => new RenderLyricsLine()
{
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtThemeColors))
{
var lyricsThemeColors = message.NewValue;
_immersiveBgColorTransition.StartTransition(lyricsThemeColors.EnvColor);
_accentColor1Transition.StartTransition(lyricsThemeColors.AccentColor1);
_accentColor2Transition.StartTransition(lyricsThemeColors.AccentColor2);
_accentColor3Transition.StartTransition(lyricsThemeColors.AccentColor3);
_accentColor4Transition.StartTransition(lyricsThemeColors.AccentColor4);
LyricsSyllables = x.LyricsSyllables,
StartMs = x.StartMs,
EndMs = x.EndMs,
PhoneticText = x.PhoneticText,
OriginalText = x.OriginalText,
TranslatedText = x.TranslatedText
}).ToList();
}
_isLayoutChanged = true;
}
private async Task ReloadCoverBackgroundResourcesAsync()
{
if (_mediaSessionsService.AlbumArtBitmapStream is IRandomAccessStream stream)
{
stream.Seek(0);
CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(Canvas, stream);
_coverRenderer.SetCoverBitmap(bitmap);
}
}
@@ -694,26 +751,26 @@ namespace BetterLyrics.WinUI3.Controls
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentLyricsData))
{
_lyricsData = message.NewValue;
UpdateRenderLyricsLines();
_isLayoutChanged = true;
}
}
}
public void Receive(PropertyChangedMessage<LyricsWindowStatus> message)
public void Receive(PropertyChangedMessage<SongInfo?> message)
{
if (message.Sender is LiveStates)
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(LiveStates.LyricsWindowStatus))
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
{
_isLayoutChanged = true;
ResetPlaybackState();
}
}
}
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is LyricsStyleSettings)
if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.PhoneticLyricsFontSize))
{
@@ -735,8 +792,20 @@ namespace BetterLyrics.WinUI3.Controls
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.PhoneticLyricsOpacity))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.OriginalLyricsOpacity))
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.TranslatedLyricsOpacity))
{
_isLayoutChanged = true;
}
}
else if (message.Sender is LyricsEffectSettings)
else if (message.Sender == LyricsWindowStatus?.LyricsEffectSettings)
{
if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollDuration))
{
@@ -767,7 +836,7 @@ namespace BetterLyrics.WinUI3.Controls
public void Receive(PropertyChangedMessage<double> message)
{
if (message.Sender is LyricsStyleSettings)
if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsLineSpacingFactor))
{
@@ -778,7 +847,7 @@ namespace BetterLyrics.WinUI3.Controls
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is LyricsEffectSettings)
if (message.Sender == LyricsWindowStatus?.LyricsEffectSettings)
{
if (message.PropertyName == nameof(LyricsEffectSettings.IsFanLyricsEnabled))
{
@@ -789,7 +858,7 @@ namespace BetterLyrics.WinUI3.Controls
_isLayoutChanged = true;
}
}
else if (message.Sender is LyricsStyleSettings)
else if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.IsDynamicLyricsFontSize))
{
@@ -800,7 +869,7 @@ namespace BetterLyrics.WinUI3.Controls
public void Receive(PropertyChangedMessage<TextAlignmentType> message)
{
if (message.Sender is LyricsStyleSettings)
if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsAlignmentType))
{
@@ -809,20 +878,9 @@ namespace BetterLyrics.WinUI3.Controls
}
}
public void Receive(PropertyChangedMessage<SongInfo?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
{
ResetPlaybackState();
}
}
}
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
{
if (message.Sender is LyricsStyleSettings)
if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFontWeight))
{
@@ -833,7 +891,7 @@ namespace BetterLyrics.WinUI3.Controls
public void Receive(PropertyChangedMessage<string> message)
{
if (message.Sender is LyricsStyleSettings)
if (message.Sender == LyricsWindowStatus?.LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCJKFontFamily))
{
@@ -845,5 +903,16 @@ namespace BetterLyrics.WinUI3.Controls
}
}
}
public void Receive(PropertyChangedMessage<IRandomAccessStream?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapStream))
{
_ = ReloadCoverBackgroundResourcesAsync();
}
}
}
}
}

View File

@@ -29,19 +29,77 @@
</dev:SettingsCard>
<!-- 辉光效果 -->
<dev:SettingsCard x:Uid="SettingsPageLyricsGlowEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE9A9;}">
<dev:SettingsExpander x:Uid="SettingsPageLyricsGlowEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE9A9;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageScope">
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsGlowEffectScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageEffectScopeLongDurationSyllable" />
<ComboBoxItem x:Uid="SettingsPageEffectLineStartToCurrentChar" />
</ComboBox>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLongSyllableDuration">
<local:ExtendedSlider
Default="700"
Maximum="1000"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsGlowEffectLongSyllableDuration, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAutoAdjust">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectAmountAutoAdjust, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsGlowEffectAmountAutoAdjust, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Maximum="16"
Minimum="0"
Value="{x:Bind LyricsEffectSettings.LyricsGlowEffectAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 缩放效果 -->
<dev:SettingsCard x:Uid="SettingsPageLyricsScaleEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8A3;}">
<dev:SettingsExpander x:Uid="SettingsPageLyricsScaleEffect" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8A3;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsScaleEffectEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageLongSyllableDuration">
<local:ExtendedSlider
Default="700"
Maximum="1000"
Minimum="0"
Unit="ms"
Value="{x:Bind LyricsEffectSettings.LyricsScaleEffectLongSyllableDuration, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAutoAdjust">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsScaleEffectAmountAutoAdjust, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsScaleEffectAmountAutoAdjust, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Default="115"
Maximum="200"
Minimum="100"
Unit="%"
Value="{x:Bind LyricsEffectSettings.LyricsScaleEffectAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 浮动动画 -->
<dev:SettingsCard x:Uid="SettingsPageLyricsFloatAnimation" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8C5;}">
<dev:SettingsExpander x:Uid="SettingsPageLyricsFloatAnimation" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8C5;}">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsFloatAnimationEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageAutoAdjust">
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsFloatAnimationAmountAutoAdjust, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageAmount" IsEnabled="{x:Bind LyricsEffectSettings.IsLyricsFloatAnimationAmountAutoAdjust, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
<local:ExtendedSlider
Default="4"
Maximum="16"
Minimum="0"
Value="{x:Bind LyricsEffectSettings.LyricsFloatAnimationAmount, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 扇形歌词 -->
<dev:SettingsExpander

View File

@@ -105,14 +105,6 @@
<Run Text="*" />
<Run x:Uid="ArtistsSplitHint" />
</Paragraph>
</RichTextBlock>
<RichTextBlock
FontSize="12"
FontWeight="Bold"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Loaded="ArtistsSplitHintRichTextBlock_Loaded"
TextWrapping="Wrap">
<Paragraph>
<Run Text="; , / " />
</Paragraph>
@@ -168,6 +160,10 @@
Style="{StaticResource AccentButtonStyle}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="LyricsSearchControlIgnoreCache">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IgnoreCacheWhenSearching, Mode=TwoWay}" />
</dev:SettingsCard>
</StackPanel>
</ScrollViewer>
</Grid>

View File

@@ -2,8 +2,6 @@ using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -25,25 +23,5 @@ namespace BetterLyrics.WinUI3.Controls
ViewModel.SelectedLyricsLine = e.OriginalSource as LyricsLine;
}
private void ArtistsSplitHintRichTextBlock_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
if (sender is RichTextBlock richTextBlock)
{
TextHighlighter highlighter = new()
{
Background = App.Current.Resources["AccentTextFillColorPrimaryBrush"] as SolidColorBrush,
Ranges =
{
new() { StartIndex = 0, Length = 1 },
new() { StartIndex = 5, Length = 1 },
new() { StartIndex = 10, Length = 1 },
new() { StartIndex = 15, Length = 1 },
new() { StartIndex = 20, Length = 1 },
new() { StartIndex = 25, Length = 1 },
}
};
richTextBlock.TextHighlighters.Add(highlighter);
}
}
}
}

View File

@@ -32,8 +32,8 @@
<dev:SettingsCard x:Uid="SettingsPageLyricsCenterTopOffset" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE78A;}">
<local:ExtendedSlider
Default="50"
Maximum="100"
Minimum="0"
Maximum="99"
Minimum="1"
Unit="%"
Value="{x:Bind LyricsStyleSettings.PlayingLineTopOffset, Mode=TwoWay}" />
</dev:SettingsCard>
@@ -227,6 +227,36 @@
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 字体不透明度 -->
<dev:SettingsExpander x:Uid="SettingsPageLyricsOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEF20;}">
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPagePhoneticText">
<local:ExtendedSlider
Default="60"
Frequency="1"
Maximum="100"
Minimum="0"
Value="{x:Bind LyricsStyleSettings.PhoneticLyricsOpacity, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageOriginalText">
<local:ExtendedSlider
Default="30"
Frequency="1"
Maximum="100"
Minimum="0"
Value="{x:Bind LyricsStyleSettings.OriginalLyricsOpacity, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageTranslatedText">
<local:ExtendedSlider
Default="60"
Frequency="1"
Maximum="100"
Minimum="0"
Value="{x:Bind LyricsStyleSettings.TranslatedLyricsOpacity, Mode=TwoWay}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
</StackPanel>
</Grid>
</ScrollViewer>

View File

@@ -11,6 +11,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<Grid>
@@ -21,24 +22,7 @@
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="SettingsPageRecordedWindowStatus"
Grid.Column="0"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Button
Grid.Column="2"
Margin="0,30,0,0"
Command="{x:Bind ViewModel.OpenConfigPanelCommand}"
Style="{StaticResource AccentButtonStyle}">
<TextBlock x:Uid="LyricsWindowSettingsControlCurrentLyricsWindowConfig" />
</Button>
</Grid>
<TextBlock x:Uid="SettingsPageRecordedWindowStatus" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<StackPanel Orientation="Horizontal" Spacing="3">
@@ -54,6 +38,7 @@
<MenuFlyoutItem x:Uid="SettingsPageDockedMode" Command="{x:Bind ViewModel.CreateDockedLyricsWindowStatusCommand}" />
<MenuFlyoutItem x:Uid="SettingsPageFullscreenMode" Command="{x:Bind ViewModel.CreateFullLyricsWindowStatusCommand}" />
<MenuFlyoutItem x:Uid="SettingsPageNarrowMode" Command="{x:Bind ViewModel.CreateNarrowLyricsWindowStatusCommand}" />
<MenuFlyoutItem x:Uid="SettingsPageTaskbarMode" Command="{x:Bind ViewModel.CreateTaskbarLyricsWindowStatusCommand}" />
</MenuFlyout>
</Button.Flyout>
</Button>
@@ -87,7 +72,7 @@
Padding="0,12"
CornerRadius="4"
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}">
SelectionMode="None">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" />
@@ -101,10 +86,6 @@
Spacing="6">
<StackPanel.ContextFlyout>
<MenuBarItemFlyout>
<MenuFlyoutItem
x:Uid="LyricsWindowSettingsControlSetDefault"
Click="SetDefaultMenuFlyoutItem_Click"
IsEnabled="{Binding IsDefault, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
<MenuFlyoutItem x:Uid="SettingsPageCreateFromCurrent" Click="CopyMenuFlyoutItem_Click" />
<MenuFlyoutItem x:Uid="LyricsWindowSettingsControlShare" Click="ShareMenuFlyoutItem_Click" />
<MenuFlyoutItem
@@ -113,13 +94,61 @@
IsEnabled="{Binding IsDefault, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
</MenuBarItemFlyout>
</StackPanel.ContextFlyout>
<uc:DemoWindowGrid LyricsWindowStatus="{Binding}" />
<Grid>
<Border
BorderBrush="{ThemeResource AccentAAFillColorDefaultBrush}"
BorderThickness="4"
CornerRadius="4"
Visibility="{Binding IsOpened, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<uc:DemoWindowGrid
Margin="4"
LyricsWindowStatus="{Binding}"
Tapped="DemoWindowGrid_Tapped" />
</Grid>
<Grid>
<ToggleButton
Grid.Column="0"
HorizontalAlignment="Stretch"
Click="SetDefaultMenuFlyoutItem_Click"
IsChecked="{Binding IsDefault, Mode=OneWay}">
<TextBlock x:Uid="LyricsWindowSettingsControlSetDefault" />
</ToggleButton>
</Grid>
<Grid ColumnSpacing="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="0"
HorizontalAlignment="Stretch"
Click="ConfigButton_Click">
<TextBlock x:Uid="LyricsWindowSettingsControlLyricsWindowConfig" />
</Button>
<Button
Grid.Column="1"
HorizontalAlignment="Stretch"
Click="CloseStatusButton_Click"
IsEnabled="{Binding IsOpened, Mode=OneWay}">
<TextBlock x:Uid="SettingsPageCloseStatus" />
</Button>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<dev:SettingsCard x:Uid="SettingsPageMultiNowPlayingWindows">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.MultiNowPlayingWindowMode, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageMusicGalleryLyrics">
<Button HorizontalAlignment="Stretch" Click="EmbeddedConfigButton_Click">
<TextBlock x:Uid="LyricsWindowSettingsControlLyricsWindowConfig" />
</Button>
</dev:SettingsCard>
</StackPanel>
</Grid>
@@ -137,8 +166,15 @@
<Grid.TranslationTransition>
<Vector3Transition />
</Grid.TranslationTransition>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Padding="36,0" Style="{StaticResource SettingsGridStyle}">
<Grid
Grid.Row="0"
Padding="36,0"
Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<Grid>
<Grid.ColumnDefinitions>
@@ -147,9 +183,9 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LyricsWindowSettingsControlCurrentLyricsWindowConfig"
Grid.Column="0"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
Text="{x:Bind LyricsWindowStatus.Name, Mode=OneWay}" />
<Button
Grid.Column="2"
Margin="0,30,0,0"
@@ -160,77 +196,39 @@
Style="{StaticResource AccentButtonStyle}" />
</Grid>
<Pivot SelectionChanged="Pivot_SelectionChanged">
<controls:Segmented
x:Name="ConfigSegmented"
SelectionChanged="ConfigSegmented_SelectionChanged"
Style="{StaticResource PivotSegmentedStyle}">
<PivotItem Tag="Window">
<PivotItem.Header>
<TextBlock
x:Uid="AppSettingsControlGeneral"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<controls:SegmentedItem x:Name="WindowSegmentedItem" Tag="Window">
<TextBlock x:Uid="AppSettingsControlGeneral" />
</controls:SegmentedItem>
<controls:SegmentedItem x:Name="LayoutSegmentedItem" Tag="Layout">
<TextBlock x:Uid="SettingsPageLayout" />
</controls:SegmentedItem>
<controls:SegmentedItem x:Name="AlbumArtStyleSegmentedItem" Tag="AlbumArtStyle">
<TextBlock x:Uid="SettingsPageAlbumStyle" />
</controls:SegmentedItem>
<controls:SegmentedItem Tag="AlbumArtEffect">
<TextBlock x:Uid="SettingsPageAlbumEffect" />
</controls:SegmentedItem>
<controls:SegmentedItem Tag="LyricsStyle">
<TextBlock x:Uid="SettingsPageLyricsStyle" />
</controls:SegmentedItem>
<controls:SegmentedItem Tag="LyricsEffect">
<TextBlock x:Uid="SettingsPageLyricsEffect" />
</controls:SegmentedItem>
<controls:SegmentedItem Tag="LyricsBackground">
<TextBlock x:Uid="SettingsPageBackgroundOverlay" />
</controls:SegmentedItem>
<PivotItem Tag="Layout">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageLayout"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="AlbumArtStyle">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageAlbumStyle"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="AlbumArtEffect">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageAlbumEffect"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="LyricsStyle">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageLyricsStyle"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="LyricsEffect">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageLyricsEffect"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
<PivotItem Tag="LyricsBackground">
<PivotItem.Header>
<TextBlock
x:Uid="SettingsPageBackgroundOverlay"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}" />
</PivotItem.Header>
</PivotItem>
</Pivot>
</controls:Segmented>
</StackPanel>
</Grid>
<controls:SwitchPresenter Margin="0,110,0,0" Value="{x:Bind ViewModel.ListViewSelectedItemTag, Mode=OneWay}">
<controls:SwitchPresenter Grid.Row="1" Value="{x:Bind ViewModel.SelectorBarSelectedItemTag, Mode=OneWay}">
<controls:SwitchPresenter.ContentTransitions>
<TransitionCollection>
<PopupThemeTransition />
@@ -239,7 +237,7 @@
<!-- Window -->
<controls:Case Value="Window">
<uc:WindowSettingsControl LyricsWindowStatus="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=OneWay}" />
<uc:WindowSettingsControl LyricsWindowStatus="{x:Bind LyricsWindowStatus, Mode=OneWay}" />
</controls:Case>
<!-- Layout -->
@@ -251,14 +249,14 @@
<TextBlock x:Uid="SettingsPageLayout" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<dev:SettingsExpander x:Uid="SettingsPageDisplayTypeSwitcher" IsExpanded="True">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.LyricsDisplayType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="MainPageAlbumArtOnly" />
<ComboBoxItem x:Uid="MainPageLyriscOnly" />
<ComboBoxItem x:Uid="MainPageSplitView" />
</ComboBox>
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageLayoutOrientation">
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsLayoutOrientation, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.LyricsLayoutOrientation, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLayoutOrientationHorizontal" />
<ComboBoxItem x:Uid="SettingsPageLayoutOrientationVertical" />
</ComboBox>
@@ -273,27 +271,27 @@
<!-- Album art area style -->
<controls:Case Value="AlbumArtStyle">
<uc:AlbumArtAreaStyleSettingsControl AlbumArtLayoutSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings, Mode=OneWay}" />
<uc:AlbumArtAreaStyleSettingsControl AlbumArtLayoutSettings="{x:Bind LyricsWindowStatus.AlbumArtLayoutSettings, Mode=OneWay}" />
</controls:Case>
<!-- Album art area effect -->
<controls:Case Value="AlbumArtEffect">
<uc:AlbumArtAreaEffectSettingsControl AlbumArtAreaEffectSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.AlbumArtAreaEffectSettings, Mode=OneWay}" />
<uc:AlbumArtAreaEffectSettingsControl AlbumArtAreaEffectSettings="{x:Bind LyricsWindowStatus.AlbumArtAreaEffectSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics style -->
<controls:Case Value="LyricsStyle">
<uc:LyricsStyleSettingsControl LyricsStyleSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings, Mode=OneWay}" />
<uc:LyricsStyleSettingsControl LyricsStyleSettings="{x:Bind LyricsWindowStatus.LyricsStyleSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics effect -->
<controls:Case Value="LyricsEffect">
<uc:LyricsEffectSettingsControl LyricsEffectSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsEffectSettings, Mode=OneWay}" />
<uc:LyricsEffectSettingsControl LyricsEffectSettings="{x:Bind LyricsWindowStatus.LyricsEffectSettings, Mode=OneWay}" />
</controls:Case>
<!-- Lyrics background -->
<controls:Case Value="LyricsBackground">
<uc:LyricsBackgroundSettingsControl LyricsBackgroundSettings="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings, Mode=OneWay}" />
<uc:LyricsBackgroundSettingsControl LyricsBackgroundSettings="{x:Bind LyricsWindowStatus.LyricsBackgroundSettings, Mode=OneWay}" />
</controls:Case>
</controls:SwitchPresenter>

View File

@@ -1,15 +1,16 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using NTextCat.Commons;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -25,7 +26,15 @@ namespace BetterLyrics.WinUI3.Controls
public LyricsWindowSettingsControlViewModel ViewModel => (LyricsWindowSettingsControlViewModel)DataContext;
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
public LyricsWindowStatus? LyricsWindowStatus
{
get { return (LyricsWindowStatus?)GetValue(LyricsWindowStatusProperty); }
set { SetValue(LyricsWindowStatusProperty, value); }
}
public static readonly DependencyProperty LyricsWindowStatusProperty =
DependencyProperty.Register(nameof(LyricsWindowStatus), typeof(LyricsWindowStatus), typeof(LyricsWindowSettingsControl), new PropertyMetadata(null));
public LyricsWindowSettingsControl()
{
@@ -39,10 +48,9 @@ namespace BetterLyrics.WinUI3.Controls
{
if (menuFlyoutItem.DataContext is LyricsWindowStatus data)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus == data)
{
_liveStatesService.LiveStates.LyricsWindowStatus = ViewModel.AppSettings.WindowBoundsRecords.First();
}
var windows = WindowHook.GetWindows<NowPlayingWindow>();
var window = windows.FirstOrDefault(x => x.LyricsWindowStatus == data);
window?.CloseWindow();
ViewModel.AppSettings.WindowBoundsRecords.Remove(data);
}
}
@@ -50,12 +58,11 @@ namespace BetterLyrics.WinUI3.Controls
private void SetDefaultMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
if (sender is MenuFlyoutItem menuFlyoutItem)
if (sender is FrameworkElement element)
{
if (menuFlyoutItem.DataContext is LyricsWindowStatus data)
if (element.DataContext is LyricsWindowStatus data)
{
ViewModel.AppSettings.WindowBoundsRecords.ForEach(x => x.IsDefault = false);
data.IsDefault = true;
data.IsDefault = !data.IsDefault;
}
}
}
@@ -115,17 +122,6 @@ namespace BetterLyrics.WinUI3.Controls
}
}
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is Pivot pivot)
{
if (pivot.SelectedItem is PivotItem pivotItem)
{
ViewModel?.ListViewSelectedItemTag = pivotItem.Tag;
}
}
}
private async void ImportButton_Click(object sender, RoutedEventArgs e)
{
string[] fileTypeFilter = [".json"];
@@ -154,5 +150,63 @@ namespace BetterLyrics.WinUI3.Controls
{
ViewModel.DisplayPanelHeight = e.NewSize.Height;
}
private void ConfigButton_Click(object sender, RoutedEventArgs e)
{
WindowSegmentedItem.IsEnabled = LayoutSegmentedItem.IsEnabled = true;
ConfigSegmented.SelectedItem = WindowSegmentedItem;
LyricsWindowStatus = (LyricsWindowStatus)((Button)sender).DataContext;
ViewModel.OpenConfigPanel();
}
private void EmbeddedConfigButton_Click(object sender, RoutedEventArgs e)
{
WindowSegmentedItem.IsEnabled = LayoutSegmentedItem.IsEnabled = false;
ConfigSegmented.SelectedItem = AlbumArtStyleSegmentedItem;
LyricsWindowStatus = _settingsService.AppSettings.MusicGallerySettings.LyricsWindowStatus;
ViewModel.OpenConfigPanel();
}
private void DemoWindowGrid_Tapped(object sender, TappedRoutedEventArgs e)
{
var status = (LyricsWindowStatus)(((FrameworkElement)sender).DataContext);
// <20>࿪ģʽ
if (_settingsService.AppSettings.GeneralSettings.MultiNowPlayingWindowMode)
{
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
}
// <20><><EFBFBD><EFBFBD>ģʽ
else
{
var openedWindows = WindowHook.GetWindows<NowPlayingWindow>();
foreach (var item in openedWindows.Where(x => x.LyricsWindowStatus != status))
{
item.CloseWindow();
}
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
}
}
private void CloseStatusButton_Click(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement element)
{
if (element.DataContext is LyricsWindowStatus data)
{
var window = WindowHook.GetWindows<NowPlayingWindow>().FirstOrDefault(x => x.LyricsWindowStatus == data);
window?.CloseWindow();
}
}
}
private void ConfigSegmented_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ViewModel.SelectorBarSelectedItemTag = (string)((SegmentedItem)((Segmented)sender).SelectedItem).Tag;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
ViewModel.CloseConfigPanelCommand.Execute(null);
}
}
}

View File

@@ -44,8 +44,9 @@
<ListView
Margin="48,56"
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}">
SelectionMode="None">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="0" VerticalSpacing="0" />
@@ -54,12 +55,14 @@
<ListView.ItemTemplate>
<DataTemplate>
<Grid
Margin="0,10"
Padding="5"
AllowFocusOnInteraction="True"
Margin="16"
CornerRadius="4"
Tapped="Grid_Tapped">
<uc:DemoWindowGrid LyricsWindowStatus="{Binding}" />
<Border
BorderBrush="{ThemeResource AccentAAFillColorDefaultBrush}"
BorderThickness="4"
Visibility="{Binding IsOpened, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<uc:DemoWindowGrid Margin="4" LyricsWindowStatus="{Binding}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>

View File

@@ -1,10 +1,13 @@
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using System.Linq;
using System.Threading.Tasks;
// To learn more about WinUI, the WinUI project structure,
@@ -16,6 +19,8 @@ namespace BetterLyrics.WinUI3.Controls
{
public LyricsWindowSwitchControlViewModel ViewModel => (LyricsWindowSwitchControlViewModel)DataContext;
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
public LyricsWindowSwitchControl()
{
InitializeComponent();
@@ -24,6 +29,22 @@ namespace BetterLyrics.WinUI3.Controls
private async void Grid_Tapped(object sender, TappedRoutedEventArgs e)
{
var status = (LyricsWindowStatus)(((FrameworkElement)sender).DataContext);
// <20>࿪ģʽ
if (_settingsService.AppSettings.GeneralSettings.MultiNowPlayingWindowMode)
{
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
}
// <20><><EFBFBD><EFBFBD>ģʽ
else
{
var openedWindows = WindowHook.GetWindows<NowPlayingWindow>();
foreach (var item in openedWindows.Where(x => x.LyricsWindowStatus != status))
{
item.CloseWindow();
}
WindowHook.OpenOrShowWindow<NowPlayingWindow>(status);
}
await HideAsync();
}
@@ -37,7 +58,7 @@ namespace BetterLyrics.WinUI3.Controls
var lyricsWindowSwitchWindow = WindowHook.GetWindow<LyricsWindowSwitchWindow>();
lyricsWindowSwitchWindow?.ViewModel.RootGridOpacity = 0;
await Task.Delay(300);
WindowHook.HideWindow<LyricsWindowSwitchWindow>();
lyricsWindowSwitchWindow?.HideWindow();
}
private void ShadowRect_Loaded(object sender, RoutedEventArgs e)

View File

@@ -0,0 +1,433 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="BetterLyrics.WinUI3.Controls.NowPlayingBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid x:Name="RootGrid">
<Grid
x:Name="BottomCommandGrid"
Background="{ThemeResource LayerOnMicaBaseAltFillColorDefaultBrush}"
Opacity="{x:Bind ViewModel.BottomCommandGridOpacity, Mode=OneWay}"
PointerEntered="BottomCommandGrid_PointerEntered"
PointerExited="BottomCommandGrid_PointerExited">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<Grid x:Name="BottomCommandContent">
<Grid HorizontalAlignment="Left">
<StackPanel
x:Name="BottomLeftCommandStackPanel"
Orientation="Horizontal"
Spacing="3">
<StackPanel
x:Name="SongInfoStackPanel"
Margin="8"
Padding="8"
VerticalAlignment="Center"
CornerRadius="4"
Opacity="{x:Bind ShowSongInfo, Mode=OneWay, Converter={StaticResource BoolToOpacityConverter}}"
Orientation="Horizontal"
Spacing="12"
Tapped="SongInfoStackPanel_Tapped"
Visibility="{x:Bind ShowSongInfo, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction PropertyName="Background" Value="Transparent" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerPressed">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorTertiaryBrush}" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerReleased">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<Grid VerticalAlignment="Center" CornerRadius="4">
<local:ImageSwitcher
x:Name="AlbumArtImageSwitcher"
Width="36"
Height="36" />
</Grid>
<StackPanel VerticalAlignment="Center">
<TextBlock x:Name="TitleTextBlock" />
<TextBlock
x:Name="ArtistsTextBlock"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
</StackPanel>
<StackPanel
x:Name="TimeStackPanel"
Margin="8"
Padding="8"
VerticalAlignment="Center"
Opacity="{x:Bind ShowTime, Mode=OneWay, Converter={StaticResource BoolToOpacityConverter}}"
Orientation="Horizontal"
Spacing="2"
Tapped="TimeStackPanel_Tapped"
Visibility="{x:Bind ShowTime, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction PropertyName="Background" Value="Transparent" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerPressed">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorTertiaryBrush}" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerReleased">
<interactivity:ChangePropertyAction PropertyName="Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{Binding ElementName=TimelineSlider, Path=Value, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
<TextBlock Text="{Binding ElementName=TimelineSlider, Path=Maximum, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
</StackPanel>
</StackPanel>
</Grid>
<Grid HorizontalAlignment="Center">
<StackPanel
x:Name="BottomCenterCommandStackPanel"
Padding="16"
Orientation="Horizontal"
Spacing="3">
<Button
Command="{x:Bind ViewModel.PreviousSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE622;}"
Style="{StaticResource GhostButtonStyle}" />
<Button
Command="{x:Bind ViewModel.PauseSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF8AE;}"
Style="{StaticResource GhostButtonStyle}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button
Command="{x:Bind ViewModel.PlaySongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF5B0;}"
Style="{StaticResource GhostButtonStyle}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
ComparisonCondition="Equal"
Value="False">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<Button
Command="{x:Bind ViewModel.NextSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE623;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</Grid>
<Grid HorizontalAlignment="Right">
<StackPanel
x:Name="BottomRightCommandStackPanel"
Padding="16"
Orientation="Horizontal"
Spacing="3">
<!-- Volume -->
<Button Click="VolumeButton_Click" Style="{StaticResource GhostButtonStyle}">
<Grid>
<!-- Volumn: 0 -->
<FontIcon FontFamily="{StaticResource IconFontFamily}" Glyph="&#xE74F;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.Volume, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.Volume, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</FontIcon>
<!-- Volumn: 1-32 -->
<FontIcon
x:Name="VolumeLevel1"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE993;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.Volume, Mode=OneWay}"
ComparisonCondition="GreaterThanOrEqual"
Value="1">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.Volume, Mode=OneWay}"
ComparisonCondition="LessThan"
Value="1">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</FontIcon>
<!-- Volumn: 33-65 -->
<FontIcon
x:Name="VolumeLevel2"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE994;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.Volume, Mode=OneWay}"
ComparisonCondition="GreaterThanOrEqual"
Value="33">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.Volume, Mode=OneWay}"
ComparisonCondition="LessThan"
Value="33">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</FontIcon>
<!-- Volumn: 66-100 -->
<FontIcon
x:Name="VolumeLevel3"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE995;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.Volume, Mode=OneWay}"
ComparisonCondition="GreaterThanOrEqual"
Value="66">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.Volume, Mode=OneWay}"
ComparisonCondition="LessThan"
Value="66">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</FontIcon>
</Grid>
<Button.ContextFlyout>
<Flyout x:Name="VolumeFlyout" ShouldConstrainToRootBounds="False">
<local:ExtendedSlider
Frequency="10"
IsSliderEnabled="False"
Maximum="100"
Minimum="0"
ResetButtonVisibility="Collapsed"
Unit="%"
ValueChangedByUser="ExtendedSlider_ValueChangedByUser"
Value="{x:Bind ViewModel.Volume, Mode=TwoWay}" />
</Flyout>
</Button.ContextFlyout>
</Button>
<!-- More -->
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE712;}" Style="{StaticResource GhostButtonStyle}">
<Button.Flyout>
<MenuFlyout>
<!-- Lyrics search window -->
<MenuFlyoutItem
x:Uid="LyricsPageLyricsSearch"
Command="{x:Bind ViewModel.OpenLyricsSearchWindowCommand}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE721;}" />
<!-- Lyrics window manager shortcut settings -->
<MenuFlyoutItem
x:Uid="LyricsPageLyricsSettings"
Click="LyricsSettingsShortcutMenuFlyoutItem_Click"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE61F;}">
<MenuFlyoutItem.ContextFlyout>
<Flyout
x:Name="LyricsSettingsFlyout"
Closed="LyricsSettingsFlyout_Closed"
FlyoutPresenterStyle="{StaticResource FlyoutPageStyle}"
Placement="Right"
ShouldConstrainToRootBounds="False" />
</MenuFlyoutItem.ContextFlyout>
</MenuFlyoutItem>
<!-- Playback shortcut settings -->
<MenuFlyoutItem
x:Uid="LyricsPagePlaybackSource"
Click="PlaybackSettingsShortcutMenuFlyoutItem_Click"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEA69;}">
<MenuFlyoutItem.ContextFlyout>
<Flyout
x:Name="PlaybackSettingsFlyout"
Closed="PlaybackSettingsFlyout_Closed"
FlyoutPresenterStyle="{StaticResource FlyoutPageStyle}"
Placement="Right"
ShouldConstrainToRootBounds="False" />
</MenuFlyoutItem.ContextFlyout>
</MenuFlyoutItem>
<!-- Settings -->
<MenuFlyoutItem
x:Uid="LyricsPageSettings"
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE713;}" />
</MenuFlyout>
</Button.Flyout>
</Button>
</StackPanel>
</Grid>
<Slider
x:Name="TimelineSlider"
Margin="0,-14,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Maximum="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DurationMs, Mode=OneWay, Converter={StaticResource MillisecondsToSecondsConverter}}"
Minimum="0"
Style="{StaticResource GhostSliderStyle}"
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}" />
<Grid
x:Name="TimelineSliderLyricsLineInfo"
Margin="0,-32,0,0"
Padding="8,4"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"
CornerRadius="6"
Opacity="{x:Bind ViewModel.TimelineSliderThumbOpacity, Mode=OneWay}">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<Grid.TranslationTransition>
<Vector3Transition />
</Grid.TranslationTransition>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock
Margin="0,0,0,2"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.TimelineSliderThumbLyricsLine.StartMs, Converter={StaticResource MillisecondsToFormattedTimeConverter}, Mode=OneWay}" />
<!-- TODO 原文翻译共同显示 -->
<TextBlock Margin="0,0,0,2" Text="{x:Bind ViewModel.TimelineSliderThumbLyricsLine.OriginalText, Mode=OneWay}" />
</StackPanel>
</Grid>
<Grid
Height="32"
Margin="0,-12,0,0"
VerticalAlignment="Top"
Background="Transparent"
PointerEntered="TimelineSliderOverlay_PointerEntered"
PointerExited="TimelineSliderOverlay_PointerExited"
PointerMoved="TimelineSliderOverlay_PointerMoved"
PointerPressed="TimelineSliderOverlay_PointerPressed" />
</Grid>
</Grid>
<!-- Bottom command flyout trigger -->
<Grid
x:Name="BottomCommandFlyoutTrigger"
Height="12"
VerticalAlignment="Bottom"
Background="Transparent"
CornerRadius="3,3,0,0"
Opacity="{x:Bind ViewModel.BottomCommandFlyoutTriggerOpacity, Mode=OneWay}"
PointerEntered="BottomCommandFlyoutTrigger_PointerEntered"
PointerExited="BottomCommandFlyoutTrigger_PointerExited"
Tapped="BottomCommandFlyoutTrigger_Tapped">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<Grid
x:Name="BottomCommandFlyoutTriggerHint"
Width="150"
Margin="4"
Background="{ThemeResource TextFillColorPrimaryBrush}"
CornerRadius="2"
Translation="0,0,0">
<Grid.TranslationTransition>
<Vector3Transition />
</Grid.TranslationTransition>
</Grid>
<Grid.ContextFlyout>
<Flyout x:Name="BottomCommandFlyout" ShouldConstrainToRootBounds="False">
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="Padding" Value="0" />
<Setter Property="MinWidth" Value="400" />
<Setter Property="MinHeight" Value="100" />
<Setter Property="CornerRadius" Value="12" />
</Style>
</Flyout.FlyoutPresenterStyle>
<Grid x:Name="BottomCommandFlyoutContainer" VerticalAlignment="Bottom" />
</Flyout>
</Grid.ContextFlyout>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,298 @@
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Numerics;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace BetterLyrics.WinUI3.Controls;
public sealed partial class NowPlayingBar : UserControl,
IRecipient<PropertyChangedMessage<SongInfo?>>,
IRecipient<PropertyChangedMessage<BitmapImage?>>,
IRecipient<PropertyChangedMessage<TimeSpan>>
{
public NowPlayingBarViewModel ViewModel => (NowPlayingBarViewModel)DataContext;
public event EventHandler? SongInfoTapped;
public event EventHandler? TimeTapped;
public bool ShowTime
{
get { return (bool)GetValue(ShowTimeProperty); }
set { SetValue(ShowTimeProperty, value); }
}
public static readonly DependencyProperty ShowTimeProperty =
DependencyProperty.Register(nameof(ShowTime), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
public bool ShowSongInfo
{
get { return (bool)GetValue(ShowSongInfoProperty); }
set { SetValue(ShowSongInfoProperty, value); }
}
public static readonly DependencyProperty ShowSongInfoProperty =
DependencyProperty.Register(nameof(ShowSongInfo), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
public bool IsCompactMode
{
get { return (bool)GetValue(IsCompactModeProperty); }
set { SetValue(IsCompactModeProperty, value); }
}
public static readonly DependencyProperty IsCompactModeProperty =
DependencyProperty.Register(nameof(IsCompactMode), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false, OnDependencyPropertyChanged));
public bool IsAutoHideEnabled
{
get { return (bool)GetValue(IsAutoHideEnabledProperty); }
set { SetValue(IsAutoHideEnabledProperty, value); }
}
public static readonly DependencyProperty IsAutoHideEnabledProperty =
DependencyProperty.Register(nameof(IsAutoHideEnabled), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false, OnDependencyPropertyChanged));
private bool _isPointerInBottomCommandGrid = false;
public NowPlayingBar()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<NowPlayingBarViewModel>();
WeakReferenceMessenger.Default.RegisterAll(this);
}
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is NowPlayingBar self)
{
if (e.Property == IsCompactModeProperty)
{
self.OnIsCompactModeChanged();
}
else if (e.Property == IsAutoHideEnabledProperty)
{
self.OnIsAutoHideEnabledChanged();
}
}
}
private void OnIsAutoHideEnabledChanged()
{
if (IsAutoHideEnabled)
{
if (!_isPointerInBottomCommandGrid)
{
ViewModel.BottomCommandGridOpacity = 0;
}
}
else
{
ViewModel.BottomCommandGridOpacity = 1;
}
}
private void OnIsCompactModeChanged()
{
if (IsCompactMode)
{
if (BottomCommandGrid.Children.Count != 0)
{
BottomCommandGrid.Children.Remove(BottomCommandContent);
BottomCommandFlyoutContainer.Children.Add(BottomCommandContent);
}
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 0, 0);
}
else
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
BottomCommandFlyout.Hide();
BottomCommandFlyoutContainer.Children.Remove(BottomCommandContent);
BottomCommandGrid.Children.Add(BottomCommandContent);
}
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 12, 0);
}
}
private void PlaybackSettingsShortcutMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
PlaybackSettingsFlyout.Content = new PlaybackSettingsControl
{
MaxHeight = 500,
MaxWidth = 850,
};
PlaybackSettingsFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void VolumeButton_Click(object sender, RoutedEventArgs e)
{
VolumeFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void PlaybackSettingsFlyout_Closed(object sender, object e)
{
PlaybackSettingsFlyout.Content = null;
}
private void LyricsSettingsFlyout_Closed(object sender, object e)
{
LyricsSettingsFlyout.Content = null;
}
private void LyricsSettingsShortcutMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
LyricsSettingsFlyout.Content = new LyricsWindowSettingsControl
{
MaxHeight = 500,
MaxWidth = 850,
};
LyricsSettingsFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void TimelineSliderOverlay_PointerPressed(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
var grid = (Grid)sender;
var pos = e.GetCurrentPoint(grid).Position;
var ratio = pos.X / grid.ActualWidth;
ViewModel.MediaSessionsService.ChangePosition(TimelineSlider.Maximum * ratio);
}
private void TimelineSliderOverlay_PointerMoved(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
float targetX;
var grid = (Grid)sender;
var pos = e.GetCurrentPoint(grid).Position;
var ratio = pos.X / grid.ActualWidth;
ViewModel.TimelineSliderThumbSeconds = TimelineSlider.Maximum * ratio;
if (pos.X + TimelineSliderLyricsLineInfo.ActualWidth > grid.ActualWidth)
{
targetX = (float)(grid.ActualWidth - TimelineSliderLyricsLineInfo.ActualWidth);
}
else
{
targetX = (float)pos.X;
}
TimelineSliderLyricsLineInfo.Translation = new Vector3(targetX, 0, 0);
}
private void TimelineSliderOverlay_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
ViewModel.TimelineSliderThumbOpacity = 0.7f;
}
private void TimelineSliderOverlay_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
ViewModel.TimelineSliderThumbOpacity = 0f;
}
private void ExtendedSlider_ValueChangedByUser(object sender, Events.ExtendedSliderValueChangedByUserEventArgs e)
{
SystemVolumeHook.MasterVolume = ViewModel.Volume;
}
private void LyricsSearchShortcutButton_Click(object sender, RoutedEventArgs e)
{
WindowHook.OpenOrShowWindow<LyricsSearchWindow>();
}
private void SongInfoStackPanel_Tapped(object sender, TappedRoutedEventArgs e)
{
SongInfoTapped?.Invoke(this, EventArgs.Empty);
}
private void TimeStackPanel_Tapped(object sender, TappedRoutedEventArgs e)
{
TimeTapped?.Invoke(this, EventArgs.Empty);
}
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
_isPointerInBottomCommandGrid = true;
if (IsAutoHideEnabled && BottomCommandGrid.Children.Count != 0)
{
ViewModel.BottomCommandGridOpacity = 1f;
}
e.Handled = true;
}
private void BottomCommandGrid_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
_isPointerInBottomCommandGrid = false;
if (IsAutoHideEnabled && BottomCommandGrid.Children.Count != 0)
{
ViewModel.BottomCommandGridOpacity = 0f;
}
e.Handled = true;
}
private void BottomCommandFlyoutTrigger_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
ViewModel.BottomCommandFlyoutTriggerOpacity = 1f;
}
}
private void BottomCommandFlyoutTrigger_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
ViewModel.BottomCommandFlyoutTriggerOpacity = 0f;
}
}
private void BottomCommandFlyoutTrigger_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
{
if (BottomCommandFlyoutContainer.Children.Count != 0)
{
BottomCommandFlyout.ShowAt(BottomCommandFlyoutTrigger);
}
}
public void Receive(PropertyChangedMessage<SongInfo?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
{
TitleTextBlock.Text = message.NewValue?.Title;
ArtistsTextBlock.Text = message.NewValue?.DisplayArtists;
}
}
}
public void Receive(PropertyChangedMessage<BitmapImage?> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapImage))
{
AlbumArtImageSwitcher.Source = message.NewValue;
}
}
}
public void Receive(PropertyChangedMessage<TimeSpan> message)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
{
TimelineSlider.Value = message.NewValue.TotalSeconds;
}
}
}
}

View File

@@ -22,8 +22,9 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" RowSpacing="18">
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
@@ -42,51 +43,53 @@
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<!-- 播放源列表 -->
<ListView
x:Name="MediaSourceProvidersListView"
<InfoBar
Grid.Row="0"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="MediaSourceProvidersListView_DragItemsCompleted"
ItemsSource="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo, Mode=OneWay}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollMode="Disabled"
SelectedItem="{x:Bind ViewModel.SelectedMediaSourceProvider, Mode=TwoWay}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:MediaSourceProviderInfo">
<StackPanel Orientation="Horizontal" Spacing="6">
<ToolTipService.ToolTip>
<ToolTip Content="{Binding Provider, Mode=OneWay}" />
</ToolTipService.ToolTip>
<FontIcon
FontFamily="Segoe UI Symbol"
FontSize="12"
Glyph="&#x283F;" />
<Grid
Width="16"
Height="16"
CornerRadius="4">
<ImageIcon Source="{Binding LogoPath}" />
</Grid>
<TextBlock
MaxWidth="200"
Text="{Binding DisplayName, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
IsClosable="False"
IsOpen="{x:Bind ViewModel.AppSettings.MusicGallerySettings.LyricsWindowStatus.IsOpened, Mode=OneWay}"
Message="音乐库窗口已打开,将忽略对其他播放源的监听"
Severity="Informational" />
<ScrollViewer Grid.Row="1" Style="{StaticResource SettingsScrollViewerStyle}">
<!-- 播放源列表 -->
<Grid Grid.Row="1" Margin="36,0,36,4">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<TextBlock x:Uid="SettingsPageConfigPlaybackSource" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<ComboBox
x:Name="MediaSourceProvidersListView"
HorizontalAlignment="Stretch"
ItemsSource="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedMediaSourceProvider, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="models:MediaSourceProviderInfo">
<Grid Padding="2,4" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ToolTipService.ToolTip>
<ToolTip Content="{Binding Provider, Mode=OneWay}" />
</ToolTipService.ToolTip>
<Grid
Grid.Column="0"
Width="16"
Height="16"
CornerRadius="4">
<ImageIcon Source="{Binding LogoPath}" />
</Grid>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding DisplayName, Mode=OneWay}"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
<!-- 播放源配置 -->
<ScrollViewer Grid.Row="2" Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
@@ -269,6 +272,7 @@
</Grid>
<!-- 播放源空白占位 -->
<StackPanel
Grid.Column="0"
HorizontalAlignment="Center"
@@ -294,6 +298,8 @@
HorizontalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
<!-- 右侧杂项设置 -->
<Grid Grid.Column="1">
<ScrollViewer Style="{StaticResource SettingsScrollViewerStyle}">
<Grid Style="{StaticResource SettingsGridStyle}">
@@ -347,7 +353,8 @@
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, TargetNullValue=N/A, Mode=OneWay}"
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
<local:PropertyRow x:Uid="LyricsPageTranslationProviderPrefix" Value="{x:Bind ViewModel.MediaSessionsService.TranslationSearchProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
<local:PropertyRow x:Uid="LyricsPageTransliterationProviderPrefix" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.TransliterationProvider, Mode=OneWay, Converter={StaticResource TransliterationSearchProviderToDisplayNameConverter}}" />
<local:PropertyRow x:Uid="LyricsPageTranslationProviderPrefix" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.TranslationProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
<local:PropertyRow
x:Uid="LyricsPageMatchPercentage"
Unit="%"
@@ -359,6 +366,10 @@
</StackPanel>
</Expander>
<dev:SettingsCard x:Uid="SettingsPageListenNewSession">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.ListenOnNewPlaybackSource, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageForceWordByWordEffect">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.GeneralSettings.IsForceWordByWordEffect, Mode=TwoWay}" />
</dev:SettingsCard>
@@ -386,16 +397,23 @@
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageLibreTranslateServer" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsLibreTranslateEnabled, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="12">
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
x:Uid="LibreTranslateServerTextBox"
Grid.Column="0"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.LibreTranslateServer, Mode=TwoWay}" />
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.LibreTranslateServer, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button
x:Uid="SettingsPageServerTestButton"
Grid.Column="1"
Command="{x:Bind ViewModel.LibreTranslateServerTestCommand}"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</StackPanel>
</Grid>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
@@ -413,9 +431,33 @@
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageJapanese">
<dev:SettingsExpander x:Uid="SettingsPageJapanese" IsExpanded="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsExpander.Items>
<dev:SettingsCard x:Uid="SettingsPageCutletDockerServer" IsEnabled="{x:Bind ViewModel.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled, Mode=OneWay}">
<dev:SettingsCard.Description>
<HyperlinkButton Content="https://github.com/jayfunc/cutlet-docker" NavigateUri="https://github.com/jayfunc/cutlet-docker" />
</dev:SettingsCard.Description>
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
x:Uid="CutletServerTextBox"
Grid.Column="0"
IsEnabled="{x:Bind ViewModel.IsCutletDockerServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
Text="{x:Bind ViewModel.AppSettings.TranslationSettings.CutletDockerServer, Mode=TwoWay}"
TextWrapping="Wrap" />
<Button
x:Uid="SettingsPageServerTestButton"
Grid.Column="1"
Command="{x:Bind ViewModel.CutletDockerServerTestCommand}"
IsEnabled="{x:Bind ViewModel.IsCutletDockerServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</Grid>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<!-- 中文简体繁体偏好 -->
<TextBlock x:Uid="SettingsPageChineseLyrics" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
@@ -514,6 +556,7 @@
</Grid>
</ScrollViewer>
</Grid>
</Grid>
</UserControl>

View File

@@ -1,17 +1,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -76,13 +66,13 @@ namespace BetterLyrics.WinUI3.Controls
private void UpdateShadowCastGridCornerRadius()
{
var minSize = Math.Min(ShadowCastGrid.ActualHeight, ShadowCastGrid.ActualWidth);
ShadowCastGrid.CornerRadius = new(CornerRadiusAmount / 100.0 * minSize);
ShadowCastGrid.CornerRadius = new(CornerRadiusAmount / 100.0 * (minSize / 2));
}
private void UpdateShadowRectCornerRadius()
{
var minSize = Math.Min(ShadowRect.ActualHeight, ShadowRect.ActualWidth);
ShadowRect.CornerRadius = new(CornerRadiusAmount / 100.0 * minSize);
ShadowRect.CornerRadius = new(CornerRadiusAmount / 100.0 * (minSize / 2));
}
private void ShadowCastGrid_SizeChanged(object sender, SizeChangedEventArgs e)

View File

@@ -14,9 +14,8 @@
x:Name="TrayIcon"
x:FieldModifier="public"
ContextMenuMode="SecondWindow"
DoubleClickCommand="{x:Bind ViewModel.OpenLyricsCommand}"
IconSource="ms-appx:///Assets/Logo.ico"
LeftClickCommand="{x:Bind ViewModel.OpenLyricsCommand}"
LeftClickCommand="{x:Bind ViewModel.OpenLyricsWindowSwitchCommand}"
NoLeftClickDelay="True"
ToolTipText="{x:Bind ViewModel.ToolTipText, Mode=OneWay}">
<tb:TaskbarIcon.ContextFlyout>
@@ -39,11 +38,6 @@
Command="{x:Bind ViewModel.OpenLyricsSearchWindowCommand}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE721;}" />
<MenuFlyoutItem
x:Uid="SystemTrayLyrics"
Command="{x:Bind ViewModel.OpenLyricsCommand}"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE90B;}" />
<MenuFlyoutItem
x:Uid="SystemTrayMusicGallery"
Command="{x:Bind ViewModel.OpenMusicGalleryCommand}"

View File

@@ -87,38 +87,25 @@
</dev:SettingsExpander>
<dev:SettingsExpander
x:Uid="SettingsPageWindowBounds"
x:Uid="SettingsPagePinToTaskbar"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF16B;}"
IsExpanded="True">
Glyph=&#xE7C4;}"
IsExpanded="{x:Bind LyricsWindowStatus.IsPinToTaskbar, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsPinToTaskbar, Mode=TwoWay}" />
<dev:SettingsExpander.Items>
<dev:SettingsCard Header="X">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind LyricsWindowStatus.WindowX, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard Header="Y">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind LyricsWindowStatus.WindowY, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageWidth">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind LyricsWindowStatus.WindowWidth, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageHeight">
<NumberBox
SmallChange="10"
SpinButtonPlacementMode="Inline"
Value="{x:Bind LyricsWindowStatus.WindowHeight, Mode=TwoWay}" />
<dev:SettingsCard x:Uid="SettingsPageTaskbarPlacement">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.TaskbarPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLeft" />
<ComboBoxItem x:Uid="SettingsPageRight" />
</ComboBox>
</dev:SettingsCard>
</dev:SettingsExpander.Items>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageAlwaysHideUnlockButton" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE785;}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsAlwaysHideUnlockButton, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsExpander
x:Uid="SettingsPageAOT"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
@@ -140,14 +127,6 @@
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsShownInSwitchers, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageClickThrough" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7C9;}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsClickThrough, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageBorderless" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8B2;}">
<ToggleSwitch IsOn="{x:Bind LyricsWindowStatus.IsBorderless, Mode=TwoWay}" />
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageDragArea" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEB41;}">
<ComboBox SelectedIndex="{x:Bind LyricsWindowStatus.TitleBarArea, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaNone" />

View File

@@ -1,23 +1,10 @@
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

View File

@@ -0,0 +1,42 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.ResourceService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Data;
using System;
namespace BetterLyrics.WinUI3.Converter
{
public partial class TransliterationSearchProviderToDisplayNameConverter : IValueConverter
{
private readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is TransliterationSearchProvider provider)
{
return provider switch
{
TransliterationSearchProvider.LrcLib => "LrcLib",
TransliterationSearchProvider.QQ => "QQ 音乐",
TransliterationSearchProvider.Netease => "网易云音乐",
TransliterationSearchProvider.Kugou => "酷狗音乐",
TransliterationSearchProvider.AmllTtmlDb => "amll-ttml-db",
TransliterationSearchProvider.AppleMusic => "Apple Music",
TransliterationSearchProvider.LocalLrcFile => _resourceService.GetLocalizedString("LyricsSearchProviderLocalLrcFile"),
TransliterationSearchProvider.LocalMusicFile => _resourceService.GetLocalizedString("LyricsSearchProviderLocalMusicFile"),
TransliterationSearchProvider.LocalEslrcFile => _resourceService.GetLocalizedString("LyricsSearchProviderEslrcFile"),
TransliterationSearchProvider.LocalTtmlFile => _resourceService.GetLocalizedString("LyricsSearchProviderTtmlFile"),
TransliterationSearchProvider.BetterLyrics => "BetterLyrics",
TransliterationSearchProvider.CutletDocker => "cutlet-docker",
_ => "N/A",
};
}
return "N/A";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Enums
namespace BetterLyrics.WinUI3.Enums
{
public enum ImageSwitchType
{

View File

@@ -1,8 +0,0 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum LineMaskType
{
Glow,
Highlight,
}
}

View File

@@ -1,11 +0,0 @@
// 2025/6/23 by Zhe Fang
namespace BetterLyrics.WinUI3.Enums
{
public enum LineRenderingType
{
CurrentChar,
LineStartToCurrentChar,
CurrentLine
}
}

View File

@@ -0,0 +1,8 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum LyricsEffectScope
{
LongDurationSyllable,
LineStartToCurrentChar,
}
}

View File

@@ -3,8 +3,6 @@
public enum ShortcutID
{
LyricsWindowShowOrHide,
Borderless,
ClickThrough,
LyricsWindowSwitch,
PlayOrPauseSong,
NextSong,

View File

@@ -0,0 +1,8 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum TaskbarPlacement
{
Left,
Right,
}
}

View File

@@ -0,0 +1,18 @@
namespace BetterLyrics.WinUI3.Enums
{
public enum TransliterationSearchProvider
{
QQ,
Kugou,
Netease,
LrcLib,
AmllTtmlDb,
AppleMusic,
LocalMusicFile,
LocalLrcFile,
LocalEslrcFile,
LocalTtmlFile,
BetterLyrics,
CutletDocker
}
}

View File

@@ -0,0 +1,16 @@
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
namespace BetterLyrics.WinUI3.Events
{
public class InteractiveAreaEventArgs : EventArgs
{
public IList<FrameworkElement> Elements { get; set; }
public InteractiveAreaEventArgs(IList<FrameworkElement> elements)
{
Elements = elements;
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Events
{
public class TaskbarFreeBoundsChangedEventArgs : EventArgs
{
public Rect TaskbarFreeBounds { get; }
public TaskbarFreeBoundsChangedEventArgs(Rect taskbarBounds)
{
TaskbarFreeBounds = taskbarBounds;
}
}
}

View File

@@ -59,6 +59,21 @@ namespace BetterLyrics.WinUI3.Extensions
LyricsSearchProvider.LocalTtmlFile => TranslationSearchProvider.LocalTtmlFile,
_ => null,
};
public TransliterationSearchProvider? ToTransliterationSearchProvider() => provider switch
{
LyricsSearchProvider.LrcLib => TransliterationSearchProvider.LrcLib,
LyricsSearchProvider.QQ => TransliterationSearchProvider.QQ,
LyricsSearchProvider.Kugou => TransliterationSearchProvider.Kugou,
LyricsSearchProvider.Netease => TransliterationSearchProvider.Netease,
LyricsSearchProvider.AmllTtmlDb => TransliterationSearchProvider.AmllTtmlDb,
LyricsSearchProvider.AppleMusic => TransliterationSearchProvider.AppleMusic,
LyricsSearchProvider.LocalMusicFile => TransliterationSearchProvider.LocalMusicFile,
LyricsSearchProvider.LocalLrcFile => TransliterationSearchProvider.LocalLrcFile,
LyricsSearchProvider.LocalEslrcFile => TransliterationSearchProvider.LocalEslrcFile,
LyricsSearchProvider.LocalTtmlFile => TransliterationSearchProvider.LocalTtmlFile,
_ => null,
};
}
}
}

View File

@@ -1,8 +1,11 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.ResourceService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Extensions
@@ -11,17 +14,17 @@ namespace BetterLyrics.WinUI3.Extensions
{
private static readonly IResourceService _resourceService = Ioc.Default.GetRequiredService<IResourceService>();
public static LyricsWindowStatus DesktopMode()
public static LyricsWindowStatus DesktopMode(Window? window = null)
{
return new LyricsWindowStatus
window ??= WindowHook.GetWindow<SystemTrayWindow>();
return new LyricsWindowStatus(window)
{
Name = _resourceService.GetLocalizedString("DesktopMode"),
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
WindowBounds = new Rect(100, 100, 600, 250),
IsLocked = true,
IsAlwaysOnTop = true,
IsAlwaysOnTopPolling = true,
IsBorderless = true,
IsClickThrough = true,
IsAdaptToEnvironment = true,
IsShownInSwitchers = false,
EnvironmentSampleMode = WindowPixelSampleMode.WindowEdge,
@@ -36,20 +39,19 @@ namespace BetterLyrics.WinUI3.Extensions
};
}
public static LyricsWindowStatus DockedMode()
public static LyricsWindowStatus DockedMode(Window? window = null)
{
var status = new LyricsWindowStatus
window ??= WindowHook.GetWindow<SystemTrayWindow>();
var status = new LyricsWindowStatus(window)
{
Name = _resourceService.GetLocalizedString("DockedMode"),
IsWorkArea = true,
IsAlwaysOnTop = true,
IsAlwaysOnTopPolling = true,
IsBorderless = true,
IsAdaptToEnvironment = true,
IsShownInSwitchers = false,
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
EnvironmentSampleMode = WindowPixelSampleMode.BelowWindow,
TitleBarArea = TitleBarArea.None,
LyricsStyleSettings = new LyricsStyleSettings
{
LyricsAlignmentType = TextAlignmentType.Center,
@@ -64,19 +66,18 @@ namespace BetterLyrics.WinUI3.Extensions
return status;
}
public static LyricsWindowStatus FullscreenMode()
public static LyricsWindowStatus FullscreenMode(Window? window = null)
{
var status = new LyricsWindowStatus
window ??= WindowHook.GetWindow<SystemTrayWindow>();
var status = new LyricsWindowStatus(window)
{
Name = _resourceService.GetLocalizedString("FullscreenMode"),
IsBorderless = true,
IsAlwaysOnTop = true,
TitleBarArea = TitleBarArea.None,
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
LyricsStyleSettings = new LyricsStyleSettings
{
LyricsAlignmentType = TextAlignmentType.Center,
},
IsFullscreen = true,
};
status.WindowBounds = new Rect(
status.MonitorBounds.X,
@@ -87,22 +88,49 @@ namespace BetterLyrics.WinUI3.Extensions
return status;
}
public static LyricsWindowStatus StandardMode()
public static LyricsWindowStatus StandardMode(Window? window = null)
{
return new LyricsWindowStatus
window ??= WindowHook.GetWindow<SystemTrayWindow>();
return new LyricsWindowStatus(window)
{
Name = _resourceService.GetLocalizedString("StandardMode"),
};
}
public static LyricsWindowStatus NarrowMode()
public static LyricsWindowStatus NarrowMode(Window? window = null)
{
return new LyricsWindowStatus
window ??= WindowHook.GetWindow<SystemTrayWindow>();
return new LyricsWindowStatus(window)
{
Name = _resourceService.GetLocalizedString("NarrowMode"),
WindowBounds = new Rect(100, 100, 400, 800),
LyricsLayoutOrientation = LyricsLayoutOrientation.Vertical,
};
}
public static LyricsWindowStatus TaskbarMode(Window? window = null)
{
window ??= WindowHook.GetWindow<SystemTrayWindow>();
return new LyricsWindowStatus(window)
{
Name = _resourceService.GetLocalizedString("TaskbarMode"),
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
IsPinToTaskbar = true,
IsLocked = true,
IsAlwaysOnTop = true,
IsAlwaysOnTopPolling = true,
IsAdaptToEnvironment = true,
IsShownInSwitchers = false,
EnvironmentSampleMode = WindowPixelSampleMode.WindowEdge,
LyricsStyleSettings = new()
{
LyricsAlignmentType = TextAlignmentType.Center,
},
LyricsBackgroundSettings = new LyricsBackgroundSettings
{
IsFluidOverlayEnabled = false,
}
};
}
}
}

View File

@@ -11,6 +11,9 @@ namespace BetterLyrics.WinUI3.Extensions
public Point AddX(double deltaX) => new(point.X + deltaX, point.Y);
public Point AddY(double deltaY) => new(point.X, point.Y + deltaY);
public Point WithX(double x) => new(x, point.Y);
public Point WithY(double y) => new(point.X, y);
}
}
}

View File

@@ -1,4 +1,5 @@
using Windows.Foundation;
using Vanara.PInvoke;
using Windows.Foundation;
using Windows.Graphics;
namespace BetterLyrics.WinUI3.Extensions
@@ -77,5 +78,15 @@ namespace BetterLyrics.WinUI3.Extensions
);
}
}
extension(RECT rect)
{
public Rect ToRect() => new(
rect.Left,
rect.Top,
rect.Right - rect.Left,
rect.Bottom - rect.Top
);
}
}
}

View File

@@ -0,0 +1,18 @@
using System.Drawing;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Extensions
{
public static class RectangleExtensions
{
extension(Rectangle rect)
{
public Rect ToRect() => new(
rect.Left,
rect.Top,
rect.Right - rect.Left,
rect.Bottom - rect.Top
);
}
}
}

View File

@@ -26,6 +26,8 @@ namespace BetterLyrics.WinUI3.Extensions
}
return "";
}
public string GetFileName() => Path.GetFileName(track.Path);
}
}
}

View File

@@ -1,8 +1,8 @@
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;
@@ -10,8 +10,6 @@ namespace BetterLyrics.WinUI3.Helper
{
public static class FontHelper
{
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies().Order().ToArray();
public static string GetLocalizedFontFamilyName(string sourceName, string langCode)
{
if (langCode == "")
@@ -35,5 +33,20 @@ namespace BetterLyrics.WinUI3.Helper
return sourceName;
}
public static List<string> GetSystemFontFamilies()
{
List<string> fontFamilies = new();
foreach (var font in Fonts.SystemFontFamilies)
{
if (font.FamilyNames.TryGetValue(XmlLanguage.GetLanguage("en-us"), out string englishFamilyName))
{
fontFamilies.Add(englishFamilyName);
}
}
return fontFamilies.Order().ToList();
}
}
}

View File

@@ -2,7 +2,6 @@
using BetterLyrics.WinUI3.Enums;
using Impressionist.Abstractions;
using Microsoft.Graphics.Canvas;
using System;
using System.IO;
using System.Linq;
@@ -53,42 +52,13 @@ namespace BetterLyrics.WinUI3.Helper
return buffer;
}
public static async Task<BitmapDecoder> MakeSquareWithThemeColor(IBuffer buffer, PaletteGeneratorType generatorType)
public static async Task<BitmapDecoder> GetBitmapDecoder(IBuffer buffer)
{
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(buffer);
var decoder = await BitmapDecoder.CreateAsync(stream);
if (decoder.PixelWidth == decoder.PixelHeight)
{
// 已经是正方形,直接返回
return decoder;
}
using var device = CanvasDevice.GetSharedDevice();
using var canvasBitmap = await CanvasBitmap.LoadAsync(device, stream);
var size = Math.Max(decoder.PixelWidth, decoder.PixelHeight);
var result = await GetAccentColorAsync(decoder, generatorType);
var color = Windows.UI.Color.FromArgb(255, (byte)result.Color.X, (byte)result.Color.Y, (byte)result.Color.Z);
using var renderTarget = new CanvasRenderTarget(device, size, size, 96);
int offsetX = (int)(size - decoder.PixelWidth) / 2;
int offsetY = (int)(size - decoder.PixelHeight) / 2;
using (var ds = renderTarget.CreateDrawingSession())
{
ds.FillRectangle(0, 0, size, size, color);
ds.DrawImage(canvasBitmap, offsetX, offsetY);
}
// 保存为 PNG 并转为 byte[]
stream.Seek(0);
stream.Size = 0;
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
stream.Seek(0);
var newDecoder = await BitmapDecoder.CreateAsync(stream);
return newDecoder;
return decoder;
}
public static byte[] GenerateNoiseBGRA(int width, int height)

View File

@@ -0,0 +1,124 @@
using BetterLyrics.WinUI3.Events;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using Vanara.PInvoke;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Helper
{
/// <summary>
/// 用于管理覆盖层窗口的鼠标交互区域检测
/// </summary>
public class OverlayInputHelper
{
private readonly Window _window;
private readonly IntPtr _hwnd;
private readonly DispatcherTimer _timer;
private readonly List<FrameworkElement> _interactiveControls = new();
private bool _wasOverControl = false;
public Action<InteractiveAreaEventArgs> OnInteractiveAreaEntered;
public Action<InteractiveAreaEventArgs> OnInteractiveAreaMoved;
public Action OnInteractiveAreaExited;
public OverlayInputHelper(Window window)
{
_window = window;
_hwnd = WinRT.Interop.WindowNative.GetWindowHandle(_window);
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(50);
_timer.Tick += Timer_Tick;
}
public void Register(FrameworkElement element)
{
if (!_interactiveControls.Contains(element)) _interactiveControls.Add(element);
}
public void Unregister(FrameworkElement element)
{
if (_interactiveControls.Contains(element)) _interactiveControls.Remove(element);
}
public void Start()
{
_timer.Start();
OnInteractiveAreaExited?.Invoke();
}
public void Stop()
{
_timer.Stop();
}
private void Timer_Tick(object? sender, object e)
{
User32.GetCursorPos(out var mousePoint);
bool isOverAnyControl = false;
List<FrameworkElement> overlappedElements = new();
foreach (var control in _interactiveControls)
{
if (control.XamlRoot == null || !control.XamlRoot.IsHostVisible) continue;
var bounds = GetElementScreenBounds(control);
if (mousePoint.X >= bounds.X &&
mousePoint.X <= (bounds.X + bounds.Width) &&
mousePoint.Y >= bounds.Y &&
mousePoint.Y <= (bounds.Y + bounds.Height))
{
isOverAnyControl = true;
overlappedElements.Add(control);
}
}
if (isOverAnyControl)
{
OnInteractiveAreaMoved?.Invoke(new InteractiveAreaEventArgs(overlappedElements));
}
if (isOverAnyControl != _wasOverControl)
{
if (isOverAnyControl)
{
OnInteractiveAreaEntered?.Invoke(new InteractiveAreaEventArgs(overlappedElements));
}
else
{
OnInteractiveAreaExited?.Invoke();
}
_wasOverControl = isOverAnyControl;
}
}
private Rect GetElementScreenBounds(FrameworkElement element)
{
try
{
var transform = element.TransformToVisual(null);
var topLeft = transform.TransformPoint(new Point(0, 0));
double dpiScale = element.XamlRoot.RasterizationScale;
int clientX = (int)(topLeft.X * dpiScale);
int clientY = (int)(topLeft.Y * dpiScale);
var point = new POINT { X = clientX, Y = clientY };
User32.ClientToScreen(_hwnd, ref point);
return new Rect(point.X, point.Y, element.ActualWidth * dpiScale, element.ActualHeight * dpiScale);
}
catch
{
return Rect.Empty;
}
}
}
}

View File

@@ -32,11 +32,6 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public static string ToRomaji(string text)
{
return Kana.Kana.KanaToRomaji(text, Kana.Error.Ignore).ToStr();
}
public static string ToPinyin(string text, Pinyin.ManTone.Style style = Pinyin.ManTone.Style.TONE)
{
return Pinyin.Pinyin.Instance.HanziToPinyin(text, style).ToStr();

View File

@@ -29,6 +29,8 @@ namespace BetterLyrics.WinUI3.Helper
public static bool IsAppleMusic(string? id) => id is PlayerID.AppleMusic or PlayerID.AppleMusicAlternative;
public static bool IsBetterLyrics(string? id) => id is PlayerID.BetterLyrics or PlayerID.BetterLyricsDebug;
public static string? GetDisplayName(string? id) => id switch
{
PlayerID.Spotify => PlayerName.Spotify,

View File

@@ -66,7 +66,7 @@ namespace BetterLyrics.WinUI3.Hooks
_hooks.Clear();
//_timer.Stop();
_timer.Stop();
}
private void Timer_Tick(object? sender, object e)

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Enums;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using Vanara.PInvoke;
@@ -19,13 +20,10 @@ namespace BetterLyrics.WinUI3.Hooks
/// <param name="id"></param>
/// <param name="keys"></param>
/// <param name="action"></param>
private static void RegisterHotKey<T>(ShortcutID id, List<string> keys, Action action)
private static void RegisterHotKey(Window window, ShortcutID id, List<string> keys, Action action)
{
if (keys.Count == 0) return;
var window = WindowHook.GetWindow<T>();
if (window == null) return;
HWND hwnd = WindowNative.GetWindowHandle(window);
User32.HotKeyModifiers modifiers = User32.HotKeyModifiers.MOD_NONE;
VirtualKey key = VirtualKey.None;
@@ -60,21 +58,18 @@ namespace BetterLyrics.WinUI3.Hooks
}
}
private static void UnregisterHotKey<T>(ShortcutID id)
private static void UnregisterHotKey(Window window, ShortcutID id)
{
var window = WindowHook.GetWindow<T>();
if (window == null) return;
HWND hwnd = WindowNative.GetWindowHandle(window);
User32.UnregisterHotKey(hwnd, (int)id);
_actions.Remove((int)id);
_keys.Remove((int)id);
}
public static void UpdateHotKey<T>(ShortcutID id, List<string> keys, Action action)
public static void UpdateHotKey(Window window, ShortcutID id, List<string> keys, Action action)
{
UnregisterHotKey<T>(id);
RegisterHotKey<T>(id, keys, action);
UnregisterHotKey(window, id);
RegisterHotKey(window, id, keys, action);
}
public static bool IsHotKeyRegistered(ShortcutID id)

View File

@@ -0,0 +1,211 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Extensions;
using FlaUI.Core.AutomationElements;
using FlaUI.Core.Definitions;
using FlaUI.Core.EventHandlers;
using FlaUI.UIA3;
using Microsoft.UI.Dispatching;
using System;
using System.Drawing;
using System.Threading;
namespace BetterLyrics.WinUI3.Hooks
{
public partial class TaskbarHook : IDisposable
{
private readonly UIA3Automation _automation;
private AutomationElement? _taskbar;
private StructureChangedEventHandlerBase? _structureHandler;
private PropertyChangedEventHandlerBase? _propertyHandler;
private TaskbarPlacement _currentPlacement;
private readonly DispatcherQueue _dispatcherQueue;
private readonly Action<TaskbarFreeBoundsChangedEventArgs> _onLayoutChanged;
private Timer? _debounceTimer;
private const int DebounceDelay = 150;
private bool _isDisposed;
public TaskbarHook(TaskbarPlacement placement, Action<TaskbarFreeBoundsChangedEventArgs> onLayoutChanged)
{
_automation = new UIA3Automation();
_onLayoutChanged = onLayoutChanged;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_currentPlacement = placement;
StartHook();
}
public void UpdatePlacement(TaskbarPlacement newPlacement)
{
_currentPlacement = newPlacement;
RequestUpdate(); // 立即刷新位置
}
private void StartHook()
{
try
{
var desktop = _automation.GetDesktop();
_taskbar = desktop.FindFirstChild(cf => cf.ByClassName("Shell_TrayWnd"));
if (_taskbar == null) return;
// 监听结构变化
// 这里的返回值就是一个可以 Dispose 的对象
_structureHandler = _taskbar.RegisterStructureChangedEvent(
TreeScope.Descendants,
(element, type, id) => RequestUpdate());
// 监听属性变化
_propertyHandler = _taskbar.RegisterPropertyChangedEvent(
TreeScope.Element,
(element, id, val) => RequestUpdate(),
_automation.PropertyLibrary.Element.BoundingRectangle);
RequestUpdate();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Hook Init Failed: {ex.Message}");
}
}
private void RequestUpdate()
{
if (_isDisposed) return;
_debounceTimer?.Dispose();
_debounceTimer = new Timer(_ =>
{
Rectangle voidRect = CalculateVoidRect(_currentPlacement);
_dispatcherQueue.TryEnqueue(() =>
{
if (!_isDisposed && voidRect != Rectangle.Empty)
{
_onLayoutChanged?.Invoke(new TaskbarFreeBoundsChangedEventArgs(voidRect.ToRect()));
}
});
}, null, DebounceDelay, Timeout.Infinite);
}
private Rectangle CalculateVoidRect(TaskbarPlacement placement)
{
try
{
if (_taskbar == null) return Rectangle.Empty;
try { var _ = _taskbar.BoundingRectangle; }
catch
{
var desktop = _automation.GetDesktop();
_taskbar = desktop.FindFirstChild(cf => cf.ByClassName("Shell_TrayWnd"));
if (_taskbar == null) return Rectangle.Empty;
}
Rectangle taskbarRect = _taskbar.BoundingRectangle;
// 绝对右边界:托盘
int barrierRight = taskbarRect.Right;
var tray = _taskbar.FindFirstDescendant(cf => cf.ByAutomationId("SystemTrayIcon")); // Win11
if (tray == null) tray = _taskbar.FindFirstDescendant(cf => cf.ByClassName("TrayNotifyWnd")); // Win10
if (tray != null) barrierRight = tray.BoundingRectangle.Left;
// 绝对左边界:任务栏左边缘 或 小组件(Win11)
int barrierLeft = taskbarRect.Left;
var widgets = _taskbar.FindFirstDescendant(cf => cf.ByAutomationId("WidgetsButton"));
// 只有当小组件确实在最左侧时 (Win11默认),它才构成左边界
// 如果用户把任务栏设为靠左对齐,小组件会在开始按钮右边,这时候不把它当做左边界
if (widgets != null && widgets.BoundingRectangle.Left < taskbarRect.Left + 100)
{
barrierLeft = (int)widgets.BoundingRectangle.Right;
}
// 寻找 中间内容区域 (Start + Search + Apps) 的 左右极值
int contentMinLeft = barrierRight;
int contentMaxRight = barrierLeft;
// 定义所有中间元素
string[] systemButtonIds = new[] {
"StartButton", "SearchButton", "TaskViewButton", "ChatButton"
};
// 系统按钮
foreach (var id in systemButtonIds)
{
var btn = _taskbar.FindFirstDescendant(cf => cf.ByAutomationId(id));
if (btn != null)
{
var rect = btn.BoundingRectangle;
// 排除不可见的
if (rect.Width <= 0) continue;
// 更新极值
if (rect.Left < contentMinLeft) contentMinLeft = (int)rect.Left;
if (rect.Right > contentMaxRight) contentMaxRight = (int)rect.Right;
}
}
// App 图标
var appIcons = _taskbar.FindAllDescendants(cf => cf.ByClassName("Taskbar.TaskListButtonAutomationPeer"));
foreach (var icon in appIcons)
{
var rect = icon.BoundingRectangle;
if (rect.Width <= 0) continue;
if (rect.Left < contentMinLeft) contentMinLeft = (int)rect.Left;
if (rect.Right > contentMaxRight) contentMaxRight = (int)rect.Right;
}
// 如果完全没找到内容,重置为中间
if (contentMinLeft == barrierRight) contentMinLeft = taskbarRect.Left;
if (contentMaxRight == barrierLeft) contentMaxRight = taskbarRect.Left;
int finalLeft, finalRight;
int padding = 10;
if (placement == TaskbarPlacement.Left)
{
// 【小组件】... [空隙] ...【开始按钮】
// 如果是 Win10 或 Win11左对齐contentMinLeft 几乎等于 barrierLeft空隙为0
finalLeft = barrierLeft + padding;
finalRight = contentMinLeft - padding;
}
else // Right
{
// 【最后一个图标】... [空隙] ...【托盘】
finalLeft = contentMaxRight + padding;
finalRight = barrierRight - padding;
}
int width = finalRight - finalLeft;
if (width < 20) return Rectangle.Empty;
return new Rectangle(finalLeft, taskbarRect.Top, width, taskbarRect.Height);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Calc Rect Error: {ex.Message}");
return Rectangle.Empty;
}
}
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
_debounceTimer?.Dispose();
_structureHandler?.Dispose();
_propertyHandler?.Dispose();
_automation?.Dispose();
}
}
}

View File

@@ -1,11 +1,8 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
@@ -26,42 +23,31 @@ namespace BetterLyrics.WinUI3.Hooks
private static List<object> _activeWindows = [];
private static List<object> _workAreas = [];
private static readonly Dictionary<HWND, WindowStyle> _defaultWindowStyle = [];
private static readonly Dictionary<HWND, ExtendedWindowStyle> _defaultExtendedWindowStyle = [];
private static WindowStyle? _defaultWindowStyle;
private static ExtendedWindowStyle? _defaultExtendedWindowStyle;
private static readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
private static readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
private static DispatcherQueueTimer? _setLyricsWindowVisibilityByPlayingStatusTimer;
public static void HideWindow<T>()
public static void HideWindow(this Window window)
{
var window = _activeWindows.Find(w => w is T);
var castedWindow = window as Window;
castedWindow?.Hide();
window.Hide();
}
public static void CloseWindow<T>()
public static void CloseWindow(this Window window)
{
if (typeof(T) == typeof(NowPlayingWindow))
if (window is NowPlayingWindow nowPlayingWindow)
{
EnsureDockModeReleased();
}
var window = _activeWindows.Find(w => w is T);
if (window is Window w)
{
w.Close();
_activeWindows.Remove(w);
if (GetWindowHandle(window) is IntPtr hwnd)
{
UnregisterWorkArea(hwnd);
}
nowPlayingWindow.LyricsWindowStatus.IsOpened = false;
}
window.Close();
_activeWindows.Remove(window);
}
public static void MinimizeWindow<T>()
public static void MinimizeWindow(this Window window)
{
var window = _activeWindows.Find(w => w is T);
if (window is Window w)
{
w.Minimize();
}
window.Minimize();
}
public static T? GetWindow<T>()
@@ -76,15 +62,28 @@ namespace BetterLyrics.WinUI3.Hooks
return default;
}
public static List<T> GetWindows<T>()
{
var windows = new List<T>();
foreach (var window in _activeWindows)
{
if (window is T castedWindow)
{
windows.Add(castedWindow);
}
}
return windows;
}
public static IntPtr? GetWindowHandle(object? obj)
{
if (obj is FrameworkElement frameworkElement)
{
return frameworkElement.XamlRoot.ContentIslandEnvironment.AppWindowId.GetWindowHandle();
}
else if (obj != null)
else if (obj is Window window)
{
return WindowNative.GetWindowHandle(obj);
return WindowNative.GetWindowHandle(window);
}
else
{
@@ -97,15 +96,22 @@ namespace BetterLyrics.WinUI3.Hooks
return GetWindowHandle(GetWindow<T>());
}
public static void OpenOrShowWindow<T>()
public static T OpenOrShowWindow<T>(LyricsWindowStatus? status = null)
{
var window = _activeWindows.Find(w => w is T);
var window = _activeWindows.Find(w =>
(typeof(T) != typeof(NowPlayingWindow) && w is T) ||
(typeof(T) == typeof(NowPlayingWindow) && w is T && ((NowPlayingWindow)w).LyricsWindowStatus == status)
);
if (window == null)
{
if (typeof(T) == typeof(NowPlayingWindow))
{
window = new NowPlayingWindow();
((NowPlayingWindow)window).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
if (status == null)
{
throw new NullReferenceException(nameof(status));
}
window = new NowPlayingWindow(status);
}
else if (typeof(T) == typeof(SettingsWindow))
{
@@ -123,6 +129,10 @@ namespace BetterLyrics.WinUI3.Hooks
{
window = new LyricsWindowSwitchWindow();
}
else if (typeof(T) == typeof(SystemTrayWindow))
{
window = new SystemTrayWindow();
}
else
{
throw new ArgumentException("Unsupported window type", nameof(T));
@@ -130,33 +140,24 @@ namespace BetterLyrics.WinUI3.Hooks
TrackWindow(window);
var castedWindow = (Window)window;
castedWindow.Restore();
castedWindow.Activate();
if (typeof(T) == typeof(SystemTrayWindow))
{
_defaultWindowStyle = castedWindow.GetWindowStyle();
_defaultExtendedWindowStyle = castedWindow.GetExtendedWindowStyle();
castedWindow.HideWindow();
}
if (typeof(T) == typeof(NowPlayingWindow))
{
_liveStatesService.InitLyricsWindowStatus();
var hwnd = WindowNative.GetWindowHandle(castedWindow);
_defaultWindowStyle.Add(hwnd, castedWindow.GetWindowStyle());
_defaultExtendedWindowStyle.Add(hwnd, castedWindow.GetExtendedWindowStyle());
var lyricsWindow = (NowPlayingWindow)window;
lyricsWindow.ViewModel.InitShortcuts();
lyricsWindow.ViewModel.InitFgWindowWatcher();
_mediaSessionsService.InitPlaybackShortcuts();
//TaskbarList.ThumbBarAddButtons(hwnd,
// [
// new Shell32.THUMBBUTTON()
// {
// szTip = "Previous",
// dwFlags = Shell32.THUMBBUTTONFLAGS.THBF_ENABLED,
// dwMask = Shell32.THUMBBUTTONMASK.THB_TOOLTIP | Shell32.THUMBBUTTONMASK.THB_FLAGS,
// }
// ]
//);
lyricsWindow.InitStatus();
lyricsWindow.InitFgWindowWatcher();
}
}
else
@@ -165,6 +166,13 @@ namespace BetterLyrics.WinUI3.Hooks
castedWindow.Activate();
castedWindow.AppWindow.MoveInZOrderAtTop();
}
if (typeof(T) == typeof(NowPlayingWindow))
{
((NowPlayingWindow)window).LyricsWindowStatus.IsOpened = true;
}
return (T)window;
}
public static void RestartApp(string args = "")
@@ -189,13 +197,19 @@ namespace BetterLyrics.WinUI3.Hooks
public static void ExitApp()
{
EnsureDockModeReleased();
EnsureAllWorkAreasReleased();
Environment.Exit(0);
}
private static void EnsureDockModeReleased()
private static void EnsureAllWorkAreasReleased()
{
SetIsWorkArea<NowPlayingWindow>(false);
foreach (var item in _workAreas)
{
if (GetWindowHandle(item) is IntPtr hwnd)
{
UnregisterWorkArea(hwnd);
}
}
}
private static void TrackWindow(object window)
@@ -210,43 +224,18 @@ namespace BetterLyrics.WinUI3.Hooks
private static void WindowHelper_Closed(object sender, WindowEventArgs args)
{
if (_activeWindows.Contains(sender))
{
_activeWindows.Remove(sender);
var hwnd = WindowNative.GetWindowHandle(sender);
_defaultWindowStyle.Remove(hwnd);
_defaultExtendedWindowStyle.Remove(hwnd);
}
_activeWindows.Remove(sender);
}
public static void SetIsClickThrough<T>(bool enable)
public static void SetIsWorkArea(this NowPlayingWindow window, bool enable)
{
Window? window = GetWindow<T>() as Window;
if (window == null) return;
IntPtr hwnd = WindowNative.GetWindowHandle(window);
if (enable)
{
window.SetExtendedWindowStyle(_defaultExtendedWindowStyle[hwnd] | ExtendedWindowStyle.Transparent | ExtendedWindowStyle.Layered);
}
else
{
window.SetExtendedWindowStyle(_defaultExtendedWindowStyle[hwnd]);
}
}
public static void SetIsWorkArea<T>(bool enable)
{
Window? window = GetWindow<T>() as Window;
if (window == null) return;
IntPtr hwnd = WindowNative.GetWindowHandle(window);
if (enable)
{
RegisterWorkArea(hwnd);
RegisterWorkArea(hwnd, window.LyricsWindowStatus);
}
else
{
@@ -254,35 +243,86 @@ namespace BetterLyrics.WinUI3.Hooks
}
}
public static void SetIsBorderless<T>(bool enable)
public static void SetIsLocked(this Window window, bool enable)
{
var window = GetWindow<T>() as Window;
if (window == null) return;
SetIsBorderless(window, enable);
SetIsClickThrough(window, enable);
}
var hwnd = WindowNative.GetWindowHandle(window);
if (enable)
public static void SetIsClickThrough(this Window window, bool enable)
{
if (_defaultExtendedWindowStyle is ExtendedWindowStyle style)
{
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
}
else
{
window.SetWindowStyle(_defaultWindowStyle[hwnd]);
if (enable)
{
window.SetExtendedWindowStyle(style | ExtendedWindowStyle.Layered | ExtendedWindowStyle.Transparent);
}
else
{
window.SetExtendedWindowStyle(style);
}
}
}
public static void SetIsShowInSwitchers<T>(bool enable)
public static void SetIsBorderless(this Window window, bool enable)
{
var window = GetWindow<T>() as Window;
if (window == null) return;
if (_defaultWindowStyle is WindowStyle style)
{
if (enable)
{
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
}
else
{
window.SetWindowStyle(style);
}
}
}
public static bool SetIsFullscreen(this Window window, bool enable, bool defaultExtendsContentIntoTitleBar = true)
{
if (window.AppWindow == null) return false;
if (enable)
{
window.ExtendsContentIntoTitleBar = false;
window.AppWindow.SetPresenter(AppWindowPresenterKind.FullScreen);
}
else
{
window.ExtendsContentIntoTitleBar = defaultExtendsContentIntoTitleBar;
window.AppWindow.SetPresenter(AppWindowPresenterKind.Overlapped);
}
return true;
}
public static bool SetIsMaximized(this Window window, bool enable)
{
if (window.AppWindow == null) return false;
if (enable)
{
window.Maximize();
}
else
{
window.Restore();
}
return true;
}
public static void SetIsShowInSwitchers(this Window window, bool enable)
{
if (window.AppWindow == null) return;
window.AppWindow.IsShownInSwitchers = enable;
}
public static void SetIsAlwaysOnTop<T>(bool enable)
public static void SetIsAlwaysOnTop(this Window window, bool enable)
{
var window = GetWindow<T>() as Window;
if (window == null) return;
if (window.AppWindow == null) return;
if (window.AppWindow.Presenter is OverlappedPresenter presenter)
{
@@ -290,36 +330,27 @@ namespace BetterLyrics.WinUI3.Hooks
}
}
public static void MoveAndResize<T>(Rect rect)
public static void MoveAndResize(this Window window, Rect rect)
{
var window = GetWindow<T>() as Window;
if (window == null) return;
if (window.AppWindow == null) return;
window.AppWindow.Move(new Windows.Graphics.PointInt32((int)rect.X, (int)rect.Y));
window.AppWindow.Resize(new Windows.Graphics.SizeInt32((int)rect.Width, (int)rect.Height));
}
public static void SetTitleBarArea<T>(TitleBarArea titleBarArea)
public static void SetTitleBarArea(this NowPlayingWindow window, TitleBarArea titleBarArea)
{
if (typeof(T) == typeof(NowPlayingWindow))
{
NowPlayingWindow? lyricsWindow = GetWindow<NowPlayingWindow>();
lyricsWindow?.SetTitleBarArea(titleBarArea);
}
else
{
throw new Exception($"Unsupported window type: {typeof(T).FullName}");
}
window.SetTitleBarArea(titleBarArea);
}
private static void RegisterWorkArea(IntPtr hwnd)
private static void RegisterWorkArea(IntPtr hwnd, LyricsWindowStatus status)
{
if (_workAreas.Contains(hwnd)) return;
var uEdge = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
var uEdge = status.DockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
double top = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? _liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top : _liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
double bottom = top + _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
double top = status.DockPlacement == DockPlacement.Top ? status.MonitorBounds.Top : status.MonitorBounds.Bottom - status.DockHeight;
double bottom = top + status.DockHeight;
Shell32.APPBARDATA abd = new()
{
@@ -328,9 +359,9 @@ namespace BetterLyrics.WinUI3.Hooks
uEdge = uEdge,
rc = new RECT
{
Left = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
Left = (int)status.MonitorBounds.Left,
Top = (int)top,
Right = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Right,
Right = (int)status.MonitorBounds.Right,
Bottom = (int)bottom,
},
};
@@ -358,23 +389,22 @@ namespace BetterLyrics.WinUI3.Hooks
_workAreas.Remove(hwnd);
}
public static void UpdateWorkArea<T>()
public static void UpdateWorkArea(this NowPlayingWindow window)
{
var window = GetWindow<T>() as Window;
if (window == null) return;
var hwnd = WindowNative.GetWindowHandle(window);
if (!_workAreas.Contains(hwnd))
return;
var uEdge = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
var status = window.LyricsWindowStatus;
double top = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ?
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top :
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
var uEdge = status.DockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
double bottom = top + _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
double top = status.DockPlacement == DockPlacement.Top ?
status.MonitorBounds.Top :
status.MonitorBounds.Bottom - status.DockHeight;
double bottom = top + status.DockHeight;
Shell32.APPBARDATA abd = new()
{
@@ -383,9 +413,9 @@ namespace BetterLyrics.WinUI3.Hooks
uEdge = uEdge,
rc = new RECT
{
Left = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
Left = (int)status.MonitorBounds.Left,
Top = (int)top,
Right = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Right,
Right = (int)status.MonitorBounds.Right,
Bottom = (int)bottom,
},
};
@@ -398,37 +428,32 @@ namespace BetterLyrics.WinUI3.Hooks
///
/// </summary>
/// <param name="dispatcherQueue">请确保此参数指向同一个对象,建议传值 BaseViewModel._dispatcherQueue</param>
public static void SetLyricsWindowVisibilityByPlayingStatus(DispatcherQueue dispatcherQueue)
public static void SetLyricsWindowVisibilityByPlayingStatus(this NowPlayingWindow window, bool isPlaying, DispatcherQueue dispatcherQueue)
{
_setLyricsWindowVisibilityByPlayingStatusTimer ??= dispatcherQueue.CreateTimer();
var status = window.LyricsWindowStatus;
_setLyricsWindowVisibilityByPlayingStatusTimer.Debounce(() =>
status.VisibilityTimer ??= dispatcherQueue.CreateTimer();
status.VisibilityTimer.Debounce(() =>
{
var window = GetWindow<NowPlayingWindow>();
if (window == null) return;
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.CurrentIsPlaying)
if (status.AutoShowOrHideWindow && !isPlaying)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
if (status.IsWorkArea)
{
_liveStatesService.LiveStates.IsLyricsWindowStatusRefreshing = true;
SetIsWorkArea<NowPlayingWindow>(false);
_liveStatesService.LiveStates.IsLyricsWindowStatusRefreshing = false;
window.SetIsWorkArea(false);
}
HideWindow<NowPlayingWindow>();
window.HideWindow();
}
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.CurrentIsPlaying)
else if (status.AutoShowOrHideWindow && isPlaying)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
if (status.IsWorkArea)
{
_liveStatesService.LiveStates.IsLyricsWindowStatusRefreshing = true;
SetIsWorkArea<NowPlayingWindow>(true);
_liveStatesService.LiveStates.IsLyricsWindowStatusRefreshing = false;
window.SetIsWorkArea(true);
}
OpenOrShowWindow<NowPlayingWindow>();
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
OpenOrShowWindow<NowPlayingWindow>(status);
if (status.IsWorkArea)
{
MoveAndResize<NowPlayingWindow>(_liveStatesService.LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
window.MoveAndResize(status.GetWindowBoundsWhenWorkArea());
}
}
}, Constants.Time.DebounceTimeout);

View File

@@ -2,6 +2,7 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.UI;
@@ -13,13 +14,14 @@ namespace BetterLyrics.WinUI3.Logic
private readonly double _highlightedScale = 1.0f;
public void UpdateLines(
LyricsData? lyricsData,
IList<RenderLyricsLine>? lines,
int startIndex,
int endIndex,
int playingLineIndex,
double canvasHeight,
double targetYScrollOffset,
double playingLineTopOffsetFactor,
LyricsStyleSettings lyricsStyle,
LyricsEffectSettings lyricsEffect,
ValueTransition<double> canvasYScrollTransition,
Color bgColor,
@@ -31,14 +33,18 @@ namespace BetterLyrics.WinUI3.Logic
bool isMouseScrollingChanged
)
{
if (lyricsData == null) return;
if (lines == null) return;
var currentPlayingLine = lyricsData.LyricsLines.ElementAtOrDefault(playingLineIndex);
var currentPlayingLine = lines.ElementAtOrDefault(playingLineIndex);
if (currentPlayingLine == null) return;
var phoneticOpacity = lyricsStyle.PhoneticLyricsOpacity / 100.0;
var originalOpacity = lyricsStyle.OriginalLyricsOpacity / 100.0;
var translatedOpacity = lyricsStyle.TranslatedLyricsOpacity / 100.0;
for (int i = startIndex; i <= endIndex + 1; i++)
{
var line = lyricsData.LyricsLines.ElementAtOrDefault(i);
var line = lines.ElementAtOrDefault(i);
if (line == null) continue;
if (isLayoutChanged || isPlayingLineChanged || isMouseScrollingChanged)
@@ -90,19 +96,23 @@ namespace BetterLyrics.WinUI3.Logic
line.PhoneticOpacityTransition.SetDuration(yScrollDuration);
line.PhoneticOpacityTransition.SetDelay(yScrollDelay);
line.PhoneticOpacityTransition.StartTransition(absLineCountDelta == 0 ? 0.6 : (isMouseScrolling ? 0.3 : (1 - distanceFactor) * 0.3));
line.PhoneticOpacityTransition.StartTransition(
absLineCountDelta == 0 ? phoneticOpacity : (isMouseScrolling ? phoneticOpacity : (1 - distanceFactor) * phoneticOpacity));
line.PlayedOriginalOpacityTransition.SetDuration(yScrollDuration);
line.PlayedOriginalOpacityTransition.SetDelay(yScrollDelay);
line.PlayedOriginalOpacityTransition.StartTransition(absLineCountDelta == 0 ? 1 : (isMouseScrolling ? 0.3 : (1 - distanceFactor) * 0.3));
line.PlayedOriginalOpacityTransition.StartTransition(
absLineCountDelta == 0 ? 1 : (isMouseScrolling ? 1.0 : (1 - distanceFactor) * originalOpacity));
line.UnplayedOriginalOpacityTransition.SetDuration(yScrollDuration);
line.UnplayedOriginalOpacityTransition.SetDelay(yScrollDelay);
line.UnplayedOriginalOpacityTransition.StartTransition(absLineCountDelta == 0 ? 0.3 : (isMouseScrolling ? 0.3 : (1 - distanceFactor) * 0.3));
line.UnplayedOriginalOpacityTransition.StartTransition(
absLineCountDelta == 0 ? originalOpacity : (isMouseScrolling ? originalOpacity : (1 - distanceFactor) * originalOpacity));
line.TranslatedOpacityTransition.SetDuration(yScrollDuration);
line.TranslatedOpacityTransition.SetDelay(yScrollDelay);
line.TranslatedOpacityTransition.StartTransition(absLineCountDelta == 0 ? 0.6 : (isMouseScrolling ? 0.3 : (1 - distanceFactor) * 0.3));
line.TranslatedOpacityTransition.StartTransition(
absLineCountDelta == 0 ? translatedOpacity : (isMouseScrolling ? translatedOpacity : (1 - distanceFactor) * translatedOpacity));
line.ColorTransition.SetDuration(yScrollDuration);
line.ColorTransition.SetDelay(yScrollDelay);

View File

@@ -25,7 +25,7 @@ namespace BetterLyrics.WinUI3.Logic
/// <param name="lyricsHeight"></param>
public static void MeasureAndArrange(
ICanvasAnimatedControl resourceCreator,
LyricsData? lyricsData,
IList<RenderLyricsLine>? lines,
LyricsWindowStatus status,
AppSettings appSettings,
double canvasWidth,
@@ -33,7 +33,7 @@ namespace BetterLyrics.WinUI3.Logic
double lyricsWidth,
double lyricsHeight)
{
if (lyricsData == null || resourceCreator == null) return;
if (lines == null || resourceCreator == null) return;
// 计算字体大小
int originalFontSize, phoneticFontSize, translatedFontSize;
@@ -60,7 +60,7 @@ namespace BetterLyrics.WinUI3.Logic
double currentY = 0;
double actualWidth = 0;
foreach (var line in lyricsData.LyricsLines)
foreach (var line in lines)
{
if (line == null) continue;
@@ -130,10 +130,9 @@ namespace BetterLyrics.WinUI3.Logic
/// 计算为了让当前歌词行的竖直几何中心点对齐到 0原点画布应该移动的距离从画布最初始状态计算的值
/// </summary>
public static double? CalculateTargetScrollOffset(
LyricsData? lyricsData,
IList<RenderLyricsLine>? lines,
int playingLineIndex)
{
var lines = lyricsData?.LyricsLines;
if (lines == null || lines.Count == 0) return null;
var currentLine = lines.ElementAtOrDefault(playingLineIndex);
@@ -150,7 +149,7 @@ namespace BetterLyrics.WinUI3.Logic
/// 返回值: (StartVisibleIndex, EndVisibleIndex)
/// </summary>
public static (int Start, int End) CalculateVisibleRange(
IList<LyricsLine>? lines,
IList<RenderLyricsLine>? lines,
double currentScrollOffset,
double lyricsY,
double lyricsHeight,
@@ -174,14 +173,14 @@ namespace BetterLyrics.WinUI3.Logic
return (start, end);
}
public static (int Start, int End) CalculateMaxRange(IList<LyricsLine>? lines)
public static (int Start, int End) CalculateMaxRange(IList<RenderLyricsLine>? lines)
{
if (lines == null || lines.Count == 0) return (-1, -1);
return (0, lines.Count - 1);
}
public static double CalculateActualHeight(IList<LyricsLine>? lines)
public static double CalculateActualHeight(IList<RenderLyricsLine>? lines)
{
if (lines == null || lines.Count == 0) return 0;
@@ -189,7 +188,7 @@ namespace BetterLyrics.WinUI3.Logic
}
public static int FindMouseHoverLineIndex(
IList<LyricsLine>? lines,
IList<RenderLyricsLine>? lines,
bool isMouseInLyricsArea,
Point mousePosition,
double currentScrollOffset,
@@ -217,7 +216,7 @@ namespace BetterLyrics.WinUI3.Logic
return result;
}
private static int FindFirstVisibleLine(IList<LyricsLine> lines, double offset, double lyricsY)
private static int FindFirstVisibleLine(IList<RenderLyricsLine> lines, double offset, double lyricsY)
{
int left = 0, right = lines.Count - 1, result = -1;
while (left <= right)
@@ -234,7 +233,7 @@ namespace BetterLyrics.WinUI3.Logic
return result;
}
private static int FindLastVisibleLine(IList<LyricsLine> lines, double offset, double lyricsY, double lyricsHeight, double canvasHeight)
private static int FindLastVisibleLine(IList<RenderLyricsLine> lines, double offset, double lyricsY, double lyricsHeight, double canvasHeight)
{
int left = 0, right = lines.Count - 1, result = -1;
while (left <= right)

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization;
namespace BetterLyrics.WinUI3.Models
{
public class CutletDockerRequest
{
[JsonPropertyName("text")]
public string Text { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization;
namespace BetterLyrics.WinUI3.Models
{
public class CutletDockerResponse
{
[JsonPropertyName("romaji")]
public string RomajiText { get; set; }
}
}

View File

@@ -2,7 +2,7 @@
namespace BetterLyrics.WinUI3.Models
{
public class TranslateResponse
public class LibreTranslateResponse
{
[JsonPropertyName("translatedText")]
public string TranslatedText { get; set; }

View File

@@ -1,19 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterLyrics.WinUI3.Models
{
public partial class LiveStates : ObservableRecipient
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsWindowStatus LyricsWindowStatus { get; set; }
/// <summary>
/// 在需要暂时禁用监听歌词窗口位置大小变化时使用
/// </summary>
public bool IsLyricsWindowStatusRefreshing { get; set; } = false;
public LiveStates()
{
LyricsWindowStatus = new LyricsWindowStatus();
}
}
}

View File

@@ -19,7 +19,6 @@ namespace BetterLyrics.WinUI3.Models
}
public bool AutoGenerated { get; set; } = false;
public string WrappedOriginalText => string.Join(StringHelper.NewLine, LyricsLines.Select(line => line.OriginalText));
public bool IsWordByWord => LyricsLines.Any(x => x.LyricsSyllables.Count != 0);
public LyricsData()
{
@@ -31,23 +30,7 @@ namespace BetterLyrics.WinUI3.Models
LyricsLines = lyricsLines;
}
public void ClearTranslatedText()
{
foreach (var line in LyricsLines)
{
line.TranslatedText = "";
}
}
public void ClearPhoneticText()
{
foreach (var line in LyricsLines)
{
line.PhoneticText = "";
}
}
public void SetTranslatedText(LyricsData translationData, int toleranceMs = 0)
public void SetTranslatedText(LyricsData translationData, int toleranceMs = 50)
{
foreach (var line in LyricsLines)
{
@@ -68,7 +51,7 @@ namespace BetterLyrics.WinUI3.Models
}
}
public void SetPhoneticText(LyricsData phoneticData, int toleranceMs = 0)
public void SetPhoneticText(LyricsData phoneticData, int toleranceMs = 50)
{
foreach (var line in LyricsLines)
{
@@ -107,28 +90,22 @@ namespace BetterLyrics.WinUI3.Models
}
}
public LyricsData CreateLyricsDataFrom(string translation)
public void SetTransliteration(string transliteration)
{
var result = new LyricsData(LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
}).ToList());
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
List<string> transliterationArr = transliteration.Split(StringHelper.NewLine).ToList();
int i = 0;
foreach (var line in result.LyricsLines)
foreach (var line in LyricsLines)
{
if (i >= translationArr.Count)
if (i >= transliterationArr.Count)
{
break;
line.PhoneticText = ""; // No transliteration available, keep empty
}
else
{
line.OriginalText = translationArr[i];
line.PhoneticText = transliterationArr[i];
}
i++;
}
return result;
}
public static LyricsData GetNotfoundPlaceholder()
@@ -141,16 +118,6 @@ namespace BetterLyrics.WinUI3.Models
}]);
}
public static LyricsData GetParseErrorPlaceholder()
{
return new LyricsData([new LyricsLine
{
StartMs = 0,
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
OriginalText = _resourceService.GetLocalizedString("LyricsParseError"),
}]);
}
public static LyricsData GetLoadingPlaceholder()
{
return new LyricsData([
@@ -175,5 +142,6 @@ namespace BetterLyrics.WinUI3.Models
}
return null;
}
}
}

View File

@@ -1,61 +1,11 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using System.Collections.Generic;
using System.Numerics;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models
{
public class LyricsLine
{
public double AnimationDuration { get; set; } = 0.3;
public ValueTransition<double> AngleTransition { get; set; }
public ValueTransition<double> BlurAmountTransition { get; set; }
public ValueTransition<double> PhoneticOpacityTransition { get; set; }
public ValueTransition<double> PlayedOriginalOpacityTransition { get; set; }
public ValueTransition<double> UnplayedOriginalOpacityTransition { get; set; }
public ValueTransition<double> TranslatedOpacityTransition { get; set; }
public ValueTransition<double> ScaleTransition { get; set; }
public ValueTransition<double> YOffsetTransition { get; set; }
public ValueTransition<Color> ColorTransition { get; set; }
public CanvasTextLayout? OriginalCanvasTextLayout { get; private set; }
public CanvasTextLayout? TranslatedCanvasTextLayout { get; private set; }
public CanvasTextLayout? PhoneticCanvasTextLayout { get; private set; }
/// <summary>
/// 原文坐标(相对于坐标原点)
/// </summary>
public Vector2 OriginalPosition { get; set; }
/// <summary>
/// 译文坐标(相对于坐标原点)
/// </summary>
public Vector2 TranslatedPosition { get; set; }
/// <summary>
/// 注音坐标(相对于坐标原点)
/// </summary>
public Vector2 PhoneticPosition { get; set; }
/// <summary>
/// 顶部坐标(相对于坐标原点)
/// </summary>
public Vector2 TopLeftPosition { get; set; }
/// <summary>
/// 中心坐标(相对于坐标原点)
/// </summary>
public Vector2 CenterPosition { get; private set; }
/// <summary>
/// 底部坐标(相对于坐标原点)
/// </summary>
public Vector2 BottomRightPosition { get; set; }
public List<LyricsSyllable> LyricsSyllables { get; set; } = [];
public int? DurationMs => EndMs - StartMs;
@@ -75,172 +25,5 @@ namespace BetterLyrics.WinUI3.Models
/// </summary>
public string PhoneticText { get; set; } = "";
public CanvasGeometry? OriginalCanvasGeometry { get; private set; }
public CanvasGeometry? TranslatedCanvasGeometry { get; private set; }
public CanvasGeometry? PhoneticCanvasGeometry { get; private set; }
public LyricsLine()
{
AngleTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
BlurAmountTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
PhoneticOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
PlayedOriginalOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
UnplayedOriginalOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
TranslatedOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
ScaleTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
YOffsetTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
ColorTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
}
public void UpdateCenterPosition(double maxWidth, TextAlignmentType type)
{
if (OriginalCanvasTextLayout == null)
{
return;
}
double centerY = (TopLeftPosition.Y + BottomRightPosition.Y) / 2;
CenterPosition = type switch
{
TextAlignmentType.Left => new Vector2(0, (float)centerY),
TextAlignmentType.Center => new Vector2((float)(0 + maxWidth / 2.0), (float)centerY),
TextAlignmentType.Right => new Vector2((float)(0 + maxWidth), (float)centerY),
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
};
}
public void DisposeTextLayout()
{
PhoneticCanvasTextLayout?.Dispose();
PhoneticCanvasTextLayout = null;
OriginalCanvasTextLayout?.Dispose();
OriginalCanvasTextLayout = null;
TranslatedCanvasTextLayout?.Dispose();
TranslatedCanvasTextLayout = null;
}
public void RecreateTextLayout(
ICanvasAnimatedControl control,
bool createPhonetic, bool createTranslated,
int phoneticTextFontSize, int originalTextFontSize, int translatedTextFontSize,
LyricsFontWeight fontWeight,
string fontFamilyCJK, string fontFamilyWestern,
double maxWidth, double maxHeight, TextAlignmentType type)
{
DisposeTextLayout();
if (createPhonetic && PhoneticText != "")
{
PhoneticCanvasTextLayout = new CanvasTextLayout(control, PhoneticText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = phoneticTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment(),
};
PhoneticCanvasTextLayout.SetFontFamily(PhoneticText, fontFamilyCJK, fontFamilyWestern);
}
OriginalCanvasTextLayout = new CanvasTextLayout(control, OriginalText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = originalTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
};
OriginalCanvasTextLayout.SetFontFamily(OriginalText, fontFamilyCJK, fontFamilyWestern);
if (createTranslated && TranslatedText != "")
{
TranslatedCanvasTextLayout = new CanvasTextLayout(control, TranslatedText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = translatedTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
};
TranslatedCanvasTextLayout.SetFontFamily(TranslatedText, fontFamilyCJK, fontFamilyWestern);
}
}
public void DisposeTextGeometry()
{
PhoneticCanvasGeometry?.Dispose();
PhoneticCanvasGeometry = null;
OriginalCanvasGeometry?.Dispose();
OriginalCanvasGeometry = null;
TranslatedCanvasGeometry?.Dispose();
TranslatedCanvasGeometry = null;
}
public void RecreateTextGeometry()
{
DisposeTextGeometry();
if (PhoneticCanvasTextLayout != null)
{
PhoneticCanvasGeometry = CanvasGeometry.CreateText(PhoneticCanvasTextLayout);
}
if (OriginalCanvasTextLayout != null)
{
OriginalCanvasGeometry = CanvasGeometry.CreateText(OriginalCanvasTextLayout);
}
if (TranslatedCanvasTextLayout != null)
{
TranslatedCanvasGeometry = CanvasGeometry.CreateText(TranslatedCanvasTextLayout);
}
}
}
}

View File

@@ -10,7 +10,7 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider Provider { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMatchingThresholdOverwritten { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 0;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 40;
public LyricsSearchProviderInfo() { }

View File

@@ -3,12 +3,15 @@ using BetterLyrics.WinUI3.Extensions;
using CommunityToolkit.Mvvm.ComponentModel;
using NTextCat.Commons;
using System;
using System.Text.Json.Serialization;
namespace BetterLyrics.WinUI3.Models
{
public partial class LyricsSearchResult : ObservableObject, ICloneable
{
public LyricsSearchProvider Provider { get; set; }
[ObservableProperty] public partial TranslationSearchProvider? TranslationProvider { get; set; }
[ObservableProperty] public partial TransliterationSearchProvider? TransliterationProvider { get; set; }
public string? Raw { get; set; }
@@ -31,11 +34,11 @@ namespace BetterLyrics.WinUI3.Models
public string? SelfPath { get; set; }
public bool IsFound => !string.IsNullOrEmpty(Raw);
[JsonIgnore] public bool IsFound => !string.IsNullOrEmpty(Raw);
public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;
[JsonIgnore] public LyricsSearchProvider? ProviderIfFound => IsFound ? Provider : null;
public string? DisplayArtists => Artists?.Join("; ");
[JsonIgnore] public string? DisplayArtists => Artists?.Join("; ");
public object Clone()
{

View File

@@ -3,7 +3,11 @@ using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using System;
using System.Linq;
using System.Text.Json.Serialization;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Models
@@ -14,11 +18,17 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty] public partial bool IsDefault { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string MonitorDeviceName { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsWorkArea { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsBorderless { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAlwaysOnTop { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAlwaysOnTopPolling { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsShownInSwitchers { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsClickThrough { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLocked { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAlwaysHideUnlockButton { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsPinToTaskbar { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TaskbarPlacement TaskbarPlacement { get; set; } = TaskbarPlacement.Right;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMaximized { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFullscreen { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsLayoutOrientation LyricsLayoutOrientation { get; set; } = LyricsLayoutOrientation.Horizontal;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsDisplayType LyricsDisplayType { get; set; } = LyricsDisplayType.SplitView;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Rect WindowBounds { get; set; } = new Rect(100, 100, 800, 500);
@@ -37,77 +47,92 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoShowOrHideWindow { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TitleBarArea TitleBarArea { get; set; } = TitleBarArea.Top;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowX { get; set; } = 100;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowY { get; set; } = 100;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowWidth { get; set; } = 800;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double WindowHeight { get; set; } = 500;
[JsonIgnore][ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsOpened { get; set; } = false;
[JsonIgnore] public DispatcherQueueTimer? VisibilityTimer { get; set; }
public LyricsWindowStatus()
{
UpdateMonitorNameAndBounds();
LyricsStyleSettings.PropertyChanged += LyricsStyleSettings_PropertyChanged;
LyricsEffectSettings.PropertyChanged += LyricsEffectSettings_PropertyChanged;
LyricsBackgroundSettings.PropertyChanged += LyricsBackgroundSettings_PropertyChanged;
AlbumArtLayoutSettings.PropertyChanged += AlbumArtLayoutSettings_PropertyChanged;
AlbumArtAreaEffectSettings.PropertyChanged += AlbumArtAreaEffectSettings_PropertyChanged;
}
public LyricsWindowStatus(Window? targetWindow = null) : this()
{
UpdateMonitorNameAndBounds(targetWindow);
UpdateDemoWindowAndMonitorBounds();
}
partial void OnLyricsStyleSettingsChanged(LyricsStyleSettings oldValue, LyricsStyleSettings newValue)
{
oldValue.PropertyChanged -= OldLyricsStyleSettings_PropertyChanged;
newValue.PropertyChanged += OldLyricsStyleSettings_PropertyChanged;
oldValue.PropertyChanged -= LyricsStyleSettings_PropertyChanged;
newValue.PropertyChanged += LyricsStyleSettings_PropertyChanged;
}
private void LyricsStyleSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(LyricsStyleSettings));
}
partial void OnLyricsEffectSettingsChanged(LyricsEffectSettings oldValue, LyricsEffectSettings newValue)
{
oldValue.PropertyChanged -= OldLyricsEffectSettings_PropertyChanged;
newValue.PropertyChanged += OldLyricsEffectSettings_PropertyChanged;
oldValue.PropertyChanged -= LyricsEffectSettings_PropertyChanged;
newValue.PropertyChanged += LyricsEffectSettings_PropertyChanged;
}
private void LyricsEffectSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(LyricsEffectSettings));
}
partial void OnLyricsBackgroundSettingsChanged(LyricsBackgroundSettings oldValue, LyricsBackgroundSettings newValue)
{
oldValue.PropertyChanged -= OldLyricsBackgroundSettings_PropertyChanged;
newValue.PropertyChanged += OldLyricsBackgroundSettings_PropertyChanged;
oldValue.PropertyChanged -= LyricsBackgroundSettings_PropertyChanged;
newValue.PropertyChanged += LyricsBackgroundSettings_PropertyChanged;
}
private void LyricsBackgroundSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(LyricsBackgroundSettings));
}
partial void OnAlbumArtLayoutSettingsChanged(AlbumArtAreaStyleSettings oldValue, AlbumArtAreaStyleSettings newValue)
{
oldValue.PropertyChanged -= OldAlbumArtLayoutSettings_PropertyChanged;
newValue.PropertyChanged += OldAlbumArtLayoutSettings_PropertyChanged;
oldValue.PropertyChanged -= AlbumArtLayoutSettings_PropertyChanged;
newValue.PropertyChanged += AlbumArtLayoutSettings_PropertyChanged;
}
private void AlbumArtLayoutSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(AlbumArtLayoutSettings));
}
partial void OnAlbumArtAreaEffectSettingsChanged(AlbumArtAreaEffectSettings oldValue, AlbumArtAreaEffectSettings newValue)
{
oldValue.PropertyChanged -= AlbumArtAreaEffectSettings_PropertyChanged;
newValue.PropertyChanged += AlbumArtAreaEffectSettings_PropertyChanged;
}
private void AlbumArtAreaEffectSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(AlbumArtAreaEffectSettings));
}
partial void OnWindowBoundsChanged(Rect value)
{
UpdateMonitorNameAndBounds();
UpdateDemoWindowAndMonitorBounds();
WindowX = WindowBounds.X;
WindowY = WindowBounds.Y;
WindowWidth = WindowBounds.Width;
WindowHeight = WindowBounds.Height;
}
private void OldLyricsStyleSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
private void UpdateMonitorNameAndBounds(Window? targetWindow = null)
{
this.OnPropertyChanged(nameof(LyricsStyleSettings));
}
targetWindow ??= WindowHook.GetWindows<NowPlayingWindow>().FirstOrDefault(x => x.LyricsWindowStatus == this);
if (targetWindow == null) return;
private void OldLyricsEffectSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.OnPropertyChanged(nameof(LyricsEffectSettings));
}
private void OldLyricsBackgroundSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.OnPropertyChanged(nameof(LyricsBackgroundSettings));
}
private void OldAlbumArtLayoutSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.OnPropertyChanged(nameof(AlbumArtLayoutSettings));
}
public void UpdateMonitorNameAndBounds()
{
var lyricsWindow = WindowHook.GetWindow<NowPlayingWindow>();
if (lyricsWindow == null) return;
var mointor = MonitorHook.GetMonitorInfoExFromWindow(lyricsWindow);
var mointor = MonitorHook.GetMonitorInfoExFromWindow(targetWindow);
MonitorDeviceName = mointor.szDevice;
MonitorBounds = new Rect(
mointor.rcMonitor.Left,
@@ -166,17 +191,24 @@ namespace BetterLyrics.WinUI3.Models
public object Clone()
{
return new LyricsWindowStatus
return new LyricsWindowStatus(null)
{
Name = this.Name,
IsDefault = this.IsDefault,
MonitorDeviceName = this.MonitorDeviceName,
IsWorkArea = this.IsWorkArea,
IsBorderless = this.IsBorderless,
IsAlwaysOnTop = this.IsAlwaysOnTop,
IsAlwaysOnTopPolling = this.IsAlwaysOnTopPolling,
IsShownInSwitchers = this.IsShownInSwitchers,
IsClickThrough = this.IsClickThrough,
IsLocked = this.IsLocked,
IsAlwaysHideUnlockButton = this.IsAlwaysHideUnlockButton,
IsPinToTaskbar = this.IsPinToTaskbar,
TaskbarPlacement = this.TaskbarPlacement,
IsMaximized = this.IsMaximized,
IsFullscreen = this.IsFullscreen,
LyricsLayoutOrientation = this.LyricsLayoutOrientation,
LyricsDisplayType = this.LyricsDisplayType,
WindowBounds = this.WindowBounds,
@@ -196,11 +228,6 @@ namespace BetterLyrics.WinUI3.Models
EnvironmentSampleMode = this.EnvironmentSampleMode,
AutoShowOrHideWindow = this.AutoShowOrHideWindow,
TitleBarArea = this.TitleBarArea,
WindowX = this.WindowX,
WindowY = this.WindowY,
WindowWidth = this.WindowWidth,
WindowHeight = this.WindowHeight,
};
}

View File

@@ -6,6 +6,7 @@ using BetterLyrics.WinUI3.Helper;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Linq;
using System.Text.Json.Serialization;
namespace BetterLyrics.WinUI3.Models
{
@@ -32,13 +33,13 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; } = [.. Enum.GetValues<AlbumArtSearchProvider>().Select(p => new AlbumArtSearchProviderInfo(p, true))];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchType LyricsSearchType { get; set; } = LyricsSearchType.Sequential;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 0;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 40;
public string LogoPath => PlayerIDHelper.GetLogoPath(Provider);
[JsonIgnore] public string LogoPath => PlayerIDHelper.GetLogoPath(Provider);
public string? DisplayName => PlayerIDHelper.GetDisplayName(Provider);
[JsonIgnore] public string? DisplayName => PlayerIDHelper.GetDisplayName(Provider);
public bool IsLXMusic => PlayerIDHelper.IsLXMusic(Provider);
[JsonIgnore] public bool IsLXMusic => PlayerIDHelper.IsLXMusic(Provider);
public MediaSourceProviderInfo()
{

View File

@@ -0,0 +1,225 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using System.Numerics;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models
{
public class RenderLyricsLine : LyricsLine
{
public double AnimationDuration { get; set; } = 0.3;
public ValueTransition<double> AngleTransition { get; set; }
public ValueTransition<double> BlurAmountTransition { get; set; }
public ValueTransition<double> PhoneticOpacityTransition { get; set; }
public ValueTransition<double> PlayedOriginalOpacityTransition { get; set; }
public ValueTransition<double> UnplayedOriginalOpacityTransition { get; set; }
public ValueTransition<double> TranslatedOpacityTransition { get; set; }
public ValueTransition<double> ScaleTransition { get; set; }
public ValueTransition<double> YOffsetTransition { get; set; }
public ValueTransition<Color> ColorTransition { get; set; }
public CanvasTextLayout? OriginalCanvasTextLayout { get; private set; }
public CanvasTextLayout? TranslatedCanvasTextLayout { get; private set; }
public CanvasTextLayout? PhoneticCanvasTextLayout { get; private set; }
/// <summary>
/// 原文坐标(相对于坐标原点)
/// </summary>
public Vector2 OriginalPosition { get; set; }
/// <summary>
/// 译文坐标(相对于坐标原点)
/// </summary>
public Vector2 TranslatedPosition { get; set; }
/// <summary>
/// 注音坐标(相对于坐标原点)
/// </summary>
public Vector2 PhoneticPosition { get; set; }
/// <summary>
/// 顶部坐标(相对于坐标原点)
/// </summary>
public Vector2 TopLeftPosition { get; set; }
/// <summary>
/// 中心坐标(相对于坐标原点)
/// </summary>
public Vector2 CenterPosition { get; private set; }
/// <summary>
/// 底部坐标(相对于坐标原点)
/// </summary>
public Vector2 BottomRightPosition { get; set; }
public CanvasGeometry? OriginalCanvasGeometry { get; private set; }
public CanvasGeometry? TranslatedCanvasGeometry { get; private set; }
public CanvasGeometry? PhoneticCanvasGeometry { get; private set; }
public RenderLyricsLine()
{
AngleTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
BlurAmountTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
PhoneticOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
PlayedOriginalOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
UnplayedOriginalOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
TranslatedOpacityTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
ScaleTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
YOffsetTransition = new(
initialValue: 0,
durationSeconds: AnimationDuration,
easingType: EasingType.EaseInOutSine
);
ColorTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
}
public void UpdateCenterPosition(double maxWidth, TextAlignmentType type)
{
if (OriginalCanvasTextLayout == null)
{
return;
}
double centerY = (TopLeftPosition.Y + BottomRightPosition.Y) / 2;
CenterPosition = type switch
{
TextAlignmentType.Left => new Vector2(0, (float)centerY),
TextAlignmentType.Center => new Vector2((float)(0 + maxWidth / 2.0), (float)centerY),
TextAlignmentType.Right => new Vector2((float)(0 + maxWidth), (float)centerY),
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
};
}
public void DisposeTextLayout()
{
PhoneticCanvasTextLayout?.Dispose();
PhoneticCanvasTextLayout = null;
OriginalCanvasTextLayout?.Dispose();
OriginalCanvasTextLayout = null;
TranslatedCanvasTextLayout?.Dispose();
TranslatedCanvasTextLayout = null;
}
public void RecreateTextLayout(
ICanvasAnimatedControl control,
bool createPhonetic, bool createTranslated,
int phoneticTextFontSize, int originalTextFontSize, int translatedTextFontSize,
LyricsFontWeight fontWeight,
string fontFamilyCJK, string fontFamilyWestern,
double maxWidth, double maxHeight, TextAlignmentType type)
{
DisposeTextLayout();
if (createPhonetic && PhoneticText != "")
{
PhoneticCanvasTextLayout = new CanvasTextLayout(control, PhoneticText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = phoneticTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment(),
};
PhoneticCanvasTextLayout.SetFontFamily(PhoneticText, fontFamilyCJK, fontFamilyWestern);
}
OriginalCanvasTextLayout = new CanvasTextLayout(control, OriginalText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = originalTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
};
OriginalCanvasTextLayout.SetFontFamily(OriginalText, fontFamilyCJK, fontFamilyWestern);
if (createTranslated && TranslatedText != "")
{
TranslatedCanvasTextLayout = new CanvasTextLayout(control, TranslatedText, new CanvasTextFormat
{
HorizontalAlignment = CanvasHorizontalAlignment.Left,
VerticalAlignment = CanvasVerticalAlignment.Top,
FontSize = translatedTextFontSize,
FontWeight = fontWeight.ToFontWeight(),
}, (float)maxWidth, (float)maxHeight)
{
HorizontalAlignment = type.ToCanvasHorizontalAlignment()
};
TranslatedCanvasTextLayout.SetFontFamily(TranslatedText, fontFamilyCJK, fontFamilyWestern);
}
}
public void DisposeTextGeometry()
{
PhoneticCanvasGeometry?.Dispose();
PhoneticCanvasGeometry = null;
OriginalCanvasGeometry?.Dispose();
OriginalCanvasGeometry = null;
TranslatedCanvasGeometry?.Dispose();
TranslatedCanvasGeometry = null;
}
public void RecreateTextGeometry()
{
DisposeTextGeometry();
if (PhoneticCanvasTextLayout != null)
{
PhoneticCanvasGeometry = CanvasGeometry.CreateText(PhoneticCanvasTextLayout);
}
if (OriginalCanvasTextLayout != null)
{
OriginalCanvasGeometry = CanvasGeometry.CreateText(OriginalCanvasTextLayout);
}
if (TranslatedCanvasTextLayout != null)
{
TranslatedCanvasGeometry = CanvasGeometry.CreateText(TranslatedCanvasTextLayout);
}
}
}
}

View File

@@ -1,8 +1,6 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models.Settings
{

View File

@@ -13,13 +13,15 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ExitOnLyricsWindowClosed { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ListenOnNewPlaybackSource { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IgnoreCacheWhenSearching { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> BorderlessShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "B" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ClickThroughShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "C" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> LyricsWindowSwitchShortcut { get; set; } = new List<string>() { "Ctrl", "Alt", "S" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> PlayOrPauseShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "P" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> NextSongShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "Right" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> PreviousSongShortcut { get; set; } = new List<string> { "Ctrl", "Alt", "Left" };
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool MultiNowPlayingWindowMode { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoStartLyricsWindow { get; set; } = true;
public GeneralSettings() { }
}
}

View File

@@ -12,6 +12,11 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsPureColorOverlayEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PureColorOverlayOpacity { get; set; } = 100; // 100 % = 1.0
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsCoverOverlayEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlayOpacity { get; set; } = 100; // 100 % = 1.0
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlaySpeed { get; set; } = 50;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlayBlurAmount { get; set; } = 100;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFluidOverlayEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int FluidOverlayOpacity { get; set; } = 100;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial PaletteGeneratorType PaletteGeneratorType { get; set; } = PaletteGeneratorType.MedianCut;
@@ -39,14 +44,25 @@ namespace BetterLyrics.WinUI3.Models.Settings
IsPureColorOverlayEnabled = this.IsPureColorOverlayEnabled,
PureColorOverlayOpacity = this.PureColorOverlayOpacity,
IsCoverOverlayEnabled = this.IsCoverOverlayEnabled,
CoverOverlayOpacity = this.CoverOverlayOpacity,
CoverOverlaySpeed = this.CoverOverlaySpeed,
CoverOverlayBlurAmount = this.CoverOverlayBlurAmount,
IsFluidOverlayEnabled = this.IsFluidOverlayEnabled,
FluidOverlayOpacity = this.FluidOverlayOpacity,
PaletteGeneratorType = this.PaletteGeneratorType,
IsSpectrumOverlayEnabled = this.IsSpectrumOverlayEnabled,
SpectrumPlacement = this.SpectrumPlacement,
SpectrumStyle = this.SpectrumStyle,
SpectrumCount = this.SpectrumCount,
IsSnowFlakeOverlayEnabled = this.IsSnowFlakeOverlayEnabled,
SnowFlakeOverlayAmount = this.SnowFlakeOverlayAmount,
SnowFlakeOverlaySpeed = this.SnowFlakeOverlaySpeed,
IsFogOverlayEnabled = this.IsFogOverlayEnabled,
};
}
}

View File

@@ -7,9 +7,21 @@ namespace BetterLyrics.WinUI3.Models.Settings
public partial class LyricsEffectSettings : ObservableRecipient, ICloneable
{
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsBlurEffectEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsGlowEffectEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsEffectScope LyricsGlowEffectScope { get; set; } = LyricsEffectScope.LongDurationSyllable;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsGlowEffectLongSyllableDuration { get; set; } = 700; // 700ms
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsGlowEffectAmountAutoAdjust { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsGlowEffectAmount { get; set; } = 8;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsScaleEffectEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScaleEffectLongSyllableDuration { get; set; } = 700; // 700ms
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsScaleEffectAmountAutoAdjust { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScaleEffectAmount { get; set; } = 115; // 115%
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFloatAnimationEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFloatAnimationAmountAutoAdjust { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFloatAnimationAmount { get; set; } = 8;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial EasingType LyricsScrollEasingType { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollDuration { get; set; }
@@ -40,9 +52,21 @@ namespace BetterLyrics.WinUI3.Models.Settings
return new LyricsEffectSettings(this.LyricsScrollTopDuration, this.LyricsScrollDuration, this.LyricsScrollBottomDuration, this.LyricsScrollEasingType)
{
IsLyricsBlurEffectEnabled = this.IsLyricsBlurEffectEnabled,
IsLyricsGlowEffectEnabled = this.IsLyricsGlowEffectEnabled,
LyricsGlowEffectLongSyllableDuration = this.LyricsGlowEffectLongSyllableDuration,
IsLyricsGlowEffectAmountAutoAdjust = this.IsLyricsGlowEffectAmountAutoAdjust,
LyricsGlowEffectAmount = this.LyricsGlowEffectAmount,
LyricsGlowEffectScope = this.LyricsGlowEffectScope,
IsLyricsScaleEffectEnabled = this.IsLyricsScaleEffectEnabled,
LyricsScaleEffectLongSyllableDuration = this.LyricsScaleEffectLongSyllableDuration,
IsLyricsScaleEffectAmountAutoAdjust = this.IsLyricsScaleEffectAmountAutoAdjust,
LyricsScaleEffectAmount = this.LyricsScaleEffectAmount,
IsLyricsFloatAnimationEnabled = this.IsLyricsFloatAnimationEnabled,
IsLyricsFloatAnimationAmountAutoAdjust = this.IsLyricsFloatAnimationAmountAutoAdjust,
LyricsFloatAnimationAmount = this.LyricsFloatAnimationAmount,
LyricsScrollEasingType = this.LyricsScrollEasingType,
LyricsScrollDuration = this.LyricsScrollDuration,

View File

@@ -15,6 +15,10 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int OriginalLyricsFontSize { get; set; } = 24;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int TranslatedLyricsFontSize { get; set; } = 12;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PhoneticLyricsOpacity { get; set; } = 60; // 60 %
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int OriginalLyricsOpacity { get; set; } = 30; // 30 %
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int TranslatedLyricsOpacity { get; set; } = 60; // 60 %
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TextAlignmentType LyricsAlignmentType { get; set; } = TextAlignmentType.Left;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFontStrokeWidth { get; set; } = 0;
@@ -30,8 +34,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial double LyricsLineSpacingFactor { get; set; } = 0.5;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsCJKFontFamily { get; set; } = FontHelper.SystemFontFamilies.FirstOrDefault() ?? "";
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsWesternFontFamily { get; set; } = FontHelper.SystemFontFamilies.FirstOrDefault() ?? "";
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsCJKFontFamily { get; set; } = "Arial";
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LyricsWesternFontFamily { get; set; } = "Arial";
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PlayingLineTopOffset { get; set; } = 50; // 50 %
@@ -45,6 +49,11 @@ namespace BetterLyrics.WinUI3.Models.Settings
PhoneticLyricsFontSize = this.PhoneticLyricsFontSize,
OriginalLyricsFontSize = this.OriginalLyricsFontSize,
TranslatedLyricsFontSize = this.TranslatedLyricsFontSize,
PhoneticLyricsOpacity = this.PhoneticLyricsOpacity,
OriginalLyricsOpacity = this.OriginalLyricsOpacity,
TranslatedLyricsOpacity = this.TranslatedLyricsOpacity,
LyricsAlignmentType = this.LyricsAlignmentType,
LyricsFontStrokeWidth = this.LyricsFontStrokeWidth,
LyricsCustomBgFontColor = this.LyricsCustomBgFontColor,

View File

@@ -11,6 +11,9 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PlayQueueIndex { get; set; } = -1;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoOpen { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool AutoPlay { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsWindowStatus LyricsWindowStatus { get; set; } = new();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ExitOnWindowClosed { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool StopOnWindowClosed { get; set; } = false;
public MusicGallerySettings() { }
}

View File

@@ -10,10 +10,10 @@ namespace BetterLyrics.WinUI3.Models.Settings
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string LibreTranslateServer { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsTranslationEnabled { get; set; } = true;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string SelectedTargetLanguageCode { get; set; } = LanguageHelper.GetDefaultTargetTranslationLanguageCode();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsJyutpingEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial ChineseRomanization ChineseRomanization { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsChineseRomanizationEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsJapaneseRomanizationEnabled { get; set; } = false;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string CutletDockerServer { get; set; } = string.Empty;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsTraditionalChineseEnabled { get; set; } = false;
public TranslationSettings() { }

View File

@@ -73,7 +73,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
if (single)
{
LyricsDataArr.Add(new LyricsData(lrcLines));
_lyricsDataArr.Add(new LyricsData(lrcLines));
}
else
{
@@ -87,8 +87,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
}
// 初始化每种语言的歌词列表
int langStartIndex = LyricsDataArr.Count;
for (int i = 0; i < languageCount; i++) LyricsDataArr.Add(new LyricsData());
int langStartIndex = _lyricsDataArr.Count;
for (int i = 0; i < languageCount; i++) _lyricsDataArr.Add(new LyricsData());
// 遍历每个时间分组
if (grouped != null)
@@ -102,7 +102,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
if (langIdx < linesInGroup.Count)
{
var lyricsLine = linesInGroup[langIdx];
LyricsDataArr[langStartIndex + langIdx].LyricsLines.Add(lyricsLine);
_lyricsDataArr[langStartIndex + langIdx].LyricsLines.Add(lyricsLine);
}
// 没有翻译行则不补原文,直接跳过
}

View File

@@ -52,7 +52,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
}
}
LyricsDataArr.Add(new LyricsData(lyricsLines));
_lyricsDataArr.Add(new LyricsData(lyricsLines));
}
}

View File

@@ -85,14 +85,14 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
ParseTtmlXRole(spans, romanLines, "x-roman", pStartMs, pEndMs);
}
LyricsDataArr.Add(new LyricsData(originalLines));
_lyricsDataArr.Add(new LyricsData(originalLines));
if (translationLines.Count > 0)
{
LyricsDataArr.Add(new LyricsData(translationLines));
_lyricsDataArr.Add(new LyricsData(translationLines));
}
if (romanLines.Count > 0)
{
LyricsDataArr.Add(new LyricsData(romanLines) { LanguageCode = PhoneticHelper.RomanCode });
_lyricsDataArr.Add(new LyricsData(romanLines) { LanguageCode = PhoneticHelper.RomanCode });
}
}
catch

View File

@@ -4,11 +4,19 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.TranslationService;
using BetterLyrics.WinUI3.Services.TransliterationService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Helpers.General;
using Lyricify.Lyrics.Parsers;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
@@ -16,15 +24,19 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
private static readonly ILogger<LyricsParser> _logger = Ioc.Default.GetRequiredService<ILogger<LyricsParser>>();
public List<LyricsData> LyricsDataArr { get; private set; } = [];
private List<LyricsData> _lyricsDataArr = [];
public void Parse(SongInfo? songInfo, LyricsSearchResult? lyricsSearchResult)
public LyricsParser()
{
}
public List<LyricsData> Parse(LyricsSearchResult? lyricsSearchResult)
{
_logger.LogInformation("LyricsParser.Parse");
LyricsDataArr = [];
_lyricsDataArr = [];
if (string.IsNullOrWhiteSpace(lyricsSearchResult?.Raw))
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
_lyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
}
else
{
@@ -47,14 +59,125 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
break;
}
if (LyricsDataArr.Count == 0)
if (_lyricsDataArr.Count == 0)
{
LyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
_lyricsDataArr.Add(LyricsData.GetNotfoundPlaceholder());
}
}
LoadTranslation(lyricsSearchResult);
LoadTransliteration(lyricsSearchResult);
GenerateTransliterationLyricsData();
return _lyricsDataArr;
}
public async Task<(LyricsData, TransliterationSearchProvider?, TranslationSearchProvider?)> Parse(
ITranslationService translationService,
ITransliterationService transliterationService,
TranslationSettings settings,
LyricsSearchResult? lyricsSearchResult,
CancellationToken token
)
{
TransliterationSearchProvider? transliterationSearchProvider = null;
TranslationSearchProvider? translationSearchProvider = null;
Parse(lyricsSearchResult);
var main = _lyricsDataArr.First();
// 应用音译
LyricsData? phoneticLyricsData = null;
// 已解析歌词内寻找
if (settings.IsChineseRomanizationEnabled && main.LanguageCode == LanguageHelper.ChineseCode)
{
phoneticLyricsData = settings.ChineseRomanization switch
{
ChineseRomanization.Pinyin => _lyricsDataArr.FirstOrDefault(x => x.LanguageCode == PhoneticHelper.PinyinCode),
ChineseRomanization.Jyutping => _lyricsDataArr.FirstOrDefault(x => x.LanguageCode == PhoneticHelper.JyutpingCode),
_ => null,
};
if (phoneticLyricsData != null)
{
main.SetPhoneticText(phoneticLyricsData);
if (phoneticLyricsData.AutoGenerated)
{
transliterationSearchProvider = TransliterationSearchProvider.BetterLyrics;
}
else
{
transliterationSearchProvider = lyricsSearchResult?.Provider.ToTransliterationSearchProvider();
}
}
}
else if (settings.IsJapaneseRomanizationEnabled && main.LanguageCode == LanguageHelper.JapaneseCode)
{
phoneticLyricsData = _lyricsDataArr.FirstOrDefault(x => x.LanguageCode == PhoneticHelper.RomanCode);
if (phoneticLyricsData != null)
{
main.SetPhoneticText(phoneticLyricsData);
transliterationSearchProvider = lyricsSearchResult?.Provider.ToTransliterationSearchProvider();
}
else
{
string romaji = string.Empty;
try
{
romaji = await transliterationService.TransliterateText(main.WrappedOriginalText, PhoneticHelper.RomanCode, token);
_lyricsDataArr.FirstOrDefault()?.SetTransliteration(romaji);
transliterationSearchProvider = TransliterationSearchProvider.CutletDocker;
}
catch (TaskCanceledException) { }
catch (Exception)
{
ToastHelper.ShowToast("CutletDockerFailed", null, InfoBarSeverity.Error);
}
}
}
// 应用翻译
if (settings.IsTranslationEnabled && main.LanguageCode != settings.SelectedTargetLanguageCode)
{
var found = _lyricsDataArr.FirstOrDefault(x => x.LanguageCode == settings.SelectedTargetLanguageCode);
if (found != null)
{
main.SetTranslatedText(found);
translationSearchProvider = lyricsSearchResult?.Provider.ToTranslationSearchProvider();
}
else if (settings.IsLibreTranslateEnabled)
{
string translated = string.Empty;
try
{
translated = await translationService.TranslateTextAsync(main.WrappedOriginalText, settings.SelectedTargetLanguageCode, token);
_lyricsDataArr.FirstOrDefault()?.SetTranslation(translated);
translationSearchProvider = TranslationSearchProvider.LibreTranslate;
}
catch (TaskCanceledException) { }
catch (Exception)
{
ToastHelper.ShowToast("LibreTranslateFailed", null, InfoBarSeverity.Error);
}
}
}
// 应用简体中文/繁体中文
if (main.LanguageCode == LanguageHelper.ChineseCode)
{
foreach (var item in main.LyricsLines)
{
item.OriginalText = settings.IsTraditionalChineseEnabled ? ChineseHelper.ToTC(item.OriginalText) : ChineseHelper.ToSC(item.OriginalText);
}
}
if (settings.SelectedTargetLanguageCode == LanguageHelper.ChineseCode)
{
foreach (var item in main.LyricsLines)
{
item.TranslatedText = settings.IsTraditionalChineseEnabled ? ChineseHelper.ToTC(item.TranslatedText) : ChineseHelper.ToSC(item.TranslatedText);
}
}
return (main, transliterationSearchProvider, translationSearchProvider);
}
private void LoadTranslation(LyricsSearchResult? lyricsSearchResult)
@@ -82,7 +205,7 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
{
case LyricsSearchProvider.Netease:
ParseLrc(lyricsSearchResult.Transliteration, true);
LyricsDataArr.LastOrDefault()?.LanguageCode = PhoneticHelper.RomanCode;
_lyricsDataArr.LastOrDefault()?.LanguageCode = PhoneticHelper.RomanCode;
break;
default:
break;
@@ -95,15 +218,15 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
/// </summary>
private void GenerateTransliterationLyricsData()
{
var main = LyricsDataArr.FirstOrDefault();
var main = _lyricsDataArr.FirstOrDefault();
if (main != null)
{
string? languageCode = main.LanguageCode;
if (languageCode == LanguageHelper.ChineseCode)
{
if (!LyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.PinyinCode))
if (!_lyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.PinyinCode))
{
LyricsDataArr.Add(new LyricsData
_lyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.PinyinCode,
AutoGenerated = true,
@@ -122,9 +245,9 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
}).ToList()
});
}
if (!LyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.JyutpingCode))
if (!_lyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.JyutpingCode))
{
LyricsDataArr.Add(new LyricsData
_lyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.JyutpingCode,
AutoGenerated = true,
@@ -144,31 +267,8 @@ namespace BetterLyrics.WinUI3.Parsers.LyricsParser
});
}
}
else if (languageCode == LanguageHelper.JapaneseCode)
{
if (!LyricsDataArr.Any(x => x.LanguageCode == PhoneticHelper.RomanCode))
{
LyricsDataArr.Add(new LyricsData
{
LanguageCode = PhoneticHelper.RomanCode,
AutoGenerated = true,
LyricsLines = main.LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
OriginalText = PhoneticHelper.ToRomaji(line.OriginalText),
LyricsSyllables = line.LyricsSyllables.Select(c => new LyricsSyllable
{
StartMs = c.StartMs,
EndMs = c.EndMs,
Text = PhoneticHelper.ToRomaji(c.Text),
StartIndex = c.StartIndex
}).ToList()
}).ToList()
});
}
}
}
}
}
}

View File

@@ -0,0 +1,249 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Numerics;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.Renderer
{
public partial class CoverBackgroundRenderer : IDisposable
{
private CanvasBitmap? _currentBitmap;
private CanvasBitmap? _previousBitmap;
private CanvasRenderTarget? _currentTargetCache;
private CanvasRenderTarget? _previousTargetCache;
private Size _lastScreenSize;
private bool _lastWasRotating = false;
private readonly ValueTransition<double> _crossfadeTransition;
private float _rotationAngle = 0f;
public bool IsEnabled { get; set; } = false;
public int Opacity { get; set; } = 100;
private bool _needsCacheUpdate = false;
private int _blurAmount = 100;
public int BlurAmount
{
get => _blurAmount;
set
{
if (_blurAmount != value)
{
_blurAmount = value;
_needsCacheUpdate = true;
}
}
}
private int _speed = 100;
public int Speed
{
get => _speed;
set
{
if (_speed != value)
{
_speed = value;
_needsCacheUpdate = true;
}
}
}
public CoverBackgroundRenderer()
{
_crossfadeTransition = new ValueTransition<double>(1.0, 0.7, easingType: EasingType.Linear);
}
public void SetCoverBitmap(CanvasBitmap? newBitmap)
{
if (_currentBitmap == newBitmap) return;
_previousBitmap = _currentBitmap;
_previousTargetCache = _currentTargetCache;
_currentTargetCache = null;
_currentBitmap = newBitmap;
if (_currentBitmap == null)
{
_crossfadeTransition.StartTransition(1.0, jumpTo: true);
}
else
{
if (_previousBitmap == null)
{
_crossfadeTransition.StartTransition(1.0, jumpTo: true);
}
else
{
_crossfadeTransition.Reset(0.0);
_crossfadeTransition.StartTransition(1.0);
}
}
_needsCacheUpdate = true;
}
public void Update(TimeSpan deltaTime)
{
if (!IsEnabled) return;
_crossfadeTransition.Update(deltaTime);
if (Speed > 0)
{
float baseSpeed = 0.6f;
float currentSpeed = (Speed / 100.0f) * baseSpeed;
_rotationAngle += currentSpeed * (float)deltaTime.TotalSeconds;
_rotationAngle %= (float)(2 * Math.PI);
}
if (_crossfadeTransition.Value >= 1.0 && _previousBitmap != null)
{
_previousBitmap = null;
_previousTargetCache?.Dispose();
_previousTargetCache = null;
}
}
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
if (!IsEnabled || Opacity <= 0) return;
if (_lastScreenSize != control.Size)
{
_lastScreenSize = control.Size;
_needsCacheUpdate = true;
}
bool isRotating = Speed > 0;
if (_lastWasRotating != isRotating)
{
_lastWasRotating = isRotating;
_needsCacheUpdate = true;
}
EnsureCachedLayer(control, _currentBitmap, ref _currentTargetCache);
float baseAlpha = Opacity / 100.0f;
float angle = isRotating ? _rotationAngle : 0f;
double fadeProgress = _crossfadeTransition.Value;
bool isCrossfading = fadeProgress < 1.0 && _previousTargetCache != null;
Vector2 screenCenter = new Vector2((float)control.Size.Width / 2f, (float)control.Size.Height / 2f);
if (isCrossfading)
{
DrawCachedLayer(ds, _previousTargetCache, screenCenter, angle, baseAlpha);
float newLayerAlpha = baseAlpha * (float)fadeProgress;
DrawCachedLayer(ds, _currentTargetCache, screenCenter, angle, newLayerAlpha);
}
else if (_currentTargetCache != null)
{
DrawCachedLayer(ds, _currentTargetCache, screenCenter, angle, baseAlpha);
}
}
private void EnsureCachedLayer(ICanvasResourceCreator resourceCreator, CanvasBitmap? sourceBitmap, ref CanvasRenderTarget? targetCache)
{
if (sourceBitmap == null)
{
targetCache?.Dispose();
targetCache = null;
return;
}
bool deviceMismatch = targetCache != null && targetCache.Device != resourceCreator.Device;
if (_needsCacheUpdate || targetCache == null || deviceMismatch)
{
targetCache?.Dispose();
float imgW = sourceBitmap.SizeInPixels.Width;
float imgH = sourceBitmap.SizeInPixels.Height;
Size screenSize = _lastScreenSize;
float scale;
if (_lastWasRotating) // Speed > 0
{
float screenDiagonal = (float)Math.Sqrt(screenSize.Width * screenSize.Width + screenSize.Height * screenSize.Height);
scale = Math.Max(screenDiagonal / imgW, screenDiagonal / imgH);
}
else
{
float scaleX = (float)screenSize.Width / imgW;
float scaleY = (float)screenSize.Height / imgH;
scale = Math.Max(scaleX, scaleY);
}
float targetW = imgW * scale;
float targetH = imgH * scale;
targetCache = new CanvasRenderTarget(resourceCreator, targetW, targetH, sourceBitmap.Dpi);
using (var ds = targetCache.CreateDrawingSession())
{
ds.Clear(Windows.UI.Color.FromArgb(0, 0, 0, 0));
using (var transformEffect = new Transform2DEffect())
using (var blurEffect = new GaussianBlurEffect())
{
transformEffect.Source = sourceBitmap;
transformEffect.TransformMatrix = Matrix3x2.CreateScale(scale);
transformEffect.InterpolationMode = CanvasImageInterpolation.Linear;
blurEffect.Source = transformEffect;
blurEffect.BlurAmount = BlurAmount;
blurEffect.BorderMode = EffectBorderMode.Hard;
ds.DrawImage(blurEffect);
}
}
if (sourceBitmap == _currentBitmap)
{
_needsCacheUpdate = false;
}
}
}
private void DrawCachedLayer(CanvasDrawingSession ds, CanvasRenderTarget? cachedTexture, Vector2 screenCenter, float rotationRadians, float alpha)
{
if (cachedTexture == null) return;
Vector2 textureCenter = new Vector2((float)cachedTexture.Size.Width / 2f, (float)cachedTexture.Size.Height / 2f);
Matrix3x2 transform =
Matrix3x2.CreateTranslation(-textureCenter) * Matrix3x2.CreateRotation(rotationRadians) * Matrix3x2.CreateTranslation(screenCenter);
Matrix3x2 previousTransform = ds.Transform;
ds.Transform = transform * previousTransform;
ds.DrawImage(cachedTexture, 0, 0, new Rect(0, 0, cachedTexture.Size.Width, cachedTexture.Size.Height), alpha);
ds.Transform = previousTransform;
}
public void Dispose()
{
_currentBitmap?.Dispose();
_previousBitmap?.Dispose();
_currentTargetCache?.Dispose();
_previousTargetCache?.Dispose();
_currentBitmap = null;
_previousBitmap = null;
_currentTargetCache = null;
_previousTargetCache = null;
}
}
}

View File

@@ -63,14 +63,14 @@ namespace BetterLyrics.WinUI3.Renderer
_timeAccumulator += (float)deltaTime.TotalSeconds;
_fluidEffect.Properties["iTime"] = _timeAccumulator;
_fluidEffect?.Properties["iTime"] = _timeAccumulator;
_fluidEffect.Properties["color1"] = _c1;
_fluidEffect.Properties["color2"] = _c2;
_fluidEffect.Properties["color3"] = _c3;
_fluidEffect.Properties["color4"] = _c4;
_fluidEffect?.Properties["color1"] = _c1;
_fluidEffect?.Properties["color2"] = _c2;
_fluidEffect?.Properties["color3"] = _c3;
_fluidEffect?.Properties["color4"] = _c4;
_fluidEffect.Properties["EnableLightWave"] = EnableLightWave;
_fluidEffect?.Properties["EnableLightWave"] = EnableLightWave;
}
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)

View File

@@ -7,6 +7,7 @@ using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Windows.UI;
@@ -23,7 +24,7 @@ namespace BetterLyrics.WinUI3.Renderer
public void Draw(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
LyricsData? lyricsData,
IList<RenderLyricsLine>? lines,
int playingLineIndex,
int mouseHoverLineIndex,
bool isMousePressing,
@@ -53,7 +54,7 @@ namespace BetterLyrics.WinUI3.Renderer
DrawLyrics(
control,
layerDs,
lyricsData,
lines,
playingLineIndex,
mouseHoverLineIndex,
isMousePressing,
@@ -84,7 +85,7 @@ namespace BetterLyrics.WinUI3.Renderer
DrawLyrics(
control,
ds,
lyricsData,
lines,
playingLineIndex,
mouseHoverLineIndex,
isMousePressing,
@@ -108,7 +109,7 @@ namespace BetterLyrics.WinUI3.Renderer
private void DrawLyrics(
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
LyricsData? lyricsData,
IList<RenderLyricsLine>? lines,
int playingLineIndex,
int mouseHoverLineIndex,
bool isMousePressing,
@@ -126,32 +127,38 @@ namespace BetterLyrics.WinUI3.Renderer
Color fgColor,
Func<int, LinePlaybackState> getPlaybackState)
{
if (lyricsData == null) return;
if (lines == null) return;
var currentPlayingLine = lyricsData.LyricsLines.ElementAtOrDefault(playingLineIndex);
var currentPlayingLine = lines.ElementAtOrDefault(playingLineIndex);
if (currentPlayingLine == null) return;
var effectSettings = windowStatus.LyricsEffectSettings;
var styleSettings = windowStatus.LyricsStyleSettings;
var rotationY = currentPlayingLine.OriginalPosition.WithX(effectSettings.FanLyricsAngle < 0 ? (float)lyricsWidth : 0);
var rotationX = effectSettings.FanLyricsAngle < 0 ? lyricsWidth : 0;
rotationX += lyricsWidth / 2 * (effectSettings.FanLyricsAngle < 0 ? 1 : -1);
for (int i = startVisibleIndex; i <= endVisibleIndex; i++)
{
var line = lyricsData.LyricsLines.ElementAtOrDefault(i);
var line = lines.ElementAtOrDefault(i);
if (line == null) continue;
if (line.OriginalCanvasTextLayout == null) continue;
if (line.OriginalCanvasTextLayout.LayoutBounds.Width <= 0) continue;
double xOffset = lyricsX;
double yOffset = line.YOffsetTransition.Value + userScrollOffset + lyricsY + lyricsHeight * playingLineTopOffsetFactor;
var transform =
Matrix3x2.CreateScale((float)line.ScaleTransition.Value, line.CenterPosition) *
Matrix3x2.CreateRotation((float)line.AngleTransition.Value, rotationY) *
Matrix3x2.CreateTranslation((float)lyricsX, (float)yOffset);
ds.Transform = Matrix3x2.CreateScale((float)line.ScaleTransition.Value, line.CenterPosition);
ds.Transform = transform;
if (effectSettings.IsFanLyricsEnabled)
{
xOffset += Math.Abs(line.AngleTransition.Value) / (Math.PI / 2) * lyricsWidth / 2 * (effectSettings.FanLyricsAngle < 0 ? 1 : -1);
var rotationY = line.CenterPosition.Y;
ds.Transform *= Matrix3x2.CreateRotation((float)line.AngleTransition.Value, new Vector2((float)rotationX, rotationY));
}
ds.Transform *= Matrix3x2.CreateTranslation((float)xOffset, (float)yOffset);
using (var textOnlyLayer = RenderBaseTextLayer(control, line, styleSettings.LyricsFontStrokeWidth, strokeColor, line.ColorTransition.Value))
{
@@ -171,7 +178,7 @@ namespace BetterLyrics.WinUI3.Renderer
byte opacity = isMousePressing ? (byte)32 : (byte)16;
double scale = isMousePressing ? 1.09 : 1.10;
ds.FillRoundedRectangle(
new Windows.Foundation.Rect(line.TopLeftPosition.ToPoint(), line.BottomRightPosition.ToPoint()).Scale(scale),
new Windows.Foundation.Rect(line.TopLeftPosition.ToPoint().WithX(0), line.BottomRightPosition.ToPoint().WithX(lyricsWidth)).Scale(scale),
8, 8, Color.FromArgb(opacity, 255, 255, 255));
}
}
@@ -182,7 +189,7 @@ namespace BetterLyrics.WinUI3.Renderer
private CanvasCommandList RenderBaseTextLayer(
ICanvasResourceCreator resourceCreator,
LyricsLine line,
RenderLyricsLine line,
double strokeWidth,
Color strokeColor,
Color fillColor)

View File

@@ -20,7 +20,7 @@ namespace BetterLyrics.WinUI3.Renderer
ICanvasAnimatedControl control,
CanvasDrawingSession ds,
ICanvasImage textOnlyLayer,
LyricsLine line,
RenderLyricsLine line,
LinePlaybackState playbackState,
Color bgColor,
Color fgColor,
@@ -31,7 +31,7 @@ namespace BetterLyrics.WinUI3.Renderer
DrawTranslated(ds, textOnlyLayer, line);
}
private void DrawPhonetic(CanvasDrawingSession ds, ICanvasImage source, LyricsLine line)
private void DrawPhonetic(CanvasDrawingSession ds, ICanvasImage source, RenderLyricsLine line)
{
if (line.PhoneticCanvasTextLayout == null) return;
@@ -63,7 +63,7 @@ namespace BetterLyrics.WinUI3.Renderer
});
}
private void DrawTranslated(CanvasDrawingSession ds, ICanvasImage source, LyricsLine line)
private void DrawTranslated(CanvasDrawingSession ds, ICanvasImage source, RenderLyricsLine line)
{
if (line.TranslatedCanvasTextLayout == null) return;
@@ -99,7 +99,7 @@ namespace BetterLyrics.WinUI3.Renderer
ICanvasResourceCreator resourceCreator,
CanvasDrawingSession ds,
ICanvasImage source,
LyricsLine line,
RenderLyricsLine line,
LinePlaybackState state,
Color bgColor,
Color fgColor,
@@ -122,7 +122,7 @@ namespace BetterLyrics.WinUI3.Renderer
ICanvasResourceCreator resourceCreator,
CanvasDrawingSession ds,
ICanvasImage source,
LyricsLine line,
RenderLyricsLine line,
CanvasTextLayoutRegion subLineRegion,
double curCharIndex,
float fadeWidth,
@@ -131,7 +131,6 @@ namespace BetterLyrics.WinUI3.Renderer
LinePlaybackState state,
LyricsEffectSettings settings)
{
var blur = line.BlurAmountTransition.Value;
var playedOpacity = line.PlayedOriginalOpacityTransition.Value;
var unplayedOpacity = line.UnplayedOriginalOpacityTransition.Value;
@@ -191,9 +190,19 @@ namespace BetterLyrics.WinUI3.Renderer
}
}
/// <summary>
///
/// </summary>
/// <param name="ds"></param>
/// <param name="line"></param>
/// <param name="charIndex">遍历的字符索引</param>
/// <param name="exactProgressIndex">当前播放字符相对于整行的索引</param>
/// <param name="source"></param>
/// <param name="state"></param>
/// <param name="settings"></param>
private void DrawSingleCharacter(
CanvasDrawingSession ds,
LyricsLine line,
RenderLyricsLine line,
int charIndex,
double exactProgressIndex,
ICanvasImage source,
@@ -223,7 +232,16 @@ namespace BetterLyrics.WinUI3.Renderer
if (settings.IsLyricsFloatAnimationEnabled)
{
double targetFloatOffset = sourceCharRect.Height * 0.1;
double targetFloatOffset;
if (settings.IsLyricsFloatAnimationAmountAutoAdjust)
{
targetFloatOffset = sourceCharRect.Height * 0.1;
}
else
{
targetFloatOffset = settings.LyricsFloatAnimationAmount;
}
// 已经浮完了的
if (charIndex < curCharIndexInt)
{
@@ -240,22 +258,74 @@ namespace BetterLyrics.WinUI3.Renderer
{
floatOffset = -targetFloatOffset;
}
// 制造句间上浮过度动画,这里用任何一个 Transition 都行,主要是获取当前行的进入视野的 Progress
floatOffset *= line.YOffsetTransition.Progress;
}
var parentSyllable = line.LyricsSyllables.FirstOrDefault(x => x.StartIndex <= charIndex && charIndex < x.StartIndex + x.Text.Length);
if (parentSyllable != null && parentSyllable.IsLongDuration && parentSyllable.StartIndex == state.SyllableStartIndex)
if (settings.IsLyricsScaleEffectEnabled)
{
if (settings.IsLyricsScaleEffectEnabled)
if (parentSyllable != null && parentSyllable.StartIndex == state.SyllableStartIndex)
{
scale += Math.Sin(state.SyllableProgress * Math.PI) * 0.15;
if (parentSyllable.DurationMs >= settings.LyricsScaleEffectLongSyllableDuration)
{
if (settings.IsLyricsScaleEffectAmountAutoAdjust)
{
scale += Math.Sin(state.SyllableProgress * Math.PI) * 0.15;
}
else
{
scale += Math.Sin(state.SyllableProgress * Math.PI) * (settings.LyricsScaleEffectAmount / 100.0 - 1);
}
}
}
if (settings.IsLyricsGlowEffectEnabled)
}
if (settings.IsLyricsGlowEffectEnabled)
{
double maxGlow;
if (settings.IsLyricsGlowEffectAmountAutoAdjust)
{
glow = Math.Sin(state.SyllableProgress * Math.PI) * sourceCharRect.Height * 0.2;
drawGlow = true;
maxGlow = sourceCharRect.Height * 0.2;
}
else
{
maxGlow = settings.LyricsGlowEffectAmount;
}
switch (settings.LyricsGlowEffectScope)
{
case Enums.LyricsEffectScope.LongDurationSyllable:
if (parentSyllable != null && parentSyllable.StartIndex == state.SyllableStartIndex)
{
if (parentSyllable.DurationMs >= settings.LyricsGlowEffectLongSyllableDuration)
{
glow = maxGlow * Math.Sin(state.SyllableProgress * Math.PI);
drawGlow = true;
}
}
break;
case Enums.LyricsEffectScope.LineStartToCurrentChar:
// 已经唱了的
if (charIndex < curCharIndexInt)
{
glow = maxGlow;
drawGlow = true;
}
// 正在唱的
else if (charIndex == curCharIndexInt)
{
var p = exactProgressIndex - curCharIndexInt;
glow = p * maxGlow;
drawGlow = true;
}
// 还没唱的
else { }
glow *= Math.Clamp(line.OriginalText.Length - exactProgressIndex, 0, 1);
break;
default:
break;
}
}

View File

@@ -12,7 +12,7 @@ namespace BetterLyrics.WinUI3.Renderer
public void Draw(
CanvasDrawingSession ds,
ICanvasImage textOnlyLayer,
LyricsLine line)
RenderLyricsLine line)
{
var blurAmount = (float)line.BlurAmountTransition.Value;

View File

@@ -7,7 +7,9 @@ using System.Text.Json.Serialization;
namespace BetterLyrics.WinUI3.Serialization
{
[JsonSerializable(typeof(TranslateResponse))]
[JsonSerializable(typeof(LibreTranslateResponse))]
[JsonSerializable(typeof(CutletDockerRequest))]
[JsonSerializable(typeof(CutletDockerResponse))]
[JsonSerializable(typeof(JsonElement))]
[JsonSerializable(typeof(AppSettings))]
[JsonSerializable(typeof(LyricsSearchResult))]

View File

@@ -1,10 +0,0 @@
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Services.LiveStatesService
{
public interface ILiveStatesService
{
LiveStates LiveStates { get; set; }
void InitLyricsWindowStatus();
}
}

View File

@@ -1,154 +0,0 @@
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using System.Linq;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.LiveStatesService
{
public partial class LiveStatesService : BaseViewModel, ILiveStatesService,
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>
{
private readonly ISettingsService _settingsService;
public LiveStates LiveStates { get; set; } = new LiveStates();
public LiveStatesService(ISettingsService settingsService)
{
_settingsService = settingsService;
}
public void InitLyricsWindowStatus()
{
var defaultLyricsWindowStatus = _settingsService.AppSettings.WindowBoundsRecords.FirstOrDefault(x => x.IsDefault);
if (defaultLyricsWindowStatus == null)
{
defaultLyricsWindowStatus = LyricsWindowStatusExtensions.StandardMode();
defaultLyricsWindowStatus.IsDefault = true;
_settingsService.AppSettings.WindowBoundsRecords.Add(defaultLyricsWindowStatus);
_settingsService.AppSettings.WindowBoundsRecords.Add(LyricsWindowStatusExtensions.DesktopMode());
_settingsService.AppSettings.WindowBoundsRecords.Add(LyricsWindowStatusExtensions.DockedMode());
_settingsService.AppSettings.WindowBoundsRecords.Add(LyricsWindowStatusExtensions.NarrowMode());
_settingsService.AppSettings.WindowBoundsRecords.Add(LyricsWindowStatusExtensions.FullscreenMode());
}
LiveStates.LyricsWindowStatus = defaultLyricsWindowStatus;
}
private async void RefreshLyricsWindowStatus()
{
LiveStates.IsLyricsWindowStatusRefreshing = true;
LiveStates.LyricsWindowStatus.UpdateMonitorBounds();
WindowHook.SetIsWorkArea<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
if (LiveStates.LyricsWindowStatus.IsWorkArea)
{
WindowHook.UpdateWorkArea<NowPlayingWindow>();
}
await Task.Delay(300);
WindowHook.SetIsShowInSwitchers<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
WindowHook.SetIsAlwaysOnTop<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
WindowHook.SetIsClickThrough<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
WindowHook.SetIsBorderless<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
WindowHook.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
WindowHook.SetTitleBarArea<NowPlayingWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
// 下述代码可以删除,但是为了避免给用户造成操作上的疑虑,暂时保留
if (LiveStates.LyricsWindowStatus.IsWorkArea)
{
LiveStates.LyricsWindowStatus.WindowBounds = LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea();
}
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.WindowBounds);
LiveStates.LyricsWindowStatus.WindowX = LiveStates.LyricsWindowStatus.WindowBounds.X;
LiveStates.LyricsWindowStatus.WindowY = LiveStates.LyricsWindowStatus.WindowBounds.Y;
LiveStates.LyricsWindowStatus.WindowWidth = LiveStates.LyricsWindowStatus.WindowBounds.Width;
LiveStates.LyricsWindowStatus.WindowHeight = LiveStates.LyricsWindowStatus.WindowBounds.Height;
LiveStates.LyricsWindowStatus.UpdateDemoWindowAndMonitorBounds();
LiveStates.IsLyricsWindowStatusRefreshing = false;
}
private void LyricsWindowStatus_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(LyricsWindowStatus.IsWorkArea):
LiveStates.IsLyricsWindowStatusRefreshing = true;
WindowHook.SetIsWorkArea<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
LiveStates.IsLyricsWindowStatusRefreshing = false;
if (LiveStates.LyricsWindowStatus.IsWorkArea)
{
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
}
break;
case nameof(LyricsWindowStatus.DockHeight):
case nameof(LyricsWindowStatus.DockPlacement):
case nameof(LyricsWindowStatus.MonitorDeviceName):
LiveStates.LyricsWindowStatus.UpdateMonitorBounds();
if (LiveStates.LyricsWindowStatus.IsWorkArea)
{
LiveStates.IsLyricsWindowStatusRefreshing = true;
WindowHook.UpdateWorkArea<NowPlayingWindow>();
LiveStates.IsLyricsWindowStatusRefreshing = false;
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.GetWindowBoundsWhenWorkArea());
}
break;
case nameof(LyricsWindowStatus.IsShownInSwitchers):
WindowHook.SetIsShowInSwitchers<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
break;
case nameof(LyricsWindowStatus.IsAlwaysOnTop):
WindowHook.SetIsAlwaysOnTop<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
break;
case nameof(LyricsWindowStatus.IsClickThrough):
WindowHook.SetIsClickThrough<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
break;
case nameof(LyricsWindowStatus.IsBorderless):
WindowHook.SetIsBorderless<NowPlayingWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
break;
case nameof(LyricsWindowStatus.WindowX):
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithX(LiveStates.LyricsWindowStatus.WindowX));
break;
case nameof(LyricsWindowStatus.WindowY):
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithY(LiveStates.LyricsWindowStatus.WindowY));
break;
case nameof(LyricsWindowStatus.WindowWidth):
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithWidth(LiveStates.LyricsWindowStatus.WindowWidth));
break;
case nameof(LyricsWindowStatus.WindowHeight):
WindowHook.MoveAndResize<NowPlayingWindow>(LiveStates.LyricsWindowStatus.WindowBounds.WithHeight(LiveStates.LyricsWindowStatus.WindowHeight));
break;
case nameof(LyricsWindowStatus.TitleBarArea):
WindowHook.SetTitleBarArea<NowPlayingWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
break;
case nameof(LyricsWindowStatus.AutoShowOrHideWindow):
WindowHook.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
break;
default:
break;
}
}
public void Receive(PropertyChangedMessage<LyricsWindowStatus> message)
{
if (message.Sender is LiveStates)
{
if (message.PropertyName == nameof(LiveStates.LyricsWindowStatus))
{
message.OldValue.PropertyChanged -= LyricsWindowStatus_PropertyChanged;
message.NewValue.PropertyChanged += LyricsWindowStatus_PropertyChanged;
RefreshLyricsWindowStatus();
}
}
}
}
}

View File

@@ -40,7 +40,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
_lrcLibHttpClient = new();
_lrcLibHttpClient.DefaultRequestHeaders.Add(
"User-Agent",
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.GitHub})"
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.BetterLyricsGitHub})"
);
_amllTtmlDbHttpClient = new();
_appleMusic = new AppleMusic();

View File

@@ -1,19 +1,17 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using Windows.UI;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
public interface IMediaSessionsService : INotifyPropertyChanged
{
event EventHandler<LyricsChangedEventArgs>? LyricsChanged;
Task PlayAsync();
Task PauseAsync();
Task PreviousAsync();
@@ -22,9 +20,6 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
Task ChangeLyricsLine(int index);
void UpdateLyrics();
void UpdateTranslations();
void InitPlaybackShortcuts();
MediaSourceProviderInfo? CurrentMediaSourceProviderInfo { get; }
@@ -34,9 +29,10 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
LyricsData? CurrentLyricsData { get; }
BitmapImage? AlbumArtBitmapImage { get; }
AlbumArtThemeColors AlbumArtThemeColors { get; }
IRandomAccessStream? AlbumArtBitmapStream { get; }
AlbumArtThemeColors CalculateAlbumArtThemeColors(LyricsWindowStatus lyricsWindowStatus, Color backdropAccentColor);
TranslationSearchProvider? TranslationSearchProvider { get; }
LyricsSearchResult? CurrentLyricsSearchResult { get; }
}
}

View File

@@ -22,12 +22,15 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
private Color _envColor = Colors.Transparent;
private List<Color> _lightAccentColors = Enumerable.Repeat(Colors.Black, 4).ToList();
private List<Color> _darkAccentColors = Enumerable.Repeat(Colors.Black, 4).ToList();
private List<Color> _lightAccentColorsMedianCut = Enumerable.Repeat(Colors.Black, 4).ToList();
private List<Color> _darkAccentColorsMedianCut = Enumerable.Repeat(Colors.Black, 4).ToList();
private List<Color> _lightAccentColorsOctTree = Enumerable.Repeat(Colors.Black, 4).ToList();
private List<Color> _darkAccentColorsOctTree = Enumerable.Repeat(Colors.Black, 4).ToList();
private BitmapDecoder? _albumArtBitmapDecoder = null;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial BitmapImage? AlbumArtBitmapImage { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AlbumArtThemeColors AlbumArtThemeColors { get; set; } = new();
[ObservableProperty][NotifyPropertyChangedRecipients] public partial IRandomAccessStream? AlbumArtBitmapStream { get; set; }
private void UpdateAlbumArt()
{
@@ -47,8 +50,6 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
IBuffer? buffer = await Task.Run(async () => await _albumArtSearchService.SearchAsync(CurrentSongInfo, _SMTCAlbumArtBuffer, token), token);
if (token.IsCancellationRequested) return;
BitmapDecoder? decoder = null;
if (buffer == null)
{
using var placeHolderStream = await ImageHelper.GetAlbumArtPlaceholderAsync();
@@ -59,39 +60,56 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
buffer = tempBuffer;
}
decoder = await ImageHelper.MakeSquareWithThemeColor(buffer, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType);
_albumArtBitmapDecoder = await ImageHelper.GetBitmapDecoder(buffer);
if (token.IsCancellationRequested) return;
var lightPalette = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, false);
var darkPalette = await ImageHelper.GetAccentColorsAsync(decoder, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, true);
if (token.IsCancellationRequested) return;
_lightAccentColorsMedianCut =
(await ImageHelper.GetAccentColorsAsync(_albumArtBitmapDecoder, 4, PaletteGeneratorType.MedianCut, false))
.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
_darkAccentColorsMedianCut =
(await ImageHelper.GetAccentColorsAsync(_albumArtBitmapDecoder, 4, PaletteGeneratorType.MedianCut, true))
.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
_lightAccentColorsOctTree =
(await ImageHelper.GetAccentColorsAsync(_albumArtBitmapDecoder, 4, PaletteGeneratorType.OctTree, false))
.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
_darkAccentColorsOctTree =
(await ImageHelper.GetAccentColorsAsync(_albumArtBitmapDecoder, 4, PaletteGeneratorType.OctTree, true))
.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
var bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(ImageHelper.ToIRandomAccessStream(buffer));
if (token.IsCancellationRequested) return;
_lightAccentColors = lightPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
_darkAccentColors = darkPalette.Palette.Select(Helper.ColorHelper.FromVector3).ToList();
UpdateAlbumArtThemeColors();
AlbumArtBitmapImage = bitmapImage;
AlbumArtBitmapStream = ImageHelper.ToIRandomAccessStream(buffer);
}
private void UpdateAlbumArtThemeColors()
public AlbumArtThemeColors CalculateAlbumArtThemeColors(LyricsWindowStatus lyricsWindowStatus, Color backdropAccentColor)
{
var status = _liveStatesService.LiveStates.LyricsWindowStatus;
var lightAccentColors = lyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType switch
{
PaletteGeneratorType.MedianCut => _lightAccentColorsMedianCut,
PaletteGeneratorType.OctTree => _lightAccentColorsOctTree,
_ => _lightAccentColorsMedianCut,
};
var darkAccentColors = lyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType switch
{
PaletteGeneratorType.MedianCut => _darkAccentColorsMedianCut,
PaletteGeneratorType.OctTree => _darkAccentColorsOctTree,
_ => _darkAccentColorsMedianCut,
};
var result = new AlbumArtThemeColors();
result.EnvColor = _envColor;
result.EnvColor = backdropAccentColor;
ElementTheme themeTypeSent;
if (status.IsAdaptToEnvironment)
if (lyricsWindowStatus.IsAdaptToEnvironment)
{
themeTypeSent = Helper.ColorHelper.GetElementThemeFromBackgroundColor(result.EnvColor);
}
else
{
themeTypeSent = status.LyricsBackgroundSettings.LyricsBackgroundTheme;
themeTypeSent = lyricsWindowStatus.LyricsBackgroundSettings.LyricsBackgroundTheme;
}
bool isLight = themeTypeSent switch
@@ -115,10 +133,10 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
// brightness = 0.7f;
grayedEnvironmentalColor = lightColor;
result.AccentColor1 = _lightAccentColors.ElementAtOrDefault(0);
result.AccentColor2 = _lightAccentColors.ElementAtOrDefault(1);
result.AccentColor3 = _lightAccentColors.ElementAtOrDefault(2);
result.AccentColor4 = _lightAccentColors.ElementAtOrDefault(3);
result.AccentColor1 = lightAccentColors.ElementAtOrDefault(0);
result.AccentColor2 = lightAccentColors.ElementAtOrDefault(1);
result.AccentColor3 = lightAccentColors.ElementAtOrDefault(2);
result.AccentColor4 = lightAccentColors.ElementAtOrDefault(3);
}
else
{
@@ -126,78 +144,53 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
// brightness = 0.3f;
grayedEnvironmentalColor = darkColor;
result.AccentColor1 = _darkAccentColors.ElementAtOrDefault(0);
result.AccentColor2 = _darkAccentColors.ElementAtOrDefault(1);
result.AccentColor3 = _darkAccentColors.ElementAtOrDefault(2);
result.AccentColor4 = _darkAccentColors.ElementAtOrDefault(3);
result.AccentColor1 = darkAccentColors.ElementAtOrDefault(0);
result.AccentColor2 = darkAccentColors.ElementAtOrDefault(1);
result.AccentColor3 = darkAccentColors.ElementAtOrDefault(2);
result.AccentColor4 = darkAccentColors.ElementAtOrDefault(3);
}
if (status.IsAdaptToEnvironment)
if (lyricsWindowStatus.IsAdaptToEnvironment)
{
adaptiveColoredFontColor = Helper.ColorHelper.GetForegroundColor(result.EnvColor);
}
else
{
if (isLight)
adaptiveColoredFontColor = _darkAccentColors.ElementAtOrDefault(0);
adaptiveColoredFontColor = darkAccentColors.ElementAtOrDefault(0);
else
adaptiveColoredFontColor = _lightAccentColors.ElementAtOrDefault(0);
adaptiveColoredFontColor = lightAccentColors.ElementAtOrDefault(0);
}
result.ThemeType = themeTypeSent;
// 背景字色
switch (status.LyricsStyleSettings.LyricsBgFontColorType)
result.BgFontColor = lyricsWindowStatus.LyricsStyleSettings.LyricsBgFontColorType switch
{
case LyricsFontColorType.AdaptiveGrayed:
result.BgFontColor = adaptiveGrayedFontColor;
break;
case LyricsFontColorType.AdaptiveColored:
result.BgFontColor = adaptiveColoredFontColor ?? adaptiveGrayedFontColor;
break;
case LyricsFontColorType.Custom:
result.BgFontColor = status.LyricsStyleSettings.LyricsCustomBgFontColor;
break;
default:
result.BgFontColor = adaptiveGrayedFontColor;
break;
}
LyricsFontColorType.AdaptiveGrayed => adaptiveGrayedFontColor,
LyricsFontColorType.AdaptiveColored => adaptiveColoredFontColor ?? adaptiveGrayedFontColor,
LyricsFontColorType.Custom => lyricsWindowStatus.LyricsStyleSettings.LyricsCustomBgFontColor,
_ => adaptiveGrayedFontColor,
};
// 前景字色
switch (status.LyricsStyleSettings.LyricsFgFontColorType)
result.FgFontColor = lyricsWindowStatus.LyricsStyleSettings.LyricsFgFontColorType switch
{
case LyricsFontColorType.AdaptiveGrayed:
result.FgFontColor = adaptiveGrayedFontColor;
break;
case LyricsFontColorType.AdaptiveColored:
result.FgFontColor = adaptiveColoredFontColor ?? adaptiveGrayedFontColor;
break;
case LyricsFontColorType.Custom:
result.FgFontColor = status.LyricsStyleSettings.LyricsCustomFgFontColor;
break;
default:
result.FgFontColor = adaptiveGrayedFontColor;
break;
}
LyricsFontColorType.AdaptiveGrayed => adaptiveGrayedFontColor,
LyricsFontColorType.AdaptiveColored => adaptiveColoredFontColor ?? adaptiveGrayedFontColor,
LyricsFontColorType.Custom => lyricsWindowStatus.LyricsStyleSettings.LyricsCustomFgFontColor,
_ => adaptiveGrayedFontColor,
};
// 描边颜色
switch (status.LyricsStyleSettings.LyricsStrokeFontColorType)
result.StrokeFontColor = lyricsWindowStatus.LyricsStyleSettings.LyricsStrokeFontColorType switch
{
case LyricsFontColorType.AdaptiveGrayed:
result.StrokeFontColor = grayedEnvironmentalColor.WithBrightness(0.7);
break;
case LyricsFontColorType.AdaptiveColored:
result.StrokeFontColor = result.EnvColor.WithBrightness(0.7);
break;
case LyricsFontColorType.Custom:
result.StrokeFontColor = status.LyricsStyleSettings.LyricsCustomStrokeFontColor;
break;
default:
result.StrokeFontColor = Colors.Transparent;
break;
}
AlbumArtThemeColors = result;
LyricsFontColorType.AdaptiveGrayed => grayedEnvironmentalColor.WithBrightness(0.7),
LyricsFontColorType.AdaptiveColored => result.EnvColor.WithBrightness(0.7),
LyricsFontColorType.Custom => lyricsWindowStatus.LyricsStyleSettings.LyricsCustomStrokeFontColor,
_ => Colors.Transparent,
};
return result;
}
}

View File

@@ -1,16 +1,9 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Parsers.LyricsParser;
using CommunityToolkit.Mvvm.ComponentModel;
using Lyricify.Lyrics.Helpers.General;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -19,188 +12,51 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
public partial class MediaSessionsService : IMediaSessionsService
{
private LatestOnlyTaskRunner _refreshLyricsRunner = new();
private LatestOnlyTaskRunner _refreshTranslationRunner = new();
private int _langIndex = 0;
private List<LyricsData> _lyricsDataArr = [];
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsData? CurrentLyricsData { get; private set; }
public event EventHandler<LyricsChangedEventArgs>? LyricsChanged;
[ObservableProperty] public partial LyricsSearchResult? CurrentLyricsSearchResult { get; private set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TranslationSearchProvider? TranslationSearchProvider { get; private set; }
[ObservableProperty] public partial bool IsTranslating { get; set; } = false;
private void SetCurrentLyricsData()
{
CurrentLyricsData = _lyricsDataArr.ElementAtOrDefault(_langIndex);
}
private async Task RefreshTranslationAsync(CancellationToken token)
{
TranslationSearchProvider = null;
_lyricsDataArr.ElementAtOrDefault(0)?.ClearTranslatedText();
App.Current.Resources.DispatcherQueue.TryEnqueue(SetCurrentLyricsData);
IsTranslating = true;
await SetPhoneticTextAsync(token);
await SetTranslatedTextAsync(token);
if (token.IsCancellationRequested) return;
IsTranslating = false;
App.Current.Resources.DispatcherQueue.TryEnqueue(SetCurrentLyricsData);
}
private async Task SetTranslatedTextAsync(CancellationToken token)
{
if (!_settingsService.AppSettings.TranslationSettings.IsTranslationEnabled) return;
_logger.LogInformation("SetTranslatedTextAsync");
string targetLangCode = _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageCode;
_logger.LogInformation("Target language code: {TargetLangCode}", targetLangCode);
string? originalText = _lyricsDataArr.FirstOrDefault()?.WrappedOriginalText;
if (originalText == null) return;
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode);
if (originalLangCode == targetLangCode)
{
_logger.LogInformation("Original lyrics already in target language: {TargetLangCode}", targetLangCode);
_lyricsDataArr.FirstOrDefault()?.ClearTranslatedText();
}
else
{
// Try get translation from itself first
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr, targetLangCode);
if (found >= 0)
{
_logger.LogInformation("Found translated text in lyrics data at index {FoundIndex}", found);
_lyricsDataArr.FirstOrDefault()?.SetTranslatedText(_lyricsDataArr[found], 50);
TranslationSearchProvider = CurrentLyricsSearchResult?.Provider.ToTranslationSearchProvider();
}
else if (_settingsService.AppSettings.TranslationSettings.IsLibreTranslateEnabled)
{
_logger.LogInformation("LibreTranslate is enabled, trying to translate lyrics...");
string translated = string.Empty;
try
{
translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
if (token.IsCancellationRequested) return;
if (translated == string.Empty) return;
_lyricsDataArr.FirstOrDefault()?.SetTranslation(translated);
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
}
catch (Exception)
{
ToastHelper.ShowToast("LibreTranslateFailed", null, InfoBarSeverity.Error);
}
}
}
}
private async Task SetPhoneticTextAsync(CancellationToken token)
{
_logger.LogInformation("Showing phonetic text for lyrics...");
string targetPhoneticCode = "";
_logger.LogInformation("Target phonetic code: {TargetPhonetic}", targetPhoneticCode);
string? originalText = _lyricsDataArr.FirstOrDefault()?.WrappedOriginalText;
if (originalText == null) return;
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
_logger.LogInformation("Original phonetic code: {OriginalLangCode}", originalLangCode);
if (originalLangCode == "zh" && _settingsService.AppSettings.TranslationSettings.IsChineseRomanizationEnabled)
{
targetPhoneticCode = _settingsService.AppSettings.TranslationSettings.ChineseRomanization.ToPhoneticCode();
}
else if (originalLangCode == "ja" && _settingsService.AppSettings.TranslationSettings.IsJapaneseRomanizationEnabled)
{
targetPhoneticCode = PhoneticHelper.RomanCode;
}
if (targetPhoneticCode == "")
{
_lyricsDataArr.FirstOrDefault()?.ClearPhoneticText();
}
// Try get phonetic text from itself
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr, targetPhoneticCode);
if (found >= 0)
{
_logger.LogInformation("Found phonetic text in lyrics data at index {FoundIndex}", found);
_lyricsDataArr.FirstOrDefault()?.SetPhoneticText(_lyricsDataArr[found], 50);
}
}
private async Task RefreshLyricsAsync(CancellationToken token)
{
_logger.LogInformation("RefreshLyricsAsync");
CurrentLyricsSearchResult = null;
_lyricsDataArr = [LyricsData.GetLoadingPlaceholder()];
App.Current.Resources.DispatcherQueue.TryEnqueue(SetCurrentLyricsData);
CurrentLyricsData = LyricsData.GetLoadingPlaceholder();
if (CurrentSongInfo != null)
{
CurrentLyricsSearchResult = await Task.Run(async () => await _lyrcsSearchService.SearchSmartlyAsync(
CurrentSongInfo,
!_settingsService.AppSettings.GeneralSettings.IgnoreCacheWhenSearching,
true,
CurrentMediaSourceProviderInfo?.LyricsSearchType,
token),
token);
if (token.IsCancellationRequested) return;
var lyricsParser = new LyricsParser();
lyricsParser.Parse(CurrentSongInfo, CurrentLyricsSearchResult);
_lyricsDataArr = lyricsParser.LyricsDataArr;
ApplyChinesePreference();
}
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _lyricsDataArr.Count);
// Show original first while loading phonetic and translated
ApplyChinesePreference();
App.Current.Resources.DispatcherQueue.TryEnqueue(SetCurrentLyricsData);
UpdateTranslations();
}
private void ApplyChinesePreference()
{
var traditionalChinesePreferred = _settingsService.AppSettings.TranslationSettings.IsTraditionalChineseEnabled;
var found = _lyricsDataArr.FindIndex(x => x.LanguageCode == "zh");
if (found >= 0)
{
foreach (var item in _lyricsDataArr[found].LyricsLines)
if (CurrentLyricsSearchResult != null)
{
item.OriginalText = traditionalChinesePreferred ? ChineseHelper.ToTC(item.OriginalText) : ChineseHelper.ToSC(item.OriginalText);
var lyricsParser = new LyricsParser();
(CurrentLyricsData, CurrentLyricsSearchResult.TransliterationProvider, CurrentLyricsSearchResult.TranslationProvider) =
await Task.Run(async () => await lyricsParser.Parse(
_translationService,
_transliterationService,
_settingsService.AppSettings.TranslationSettings,
CurrentLyricsSearchResult,
token),
token);
}
}
}
public void UpdateLyrics()
public async void UpdateLyrics()
{
_ = _refreshLyricsRunner.RunAsync(RefreshLyricsAsync);
await _refreshLyricsRunner.RunAsync(async (token) =>
{
await RefreshLyricsAsync(token);
});
}
public void UpdateTranslations()
{
_ = _refreshTranslationRunner.RunAsync(RefreshTranslationAsync);
}
}
}

View File

@@ -12,10 +12,10 @@ using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.DiscordService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using BetterLyrics.WinUI3.Services.TranslationService;
using BetterLyrics.WinUI3.Services.TransliterationService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -25,10 +25,8 @@ using CommunityToolkit.WinUI;
using EvtSource;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.Json;
@@ -36,7 +34,6 @@ using System.Threading.Tasks;
using Vanara.Windows.Shell;
using Windows.Media.Control;
using Windows.Storage.Streams;
using Windows.UI;
using WindowsMediaController;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
@@ -44,13 +41,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
public partial class MediaSessionsService : BaseViewModel, IMediaSessionsService,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<string>>,
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>,
IRecipient<PropertyChangedMessage<PaletteGeneratorType>>,
IRecipient<PropertyChangedMessage<ChineseRomanization>>,
IRecipient<PropertyChangedMessage<List<string>>>,
IRecipient<PropertyChangedMessage<Color>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<LyricsFontColorType>>
IRecipient<PropertyChangedMessage<ChineseRomanization>>
{
private EventSourceReader? _sse = null;
private readonly MediaManager _mediaManager = new();
@@ -58,10 +49,10 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private readonly IAlbumArtSearchService _albumArtSearchService;
private readonly ILyricsSearchService _lyrcsSearchService;
private readonly ITranslateService _translateService;
private readonly ITranslationService _translationService;
private readonly ITransliterationService _transliterationService;
private readonly ISettingsService _settingsService;
private readonly ILibWatcherService _libWatcherService;
private readonly ILiveStatesService _liveStatesService;
private readonly IDiscordService _discordService;
private readonly ILogger<MediaSessionsService> _logger;
@@ -79,19 +70,19 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
public MediaSessionsService(
ISettingsService settingsService,
IAlbumArtSearchService albumArtSearchService,
ILyricsSearchService musicSearchService,
ILyricsSearchService lyricsSearchService,
ILibWatcherService libWatcherService,
ILiveStatesService liveStatesService,
IDiscordService discordService,
ITranslateService libreTranslateService,
ITranslationService libreTranslateService,
ITransliterationService transliterationService,
ILogger<MediaSessionsService> logger)
{
_settingsService = settingsService;
_albumArtSearchService = albumArtSearchService;
_lyrcsSearchService = musicSearchService;
_lyrcsSearchService = lyricsSearchService;
_libWatcherService = libWatcherService;
_translateService = libreTranslateService;
_liveStatesService = liveStatesService;
_translationService = libreTranslateService;
_transliterationService = transliterationService;
_discordService = discordService;
_logger = logger;
@@ -108,7 +99,6 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged;
InitMediaManager();
InitPlaybackShortcuts();
}
private void MappedSongSearchQueries_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
@@ -121,44 +111,6 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
UpdateLyrics();
}
public void InitPlaybackShortcuts()
{
UpdatePlayOrPauseSongShortcut();
UpdatePreviousSongShortcut();
UpdateNextSongShortcut();
}
private void UpdatePlayOrPauseSongShortcut()
{
GlobalHotKeyHook.UpdateHotKey<NowPlayingWindow>(ShortcutID.PlayOrPauseSong, _settingsService.AppSettings.GeneralSettings.PlayOrPauseShortcut, (() =>
{
if (CurrentIsPlaying)
{
_ = PauseAsync();
}
else
{
_ = PlayAsync();
}
}));
}
private void UpdatePreviousSongShortcut()
{
GlobalHotKeyHook.UpdateHotKey<NowPlayingWindow>(ShortcutID.PreviousSong, _settingsService.AppSettings.GeneralSettings.PreviousSongShortcut, () =>
{
_ = PreviousAsync();
});
}
private void UpdateNextSongShortcut()
{
GlobalHotKeyHook.UpdateHotKey<NowPlayingWindow>(ShortcutID.NextSong, _settingsService.AppSettings.GeneralSettings.NextSongShortcut, () =>
{
_ = NextAsync();
});
}
private void LocalMediaFolders_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
{
UpdateAlbumArt();
@@ -206,7 +158,22 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private bool IsMediaSourceEnabled(string id)
{
return _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id)?.IsEnabled ?? true;
var found = _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id);
if (_settingsService.AppSettings.MusicGallerySettings.LyricsWindowStatus.IsOpened)
{
if (PlayerIDHelper.IsBetterLyrics(found?.Provider))
{
return found?.IsEnabled ?? true;
}
else
{
return false;
}
}
else
{
return found?.IsEnabled ?? true;
}
}
private bool IsMediaSourceTimelineSyncEnabled(string? id)
@@ -238,24 +205,32 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties? timelineProperties)
{
if (!_mediaManager.IsStarted) return;
if (mediaSession == null) return;
var desiredSession = GetCurrentSession();
if (mediaSession != desiredSession) return;
if (!IsMediaSourceEnabled(mediaSession.Id))
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
CurrentPosition = TimeSpan.Zero;
}
else
{
if (IsMediaSourceTimelineSyncEnabled(mediaSession.Id))
if (!_mediaManager.IsStarted) return;
if (mediaSession == null)
{
CurrentPosition = timelineProperties?.Position ?? TimeSpan.Zero;
CurrentPosition = TimeSpan.Zero;
return;
}
}
var desiredSession = GetCurrentSession();
if (mediaSession != desiredSession) return;
if (!IsMediaSourceEnabled(mediaSession.Id))
{
CurrentPosition = TimeSpan.Zero;
}
else
{
if (IsMediaSourceTimelineSyncEnabled(mediaSession.Id))
{
CurrentPosition = timelineProperties?.Position ?? TimeSpan.Zero;
CurrentSongInfo?.DurationMs = timelineProperties?.EndTime.TotalMilliseconds ?? 0;
}
}
});
}
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo? playbackInfo)
@@ -263,7 +238,11 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, (() =>
{
if (!_mediaManager.IsStarted) return;
if (mediaSession == null) return;
if (mediaSession == null)
{
CurrentIsPlaying = false;
return;
}
var desiredSession = GetCurrentSession();
@@ -501,14 +480,12 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
var desiredSession = GetCurrentSession();
//if (desiredSession == null || desiredSession.ControlSession == null) return;
try
{
mediaProps = await desiredSession?.ControlSession?.TryGetMediaPropertiesAsync();
}
catch (Exception) { }
//if (desiredSession == null || desiredSession.ControlSession == null) return;
MediaManager_OnAnyTimelinePropertyChanged(desiredSession, desiredSession?.ControlSession?.GetTimelineProperties());
MediaManager_OnAnyMediaPropertyChanged(desiredSession, mediaProps);
@@ -690,42 +667,30 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (message.PropertyName == nameof(TranslationSettings.IsLibreTranslateEnabled))
{
UpdateTranslations();
UpdateLyrics();
}
else if (message.PropertyName == nameof(TranslationSettings.IsTranslationEnabled))
{
UpdateTranslations();
UpdateLyrics();
}
else if (message.PropertyName == nameof(TranslationSettings.IsChineseRomanizationEnabled))
{
UpdateTranslations();
UpdateLyrics();
}
else if (message.PropertyName == nameof(TranslationSettings.IsJapaneseRomanizationEnabled))
{
UpdateTranslations();
UpdateLyrics();
}
else if (message.PropertyName == nameof(TranslationSettings.IsTraditionalChineseEnabled))
{
UpdateLyrics();
}
}
}
public void Receive(PropertyChangedMessage<List<string>> message)
{
if (message.Sender is GeneralSettings)
else if (message.Sender is LyricsWindowStatus)
{
if (message.PropertyName == nameof(GeneralSettings.PlayOrPauseShortcut))
if (message.PropertyName == nameof(MusicGallerySettings.LyricsWindowStatus.IsOpened))
{
UpdatePlayOrPauseSongShortcut();
}
else if (message.PropertyName == nameof(GeneralSettings.PreviousSongShortcut))
{
UpdatePreviousSongShortcut();
}
else if (message.PropertyName == nameof(GeneralSettings.NextSongShortcut))
{
UpdateNextSongShortcut();
MediaManager_OnFocusedSessionChanged(null);
}
}
}
@@ -737,20 +702,15 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageCode))
{
_logger.LogInformation("Target LibreTranslate language code changed: {code}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageCode);
UpdateTranslations();
UpdateLyrics();
}
}
}
public void Receive(PropertyChangedMessage<LyricsWindowStatus> message)
{
if (message.Sender is LiveStates)
{
if (message.PropertyName == nameof(LiveStates.LyricsWindowStatus))
else if (message.PropertyName == nameof(TranslationSettings.CutletDockerServer))
{
UpdateAlbumArtThemeColors();
UpdateTranslations();
UpdateLyrics();
}
else if (message.PropertyName == nameof(TranslationSettings.LibreTranslateServer))
{
UpdateLyrics();
}
}
}
@@ -761,77 +721,10 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (message.PropertyName == nameof(TranslationSettings.ChineseRomanization))
{
UpdateTranslations();
UpdateLyrics();
}
}
}
public void Receive(PropertyChangedMessage<PaletteGeneratorType> message)
{
if (message.Sender is LyricsBackgroundSettings)
{
if (message.PropertyName == nameof(LyricsBackgroundSettings.PaletteGeneratorType))
{
UpdateAlbumArt();
}
}
}
public void Receive(PropertyChangedMessage<Color> message)
{
if (message.Sender is NowPlayingWindowViewModel)
{
if (message.PropertyName == nameof(NowPlayingWindowViewModel.BackdropAccentColor))
{
_envColor = message.NewValue;
UpdateAlbumArtThemeColors();
}
}
else if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomBgFontColor))
{
UpdateAlbumArtThemeColors();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomFgFontColor))
{
UpdateAlbumArtThemeColors();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsCustomStrokeFontColor))
{
UpdateAlbumArtThemeColors();
}
}
}
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
if (message.Sender is LyricsBackgroundSettings)
{
if (message.PropertyName == nameof(LyricsBackgroundSettings.LyricsBackgroundTheme))
{
UpdateAlbumArtThemeColors();
}
}
}
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsBgFontColorType))
{
UpdateAlbumArtThemeColors();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsFgFontColorType))
{
UpdateAlbumArtThemeColors();
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsStrokeFontColorType))
{
UpdateAlbumArtThemeColors();
}
}
}
}
}

View File

@@ -2,6 +2,7 @@
using BetterLyrics.WinUI3.Collections;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Extensions;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
@@ -19,7 +20,7 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
// 新建一个 AppSettings 类
public partial class SettingsService : BaseViewModel, ISettingsService
{
private DispatcherQueueTimer _writeAppSettingsTimer;
private readonly DispatcherQueueTimer _writeAppSettingsTimer;
public AppSettings AppSettings { get; set; }

View File

@@ -3,12 +3,10 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.TranslateService
namespace BetterLyrics.WinUI3.Services.TranslationService
{
public interface ITranslateService
public interface ITranslationService
{
Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken token);
int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr, string targetLangCode);
}
}

View File

@@ -9,14 +9,14 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.TranslateService
namespace BetterLyrics.WinUI3.Services.TranslationService
{
public partial class TranslateService : BaseViewModel, ITranslateService
public partial class TranslationService : BaseViewModel, ITranslationService
{
private readonly ISettingsService _settingsService;
private readonly HttpClient _httpClient;
public TranslateService(ISettingsService settingsService)
public TranslationService(ISettingsService settingsService)
{
_settingsService = settingsService;
_httpClient = new HttpClient();
@@ -51,31 +51,8 @@ namespace BetterLyrics.WinUI3.Services.TranslateService
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync(token);
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.TranslateResponse);
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LibreTranslateResponse);
return result?.TranslatedText ?? string.Empty;
}
public int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr, string targetLangCode)
{
int ret = -1;
float maxTranslatinRate = 0.0f;
if (lyricsDataArr.Count > 1)
{
for (int i = 1; i < lyricsDataArr.Count; i++)
{
if (lyricsDataArr[i].LanguageCode == targetLangCode)
{
float translationRate = lyricsDataArr[i].LyricsLines.Count / (float)lyricsDataArr[0].LyricsLines.Count;
if (translationRate > maxTranslatinRate)
{
maxTranslatinRate = translationRate;
ret = i;
}
}
}
}
return ret;
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.TransliterationService
{
public interface ITransliterationService
{
Task<string> TransliterateText(string text, string targetLangCode, CancellationToken token);
}
}

View File

@@ -0,0 +1,52 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization;
using BetterLyrics.WinUI3.Services.SettingsService;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
namespace BetterLyrics.WinUI3.Services.TransliterationService
{
public class TransliterationService : ITransliterationService
{
private readonly ISettingsService _settingsService;
private readonly HttpClient _httpClient;
public TransliterationService(ISettingsService settingsService)
{
_settingsService = settingsService;
_httpClient = new HttpClient();
}
public async Task<string> TransliterateText(string text, string targetLangCode, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(text))
{
throw new Exception(text + " is empty or null.");
}
if (string.IsNullOrEmpty(_settingsService.AppSettings.TranslationSettings.CutletDockerServer))
{
throw new Exception("cutlet-docker server URL is not set in settings.");
}
var request = new CutletDockerRequest { Text = text };
var reqJson = System.Text.Json.JsonSerializer.Serialize(request, SourceGenerationContext.Default.CutletDockerRequest);
var url = $"{_settingsService.AppSettings.TranslationSettings.CutletDockerServer}/convert";
var response = await _httpClient.PostAsync(url, new StringContent(reqJson, Encoding.UTF8, "application/json"));
response.EnsureSuccessStatusCode();
var resJson = await response.Content.ReadAsStringAsync(token);
var result = System.Text.Json.JsonSerializer.Deserialize(resJson, SourceGenerationContext.Default.CutletDockerResponse);
return result?.RomajiText ?? string.Empty;
}
}
}

View File

@@ -150,12 +150,15 @@
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>Playlist was created successfully</value>
</data>
<data name="CutletDockerFailed" xml:space="preserve">
<value>Failed to request transliteration from cutlet-docker, please check settings or native cutlet-docker configuration</value>
</data>
<data name="CutletServerTextBox.PlaceholderText" xml:space="preserve">
<value>For example http://localhost:23333</value>
</data>
<data name="DemoWindowControlDefault.Text" xml:space="preserve">
<value>Default</value>
</data>
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
<value>Dependencies</value>
</data>
<data name="DesktopMode" xml:space="preserve">
<value>Desktop mode (transparent)</value>
</data>
@@ -198,6 +201,9 @@
<data name="Jyutping" xml:space="preserve">
<value>Cantonese pinyin</value>
</data>
<data name="KeepAtLeastOneStatusDefault" xml:space="preserve">
<value>Please make sure at least one status is set to default</value>
</data>
<data name="LastFMAuthFailed" xml:space="preserve">
<value>Authorization failed, please try again</value>
</data>
@@ -240,22 +246,22 @@
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>Lyrics provider</value>
</data>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
<data name="LyricsPageLyricsSearch.Text" xml:space="preserve">
<value>Manually search lyrics</value>
</data>
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<data name="LyricsPageLyricsSettings.Text" xml:space="preserve">
<value>Lyrics Window Management Shortcut Settings</value>
</data>
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>Match percentage</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
<data name="LyricsPagePlaybackSource.Text" xml:space="preserve">
<value>Play source shortcut settings</value>
</data>
<data name="LyricsPagePositionOffsetHint.Header" xml:space="preserve">
<value>Reset to 0 when switching songs</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<data name="LyricsPageSettings.Text" xml:space="preserve">
<value>Settings</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
@@ -276,6 +282,9 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>Translation provider</value>
</data>
<data name="LyricsPageTransliterationProviderPrefix.Header" xml:space="preserve">
<value>Transliteration source</value>
</data>
<data name="LyricsParseError" xml:space="preserve">
<value>Lyrics parsing failed</value>
</data>
@@ -342,8 +351,8 @@
<data name="LyricsWindowImmersiveButtonToolTip.Content" xml:space="preserve">
<value>Immersive mode</value>
</data>
<data name="LyricsWindowSettingsControlCurrentLyricsWindowConfig.Text" xml:space="preserve">
<value>Current lyrics window config</value>
<data name="LyricsWindowSettingsControlLyricsWindowConfig.Text" xml:space="preserve">
<value>Configuration</value>
</data>
<data name="LyricsWindowSettingsControlLyricsWindowMode.Header" xml:space="preserve">
<value>Lyrics window mode</value>
@@ -487,7 +496,7 @@
<value>Single loop</value>
</data>
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
<value>Search for song title, artist, album or folder path</value>
<value>Search for the song name, artist, album, file name, or folder path where the file is located</value>
</data>
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
<value>Album</value>
@@ -513,6 +522,12 @@
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>Music gallery - BetterLyrics</value>
</data>
<data name="MusicGalleryWindowDownButtonToolTip.Content" xml:space="preserve">
<value>Retract</value>
</data>
<data name="MusicGalleryWindowUpButtonToolTip.Content" xml:space="preserve">
<value>Unwrap</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>Narrow mode</value>
</data>
@@ -534,6 +549,9 @@
<data name="SetingsPageContributors.Text" xml:space="preserve">
<value>Contributors</value>
</data>
<data name="SetingsPageDeps.Text" xml:space="preserve">
<value>Code Reference</value>
</data>
<data name="SetingsPageDonation.Text" xml:space="preserve">
<value>Donation</value>
</data>
@@ -543,9 +561,15 @@
<data name="SetingsPageInstructions.Text" xml:space="preserve">
<value>Instructions</value>
</data>
<data name="SetingsPageSpecialThanks.Text" xml:space="preserve">
<value>Special Thanks</value>
</data>
<data name="SetingsPageThanks.Text" xml:space="preserve">
<value>If you like this project, please consider supporting it by donating. Your support will help keep the project alive and encourage further development.</value>
</data>
<data name="SetingsPageUIUXRef.Text" xml:space="preserve">
<value>UI/UX Design Reference</value>
</data>
<data name="SettingsPage3DLyrics.Header" xml:space="preserve">
<value>[Experimental] Depth effect</value>
</data>
@@ -612,6 +636,9 @@
<data name="SettingsPageAlignment.Header" xml:space="preserve">
<value>Alignment</value>
</data>
<data name="SettingsPageAlwaysHideUnlockButton.Header" xml:space="preserve">
<value>Always hide unlock button</value>
</data>
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
<value>Base URL</value>
</data>
@@ -639,6 +666,9 @@
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>Automatic adjustment</value>
</data>
<data name="SettingsPageAutoOpenLyricsWindow.Header" xml:space="preserve">
<value>Automatically opens the default lyrics window when the program starts</value>
</data>
<data name="SettingsPageAutoOpenMusicGalleryWindow.Header" xml:space="preserve">
<value>Open the music library window when the app starts</value>
</data>
@@ -660,6 +690,9 @@
<data name="SettingsPageBackgroundOverlay.Text" xml:space="preserve">
<value>Lyrics background</value>
</data>
<data name="SettingsPageBlurAmount.Header" xml:space="preserve">
<value>Blur amount</value>
</data>
<data name="SettingsPageBorderless.Header" xml:space="preserve">
<value>Borderless window</value>
</data>
@@ -705,6 +738,9 @@
<data name="SettingsPageClickThroughHotKey.Header" xml:space="preserve">
<value>Click-through shortcut keys</value>
</data>
<data name="SettingsPageCloseStatus.Text" xml:space="preserve">
<value>Close</value>
</data>
<data name="SettingsPageCollapseDropdown.Content" xml:space="preserve">
<value>Collapse dropdown</value>
</data>
@@ -717,6 +753,9 @@
<data name="SettingsPageConfigName.Header" xml:space="preserve">
<value>Configuration name</value>
</data>
<data name="SettingsPageConfigPlaybackSource.Text" xml:space="preserve">
<value>Configure playback source</value>
</data>
<data name="SettingsPageCreateFromCurrent.Text" xml:space="preserve">
<value>Copy</value>
</data>
@@ -729,6 +768,9 @@
<data name="SettingsPageCurrentLyricsWindowStatus.Text" xml:space="preserve">
<value>Current lyrics window status</value>
</data>
<data name="SettingsPageCutletDockerServer.Header" xml:space="preserve">
<value>cutlet-docker transliteration service</value>
</data>
<data name="SettingsPageDark.Content" xml:space="preserve">
<value>Dark</value>
</data>
@@ -813,6 +855,12 @@
<data name="SettingsPageEasingTypeSmoothStep.Content" xml:space="preserve">
<value>Smooth step</value>
</data>
<data name="SettingsPageEffectLineStartToCurrentChar.Content" xml:space="preserve">
<value>Line start to current char</value>
</data>
<data name="SettingsPageEffectScopeLongDurationSyllable.Content" xml:space="preserve">
<value>Long duration syllable</value>
</data>
<data name="SettingsPageEN.Content" xml:space="preserve">
<value>English</value>
</data>
@@ -831,6 +879,9 @@
<data name="SettingsPageEnvColorSampleInner.Content" xml:space="preserve">
<value>Inside the window</value>
</data>
<data name="SettingsPageExitOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Exit the program when closing the music library window</value>
</data>
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>Exit the program when you close the lyrics window</value>
</data>
@@ -885,6 +936,9 @@
<data name="SettingsPageHeight.Header" xml:space="preserve">
<value>Height</value>
</data>
<data name="SettingsPageHello.Text" xml:space="preserve">
<value>Hello</value>
</data>
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
<value>Automatically hide/show lyric window when music is not playing</value>
</data>
@@ -912,6 +966,9 @@
<data name="SettingsPageJA.Content" xml:space="preserve">
<value>日本語</value>
</data>
<data name="SettingsPageJapanese.Description" xml:space="preserve">
<value>The transliteration of the lyrics will be read first, and if there is no match, the machine transliteration will be requested from the cutlet-docker server</value>
</data>
<data name="SettingsPageJapanese.Header" xml:space="preserve">
<value>Japanese annotation</value>
</data>
@@ -987,6 +1044,9 @@
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>Log record</value>
</data>
<data name="SettingsPageLongSyllableDuration.Header" xml:space="preserve">
<value>Long syllable threshold</value>
</data>
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
<value>LX Music Server</value>
</data>
@@ -1057,7 +1117,7 @@
<value>Custom</value>
</data>
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
<value>Lyrics font family</value>
<value>Font family</value>
</data>
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
<value>Font size</value>
@@ -1068,9 +1128,6 @@
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>Font weight</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Description" xml:space="preserve">
<value>Enable glow effect for syllables with long duration</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
<value>Glow effect</value>
</data>
@@ -1101,6 +1158,9 @@
<data name="SettingsPageLyricsNormal.Content" xml:space="preserve">
<value>Normal</value>
</data>
<data name="SettingsPageLyricsOpacity.Header" xml:space="preserve">
<value>Font opacity</value>
</data>
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
<value>Lyrics area height factor</value>
</data>
@@ -1158,6 +1218,9 @@
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>Top and bottom edge opacity</value>
</data>
<data name="SettingsPageLyricsWindow.Text" xml:space="preserve">
<value>Lyrics window</value>
</data>
<data name="SettingsPageLyricsWindowManager.Text" xml:space="preserve">
<value>Lyrics window manager</value>
</data>
@@ -1200,6 +1263,15 @@
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
<value>Play test music</value>
</data>
<data name="SettingsPageMultiNowPlayingWindows.Header" xml:space="preserve">
<value>Multi-window coexistence mode</value>
</data>
<data name="SettingsPageMusicGallery.Text" xml:space="preserve">
<value>Music gallery</value>
</data>
<data name="SettingsPageMusicGalleryLyrics.Header" xml:space="preserve">
<value>Embedded lyrics window in music library</value>
</data>
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
<value>Add folders storing music or lyrics</value>
</data>
@@ -1248,12 +1320,18 @@
<data name="SettingsPagePathNotFound.Text" xml:space="preserve">
<value>The path cannot be found on your computer</value>
</data>
<data name="SettingsPagePatrons.Text" xml:space="preserve">
<value>Sponsor</value>
</data>
<data name="SettingsPagePhonetic.Text" xml:space="preserve">
<value>Lyric annotation</value>
</data>
<data name="SettingsPagePhoneticText.Header" xml:space="preserve">
<value>Phonetic symbols</value>
</data>
<data name="SettingsPagePinToTaskbar.Header" xml:space="preserve">
<value>Pin to taskbar</value>
</data>
<data name="SettingsPagePinyin.Content" xml:space="preserve">
<value>Pinyin of Mandarin</value>
</data>
@@ -1263,9 +1341,6 @@
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>No playback source captured</value>
</data>
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>Play</value>
</data>
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>Playback source</value>
</data>
@@ -1371,6 +1446,9 @@
<data name="SettingsPageShareHub.Content" xml:space="preserve">
<value>Browse online resources sharing hub</value>
</data>
<data name="SettingsPageShortcut.Text" xml:space="preserve">
<value>Play</value>
</data>
<data name="SettingsPageShortcutRegFailInfo" xml:space="preserve">
<value>This hotkey was not successfully registered</value>
</data>
@@ -1443,6 +1521,12 @@
<data name="SettingsPageStandardMode.Text" xml:space="preserve">
<value>Standard mode</value>
</data>
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>Start</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Stop playing when music library is closed</value>
</data>
<data name="SettingsPageStrokeFontColor.Header" xml:space="preserve">
<value>Stroke color</value>
</data>
@@ -1452,12 +1536,24 @@
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>Target language</value>
</data>
<data name="SettingsPageTaskbarMode.Text" xml:space="preserve">
<value>Taskbar mode</value>
</data>
<data name="SettingsPageTaskbarPlacement.Header" xml:space="preserve">
<value>Taskbar fixed position</value>
</data>
<data name="SettingsPageTC.Content" xml:space="preserve">
<value>繁體中文</value>
</data>
<data name="SettingsPageTelegram.Content" xml:space="preserve">
<value>Telegram</value>
</data>
<data name="SettingsPageThanksForPurchasing.Text" xml:space="preserve">
<value>Thank you for purchasing BetterLyrics</value>
</data>
<data name="SettingsPageThanksList.Header" xml:space="preserve">
<value>Credits</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>Lyrics background theme</value>
</data>
@@ -1503,6 +1599,9 @@
<data name="SettingsPageTransparent.Content" xml:space="preserve">
<value>Transparent</value>
</data>
<data name="SettingsPageUserWhoPurchased.Text" xml:space="preserve">
<value>And buy users who support BetterLyrics</value>
</data>
<data name="SettingsPageVersion.Text" xml:space="preserve">
<value>Version</value>
</data>
@@ -1527,6 +1626,9 @@
<data name="SettingsPageWorkAreaHeight.Header" xml:space="preserve">
<value>Work area height</value>
</data>
<data name="SettingsPageYouNowUsing.Text" xml:space="preserve">
<value>You who are using the software</value>
</data>
<data name="StandardMode" xml:space="preserve">
<value>Standard mode</value>
</data>
@@ -1557,6 +1659,9 @@
<data name="SystemTraySwitch.Text" xml:space="preserve">
<value>Lyrics window switcher</value>
</data>
<data name="TaskbarMode" xml:space="preserve">
<value>Taskbar mode</value>
</data>
<data name="TermsOfService.Content" xml:space="preserve">
<value>Terms of service</value>
</data>

View File

@@ -150,12 +150,15 @@
<data name="CreatePlaylistSuccessfully" xml:space="preserve">
<value>正常に作成されました</value>
</data>
<data name="CutletDockerFailed" xml:space="preserve">
<value>cutlet - dockerからの音訳のリクエストに失敗しました。設定またはネイティブのcutlet - docker設定を確認してください</value>
</data>
<data name="CutletServerTextBox.PlaceholderText" xml:space="preserve">
<value>たとえば、http://localhost:23333</value>
</data>
<data name="DemoWindowControlDefault.Text" xml:space="preserve">
<value>デフォルト</value>
</data>
<data name="DependenciesSettingsExpander.Header" xml:space="preserve">
<value>依存関係</value>
</data>
<data name="DesktopMode" xml:space="preserve">
<value>デスクトップモード(透明) </value>
</data>
@@ -198,6 +201,9 @@
<data name="Jyutping" xml:space="preserve">
<value>広東語のピンイン</value>
</data>
<data name="KeepAtLeastOneStatusDefault" xml:space="preserve">
<value>少なくとも1つのステータスがデフォルトに設定されていることを確認してください</value>
</data>
<data name="LastFMAuthFailed" xml:space="preserve">
<value>承認が失敗しました、もう一度やり直してください</value>
</data>
@@ -240,22 +246,22 @@
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
<value>歌詞プロバイダー</value>
</data>
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
<data name="LyricsPageLyricsSearch.Text" xml:space="preserve">
<value>手動で歌詞を検索します</value>
</data>
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
<data name="LyricsPageLyricsSettings.Text" xml:space="preserve">
<value>歌詞ウィンドウ管理ショートカット設定</value>
</data>
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
<value>マッチ率</value>
</data>
<data name="LyricsPagePlaybackSourceButtonToolTip.Content" xml:space="preserve">
<data name="LyricsPagePlaybackSource.Text" xml:space="preserve">
<value>再生ソースクイック設定</value>
</data>
<data name="LyricsPagePositionOffsetHint.Header" xml:space="preserve">
<value>曲を切り替えるときに0にリセットします</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<data name="LyricsPageSettings.Text" xml:space="preserve">
<value>設定</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
@@ -276,6 +282,9 @@
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
<value>翻訳プロバイダー</value>
</data>
<data name="LyricsPageTransliterationProviderPrefix.Header" xml:space="preserve">
<value>文字起こしソース</value>
</data>
<data name="LyricsParseError" xml:space="preserve">
<value>歌詞の解析に失敗しました</value>
</data>
@@ -342,8 +351,8 @@
<data name="LyricsWindowImmersiveButtonToolTip.Content" xml:space="preserve">
<value>没入モード</value>
</data>
<data name="LyricsWindowSettingsControlCurrentLyricsWindowConfig.Text" xml:space="preserve">
<value>現在の歌詞ウィンドウ設定</value>
<data name="LyricsWindowSettingsControlLyricsWindowConfig.Text" xml:space="preserve">
<value>構成</value>
</data>
<data name="LyricsWindowSettingsControlLyricsWindowMode.Header" xml:space="preserve">
<value>歌詞ウィンドウモード</value>
@@ -487,7 +496,7 @@
<value>シングルループ</value>
</data>
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
<value>曲名、アーティスト、アルバム、フォルダパスを検索してください</value>
<value>ファイルが置かれている曲名、アーティスト、アルバム、ファイル名、またはフォルダパスを検索します</value>
</data>
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
<value>アルバム</value>
@@ -513,6 +522,12 @@
<data name="MusicGalleryPageTitle" xml:space="preserve">
<value>音楽ギャラリー - BetterLyrics</value>
</data>
<data name="MusicGalleryWindowDownButtonToolTip.Content" xml:space="preserve">
<value>取り戻せ</value>
</data>
<data name="MusicGalleryWindowUpButtonToolTip.Content" xml:space="preserve">
<value>展開</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>狭い</value>
</data>
@@ -534,6 +549,9 @@
<data name="SetingsPageContributors.Text" xml:space="preserve">
<value>投稿者</value>
</data>
<data name="SetingsPageDeps.Text" xml:space="preserve">
<value>コードリファレンス</value>
</data>
<data name="SetingsPageDonation.Text" xml:space="preserve">
<value>寄付</value>
</data>
@@ -543,9 +561,15 @@
<data name="SetingsPageInstructions.Text" xml:space="preserve">
<value>手順</value>
</data>
<data name="SetingsPageSpecialThanks.Text" xml:space="preserve">
<value>スペシャルサンクス</value>
</data>
<data name="SetingsPageThanks.Text" xml:space="preserve">
<value>このプロジェクトが気に入っている場合は、寄付してサポートすることを検討してください。あなたのサポートは、プロジェクトを生かし続け、さらなる開発を促進するのに役立ちます。</value>
</data>
<data name="SetingsPageUIUXRef.Text" xml:space="preserve">
<value>UI/UXデザインリファレンス</value>
</data>
<data name="SettingsPage3DLyrics.Header" xml:space="preserve">
<value>[実験的] 奥行き効果</value>
</data>
@@ -612,6 +636,9 @@
<data name="SettingsPageAlignment.Header" xml:space="preserve">
<value>アライメント</value>
</data>
<data name="SettingsPageAlwaysHideUnlockButton.Header" xml:space="preserve">
<value>ロック解除ボタンは常に隠しておきましょう</value>
</data>
<data name="SettingsPageAmllTtmlDbBaseUrl.Header" xml:space="preserve">
<value>ベースURL</value>
</data>
@@ -639,6 +666,9 @@
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
<value>自動調整</value>
</data>
<data name="SettingsPageAutoOpenLyricsWindow.Header" xml:space="preserve">
<value>プログラムが開始されると、デフォルトの歌詞ウィンドウが自動的に開きます</value>
</data>
<data name="SettingsPageAutoOpenMusicGalleryWindow.Header" xml:space="preserve">
<value>アプリが起動したら、音楽ライブラリウィンドウを開きます</value>
</data>
@@ -660,6 +690,9 @@
<data name="SettingsPageBackgroundOverlay.Text" xml:space="preserve">
<value>歌詞の背景</value>
</data>
<data name="SettingsPageBlurAmount.Header" xml:space="preserve">
<value>あいまい</value>
</data>
<data name="SettingsPageBorderless.Header" xml:space="preserve">
<value>ボーダーレスウィンドウ</value>
</data>
@@ -705,6 +738,9 @@
<data name="SettingsPageClickThroughHotKey.Header" xml:space="preserve">
<value>クリックスルーショートカットキー</value>
</data>
<data name="SettingsPageCloseStatus.Text" xml:space="preserve">
<value>閉じる</value>
</data>
<data name="SettingsPageCollapseDropdown.Content" xml:space="preserve">
<value>ドロップダウンを折りたたみます</value>
</data>
@@ -717,6 +753,9 @@
<data name="SettingsPageConfigName.Header" xml:space="preserve">
<value>設定名</value>
</data>
<data name="SettingsPageConfigPlaybackSource.Text" xml:space="preserve">
<value>再生ソースを設定します</value>
</data>
<data name="SettingsPageCreateFromCurrent.Text" xml:space="preserve">
<value>コピー</value>
</data>
@@ -729,6 +768,9 @@
<data name="SettingsPageCurrentLyricsWindowStatus.Text" xml:space="preserve">
<value>現在の歌詞ウィンドウステータス</value>
</data>
<data name="SettingsPageCutletDockerServer.Header" xml:space="preserve">
<value>cutlet-docker 音声翻訳サービス</value>
</data>
<data name="SettingsPageDark.Content" xml:space="preserve">
<value>暗い</value>
</data>
@@ -813,6 +855,12 @@
<data name="SettingsPageEasingTypeSmoothStep.Content" xml:space="preserve">
<value>スムーズなステップ</value>
</data>
<data name="SettingsPageEffectLineStartToCurrentChar.Content" xml:space="preserve">
<value>現在の文字への行開始</value>
</data>
<data name="SettingsPageEffectScopeLongDurationSyllable.Content" xml:space="preserve">
<value>長時間の音節</value>
</data>
<data name="SettingsPageEN.Content" xml:space="preserve">
<value>English</value>
</data>
@@ -831,6 +879,9 @@
<data name="SettingsPageEnvColorSampleInner.Content" xml:space="preserve">
<value>窓の中だ</value>
</data>
<data name="SettingsPageExitOnGalleryWindowClosed.Header" xml:space="preserve">
<value>音楽ライブラリウィンドウを閉じてプログラムを終了してください</value>
</data>
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>歌詞ウィンドウを閉じてプログラムを終了してください</value>
</data>
@@ -885,6 +936,9 @@
<data name="SettingsPageHeight.Header" xml:space="preserve">
<value>高さ</value>
</data>
<data name="SettingsPageHello.Text" xml:space="preserve">
<value>こんにちは</value>
</data>
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
<value>音楽が再生されていないときに、自動的に歌詞ウィンドウを非表示/表示します</value>
</data>
@@ -912,6 +966,9 @@
<data name="SettingsPageJA.Content" xml:space="preserve">
<value>日本語</value>
</data>
<data name="SettingsPageJapanese.Description" xml:space="preserve">
<value>歌詞の音訳が最初に読み込まれ、一致しない場合は機械音訳がcutlet - dockerサーバーから要求されます</value>
</data>
<data name="SettingsPageJapanese.Header" xml:space="preserve">
<value>日本の注釈</value>
</data>
@@ -987,11 +1044,14 @@
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>ログレコード</value>
</data>
<data name="SettingsPageLongSyllableDuration.Header" xml:space="preserve">
<value>長い音節のしきい値</value>
</data>
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
<value>LX Music Server</value>
</data>
<data name="SettingsPageLXMusicServerInput.PlaceholderText" xml:space="preserve">
<value>例http://127.0.0.1: 23330</value>
<value>例http://127.0.0.1:23330</value>
</data>
<data name="SettingsPageLyricsAlignment.Header" xml:space="preserve">
<value>アライメント</value>
@@ -1057,7 +1117,7 @@
<value>カスタマイズ</value>
</data>
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
<value>歌詞フォントファミリー</value>
<value>フォントファミリー</value>
</data>
<data name="SettingsPageLyricsFontSize.Header" xml:space="preserve">
<value>フォントサイズ</value>
@@ -1068,9 +1128,6 @@
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
<value>フォント重量</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Description" xml:space="preserve">
<value>長時間の音節にグロウ効果を有効にします</value>
</data>
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
<value>グロー効果</value>
</data>
@@ -1101,6 +1158,9 @@
<data name="SettingsPageLyricsNormal.Content" xml:space="preserve">
<value>普通</value>
</data>
<data name="SettingsPageLyricsOpacity.Header" xml:space="preserve">
<value>フォントの不透明度</value>
</data>
<data name="SettingsPageLyricsRowFactor.Header" xml:space="preserve">
<value>歌詞エリアの高さ係数</value>
</data>
@@ -1158,6 +1218,9 @@
<data name="SettingsPageLyricsVerticalEdgeOpacity.Header" xml:space="preserve">
<value>上端と下端の不透明度</value>
</data>
<data name="SettingsPageLyricsWindow.Text" xml:space="preserve">
<value>歌詞ウィンドウ</value>
</data>
<data name="SettingsPageLyricsWindowManager.Text" xml:space="preserve">
<value>歌詞ウィンドウマネージャー</value>
</data>
@@ -1200,6 +1263,15 @@
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
<value>テスト音楽を再生します</value>
</data>
<data name="SettingsPageMultiNowPlayingWindows.Header" xml:space="preserve">
<value>マルチウィンドウ共存モード</value>
</data>
<data name="SettingsPageMusicGallery.Text" xml:space="preserve">
<value>ミュージックギャラリー</value>
</data>
<data name="SettingsPageMusicGalleryLyrics.Header" xml:space="preserve">
<value>歌詞ウィンドウが埋め込まれた音楽ライブラリ</value>
</data>
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
<value>音楽や歌詞を保存するフォルダーを追加します</value>
</data>
@@ -1248,12 +1320,18 @@
<data name="SettingsPagePathNotFound.Text" xml:space="preserve">
<value>パスはコンピューターでは見つかりません</value>
</data>
<data name="SettingsPagePatrons.Text" xml:space="preserve">
<value>後援しました</value>
</data>
<data name="SettingsPagePhonetic.Text" xml:space="preserve">
<value>リリックアノテーション</value>
</data>
<data name="SettingsPagePhoneticText.Header" xml:space="preserve">
<value>発音</value>
</data>
<data name="SettingsPagePinToTaskbar.Header" xml:space="preserve">
<value>タスクバーに移動</value>
</data>
<data name="SettingsPagePinyin.Content" xml:space="preserve">
<value>マンダリンのピンイン</value>
</data>
@@ -1263,9 +1341,6 @@
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>キャプチャされた再生ソースはありません</value>
</data>
<data name="SettingsPagePlaybackShortcut.Text" xml:space="preserve">
<value>遊ぶ</value>
</data>
<data name="SettingsPagePlaybackSource.Header" xml:space="preserve">
<value>再生ソース</value>
</data>
@@ -1371,6 +1446,9 @@
<data name="SettingsPageShareHub.Content" xml:space="preserve">
<value>オンラインリソース共有ハブを閲覧しましょう</value>
</data>
<data name="SettingsPageShortcut.Text" xml:space="preserve">
<value>ショートカットキー</value>
</data>
<data name="SettingsPageShortcutRegFailInfo" xml:space="preserve">
<value>このホットキーは正常に登録されていません</value>
</data>
@@ -1443,6 +1521,12 @@
<data name="SettingsPageStandardMode.Text" xml:space="preserve">
<value>標準モード</value>
</data>
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>起動</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>音楽ライブラリが閉じられたら、再生を停止してください</value>
</data>
<data name="SettingsPageStrokeFontColor.Header" xml:space="preserve">
<value>ストロークカラー</value>
</data>
@@ -1452,12 +1536,24 @@
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>ターゲット言語</value>
</data>
<data name="SettingsPageTaskbarMode.Text" xml:space="preserve">
<value>タスクバーモード</value>
</data>
<data name="SettingsPageTaskbarPlacement.Header" xml:space="preserve">
<value>タスクバーの位置が固定されました</value>
</data>
<data name="SettingsPageTC.Content" xml:space="preserve">
<value>繁體中文</value>
</data>
<data name="SettingsPageTelegram.Content" xml:space="preserve">
<value>Telegram</value>
</data>
<data name="SettingsPageThanksForPurchasing.Text" xml:space="preserve">
<value>BetterLyrics をご購入いただきありがとうございます</value>
</data>
<data name="SettingsPageThanksList.Header" xml:space="preserve">
<value>ありがとうございました</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>歌詞の背景テーマ</value>
</data>
@@ -1503,6 +1599,9 @@
<data name="SettingsPageTransparent.Content" xml:space="preserve">
<value>透明</value>
</data>
<data name="SettingsPageUserWhoPurchased.Text" xml:space="preserve">
<value>そして、BetterLyricsをサポートするユーザーを購入しましょう</value>
</data>
<data name="SettingsPageVersion.Text" xml:space="preserve">
<value>バージョン</value>
</data>
@@ -1527,6 +1626,9 @@
<data name="SettingsPageWorkAreaHeight.Header" xml:space="preserve">
<value>作業エリアの高さ</value>
</data>
<data name="SettingsPageYouNowUsing.Text" xml:space="preserve">
<value>ソフトウェアを使用しているあなた</value>
</data>
<data name="StandardMode" xml:space="preserve">
<value>標準モード</value>
</data>
@@ -1557,6 +1659,9 @@
<data name="SystemTraySwitch.Text" xml:space="preserve">
<value>歌詞ウィンドウスイッチャー</value>
</data>
<data name="TaskbarMode" xml:space="preserve">
<value>タスクバーモード</value>
</data>
<data name="TermsOfService.Content" xml:space="preserve">
<value>利用規約</value>
</data>

Some files were not shown because too many files have changed in this diff Show More